Initial commit — app fidélité magasin (tablette)
App Flutter de programme de fidélité : login staff, clients (nom/prénom), scan QR, factures (€→points), récompenses, réglages. Backend Supabase (schéma SQL + RLS anti-triche dans supabase/). Icône incluse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
-- ============================================================================
|
||||
-- FIDÉLITÉ — Schéma Supabase (Postgres)
|
||||
-- À coller dans Supabase → SQL Editor → New query → Run.
|
||||
-- Idempotent : on peut le relancer sans casser l'existant.
|
||||
-- ============================================================================
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 1. Fonction de génération de code (utilisée en défaut de la table clients,
|
||||
-- donc définie AVANT la table).
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- Génère un code client unique du type « FID-3F9K2A » (sans I, O, 0, 1).
|
||||
create or replace function public.generer_code_client()
|
||||
returns text
|
||||
language plpgsql
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
chars text := 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
nouveau_code text;
|
||||
i int;
|
||||
begin
|
||||
loop
|
||||
nouveau_code := 'FID-';
|
||||
for i in 1..6 loop
|
||||
nouveau_code := nouveau_code ||
|
||||
substr(chars, floor(random() * length(chars))::int + 1, 1);
|
||||
end loop;
|
||||
-- variable renommée pour éviter l'ambiguïté avec la colonne clients.code
|
||||
exit when not exists
|
||||
(select 1 from public.clients c where c.code = nouveau_code);
|
||||
end loop;
|
||||
return nouveau_code;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 2. Tables
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- Personnel du magasin (droits d'écriture). On y ajoute l'ID d'un compte auth
|
||||
-- pour qu'il devienne « staff » (voir SETUP.md).
|
||||
create table if not exists public.staff (
|
||||
user_id uuid primary key references auth.users(id) on delete cascade,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
-- Un client fidélité. Lié à un compte de connexion (auth.users) quand le client
|
||||
-- s'inscrit lui-même depuis l'app client. user_id peut être null si le staff
|
||||
-- crée un client « au comptoir » sans compte.
|
||||
create table if not exists public.clients (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid unique references auth.users(id) on delete set null,
|
||||
code text unique not null default public.generer_code_client(),
|
||||
nom text not null,
|
||||
prenom text,
|
||||
telephone text,
|
||||
points numeric not null default 0,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
-- Ajout de la colonne prénom si la table existait déjà (migration).
|
||||
alter table public.clients add column if not exists prenom text;
|
||||
|
||||
-- Historique : gain (facture) / dépense (récompense) / ajustement.
|
||||
create table if not exists public.mouvements (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
client_id uuid not null references public.clients(id) on delete cascade,
|
||||
type text not null check (type in ('facture','recompense','ajustement')),
|
||||
montant_euros numeric,
|
||||
points numeric not null default 0,
|
||||
libelle text not null default '',
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
create index if not exists idx_mouvements_client on public.mouvements(client_id);
|
||||
|
||||
-- Catalogue des récompenses.
|
||||
create table if not exists public.recompenses (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
nom text not null,
|
||||
cout_points numeric not null default 0,
|
||||
actif boolean not null default true,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
-- Réglages globaux du magasin (une seule ligne, id = 1).
|
||||
create table if not exists public.reglages (
|
||||
id int primary key default 1 check (id = 1),
|
||||
euros_par_point numeric not null default 10,
|
||||
nom_magasin text not null default ''
|
||||
);
|
||||
insert into public.reglages (id) values (1) on conflict (id) do nothing;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 3. Fonctions & triggers de logique métier
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- Vrai si l'utilisateur connecté fait partie du personnel.
|
||||
-- SECURITY DEFINER pour pouvoir lire la table staff malgré la RLS.
|
||||
create or replace function public.est_staff()
|
||||
returns boolean
|
||||
language sql
|
||||
security definer
|
||||
stable
|
||||
set search_path = public
|
||||
as $$
|
||||
select exists (select 1 from public.staff s where s.user_id = auth.uid());
|
||||
$$;
|
||||
|
||||
-- Tient à jour le solde de points du client à chaque mouvement, pour que le
|
||||
-- solde reste fiable quelle que soit l'app qui écrit.
|
||||
create or replace function public.maj_points()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
begin
|
||||
if (tg_op = 'INSERT') then
|
||||
update public.clients set points = points + new.points where id = new.client_id;
|
||||
elsif (tg_op = 'UPDATE') then
|
||||
update public.clients set points = points - old.points + new.points
|
||||
where id = new.client_id;
|
||||
elsif (tg_op = 'DELETE') then
|
||||
update public.clients set points = points - old.points where id = old.client_id;
|
||||
end if;
|
||||
return null;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists trg_maj_points on public.mouvements;
|
||||
create trigger trg_maj_points
|
||||
after insert or update or delete on public.mouvements
|
||||
for each row execute function public.maj_points();
|
||||
|
||||
-- Empêche un client (non-staff) de modifier son solde, son code ou son user_id.
|
||||
create or replace function public.proteger_client()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
begin
|
||||
if not public.est_staff() then
|
||||
new.points := old.points;
|
||||
new.code := old.code;
|
||||
new.user_id := old.user_id;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists trg_proteger_client on public.clients;
|
||||
create trigger trg_proteger_client
|
||||
before update on public.clients
|
||||
for each row execute function public.proteger_client();
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 4. Sécurité (Row Level Security)
|
||||
-- Client = lit uniquement SES données. Staff = accès total.
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
alter table public.staff enable row level security;
|
||||
alter table public.clients enable row level security;
|
||||
alter table public.mouvements enable row level security;
|
||||
alter table public.recompenses enable row level security;
|
||||
alter table public.reglages enable row level security;
|
||||
|
||||
-- staff : seul le staff peut lire la liste (personne ne s'auto-déclare staff).
|
||||
drop policy if exists staff_select on public.staff;
|
||||
create policy staff_select on public.staff
|
||||
for select using (public.est_staff());
|
||||
|
||||
-- clients
|
||||
drop policy if exists clients_select on public.clients;
|
||||
create policy clients_select on public.clients
|
||||
for select using (public.est_staff() or user_id = auth.uid());
|
||||
|
||||
drop policy if exists clients_insert on public.clients;
|
||||
create policy clients_insert on public.clients
|
||||
for insert with check (public.est_staff() or user_id = auth.uid());
|
||||
|
||||
drop policy if exists clients_update on public.clients;
|
||||
create policy clients_update on public.clients
|
||||
for update using (public.est_staff() or user_id = auth.uid());
|
||||
|
||||
drop policy if exists clients_delete on public.clients;
|
||||
create policy clients_delete on public.clients
|
||||
for delete using (public.est_staff());
|
||||
|
||||
-- mouvements : le client lit les siens ; seul le staff écrit (anti-triche).
|
||||
drop policy if exists mouvements_select on public.mouvements;
|
||||
create policy mouvements_select on public.mouvements
|
||||
for select using (
|
||||
public.est_staff()
|
||||
or client_id in (select id from public.clients where user_id = auth.uid())
|
||||
);
|
||||
|
||||
drop policy if exists mouvements_insert on public.mouvements;
|
||||
create policy mouvements_insert on public.mouvements
|
||||
for insert with check (public.est_staff());
|
||||
|
||||
drop policy if exists mouvements_update on public.mouvements;
|
||||
create policy mouvements_update on public.mouvements
|
||||
for update using (public.est_staff()) with check (public.est_staff());
|
||||
|
||||
drop policy if exists mouvements_delete on public.mouvements;
|
||||
create policy mouvements_delete on public.mouvements
|
||||
for delete using (public.est_staff());
|
||||
|
||||
-- recompenses : tout compte connecté lit ; seul le staff modifie.
|
||||
drop policy if exists recompenses_select on public.recompenses;
|
||||
create policy recompenses_select on public.recompenses
|
||||
for select using (auth.role() = 'authenticated');
|
||||
|
||||
drop policy if exists recompenses_write on public.recompenses;
|
||||
create policy recompenses_write on public.recompenses
|
||||
for all using (public.est_staff()) with check (public.est_staff());
|
||||
|
||||
-- reglages : tout compte connecté lit ; seul le staff modifie.
|
||||
drop policy if exists reglages_select on public.reglages;
|
||||
create policy reglages_select on public.reglages
|
||||
for select using (auth.role() = 'authenticated');
|
||||
|
||||
drop policy if exists reglages_update on public.reglages;
|
||||
create policy reglages_update on public.reglages
|
||||
for update using (public.est_staff()) with check (public.est_staff());
|
||||
Reference in New Issue
Block a user