Modèle de données¶
Schéma d'origine (Etude.xlsx)¶
Premier jet, issu du fichier d'étude initial. Sert de base mais a été jugé incomplet pour le cas d'usage horeca (pas de suivi d'exécution, pas de notion de personnel/appareil) — voir schéma révisé ci-dessous.
| AlarmesTypes | Alarmes | Scheduler | ScheduleType | Actions |
|---|---|---|---|---|
| id | id | id | id | id |
| name | idtype | idtype | period | name |
| description | idschedule | idscheduletype | (énum period ci-dessous) | procedure |
| yesno | yesno | massage (sic, = message) | ||
| text | text | |||
| picture | picture | alarme | ||
| procedure | date | |||
| idaction | time | |||
| annuler |
Valeurs de l'énumération period (ScheduleType) : journalier, hebdomadaire, mensuel, annuel, journalier
répétitif, autre, oneshot.
Ambiguïté non résolue
Scheduler.idtype fait doublon avec AlarmesTypes.id dans le fichier source — pas encore clarifié.
Multi-tenant¶
Décision prise le 2026-08-30 (voir architecture pour le raisonnement coût) : un seul projet Supabase partagé sert plusieurs établissements clients, au lieu d'un projet dédié par client. Concrètement :
- Nouvelle table
etablissements(le "tenant") : id, nom, actif. - Une colonne
id_etablissement(non nulle,references etablissements(id)) ajoutée sur chaque table métier :postes,personnel,devices,pairing_tokens,alarmes_types,schedules,alarmes,actions. Dénormalisée (redondante avec ce qui serait déductible viaid_poste) volontairement — ça rend les futures policies de sécurité (RLS) simples et rapides à écrire, sans jointure. - Toutes les données de test existantes sont rattachées à un établissement pilote fixe, id
00000000-0000-0000-0000-000000000001, via une valeurDEFAULTsur la colonne — la migration ne casse rien de ce qui existait déjà.
Isolation pas encore garantie par la base
La colonne existe mais Row Level Security n'est pas activée — voir l'avertissement détaillé dans architecture.
Schéma révisé (validé, à traduire en DDL Postgres)¶
Ajouts par rapport au schéma d'origine : gestion du personnel, des appareils appairés par QR code, du ciblage des tâches, et surtout d'un statut de suivi d'exécution (absent du schéma d'origine, qui n'avait qu'un flag d'annulation).
- personnel — membres du staff (nom, poste, actif)
- postes — zones/stations génériques (cuisine, bar, salle, sanitaires…) pour cibler une tâche sans forcément nommer une personne
- devices — tablette/téléphone appairé via QR code : token, lié à une personne ou un poste, horodatage du dernier appairage et du dernier vu (heartbeat en ligne/hors ligne) — voir encadré ci-dessous
- alarmes_types — catalogue réutilisable des types de tâche (ex. Réassort, Nettoyage sanitaire)
- schedules — règles de récurrence (journalier, hebdomadaire, mensuel, annuel, journalier répétitif,
oneshot, autre), détachées du type pour pouvoir planifier un même type différemment selon les jours.
L'heure/fréquence précise est stockée en expression cron dans
cron_expr— voir encadré ci-dessous. - alarmes — tâches concrètes générées par un planning ou créées à la main par le responsable
(
id_schedulenullable pour couvrir l'envoi de listes ad hoc). Statut :en_attente/vue/faite/annulée/expirée, avec qui l'a traitée et quand. - actions — simplifié à
message_ecranetprocedurepour l'instant (mail et push retirés du scope, voir architecture) - pairing_tokens — jeton QR éphémère (5 min) utilisé pour appairer un téléphone à un poste (ajouté à l'implémentation, voir développement)
À quoi sert devices ?
C'est la mémoire de "qui/quel poste est branché sur quel appareil physique", remplie via le flux QR code — sans jamais demander de mot de passe.
- Le
token= l'identité de l'appareil. Quand un téléphone scanne le QR du kiosque, il reçoit un token généré côté client, stocké dans sonlocalStorage. À chaque réouverture de l'app, le token est relu et cherché dansdevicespour savoir "je suis le téléphone du poste Bar" (voir développement). - Cible du device : un poste OU une personne, jamais les deux (
id_posteXORid_personnel, contrainteCHECK). Une tablette de poste (kiosque fixe cuisine/bar/...) aid_posterempli ; un téléphone nominatif auraitid_personnelrempli (pas encore utilisé dans le flux actuel, qui reste au niveau poste — voir points ouverts). dernier_vu: horodatage mis à jour à l'ouverture — un futur "heartbeat" en ligne/hors ligne, pas encore exploité visuellement.actif: permet de désactiver un appairage (ex. bouton "DÉCONNECTER" du handoff design, pas encore construit) sans supprimer l'historique.- Usage concret dans le code : c'est cette table qui alimente la liste de noms dans l'overlay "Qui a
fait cette tâche ?" du kiosque (
Kiosque.jsxinterrogepersonneljoint àdevicesoùactif=truepour le poste courant) — seules les personnes réellement appairées à un téléphone apparaissent.
C'est quoi une expression cron ?
La notation standard (utilisée depuis des décennies dans plein d'outils) pour décrire une récurrence,
en 5 champs séparés par des espaces : minute heure jour-du-mois mois jour-de-semaine. * veut dire
"n'importe quelle valeur", */N veut dire "tous les N". Exemples issus des données de test :
cron_expr |
Signification |
|---|---|
0 10 * * * |
tous les jours à 10h00 |
0 8 * * * |
tous les jours à 8h00 |
0 */2 * * * |
toutes les 2 heures, pile à l'heure |
0 */4 * * * |
toutes les 4 heures |
Ce champ n'est pas encore exploité : rien ne lit cron_expr pour générer automatiquement une
alarme à l'échéance — les alarmes de test ont été créées à la main. C'est le rôle du scheduler,
dernière étape de la feuille de route (service qui regarde périodiquement les plannings et crée les
tâches dues).
Schéma entité-relation¶
erDiagram
ETABLISSEMENTS ||--o{ POSTES : "tenant"
ETABLISSEMENTS ||--o{ PERSONNEL : "tenant"
ETABLISSEMENTS ||--o{ DEVICES : "tenant"
ETABLISSEMENTS ||--o{ ACTIONS : "tenant"
ETABLISSEMENTS ||--o{ ALARMES_TYPES : "tenant"
ETABLISSEMENTS ||--o{ SCHEDULES : "tenant"
ETABLISSEMENTS ||--o{ ALARMES : "tenant"
POSTES ||--o{ PERSONNEL : "poste habituel"
POSTES ||--o{ DEVICES : "appairé à"
PERSONNEL ||--o{ DEVICES : "appairé à"
ACTIONS ||--o{ ALARMES_TYPES : "action par défaut"
ALARMES_TYPES ||--o{ SCHEDULES : "planifié par"
ALARMES_TYPES ||--o{ ALARMES : "génère"
SCHEDULES ||--o{ ALARMES : "déclenche"
POSTES ||--o{ ALARMES : "cible"
PERSONNEL ||--o{ ALARMES : "assignée à"
PERSONNEL ||--o{ ALARMES : "traitée par"
ETABLISSEMENTS {
uuid id PK
text nom
bool actif
}
POSTES {
uuid id PK
uuid id_etablissement FK
text nom
}
PERSONNEL {
uuid id PK
uuid id_etablissement FK
text nom
uuid id_poste FK
bool actif
}
DEVICES {
uuid id PK
uuid id_etablissement FK
text token
text type
uuid id_personnel FK
uuid id_poste FK
timestamp date_appairage
timestamp dernier_vu
bool actif
}
ACTIONS {
uuid id PK
uuid id_etablissement FK
text nom
text type
}
ALARMES_TYPES {
uuid id PK
uuid id_etablissement FK
text nom
text description
uuid id_poste_defaut FK
uuid id_action FK
bool actif
}
SCHEDULES {
uuid id PK
uuid id_etablissement FK
uuid id_alarme_type FK
text type_recurrence
text cron_expr
bool actif
}
ALARMES {
uuid id PK
uuid id_etablissement FK
uuid id_alarme_type FK
uuid id_schedule FK
uuid id_poste FK
uuid id_personnel_assigne FK
text titre
timestamp date_echeance
text statut
uuid id_personnel_traitant FK
timestamp date_traitement
timestamp created_at
}
devices.id_personnel et devices.id_poste sont mutuellement exclusifs (un appareil est appairé à une
personne ou à un poste, pas les deux) — de même pour alarmes.id_poste et alarmes.id_personnel_assigne,
tant que le point ouvert sur le ciblage n'est pas tranché.
Points encore ouverts : voir besoin fonctionnel (ciblage poste vs
personne, définition concrète de procedure).
Implémentation¶
Le DDL Postgres est dans db/schema.sql
(appliqué sur le projet Supabase du projet), avec les exclusions mutuelles ci-dessus en CHECK constraints.
Des données de test représentatives (postes, personnel, types d'alarme, plannings, alarmes dans tous les
statuts, devices appairés) sont dans db/seed.sql,
complété par db/seed_extra.sql
(plus de volume par poste, dont un poste rempli à la capacité de la grille kiosque). Les deux fichiers
s'appliquent l'un après l'autre, une seule fois chacun (pas de ON CONFLICT — pas idempotents).