Initial commit
Generated by create-expo-app 3.5.3.
This commit is contained in:
224
supabase/migrations/20260429180000_mdb_turbo_dossiers.sql
Normal file
224
supabase/migrations/20260429180000_mdb_turbo_dossiers.sql
Normal file
@ -0,0 +1,224 @@
|
||||
-- MDB-Turbo : cœur métier dossiers + checklist visite + base investisseurs
|
||||
-- Exécuter après création du projet Supabase (auth.users existe déjà).
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Profil utilisateur (lien auth)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create table if not exists public.profiles (
|
||||
id uuid primary key references auth.users (id) on delete cascade,
|
||||
full_name text,
|
||||
company_name text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Dossier = opportunité / deal en cours
|
||||
-- ---------------------------------------------------------------------------
|
||||
create type public.dossier_status as enum (
|
||||
'draft',
|
||||
'sourcing',
|
||||
'analysis',
|
||||
'visit',
|
||||
'offer',
|
||||
'under_promise',
|
||||
'resale',
|
||||
'closed_won',
|
||||
'closed_lost'
|
||||
);
|
||||
|
||||
create table if not exists public.dossiers (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid not null references auth.users (id) on delete cascade,
|
||||
title text not null default 'Nouveau dossier',
|
||||
status public.dossier_status not null default 'draft',
|
||||
|
||||
-- Localisation
|
||||
address_line text,
|
||||
city text,
|
||||
postal_code text,
|
||||
insee_code text,
|
||||
latitude double precision,
|
||||
longitude double precision,
|
||||
|
||||
-- Physique
|
||||
surface_m2 numeric(12, 2),
|
||||
land_surface_m2 numeric(12, 2),
|
||||
rooms_count smallint,
|
||||
dpe_class text check (dpe_class is null or dpe_class ~ '^[ABCDEFG]$'),
|
||||
|
||||
-- Financier (saisie terrain / desk)
|
||||
purchase_price_target numeric(14, 2),
|
||||
resale_price_estimate numeric(14, 2),
|
||||
dvf_reference_price_m2 numeric(14, 2),
|
||||
works_estimate_total numeric(14, 2) not null default 0,
|
||||
works_visit_adjustment numeric(14, 2) not null default 0,
|
||||
|
||||
notary_fee_rate numeric(6, 5) not null default 0.077,
|
||||
sale_agency_fee_rate numeric(6, 5) not null default 0.05,
|
||||
misc_acquisition_cost numeric(14, 2) not null default 0,
|
||||
misc_sale_cost numeric(14, 2) not null default 0,
|
||||
|
||||
carrying_months smallint not null default 6,
|
||||
carrying_annual_rate numeric(6, 5) not null default 0.05,
|
||||
carrying_principal numeric(14, 2),
|
||||
|
||||
-- Urbanisme & stratégies "quick win"
|
||||
plu_zone_code text,
|
||||
plu_notes text,
|
||||
parcel_subdivision_candidate boolean not null default false,
|
||||
deficit_foncier_candidate boolean not null default false,
|
||||
|
||||
-- IA (agents) — JSON pour itérer vite sans migrations à chaque prompt
|
||||
sourcing_agent_output jsonb,
|
||||
tech_estimator_output jsonb,
|
||||
financier_agent_output jsonb,
|
||||
deal_maker_output jsonb,
|
||||
|
||||
under_promise_at timestamptz,
|
||||
teaser_pdf_url text,
|
||||
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists dossiers_user_id_idx on public.dossiers (user_id);
|
||||
create index if not exists dossiers_status_idx on public.dossiers (status);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Catalogue "points noirs" visite (anti-erreur)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create table if not exists public.visit_finding_definitions (
|
||||
code text primary key,
|
||||
label text not null,
|
||||
default_works_delta_eur numeric(14, 2) not null default 0,
|
||||
severity smallint not null default 1,
|
||||
sort_order int not null default 0
|
||||
);
|
||||
|
||||
insert into public.visit_finding_definitions (code, label, default_works_delta_eur, severity, sort_order)
|
||||
values
|
||||
('structural_crack', 'Fissure structurelle / désordre porteur', 15000, 5, 10),
|
||||
('roof_full_replace', 'Toiture à refaire (complète)', 35000, 5, 20),
|
||||
('roof_partial', 'Toiture partielle / zinguerie lourde', 8000, 3, 30),
|
||||
('humidity_basement', 'Infiltrations cave / vide sanitaire', 12000, 3, 40),
|
||||
('electrical_rewire', 'Rénovation électrique complète', 15000, 4, 50),
|
||||
('asbestos', 'Présence amiante / désamiantage à prévoir', 10000, 4, 60),
|
||||
('septic_non_conform', 'Assainissement non conforme', 12000, 3, 70),
|
||||
('facade_insulation', 'ITE / ravalement lourd', 25000, 3, 80),
|
||||
('heat_pump_full', 'Chauffage à refaire (pompe à chaleur + réseau)', 18000, 2, 90)
|
||||
on conflict (code) do nothing;
|
||||
|
||||
create table if not exists public.dossier_visit_findings (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
dossier_id uuid not null references public.dossiers (id) on delete cascade,
|
||||
finding_code text not null references public.visit_finding_definitions (code),
|
||||
checked boolean not null default false,
|
||||
works_delta_override_eur numeric(14, 2),
|
||||
checked_at timestamptz,
|
||||
unique (dossier_id, finding_code)
|
||||
);
|
||||
|
||||
create index if not exists dossier_visit_findings_dossier_idx
|
||||
on public.dossier_visit_findings (dossier_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Investisseurs (module "Flash" — critères en JSON)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create table if not exists public.investisseurs (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid not null references auth.users (id) on delete cascade,
|
||||
display_name text not null,
|
||||
email text,
|
||||
phone text,
|
||||
min_margin_pct numeric(5, 2) not null default 12,
|
||||
max_ticket_eur numeric(14, 2),
|
||||
zones jsonb,
|
||||
strategies jsonb,
|
||||
notes text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists investisseurs_user_id_idx on public.investisseurs (user_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- updated_at trigger
|
||||
-- ---------------------------------------------------------------------------
|
||||
create or replace function public.set_updated_at()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
new.updated_at = now();
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists set_profiles_updated_at on public.profiles;
|
||||
create trigger set_profiles_updated_at
|
||||
before update on public.profiles
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
drop trigger if exists set_dossiers_updated_at on public.dossiers;
|
||||
create trigger set_dossiers_updated_at
|
||||
before update on public.dossiers
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
drop trigger if exists set_investisseurs_updated_at on public.investisseurs;
|
||||
create trigger set_investisseurs_updated_at
|
||||
before update on public.investisseurs
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- RLS
|
||||
-- ---------------------------------------------------------------------------
|
||||
alter table public.profiles enable row level security;
|
||||
alter table public.dossiers enable row level security;
|
||||
alter table public.dossier_visit_findings enable row level security;
|
||||
alter table public.investisseurs enable row level security;
|
||||
|
||||
-- lecture / écriture : uniquement son user_id
|
||||
create policy "profiles_self" on public.profiles
|
||||
for all using (auth.uid() = id) with check (auth.uid() = id);
|
||||
|
||||
create policy "dossiers_self" on public.dossiers
|
||||
for all using (auth.uid() = user_id) with check (auth.uid() = user_id);
|
||||
|
||||
create policy "visit_findings_via_dossier" on public.dossier_visit_findings
|
||||
for all using (
|
||||
exists (
|
||||
select 1 from public.dossiers d
|
||||
where d.id = dossier_id and d.user_id = auth.uid()
|
||||
)
|
||||
)
|
||||
with check (
|
||||
exists (
|
||||
select 1 from public.dossiers d
|
||||
where d.id = dossier_id and d.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
create policy "investisseurs_self" on public.investisseurs
|
||||
for all using (auth.uid() = user_id) with check (auth.uid() = user_id);
|
||||
|
||||
-- Catalogue visites : lecture pour tous utilisateurs authentifiés
|
||||
alter table public.visit_finding_definitions enable row level security;
|
||||
create policy "visit_finding_definitions_read" on public.visit_finding_definitions
|
||||
for select to authenticated using (true);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Auto-création profil à l'inscription (optionnel)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create or replace function public.handle_new_user()
|
||||
returns trigger language plpgsql security definer set search_path = public as $$
|
||||
begin
|
||||
insert into public.profiles (id, full_name)
|
||||
values (new.id, coalesce(new.raw_user_meta_data->>'full_name', ''))
|
||||
on conflict (id) do nothing;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists on_auth_user_created on auth.users;
|
||||
create trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute function public.handle_new_user();
|
||||
Reference in New Issue
Block a user