-- ============================================================ -- SCHÉMA SUPABASE — Application Marchand de Biens -- À exécuter dans Supabase > SQL Editor -- ============================================================ -- Extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "unaccent"; -- ============================================================ -- TABLES PRINCIPALES -- ============================================================ -- Profil utilisateur (complète auth.users de Supabase) CREATE TABLE public.profiles ( id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, nom TEXT, prenom TEXT, telephone TEXT, raison_sociale TEXT, -- Nom de la société (SCI, SARL, etc.) siret TEXT, taux_credit_defaut DECIMAL(5,3) DEFAULT 3.5, -- Taux crédit par défaut en % taux_impot_defaut DECIMAL(5,3) DEFAULT 25.0, -- Taux IS par défaut en % created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- MODULE PROSPECTION / BIENS -- ============================================================ -- Étapes du pipeline (customisables) CREATE TABLE public.etapes_pipeline ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, nom TEXT NOT NULL, ordre INTEGER NOT NULL, couleur TEXT DEFAULT '#6B7280', is_terminal BOOLEAN DEFAULT FALSE, -- Étape finale (Vendu, Abandonné) created_at TIMESTAMPTZ DEFAULT NOW() ); -- Biens immobiliers CREATE TABLE public.biens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, etape_id UUID REFERENCES public.etapes_pipeline(id), -- Identification titre TEXT, -- Nom court ex: "T3 Bordeaux Caudéran" reference TEXT UNIQUE, -- Référence interne auto-générée type_bien TEXT, -- appartement, maison, immeuble, terrain, local_commercial, parking -- Localisation adresse TEXT, code_postal TEXT, ville TEXT, departement TEXT, latitude DECIMAL(10,7), longitude DECIMAL(10,7), -- Caractéristiques physiques surface_habitable DECIMAL(8,2), -- m² loi Carrez surface_totale DECIMAL(8,2), -- m² totaux nb_pieces INTEGER, nb_chambres INTEGER, nb_etages INTEGER, etage INTEGER, annee_construction INTEGER, dpe_lettre TEXT, -- A, B, C, D, E, F, G dpe_valeur INTEGER, -- kWh/m²/an ges_lettre TEXT, ges_valeur INTEGER, -- Source source TEXT, -- particulier, agence, notaire, tribunal, succession, autre source_contact_id UUID, -- Lien vers contact (notaire, agent...) url_annonce TEXT, -- Statut statut TEXT DEFAULT 'actif', -- actif, abandonné, vendu priorite INTEGER DEFAULT 2, -- 1=haute, 2=normale, 3=basse is_off_market BOOLEAN DEFAULT FALSE, -- Dates importantes date_premiere_visite DATE, date_offre DATE, date_compromis DATE, date_acte DATE, date_mise_en_vente DATE, date_acte_revente DATE, -- Notes description TEXT, points_forts TEXT, points_faibles TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Analyse financière par bien CREATE TABLE public.analyses_financieres ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, -- Acquisition prix_achat DECIMAL(12,2), type_bien_fiscal TEXT DEFAULT 'ancien', -- ancien (7.5%) ou neuf (2%) frais_notaire DECIMAL(12,2), -- calculé ou saisi manuellement frais_agence_achat DECIMAL(12,2), frais_agence_achat_pct DECIMAL(5,2), -- Financement apport DECIMAL(12,2), montant_emprunt DECIMAL(12,2), taux_credit DECIMAL(5,3), duree_credit_mois INTEGER, mensualite DECIMAL(10,2), -- Travaux budget_travaux DECIMAL(12,2), reserve_imprevus_pct DECIMAL(5,2) DEFAULT 10, -- Portage duree_portage_mois INTEGER DEFAULT 12, taxe_fonciere_annuelle DECIMAL(10,2), charges_copropriete_mensuelle DECIMAL(10,2), assurance_mensuelle DECIMAL(10,2), frais_divers_mensuel DECIMAL(10,2), -- Revente prix_revente_cible DECIMAL(12,2), frais_agence_vente_pct DECIMAL(5,2) DEFAULT 5, frais_agence_vente DECIMAL(12,2), taux_impot DECIMAL(5,2) DEFAULT 25, -- Résultats calculés (mis à jour automatiquement) prix_revient DECIMAL(12,2), frais_portage_total DECIMAL(12,2), marge_brute DECIMAL(12,2), marge_brute_pct DECIMAL(7,2), marge_nette DECIMAL(12,2), marge_nette_pct DECIMAL(7,2), -- Scénarios scenario_pessimiste_prix DECIMAL(12,2), scenario_optimiste_prix DECIMAL(12,2), notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- MODULE CONTACTS / ANNUAIRE -- ============================================================ CREATE TABLE public.contacts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, -- Identité nom TEXT NOT NULL, prenom TEXT, societe TEXT, poste TEXT, -- Catégorie categorie TEXT NOT NULL, -- notaire, agent_immo, artisan_gros_oeuvre, artisan_second_oeuvre, -- artisan_finitions, banquier, courtier, diagnostiqueur, -- geometre, avocat, comptable, vendeur, acheteur, autre specialite TEXT, -- ex: "Plomberie chauffage", "Électricité", "Charpente" -- Coordonnées telephone TEXT, telephone_2 TEXT, email TEXT, adresse TEXT, code_postal TEXT, ville TEXT, zone_intervention TEXT, -- ex: "Bordeaux 33000-33100" -- Qualité note INTEGER CHECK (note BETWEEN 1 AND 5), -- Note de 1 à 5 étoiles fiabilite TEXT, -- excellent, bon, moyen, mauvais recommande BOOLEAN DEFAULT FALSE, -- Tarification artisans taux_horaire DECIMAL(8,2), remise_habituelle_pct DECIMAL(5,2), notes TEXT, is_favori BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Lien bien ↔ contact (ex: notaire assigné à ce dossier) CREATE TABLE public.biens_contacts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, contact_id UUID NOT NULL REFERENCES public.contacts(id) ON DELETE CASCADE, role TEXT, -- notaire_vendeur, notaire_acheteur, agent_vendeur, artisan_principal, banque created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(bien_id, contact_id, role) ); -- ============================================================ -- MODULE VISITES -- ============================================================ CREATE TABLE public.visites ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, date_visite TIMESTAMPTZ NOT NULL, duree_minutes INTEGER DEFAULT 60, type_visite TEXT DEFAULT 'premiere', -- premiere, seconde, expert, contradiction -- Notes brutes pendant la visite notes_brutes TEXT, -- Rapport généré par IA rapport_genere TEXT, rapport_genere_at TIMESTAMPTZ, -- Estimations sur place estimation_travaux_min DECIMAL(12,2), estimation_travaux_max DECIMAL(12,2), estimation_duree_travaux_mois INTEGER, -- Avis global avis_global TEXT, -- coup_de_coeur, interessant, neutre, a_eviter score_opportunite INTEGER CHECK (score_opportunite BETWEEN 1 AND 10), contacts_presents TEXT[], -- Noms des personnes présentes created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Check-list de visite (items configurables) CREATE TABLE public.checklist_items ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, categorie TEXT NOT NULL, -- structure, toiture, electricite, plomberie, isolation, humidite, exterieur, administratif question TEXT NOT NULL, description TEXT, -- Aide / explication ordre INTEGER DEFAULT 0, is_actif BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Réponses à la check-list pour une visite CREATE TABLE public.checklist_reponses ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), visite_id UUID NOT NULL REFERENCES public.visites(id) ON DELETE CASCADE, item_id UUID NOT NULL REFERENCES public.checklist_items(id), reponse TEXT, -- ok, attention, probleme, non_verifie note TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(visite_id, item_id) ); -- ============================================================ -- MODULE DOCUMENTS & MÉDIAS -- ============================================================ CREATE TABLE public.photos_biens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, visite_id UUID REFERENCES public.visites(id), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, storage_path TEXT NOT NULL, -- chemin dans Supabase Storage nom TEXT, legende TEXT, categorie TEXT, -- facade, interieur, travaux, avant, apres ordre INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE public.documents_biens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, storage_path TEXT NOT NULL, nom TEXT NOT NULL, type_document TEXT, -- compromis, acte, dpe, diagnostics, devis, facture, titre_propriete, autre date_document DATE, notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- MODULE AGENDA / TÂCHES -- ============================================================ CREATE TABLE public.taches ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, bien_id UUID REFERENCES public.biens(id) ON DELETE SET NULL, contact_id UUID REFERENCES public.contacts(id) ON DELETE SET NULL, titre TEXT NOT NULL, description TEXT, type_tache TEXT, -- visite, appel, email, relance, administratif, travaux, banque, autre priorite INTEGER DEFAULT 2, statut TEXT DEFAULT 'a_faire', -- a_faire, en_cours, fait, annule date_echeance TIMESTAMPTZ, date_rappel TIMESTAMPTZ, date_realisation TIMESTAMPTZ, recurrence TEXT, -- null, quotidien, hebdomadaire, mensuel is_urgent BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Notes libres CREATE TABLE public.notes_biens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, contenu TEXT NOT NULL, type_note TEXT DEFAULT 'libre', -- libre, contact_vendeur, negociation, info_marche created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- MODULE TRAVAUX -- ============================================================ CREATE TABLE public.devis_travaux ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), bien_id UUID NOT NULL REFERENCES public.biens(id) ON DELETE CASCADE, contact_id UUID REFERENCES public.contacts(id), -- L'artisan user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, lot TEXT NOT NULL, -- Gros œuvre, Électricité, Plomberie, Menuiserie, etc. description TEXT, montant_ht DECIMAL(12,2), taux_tva DECIMAL(5,2) DEFAULT 10, montant_ttc DECIMAL(12,2), statut TEXT DEFAULT 'en_attente', -- en_attente, refuse, accepte, en_cours, termine, paye date_devis DATE, date_debut_prevu DATE, date_fin_prevu DATE, date_fin_reel DATE, storage_path_pdf TEXT, notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- DONNÉES PAR DÉFAUT -- ============================================================ -- Étapes pipeline par défaut (seront copiées pour chaque nouvel utilisateur) -- (géré via trigger ou Edge Function à l'inscription) -- Check-list de visite par défaut INSERT INTO public.checklist_items (user_id, categorie, question, description, ordre) VALUES -- Sera remplacé par un trigger, voici la structure des items par défaut -- Ces items sont créés via la Edge Function on-user-create ('00000000-0000-0000-0000-000000000000', 'structure', 'Fissures visibles sur les murs porteurs ?', 'Observer les fissures en H ou diagonales, signe de mouvement de structure', 1), ('00000000-0000-0000-0000-000000000000', 'toiture', 'État général de la toiture ?', 'Tuiles manquantes, mousse, faîtage, chéneaux', 2), ('00000000-0000-0000-0000-000000000000', 'electricite', 'Tableau électrique aux normes ?', 'Vérifier disjoncteurs, présence de terre, type de tableau', 3), ('00000000-0000-0000-0000-000000000000', 'plomberie', 'Type de canalisations (plomb, cuivre, PVC) ?', 'Canalisations en plomb = remplacement obligatoire', 4), ('00000000-0000-0000-0000-000000000000', 'humidite', 'Traces d''humidité ou de moisissures ?', 'Vérifier les angles, sous les fenêtres, la cave', 5), ('00000000-0000-0000-0000-000000000000', 'isolation', 'Type et état de l''isolation ?', 'Combles, murs, sol. DPE cohérent avec le ressenti', 6), ('00000000-0000-0000-0000-000000000000', 'exterieur', 'État des menuiseries extérieures ?', 'Simple ou double vitrage, état des joints, volets', 7), ('00000000-0000-0000-0000-000000000000', 'administratif', 'Charges de copropriété et procédures en cours ?', 'Demander les 3 derniers PV d''AG, montant des charges', 8); -- ============================================================ -- ROW LEVEL SECURITY (RLS) -- ============================================================ ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE public.biens ENABLE ROW LEVEL SECURITY; ALTER TABLE public.etapes_pipeline ENABLE ROW LEVEL SECURITY; ALTER TABLE public.analyses_financieres ENABLE ROW LEVEL SECURITY; ALTER TABLE public.contacts ENABLE ROW LEVEL SECURITY; ALTER TABLE public.biens_contacts ENABLE ROW LEVEL SECURITY; ALTER TABLE public.visites ENABLE ROW LEVEL SECURITY; ALTER TABLE public.checklist_items ENABLE ROW LEVEL SECURITY; ALTER TABLE public.checklist_reponses ENABLE ROW LEVEL SECURITY; ALTER TABLE public.photos_biens ENABLE ROW LEVEL SECURITY; ALTER TABLE public.documents_biens ENABLE ROW LEVEL SECURITY; ALTER TABLE public.taches ENABLE ROW LEVEL SECURITY; ALTER TABLE public.notes_biens ENABLE ROW LEVEL SECURITY; ALTER TABLE public.devis_travaux ENABLE ROW LEVEL SECURITY; -- Politique : chaque utilisateur voit uniquement ses propres données CREATE POLICY "user_own_data" ON public.profiles FOR ALL USING (auth.uid() = id); CREATE POLICY "user_own_data" ON public.biens FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.etapes_pipeline FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.analyses_financieres FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.contacts FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.visites FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.taches FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.notes_biens FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.devis_travaux FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.photos_biens FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.documents_biens FOR ALL USING (auth.uid() = user_id); CREATE POLICY "user_own_data" ON public.checklist_items FOR ALL USING (auth.uid() = user_id); -- biens_contacts : visible si l'utilisateur possède le bien CREATE POLICY "user_own_biens_contacts" ON public.biens_contacts FOR ALL USING (EXISTS (SELECT 1 FROM public.biens WHERE id = bien_id AND user_id = auth.uid())); -- checklist_reponses : visible si l'utilisateur possède la visite CREATE POLICY "user_own_checklist_reponses" ON public.checklist_reponses FOR ALL USING (EXISTS (SELECT 1 FROM public.visites WHERE id = visite_id AND user_id = auth.uid())); -- ============================================================ -- TRIGGER : updated_at automatique -- ============================================================ CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.biens FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.contacts FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.visites FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.taches FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.analyses_financieres FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.devis_travaux FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER set_updated_at BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at(); -- ============================================================ -- TRIGGER : Créer profil + données par défaut à l'inscription -- ============================================================ CREATE OR REPLACE FUNCTION handle_new_user() RETURNS TRIGGER AS $$ DECLARE etape_ids UUID[]; BEGIN -- Créer le profil INSERT INTO public.profiles (id) VALUES (NEW.id); -- Créer les étapes du pipeline par défaut INSERT INTO public.etapes_pipeline (user_id, nom, ordre, couleur) VALUES (NEW.id, 'Piste', 1, '#6B7280'), (NEW.id, 'En analyse', 2, '#3B82F6'), (NEW.id, 'Offre faite', 3, '#F59E0B'), (NEW.id, 'Compromis signé', 4, '#8B5CF6'), (NEW.id, 'Acte signé', 5, '#10B981'), (NEW.id, 'En travaux', 6, '#F97316'), (NEW.id, 'En vente', 7, '#EC4899'), (NEW.id, 'Vendu', 8, '#22C55E'), (NEW.id, 'Abandonné', 9, '#EF4444'); -- Copier la check-list de visite par défaut INSERT INTO public.checklist_items (user_id, categorie, question, description, ordre) SELECT NEW.id, categorie, question, description, ordre FROM public.checklist_items WHERE user_id = '00000000-0000-0000-0000-000000000000'; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION handle_new_user(); -- ============================================================ -- INDEX pour les performances -- ============================================================ CREATE INDEX idx_biens_user_id ON public.biens(user_id); CREATE INDEX idx_biens_etape_id ON public.biens(etape_id); CREATE INDEX idx_biens_ville ON public.biens(ville); CREATE INDEX idx_contacts_user_id ON public.contacts(user_id); CREATE INDEX idx_contacts_categorie ON public.contacts(categorie); CREATE INDEX idx_taches_user_id ON public.taches(user_id); CREATE INDEX idx_taches_date_echeance ON public.taches(date_echeance); CREATE INDEX idx_visites_bien_id ON public.visites(bien_id);