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:
2026-07-17 21:37:06 +02:00
commit 1d3ee69c6d
60 changed files with 4468 additions and 0 deletions
+226
View File
@@ -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());