This commit is contained in:
Bastien COIGNOUX
2026-05-03 20:18:33 +02:00
parent ffc2e6b895
commit bd325fe456
113 changed files with 29532 additions and 220 deletions

489
schema.sql Normal file
View File

@ -0,0 +1,489 @@
-- ============================================================
-- 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);