From fae5c237226161bfb72039c9022cded1efccb4ee Mon Sep 17 00:00:00 2001 From: Imugiii Date: Mon, 15 Jun 2026 08:04:13 +0000 Subject: [PATCH] docs(uml): add security sequence + v0.2 drift fixes (sequence, state, use-cases) --- docs/uml/security-sequence.md | 232 +++++++++++++++++ docs/uml/sequence-passer-commande.md | 151 ++++++----- docs/uml/state-commande.md | 152 +++++------ docs/uml/use-cases.md | 374 ++++++++++++++++----------- 4 files changed, 622 insertions(+), 287 deletions(-) create mode 100644 docs/uml/security-sequence.md diff --git a/docs/uml/security-sequence.md b/docs/uml/security-sequence.md new file mode 100644 index 0000000..1d39c69 --- /dev/null +++ b/docs/uml/security-sequence.md @@ -0,0 +1,232 @@ +# Diagramme de sequence securite - Annulation de commande avec PIN (CANCEL_ORDER) + +**Phase UML** : P1 - Conception, complement UML (passe security-by-design) +**Statut** : v0.2 - flux sensible PIN-gate + audit_log (controle anti-fraude interne) +**Date** : 2026-06-12 +**Branche** : `feat/p1-conception` +**Auteur methodologie** : BYAN + +--- + +## 1. Objet du document + +Ce document decrit le **flux temporel securise** de l'annulation d'une commande +en back-office (`CANCEL_ORDER`). L'annulation est une action de **manipulation +d'argent** : annuler une commande deja `paid` peut servir a masquer un +detournement d'especes (l'equipier encaisse, annule, garde le cash). Le flux +ci-dessous materialise le controle qui adresse ce risque : une **re-authentification +par PIN par equipier** (`RG-T13`) avant l'execution, et l'ecriture d'une ligne +**`audit_log` immuable** dans la meme transaction que l'effet (`RG-T14`), de sorte +que chaque annulation est rattachee a une personne meme sur un poste partage. + +Le diagramme reste au niveau conceptuel / logique. Il nomme les echanges entre +participants sans detailler l'implementation PHP ni le SQL exact. Il complete +l'operation `CANCEL_ORDER` du `docs/merise/mlt.md` (7.1), la transition T5 de +`docs/uml/state-commande.md` (`paid -> cancelled`, re-credit du stock) et le cas +d'utilisation "Annuler une commande" de `docs/uml/use-cases.md` (UC13). + +**Sources** : +- `docs/merise/mlt.md` 7.1 `CANCEL_ORDER` (PRE-1..PRE-3, RG-1..RG-6, POST, ERR) +- `docs/merise/mlt.md` section 2 : `RG-T13` (PIN sensible), `RG-T14` (audit_log), `RG-T11` (re-credit dans la meme transaction), `RG-T07` (garde de concurrence), `RG-T08` (transaction atomique) +- `docs/merise/dictionary.md` 3.14 (`user.pin_hash`, argon2id) et 3.20 (`audit_log`) +- `docs/uml/state-commande.md` T5 (`paid -> cancelled`, `stock_movement` type `cancellation`) + +--- + +## 2. Participants + +| Participant | Role | Couche | +|---|---|---| +| **Equipier** | Counter / Drive / Admin titulaire de `order.cancel`, sur poste partage | Acteur | +| **Admin UI** | Interface back-office (Bloc 3, formulaire d'annulation + saisie PIN) | Presentation | +| **Controleur** | Back-end REST sous `/api/*` (Bloc 2), orchestre la transaction | Application | +| **Verif PIN** | Service de verification du PIN (`password_verify` argon2id) | Application | +| **BDD** | Base de donnees MariaDB | Persistance | + +La session est **partagee par poste de travail** pour le routine 95% ; le PIN +re-introduit une attribution individuelle sur le sous-ensemble sensible +(`mlt.md` `RG-T13`). Le PIN n'est pas une session : il est verifie a chaque action +sensible et sert a capturer le `user_id` qui sera ecrit dans `audit_log`. + +--- + +## 3. Diagramme de sequence + +```mermaid +sequenceDiagram + actor Equipier + participant AdminUI as Admin UI + participant Ctrl as Controleur + participant PIN as Verif PIN + participant BDD + + Note over Equipier,BDD: Phase 1 - Demande et controle de permission + + Equipier->>AdminUI: demander l'annulation d'une commande + AdminUI->>Ctrl: POST /api/orders/{id}/cancel + Ctrl->>Ctrl: verifier session active + is_active = 1 (RG-T02) + Ctrl->>BDD: lire role_permission (permission order.cancel ?) (RG-T03, PRE-1) + BDD-->>Ctrl: permission presente + Ctrl->>BDD: lire customer_order (existe ? statut ?) (PRE-2, PRE-3) + BDD-->>Ctrl: status courant + + alt status hors ['pending_payment','paid'] (delivered ou cancelled) + Ctrl-->>AdminUI: 422 CANNOT_CANCEL_IN_STATE {current_status} + AdminUI-->>Equipier: refus, aucun changement d'etat (ERR-1) + else status annulable + Note over Equipier,BDD: Phase 2 - Re-authentification PIN (RG-T13) + + Ctrl-->>AdminUI: demander la saisie du PIN + Equipier->>AdminUI: saisir le PIN + AdminUI->>Ctrl: soumettre le PIN (re-auth action sensible) + Ctrl->>PIN: verifier le PIN soumis + PIN->>BDD: lire user.pin_hash de l'equipier + BDD-->>PIN: pin_hash (argon2id) + PIN->>PIN: password_verify(pin, pin_hash) + + alt PIN incorrect + PIN-->>Ctrl: echec + Ctrl-->>AdminUI: refus du PIN, action rejetee + AdminUI-->>Equipier: PIN incorrect, aucun changement d'etat + else PIN correct + PIN-->>Ctrl: succes, acting user_id capture (RG-T13) + + Note over Equipier,BDD: Phase 3 - Transaction atomique (RG-T08, RG-T11) + + Ctrl->>BDD: BEGIN transaction + Ctrl->>BDD: UPDATE customer_order SET status = 'cancelled', cancelled_at = NOW() WHERE id = :id AND status IN ('pending_payment','paid') (RG-1, RG-T07) + BDD-->>Ctrl: lignes affectees + + alt 0 ligne affectee (annulation concurrente) + Ctrl->>BDD: ROLLBACK + Ctrl-->>AdminUI: 409 INVALID_TRANSITION (ERR-2) + AdminUI-->>Equipier: deja annulee par un autre poste + else 1 ligne affectee + opt statut anterieur = paid (re-credit du stock) + Ctrl->>BDD: UPDATE ingredient SET stock_quantity = stock_quantity + :units (par ingredient consomme) (RG-3) + Ctrl->>BDD: INSERT stock_movement (type cancellation, delta +units, order_id, user_id de l'equipier) (RG-3) + end + Ctrl->>BDD: INSERT audit_log (action_code order.cancel, actor_user_id, actor_role_id, entity_type customer_order, entity_id, summary [statut anterieur + montant re-credite]) (RG-6, RG-T14) + Ctrl->>BDD: COMMIT + BDD-->>Ctrl: transaction validee + Ctrl-->>AdminUI: 200 annulation confirmee (OUT-1) + AdminUI-->>Equipier: commande annulee, trace enregistree + end + end + end +``` + +--- + +## 4. Notes de modelisation : chaque pas et sa regle + +Le tableau ci-dessous mappe chaque interaction du diagramme a la regle +`mlt.md` 7.1 ou a la regle transverse correspondante, et a l'entite ecrite. + +| # | Interaction | Regle (mlt.md) | Entite ecrite / lue | +|---|---|---|---| +| 1 | Verifier session active + `is_active = 1` | `RG-T02` | `user` (lecture) | +| 2 | Verifier `order.cancel` via `role_permission` | `RG-T03`, 7.1 PRE-1 | `role_permission` (lecture) | +| 3 | Charger la commande et lire son `status` | 7.1 PRE-2, PRE-3 | `customer_order` (lecture) | +| 4 | Bloquer si `status` est `delivered` ou `cancelled` | 7.1 ERR-1 | aucune ecriture (HTTP 422) | +| 5 | Demander + verifier le PIN (`password_verify` argon2id) | `RG-T13`, 7.1 RG-6 | `user.pin_hash` (lecture) | +| 6 | Rejeter si PIN incorrect, sans changement d'etat | `RG-T13` | aucune ecriture | +| 7 | Capturer l'`acting user_id` pour l'audit | `RG-T13` | (en memoire, sert aux pas 11-12) | +| 8 | `BEGIN` transaction | `RG-T08` | transaction | +| 9 | `UPDATE customer_order SET status='cancelled'` avec garde `AND status IN (...)` | 7.1 RG-1, `RG-T07` | `customer_order` (ecriture) | +| 10 | `ROLLBACK` + 409 si 0 ligne affectee (concurrence) | 7.1 ERR-2, `RG-T07` | aucune ecriture nette | +| 11 | Re-credit conditionnel du stock si statut anterieur `paid` | 7.1 RG-3, `RG-T11` | `ingredient`, `stock_movement` (type `cancellation`) | +| 12 | `INSERT audit_log` dans la meme transaction | 7.1 RG-6, `RG-T14` | `audit_log` (ecriture) | +| 13 | `COMMIT` (tout ou rien) | `RG-T08`, `RG-T11` | transaction | +| 14 | Reponse 200 de confirmation | 7.1 OUT-1 | aucune ecriture | + +### 4.1 Re-credit conditionnel du stock (`RG-T11`) + +Le re-credit ne s'applique que si la commande etait au statut `paid` avant +l'annulation (7.1 RG-3). Une commande `pending_payment` n'avait pas encore +decremente le stock (le decrement a lieu a la transition `paid`), donc il n'y a +rien a re-crediter. Pour chaque `order_item` d'une commande `paid`, les unites +consommees sont recalculees (format `normal`/`maxi`, ajustees par les +`order_item_modifier`), `ingredient.stock_quantity` est re-incremente et un +`stock_movement` de type `cancellation` est insere. `RG-T11` garantit que ce +re-credit et l'`UPDATE` du statut sont dans la **meme transaction** : il n'y a pas +de decrement orphelin si une etape echoue. + +### 4.2 Garde de concurrence (`RG-T07`) + +L'`UPDATE` porte la clause `AND status IN ('pending_payment','paid')`. Si deux +postes tentent d'annuler la meme commande au meme instant, seul le premier +obtient une ligne affectee ; le second recoit 0 ligne et le controleur repond +409 `INVALID_TRANSITION` apres `ROLLBACK` (7.1 ERR-2). Cette garde optimiste +reduit le risque d'une double annulation et d'un double re-credit du stock. + +### 4.3 PIN distinct de la session (`RG-T13`) + +La session reste **partagee par poste** pour le flux routine. Le PIN est verifie +a chaque action du sous-ensemble sensible (annulation, prix/VAT, RBAC, gestion +utilisateur, correction d'inventaire), et c'est lui qui fournit l'`actor_user_id` +ecrit dans `audit_log`. Le `pin_hash` est un hash argon2id (`dictionary.md` 3.14), +compare via `password_verify` ; il fait partie des champs RESTRICTED tenus hors +des logs et des reponses API. + +--- + +## 5. Menace adressee : repudiation et detournement d'especes + +L'annulation d'une commande `paid` est le geste qui permet le schema de fraude +"encaisser puis annuler pour garder le cash" (insider cash-skim). Sans controle, +sur un poste a session partagee, une annulation ne serait rattachee a personne : +l'auteur pourrait nier l'avoir faite (repudiation). Le flux ci-dessus reduit le +risque de ce schema en combinant deux mecanismes concrets : + +- **PIN par equipier (`RG-T13`)** : l'annulation exige une re-authentification + individuelle. Sur un poste partage, cela rattache l'acte a une personne et non + au seul poste. Le PIN tend a dissuader l'usage opportuniste d'une session + laissee ouverte par un collegue. +- **`audit_log` immuable (`RG-T14`)** : chaque annulation ecrit une ligne + `audit_log` (`action_code = order.cancel`, `actor_user_id`, `actor_role_id`, + `entity_type`, `entity_id`, `summary` avec le statut anterieur et le montant + re-credite) dans la **meme transaction** que l'`UPDATE` du statut. La table + n'accepte ni `UPDATE` ni `DELETE` au niveau applicatif (`dictionary.md` 3.20). + Une annulation ne peut donc pas exister sans sa trace, et la trace ne peut pas + etre effacee par l'auteur. + +L'effet combine : un pic d'annulations rattachees a un meme `actor_user_id` +devient visible et opposable lors d'une revue. Ceci ne supprime pas le risque, +mais le **reduit** en transformant un acte anonyme et niable en un acte attribue +et trace. La residualite (collusion, partage de PIN) releve de controles +organisationnels hors du modele de donnees. + +> Note : `audit_log` enregistre des **noms de champs** et un `summary` +> non-personnel (`details` stocke les noms de champs modifies, pas de PII), +> conformement a `RG-T14` et a la classification de `PROJECT_CONTEXT.md` section 19. +> L'attribution `stock_movement.user_id` du re-credit complete la trace cote stock +> sans double journalisation. + +--- + +## 6. Coherence avec les autres livrables + +| Verification | Resultat | +|---|---| +| Statuts annulables coherents avec `state-commande.md` | Oui : `pending_payment` et `paid` (T3, T5) ; `delivered` non annulable (7.1 ERR-1) | +| Transition `paid -> cancelled` avec re-credit | Couverte par T5 et 7.1 RG-3 (`stock_movement` type `cancellation`) | +| Entites ecrites presentes au dictionnaire | `customer_order` (3.10), `ingredient`, `stock_movement`, `audit_log` (3.20) | +| Regle PIN appliquee | `RG-T13` (sous-ensemble sensible inclut 7.1) ; `user.pin_hash` (3.14) | +| Regle audit appliquee | `RG-T14` ; colonnes conformes a `audit_log` (3.20) | +| Atomicite re-credit + statut + audit | `RG-T08` + `RG-T11` (une transaction, `COMMIT` / `ROLLBACK`) | +| Codes d'erreur | 422 `CANNOT_CANCEL_IN_STATE` (ERR-1), 409 `INVALID_TRANSITION` (ERR-2) | + +--- + +## 7. Arbitrage tranche + +Le flux retient la re-authentification **par PIN** plutot qu'une re-saisie du mot +de passe complet : le PIN couvre le sous-ensemble sensible sans casser le routine +95% a session partagee (`RG-T13`), tout en fournissant l'attribution +individuelle. L'`audit_log` est ecrit dans la **meme transaction** que l'effet +(`RG-T14` + `RG-T08`) : une annulation sans trace ne peut pas etre committee. Le +re-credit du stock est conditionnel au statut anterieur `paid` (`RG-T11`), ce qui +ecarte un re-credit indu sur une commande `pending_payment` qui n'a pas ete +decrementee. Les codes d'erreur (`CANNOT_CANCEL_IN_STATE`, `INVALID_TRANSITION`) +reprennent ceux de `mlt.md` 7.1, sans en inventer de nouveaux. diff --git a/docs/uml/sequence-passer-commande.md b/docs/uml/sequence-passer-commande.md index 3b00cfe..9ffd0a8 100644 --- a/docs/uml/sequence-passer-commande.md +++ b/docs/uml/sequence-passer-commande.md @@ -1,8 +1,8 @@ # Diagramme de sequence - Passer une commande (borne client) **Phase UML** : P1 - Conception, complement UML (apres MCD) -**Statut** : v0.1 -**Date** : 2026-05-21 +**Statut** : v0.2 - prod-like, creation atomique (create + pay) +**Date** : 2026-06-11 **Branche** : `feat/p1-conception` **Auteur methodologie** : BYAN @@ -12,18 +12,24 @@ Ce document decrit le **flux temporel** du parcours "passer une commande" cote **Client sur la borne kiosk** : navigation dans les categories, selection d'un -produit ou composition d'un menu, gestion du panier, validation avec saisie du -numero de retrait, paiement, puis confirmation. +produit ou composition d'un menu (slots + format Normal/Maxi + modifiers +d'ingredients), gestion du panier, validation avec saisie du numero de retrait, +et confirmation. Dans le modele v0.2, la **creation et le paiement sont +atomiques** : un seul appel `POST /api/orders` cree la commande, la fait passer a +`paid`, decremente le stock et journalise les mouvements, dans une meme +transaction. -Le diagramme reste au niveau **conceptuel / logique**. Il nomme les echanges -entre participants sans detailler l'implementation PHP (controllers, models) -ni le SQL exact. Il complete le cas d'utilisation "Passer une commande" de -`docs/uml/use-cases.md` et la machine a etats de `docs/uml/state-commande.md`. +Le diagramme reste au niveau conceptuel / logique. Il nomme les echanges entre +participants sans detailler l'implementation PHP ni le SQL exact. Il complete le +cas d'utilisation "Passer une commande" de `docs/uml/use-cases.md` (4.1), la +machine a etats de `docs/uml/state-commande.md` (T1/T2) et l'operation +`CREATE_ORDER` du `docs/merise/mct.md` (3.3). **Sources** : - `docs/PROJECT_CONTEXT.md` section 2 (processus metier), section 7 (endpoints API) -- `docs/merise/dictionary.md` (`commande`, `ligne_commande`, `menu`, `produit`) -- `docs/uml/state-commande.md` (transitions `pending_payment -> paid`) +- `docs/merise/dictionary.md` 3.10-3.13 (`customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`) +- `docs/merise/mct.md` 3.3 CREATE_ORDER (transaction, snapshots, decrement stock) +- `docs/uml/state-commande.md` (transitions T1/T2, atomicite `pending_payment -> paid`) --- @@ -71,16 +77,18 @@ sequenceDiagram alt Produit a la carte Client->>Borne: selectionner un produit - Client->>Borne: regler taille / options + opt Personnaliser les ingredients + Client->>Borne: retirer / ajouter un ingredient + end Borne->>Borne: ajouter la ligne au panier local else Composition d'un menu Client->>Borne: selectionner un menu - Borne->>API: GET /api/menus (composition du menu) - API->>BDD: lire menu et composition - BDD-->>API: menu + produits par role + Borne->>API: GET /api/menus (slots + options eligibles) + API->>BDD: lire menu, menu_slot, menu_slot_option + BDD-->>API: menu + slots + options API-->>Borne: composition (JSON) - Borne-->>Client: afficher les choix par slot (burger, accompagnement, boisson, sauce) - Client->>Borne: choisir chaque composant + tailles + Borne-->>Client: afficher les slots (boisson, accompagnement, sauce) + format Normal/Maxi + Client->>Borne: choisir chaque slot + format + modifiers du burger Borne->>Borne: ajouter la ligne menu au panier local end @@ -94,36 +102,35 @@ sequenceDiagram Borne-->>Client: panier mis a jour end - Note over Client,BDD: Phase 4 - Validation du panier et saisie du numero + Note over Client,BDD: Phase 4 - Validation, saisie du numero, creation atomique (create + pay) Client->>Borne: valider la commande Client->>Borne: saisir le numero de retrait - Borne->>Borne: valider le panier (au moins 1 ligne) - Borne->>API: POST /api/orders (lignes + mode_consommation + numero) + Borne->>Borne: valider le panier (au moins 1 ligne, numero non vide) + Borne->>API: POST /api/orders (lignes + selections + modifiers + service_mode + numero) - API->>API: recalculer les totaux cote serveur - API->>BDD: creer la commande (statut pending_payment) - API->>BDD: creer les lignes (snapshot libelle + prix) - BDD-->>API: commande persistee {id, numero, statut: pending_payment} - API-->>Borne: 201 Created {id, numero, statut: pending_payment, total} - Borne-->>Client: afficher le total a regler + API->>API: recalculer les totaux cote serveur (HT / TVA / TTC, taux par produit) + API->>BDD: BEGIN transaction + API->>BDD: INSERT customer_order (status pending_payment, source kiosk) + API->>BDD: INSERT order_item (snapshot libelle + prix + vat_rate) + API->>BDD: INSERT order_item_selection (par slot de menu rempli) + API->>BDD: INSERT order_item_modifier (par modification d'ingredient) + API->>BDD: UPDATE ingredient.stock_quantity (decrement, ajuste par modifiers) + API->>BDD: INSERT stock_movement (type sale, par unite consommee) + API->>BDD: UPDATE customer_order status -> paid, paid_at = NOW() + API->>BDD: COMMIT + BDD-->>API: commande persistee {id, order_number, status: paid} - Note over Client,BDD: Phase 5 - Paiement (pending_payment -> paid) + Note over Client,BDD: Phase 5 - Confirmation - Client->>Borne: payer la commande - Borne->>API: POST /api/orders/{id}/pay - API->>BDD: enregistrer le paiement, passer la commande a paid (paye_a) - BDD-->>API: commande mise a jour {id, numero, statut: paid} - - Note over Client,BDD: Phase 6 - Confirmation - - API-->>Borne: 200 OK {id, numero, statut: paid} - Borne-->>Client: ecran de confirmation avec le numero + API-->>Borne: 201 Created {id, order_number, status: paid, total_ttc} + Borne-->>Client: ecran de confirmation avec le numero de retrait Note over Client,BDD: Cas d'erreur - alt Panier vide ou donnees invalides - API-->>Borne: 4xx {error: code, message} + alt Panier vide, produit indisponible ou donnees invalides + API->>BDD: ROLLBACK (si transaction entamee) + API-->>Borne: 4xx {error: {code, message}} Borne-->>Client: message d'erreur, retour au panier end ``` @@ -132,27 +139,31 @@ sequenceDiagram ## 4. Notes de modelisation -### 4.1 Recalcul des totaux cote serveur +### 4.1 Recalcul des totaux cote serveur (controle de securite) La Borne affiche un total **provisoire** calcule localement pour l'experience utilisateur. L'API recalcule les totaux a la reception du `POST /api/orders` a -partir des prix en base, puis fige les snapshots -(`prix_unitaire_ttc_cents_snapshot`, `libelle_snapshot` dans `ligne_commande`, -voir `dictionary.md` 3.6). Le total affiche par le client n'est pas considere -comme la source de verite : ceci limite la falsification du prix cote client. +partir des prix en base (HT, TVA ligne par ligne via `vat_rate`, TTC), puis fige +les snapshots (`unit_price_cents_snapshot`, `vat_rate_snapshot`, +`label_snapshot` sur `order_item`, voir `dictionary.md` 3.11). Le total affiche +par le client n'est pas considere comme la source de verite : ceci limite la +falsification du prix cote client. -### 4.2 Transitions de statut +### 4.2 Creation atomique (create + pay) Le parcours materialise les transitions T1 et T2 de -`docs/uml/state-commande.md`, en deux phases successives conformes a la regle -metier : +`docs/uml/state-commande.md` dans **un seul appel et une seule transaction** : -- `POST /api/orders` cree la commande composee en `pending_payment` (T1). -- `POST /api/orders/{id}/pay` enregistre le paiement et fait passer la commande - a `paid` (T2), avec l'horodatage `paye_a`. +- `POST /api/orders` cree la commande en `pending_payment` (T1) puis la fait + passer a `paid` (T2) avant le `COMMIT`. `paid_at` est renseigne. +- La saisie du numero de retrait tient lieu de paiement (cadre RNCP) ; il n'y a + pas d'appel `POST /api/orders/{id}/pay` separe (supprime par rapport au v0.1). +- Le decrement du stock (`ingredient.stock_quantity`) et la journalisation + (`stock_movement` type `sale`) sont inclus dans la meme transaction que + l'insert de la commande : soit tout reussit, soit tout est annule (`ROLLBACK`). -La separation des deux appels reflete les deux phases du cycle de vie : -composer la commande, puis la payer. +Le statut `pending_payment` n'est donc pas observable en dehors de la +transaction (coherent avec `mct.md` section 13). ### 4.3 Panier local jusqu'a la validation @@ -166,9 +177,20 @@ navigateur peut etre envisage plus tard. `PROJECT_CONTEXT.md` section 4 prevoit un mode de repli ou la Borne lit des fichiers JSON statiques si l'API est indisponible. Ce mode concerne uniquement -les lectures (phases 1 a 2). La validation (phase 4) et le paiement (phase 5) -requierent l'API ; sans elle, la commande n'est ni persistee ni payee. Ce cas -degrade n'est pas detaille dans le diagramme nominal ci-dessus. +les lectures (phases 1 a 2). La validation et la creation (phase 4) requierent +l'API ; sans elle, la commande n'est ni persistee ni payee. Ce cas degrade +n'est pas detaille dans le diagramme nominal ci-dessus. + +### 4.5 Garde-fous securite a venir (passe security-by-design) + +Le flux ci-dessus est la cible **fonctionnelle** v0.2. La passe security-by-design +ajoutera, en complement (append, sans reecrire ce flux), des garde-fous sur le +`POST /api/orders` anonyme : cle d'idempotence (`idempotency_key` UNIQUE pour +dedupliquer les POST rejoues), limitation de debit / anti-spam, et verrou +pessimiste `SELECT ... FOR UPDATE` sur les ingredients pendant le decrement +(anti-oversell multi-bornes). Ces ajouts dependent de decisions encore a +trancher (oversell/idempotence, throttling) et seront documentes dans un artefact +`docs/uml/security-sequence.md` dedie. --- @@ -176,18 +198,21 @@ degrade n'est pas detaille dans le diagramme nominal ci-dessus. | Verification | Resultat | |---|---| -| Endpoints utilises existent dans `PROJECT_CONTEXT.md` section 7 | `GET /api/categories`, `GET /api/products`, `GET /api/menus`, `POST /api/orders` ; `POST /api/orders/{id}/pay` est a confirmer en section 7 du brief | -| Entites manipulees presentes au MCD | Oui : `categorie`, `produit`, `menu`, `menu_produit`, `commande`, `ligne_commande` | -| Statuts utilises coherents avec `state-commande.md` | Oui : `pending_payment` puis `paid` (T1, T2), valeurs ENUM anglaises | -| Format de reponse JSON | Coherent avec `PROJECT_CONTEXT.md` section 7 (`{data, error}`) et la reponse `{id, number, status}` du POST orders | +| Endpoints utilises existent dans `PROJECT_CONTEXT.md` section 7 | `GET /api/categories`, `GET /api/products`, `GET /api/menus`, `POST /api/orders` ; l'appel `POST /api/orders/{id}/pay` du v0.1 est supprime (creation atomique) | +| Entites manipulees presentes au MCD / dictionnaire | Oui : `category`, `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`, `stock_movement` | +| Statuts utilises coherents avec `state-commande.md` | Oui : `pending_payment` puis `paid` (T1, T2), atomiques | +| Operation MCT correspondante | `mct.md` 3.3 CREATE_ORDER (transaction unique, snapshots, decrement stock, transition atomique) | +| Format de reponse JSON | Coherent avec `PROJECT_CONTEXT.md` section 7 (`{data, error}`) et la reponse `{id, order_number, status, total_ttc}` du POST orders | --- ## 6. Arbitrage tranche -La phase de paiement est integree au flux conformement a la regle metier des -deux phases (composer puis payer). La sequence suit la machine canonique de -`state-commande.md` : creation en `pending_payment` (T1) puis paiement vers -`paid` (T2), avec des valeurs ENUM en anglais. Point a confirmer au MCT : -l'endpoint de paiement (`POST /api/orders/{id}/pay`) doit etre reporte dans la -section 7 du brief s'il n'y figure pas encore. +La phase de paiement separee du v0.1 (`POST /api/orders/{id}/pay`) est supprimee : +la creation et le passage a `paid` sont atomiques dans `POST /api/orders`, +conformement au MCT v0.2 (3.3) et a la regle metier (saisie du numero = substitut +de paiement). Le decrement de stock et la journalisation `stock_movement` sont +inclus dans la meme transaction, garantissant la coherence stock/commande. Les +valeurs ENUM sont en anglais (`pending_payment`, `paid`). Les garde-fous de +securite (idempotence, rate-limit, verrou pessimiste) relevent de la passe +security-by-design et seront ajoutes en complement (section 4.5). diff --git a/docs/uml/state-commande.md b/docs/uml/state-commande.md index a99f309..55c38cd 100644 --- a/docs/uml/state-commande.md +++ b/docs/uml/state-commande.md @@ -1,8 +1,8 @@ # Diagramme d'etats-transitions - Commande **Phase UML** : P1 - Conception, complement UML (apres MCD) -**Statut** : v0.1 -**Date** : 2026-05-21 +**Statut** : v0.2 - prod-like, machine a 4 etats +**Date** : 2026-06-11 **Branche** : `feat/p1-conception` **Auteur methodologie** : BYAN @@ -10,38 +10,41 @@ ## 1. Objet du document -Ce document formalise la **machine a etats** de l'attribut `commande.statut`. +Ce document formalise la **machine a etats** de l'attribut `customer_order.status`. Il decrit les etats possibles d'une commande, les transitions autorisees entre ces etats, les **evenements** qui les declenchent et les **gardes** (conditions) qui les conditionnent. -Il complete le MCD (`docs/merise/mcd.md` section 9, qui esquisse le cycle de -vie) et le dictionnaire (`docs/merise/dictionary.md` 3.5, qui declare l'ENUM). +Il complete le MCD (`docs/merise/mcd.md`, cycle de vie de la commande), le +dictionnaire (`docs/merise/dictionary.md` 3.10, qui declare l'ENUM `status`) et +le MCT (`docs/merise/mct.md` section 13, qui resume les transitions par +operation). --- ## 2. Source de verite et regle metier -La regle metier confirmee fixe deux phases successives dans le cycle de vie -d'une commande : le client **compose** sa commande, **puis** il **paie**. Une -fois payee, la commande entre en preparation. Le paiement fait partie integrante -du cycle. Les valeurs d'etat sont en anglais et alignees sur l'ENUM du -dictionnaire. +Le modele v0.2 (prod-like) reduit la machine a **quatre etats**. La regle metier +distingue la **composition payee** de la **remise** : une commande est creee et +payee en une operation atomique (la saisie du numero de retrait tient lieu de +paiement dans le cadre RNCP), puis elle est remise au client en un geste unique. | Source | Valeurs de statut | |---|---| -| `dictionary.md` 3.5 (ENUM SQL) | `pending_payment`, `paid`, `preparing`, `ready`, `delivered`, `cancelled` | -| Regle metier confirmee | composer -> payer -> preparer -> pret -> remettre | +| `dictionary.md` 3.10 (ENUM SQL) | `pending_payment`, `paid`, `delivered`, `cancelled` | +| `mct.md` section 13 (transitions) | creer+payer -> remettre, annulation depuis tout etat non terminal | -**Machine a etats canonique** : la machine ci-dessous est la seule autorisee. -Elle suit l'ENUM du dictionnaire et la regle metier des deux phases : +> Le dictionnaire (`dictionary.md` 3.10) et la machine ci-dessous partagent la +> meme ENUM a 4 valeurs, ce qui maintient la coherence entre le modele de +> donnees et le modele d'etats (cross-validation, mantra #34). -- `pending_payment` : commande composee, en attente de paiement. -- `paid` : paiement effectue ; la commande peut entrer en file de preparation. - -> Le dictionnaire (`dictionary.md` 3.5) et la machine ci-dessous partagent la -> meme ENUM, ce qui maintient la coherence entre le modele de donnees et le -> modele d'etats (cross-validation, mantra #34). +**Etats supprimes par rapport au v0.1** : `preparing` et `ready`. En contexte +fast-food, l'affichage cuisine (KDS) est un dispositif visuel : l'equipier lit +le ticket et agit. Ces deux etats intermediaires ajoutaient des transitions sans +valeur metier proportionnelle. La cuisine est en **lecture seule** ; la remise +(`DELIVER_ORDER`) est le geste unique qui fait avancer le statut. Le KPI est le +temps total `delivered_at - paid_at` (SLA ~10 min) ; la couleur du KDS est +calculee a l'affichage depuis `now - paid_at`, sans etat stocke supplementaire. --- @@ -49,12 +52,16 @@ Elle suit l'ENUM du dictionnaire et la regle metier des deux phases : | Etat | Valeur ENUM | Signification | Acteur qui declenche l'entree | |---|---|---|---| -| En attente de paiement | `pending_payment` | Commande composee, panier fige, en attente de paiement. | Client (kiosk) ou Accueil (counter/drive) | -| Payee | `paid` | Paiement effectue ; la commande peut entrer en file de preparation. | Client (paiement) ou Accueil | -| En preparation | `preparing` | Prise en charge par la Preparation, en cuisine. | Preparation | -| Prete | `ready` | Preparation terminee, prete au comptoir. | Preparation | -| Livree | `delivered` | Remise effectuee au client. Etat **final**. | Accueil | -| Annulee | `cancelled` | Commande abandonnee ou annulee. Etat **final**. | Client, Accueil ou Administration | +| En attente de paiement | `pending_payment` | Etat initial transitoire : commande composee, en attente de paiement. Non observable hors transaction (voir section 7). | Client (kiosk) ou Counter/Drive (back-office) | +| Payee | `paid` | Paiement effectue ; la commande entre en file de preparation (lecture seule cuisine). | Client (kiosk) ou Counter/Drive | +| Livree | `delivered` | Remise effectuee au client. Etat **final**. | Counter ou Drive | +| Annulee | `cancelled` | Commande abandonnee ou annulee avant remise. Etat **final**. | Counter, Drive ou Admin | + +`pending_payment` est l'etat par defaut a l'INSERT (`dictionary.md` 3.10), mais +la transition vers `paid` est realisee dans la **meme transaction** que la +creation (operations `CREATE_ORDER` / `CREATE_COUNTER_ORDER` du MCT). Il est donc +conserve dans l'ENUM pour la lisibilite du cycle et pour laisser la porte +ouverte a un paiement reel ulterieur sans migration destructive. --- @@ -62,19 +69,13 @@ Elle suit l'ENUM du dictionnaire et la regle metier des deux phases : ```mermaid stateDiagram-v2 - [*] --> pending_payment : creer commande (kiosk / counter / drive) + [*] --> pending_payment : creer la commande (kiosk / counter / drive) - pending_payment --> paid : payer\n[panier contient au moins 1 ligne] - pending_payment --> cancelled : abandonner\n[avant paiement] + pending_payment --> paid : payer\n[atomique dans CREATE_ORDER / CREATE_COUNTER_ORDER\nsaisie du numero = substitut de paiement] + pending_payment --> cancelled : annuler\n[avant remise] - paid --> preparing : prendre en charge\n[acteur Preparation, file triee par heure croissante] - paid --> cancelled : annuler\n[Accueil ou Administration] - - preparing --> ready : declarer preparee\n[acteur Preparation] - preparing --> cancelled : annuler\n[rupture produit / decision Administration] - - ready --> delivered : remettre au client\n[acteur Accueil] - ready --> cancelled : annuler\n[client absent / non recuperee] + paid --> delivered : remettre au client\n[acteur Counter / Drive, geste unique] + paid --> cancelled : annuler\n[Counter / Drive / Admin, re-credit du stock] delivered --> [*] cancelled --> [*] @@ -86,29 +87,29 @@ stateDiagram-v2 | # | De | Vers | Evenement declencheur | Garde (condition) | Acteur | |---|---|---|---|---|---| -| T1 | (initial) | `pending_payment` | Creation de la commande composee | Au moins un item ajoute au panier en cours | Client / Accueil | -| T2 | `pending_payment` | `paid` | Paiement de la commande | La commande contient au moins une `ligne_commande` ; le paiement aboutit | Client / Accueil | -| T3 | `pending_payment` | `cancelled` | Abandon avant paiement | Commande pas encore payee | Client / Accueil | -| T4 | `paid` | `preparing` | Prise en charge en file | La commande est la plus ancienne non traitee (tri par heure de livraison croissante) | Preparation | -| T5 | `paid` | `cancelled` | Annulation avant preparation | Decision operationnelle | Accueil / Administration | -| T6 | `preparing` | `ready` | Declaration "preparee" | Preparation terminee | Preparation | -| T7 | `preparing` | `cancelled` | Annulation pendant preparation | Rupture produit ou decision Administration | Preparation / Administration | -| T8 | `ready` | `delivered` | Remise physique au client | Le client se presente avec le bon numero | Accueil | -| T9 | `ready` | `cancelled` | Annulation apres preparation | Client non present / commande non recuperee | Accueil / Administration | +| T1 | (initial) | `pending_payment` | Creation de la commande composee | Au moins une ligne (`order_item`) ; numero de retrait non vide | Client / Counter / Drive | +| T2 | `pending_payment` | `paid` | Paiement (atomique a la creation) | La commande contient au moins une `order_item` ; decrement du stock et insert dans la meme transaction | Client / Counter / Drive | +| T3 | `pending_payment` | `cancelled` | Abandon avant paiement | Commande pas encore payee | Counter / Drive / Admin | +| T4 | `paid` | `delivered` | Remise physique au client | La commande est `paid` ; l'acteur detient `order.deliver` ; source compatible avec son role (`role_visible_source`) | Counter / Drive | +| T5 | `paid` | `cancelled` | Annulation avant remise | L'acteur detient `order.cancel` ; le stock consomme est re-credite (`stock_movement` type `cancellation`) | Counter / Drive / Admin | ### Invariants de la machine a etats - `delivered` et `cancelled` sont des etats **finaux** : aucune transition n'en sort. -- Aucune transition ne revient en arriere (pas de `preparing -> paid`). Une - erreur operationnelle se traite par annulation puis nouvelle commande, pour - preserver l'integrite de l'historique et des snapshots de prix. -- La transition vers `cancelled` est possible depuis tous les etats **sauf** - `delivered` (une commande remise ne s'annule pas dans ce modele). Ceci est - coherent avec `mcd.md` section 9 : "Annuler : transition vers `cancelled` - (depuis tout statut sauf `delivered`)". -- `paye_a` (DATETIME, `dictionary.md` 3.5) est renseigne au moment de la - transition T2 (`pending_payment -> paid`) et reste NULL avant. +- Aucune transition ne revient en arriere. Une erreur operationnelle se traite + par annulation puis nouvelle commande, pour preserver l'integrite de + l'historique et des snapshots (`label_snapshot`, `unit_price_cents_snapshot`, + `vat_rate_snapshot` sur `order_item`). +- La transition vers `cancelled` est possible depuis `pending_payment` et + `paid`, mais pas depuis `delivered` (une commande remise ne s'annule pas dans + ce modele). Coherent avec `mct.md` 7.1 (`CANCEL_ORDER`). +- `paid_at` (DATETIME, `dictionary.md` 3.10) est renseigne a la transition T2. + `delivered_at` est renseigne a T4. `cancelled_at` est renseigne a T3/T5. Les + trois colonnes sont NULL tant que la transition correspondante n'a pas eu lieu. +- La cuisine (`kitchen`) ne declenche aucune transition : son acces a la file + des commandes `paid` est en **lecture seule** (`mct.md` 5.1, + `LIST_ORDERS_DISPLAY`). --- @@ -116,29 +117,36 @@ stateDiagram-v2 | Verification | Resultat | |---|---| -| Tous les etats du diagramme existent dans l'ENUM `dictionary.md` 3.5 | Oui (6 valeurs, toutes utilisees) | -| La regle "annulation possible sauf depuis delivered" de `mcd.md` 9 | Respectee (T5, T7, T9 ; pas de transition depuis `delivered`) | -| Cycle de vie esquisse dans `mcd.md` 9 | Couvert : `pending_payment` -> `paid` (payer), `paid` -> `preparing` (preparer), `preparing` -> `ready` (marquer pret), `ready` -> `delivered` (remettre) | -| Acteurs de `use-cases.md` | Preparation declenche T4/T6/T7 ; Accueil declenche T8/T9 ; Administration peut annuler | +| Tous les etats du diagramme existent dans l'ENUM `dictionary.md` 3.10 | Oui (4 valeurs, toutes utilisees) | +| La regle "annulation possible sauf depuis delivered" de `mct.md` 7.1 | Respectee (T3, T5 ; pas de transition depuis `delivered`) | +| Transition `paid -> delivered` en geste unique de `mct.md` 6.1 | Couvert par T4 (`DELIVER_ORDER`) | +| Atomicite `pending_payment -> paid` de `mct.md` 3.3 / 4.1 / section 13 | Couvert par T2 (saisie du numero = substitut de paiement) | +| Acteurs de `use-cases.md` | Counter/Drive declenchent T4/T5 ; Admin peut annuler (T3/T5) ; Kitchen en lecture seule | +| Timestamps de phase `paid_at` / `delivered_at` / `cancelled_at` | Renseignes a T2 / T4 / T3-T5 | --- ## 7. Arbitrage tranche -La divergence historique entre l'ENUM du dictionnaire et un parcours sans -paiement est resolue par la regle metier confirmee : le cycle de vie comporte -deux phases successives, la composition de la commande puis son paiement. Le -paiement fait partie integrante du cycle. - -La machine canonique retenue est donc : +La machine retenue est reduite a quatre etats (Decision 4, +`docs/notes/revue-alignement-p1.md` section 7) : ``` -pending_payment -> paid -> preparing -> ready -> delivered - (cancelled atteignable depuis pending_payment, paid, preparing) +pending_payment -> paid -> delivered + | | + +------------+--------> cancelled (depuis pending_payment ou paid) ``` -Cette machine est la source de verite partagee par `dictionary.md` 3.5, -`use-cases.md` (cas "Payer la commande" cote Client) et -`sequence-passer-commande.md` (etape paiement entre validation du panier et -confirmation). La colonne `paye_a` est renseignee a la transition T2. A -revalider lors du MCT. +**Note sur la transition `pending_payment -> paid`** : dans le cadre RNCP, le +paiement est remplace par la saisie du numero de commande (kiosk) ou par la +validation de l'equipier (counter/drive). La transition est **atomique** dans +`CREATE_ORDER` et `CREATE_COUNTER_ORDER` : le statut `pending_payment` n'est pas +observable en dehors de la transaction de creation. Il reste declare dans l'ENUM +pour exprimer le cycle de vie complet et pour autoriser un paiement reel +ulterieur (cout d'une valeur d'ENUM). + +Les etats `preparing` et `ready` du v0.1 sont supprimes : la cuisine est un +affichage visuel en lecture seule, et la remise fusionne preparation+remise en +un geste unique (`DELIVER_ORDER`). Effet de bord propage : les operations +`MARK_IN_PREPARATION` et `MARK_READY` disparaissent du MCT (voir `mct.md` +sections 1 et 13). diff --git a/docs/uml/use-cases.md b/docs/uml/use-cases.md index c9897be..3a615a7 100644 --- a/docs/uml/use-cases.md +++ b/docs/uml/use-cases.md @@ -1,8 +1,8 @@ # Diagramme de cas d'utilisation - Wakdo **Phase UML** : P1 - Conception, complement UML (apres MCD) -**Statut** : v0.1 -**Date** : 2026-05-21 +**Statut** : v0.2 - prod-like, 5 roles RBAC + catalogue de 23 permissions +**Date** : 2026-06-11 **Branche** : `feat/p1-conception` **Auteur methodologie** : BYAN @@ -12,174 +12,247 @@ Ce document recense les **cas d'utilisation** de Wakdo, c'est-a-dire les fonctionnalites observables du systeme du point de vue de ses acteurs. Il -complete le MCD (`docs/merise/mcd.md`) et le dictionnaire -(`docs/merise/dictionary.md`) en passant de la vue **donnees** a la vue -**usages**. +complete le MCD (`docs/merise/mcd.md`), le dictionnaire +(`docs/merise/dictionary.md`) et le MCT (`docs/merise/mct.md`, 26 operations) en +passant de la vue **donnees / traitements** a la vue **usages**. -Le diagramme reste au niveau conceptuel. Il ne prejuge pas de l'ecran ou de -l'endpoint qui realisera chaque cas, mais identifie qui fait quoi. +Le diagramme reste au niveau conceptuel : il identifie qui fait quoi, sans +prejuger de l'ecran ou de l'endpoint qui realise chaque cas. Chaque cas +back-office est rattache a la **permission** qui le conditionne (catalogue fige +de 23 codes, `dictionary.md` 3.17), conformement a la regle RBAC +permission-driven : le code teste une permission, pas un nom de role. **Sources** : -- `docs/PROJECT_CONTEXT.md` sections 2 (acteurs, processus), 7 (scope RBAC) -- `docs/merise/dictionary.md` (entites `commande`, `role`, `user`) +- `docs/PROJECT_CONTEXT.md` sections 2 (acteurs, processus), 7 (scope back-office) +- `docs/merise/dictionary.md` 3.14-3.18 (`user`, `role`, `role_visible_source`, `permission`, `role_permission`) +- `docs/merise/mct.md` (operations, acteurs, permissions par operation) --- ## 2. Acteurs - perimetre et challenge de pertinence -Le brief (`PROJECT_CONTEXT.md` section 2 et section 7) definit les acteurs -metier. Avant de les retenir, chaque acteur propose dans la consigne initiale -est confronte au perimetre reel du projet. +Le brief initial (`PROJECT_CONTEXT.md` section 2) decrivait quatre acteurs +metier (Client, Accueil, Preparation, Administration) adosses a 3 roles RBAC. Le +modele v0.2 (prod-like, Decision 4 de `revue-alignement-p1.md` section 7) raffine +le back-office en **5 roles** pour coller a l'organisation reelle d'un fast-food +multi-canal. Chaque acteur candidat est confronte au perimetre reel. -| Acteur candidat | Statut | Justification (perimetre reel) | +| Acteur candidat (brief) | Statut v0.2 | Justification (perimetre reel) | |---|---|---| -| **Client (borne kiosk)** | Retenu | Acteur central du Bloc 1. Compose et valide une commande sur la borne tactile autonome (canal `kiosk`). Non authentifie. | -| **Manager / Admin** | Retenu, fusionne en **Administration** | Le brief ne distingue pas "manager" et "admin" comme deux roles. Le role RBAC reel est `admin` (section 7). Il porte le CRUD catalogue, la gestion des utilisateurs/roles et les stats. On nomme l'acteur **Administration** pour coller au vocabulaire du brief. | -| **Cuisine** | Retenu, renomme **Preparation** | Correspond au role RBAC `preparation` (section 7). Voit la file des commandes a preparer triees par heure de livraison croissante et fait avancer leur statut. Le terme "Cuisine" est un synonyme metier ; le role technique est `preparation`. | -| **Caisse** | Ecarte comme acteur distinct | Challenge : il n'existe pas de role RBAC `caisse` (les 3 roles sont `admin`, `preparation`, `accueil`). Le paiement existe dans le cycle (cote Client sur la borne et cote Accueil au comptoir/drive), mais aucun acteur "Caisse" dedie n'est modelise. L'equivalent operationnel le plus proche est l'**Accueil** (role `accueil`) qui saisit les commandes au comptoir/drive et remet les commandes livrees. | -| **Accueil** | Retenu (non liste dans la consigne mais present au brief) | Role RBAC `accueil`. Saisit les commandes au comptoir (canal `counter`) ou au drive (canal `drive`), puis remet les commandes au client (passage a `delivered`). C'est l'acteur qui recouvre le besoin que la consigne attribuait a "Caisse". | +| **Client (borne kiosk)** | Retenu (acteur `CUSTOMER`) | Acteur central du Bloc 1. Compose et valide une commande sur la borne tactile autonome (canal `kiosk`). **Non authentifie**. | +| **Accueil** | **Scinde** en `counter` et `drive` | Le besoin "Accueil" recouvre deux canaux operationnels distincts : le comptoir (`counter`) et le drive (`drive`). Le v0.2 les separe car le tag `source` de la commande et le filtre de dashboard (`role_visible_source`) different. Tous deux saisissent des commandes, les remettent et les annulent. | +| **Preparation** | Retenu, renomme `kitchen` | Role RBAC `kitchen`. Voit la file des commandes `paid` triees par `paid_at` croissant. **Lecture seule** : ne declenche aucune transition de statut (le KDS est un dispositif visuel ; la remise revient a `counter`/`drive`). | +| **Administration** | **Scinde** en `admin` et `manager` | Le v0.1 fusionnait "Manager/Admin". Le v0.2 distingue : `admin` (gestion des utilisateurs, des roles et permissions, suppressions catalogue) et `manager` (catalogue create/update, stock/reappro, stats), sans acces aux utilisateurs ni au RBAC. Resout le point ouvert v0.1 "Manager vs Admin". | +| **Caisse** | Ecarte (recouvert par `counter`/`drive`) | Aucun role `caisse` n'existe. L'encaissement est atomique a la creation de commande (saisie du numero = substitut de paiement) ; il est realise par le Client (kiosk) ou par `counter`/`drive` (back-office). Resout le point ouvert v0.1 "Caisse absente du RBAC". | +| **Systeme** | Retenu (acteur `SYS`) | Logique interne (generation du numero, reponse API de confirmation). Apparait dans le MCT (3.4 `DISPLAY_CONFIRMATION`) ; non represente comme acteur humain au diagramme. | ### Decision sur les acteurs retenus -Quatre acteurs sont conserves au diagramme : +Six acteurs sont conserves : un acteur public et cinq roles back-office. -1. **Client** (borne, non authentifie) -2. **Administration** (role `admin`) -3. **Preparation** (role `preparation`, ex-"Cuisine") -4. **Accueil** (role `accueil`, recouvre le besoin "Caisse") +1. **Customer** (borne kiosk, non authentifie) +2. **Admin** (role `admin`) +3. **Manager** (role `manager`) +4. **Kitchen** (role `kitchen`, ex-"Preparation", lecture seule) +5. **Counter** (role `counter`, ex-"Accueil" comptoir) +6. **Drive** (role `drive`, ex-"Accueil" drive) -> Decision actee : il n'y a **pas** de parcours employe dedie modelise a part. -> Les cas d'usage des employes (Administration, Preparation, Accueil) sont -> couverts directement ici. Cette decision suit le mantra du Rasoir d'Ockham -> (#37) : on evite une couche de modelisation redondante tant qu'aucun besoin -> ne la justifie. +> Regle RBAC permission-driven (`dictionary.md` 3.15) : les rattachements +> acteur -> cas ci-dessous refletent la **matrice de permissions par defaut au +> seed**. Le gardien reel est la permission, pas le nom du role : un role +> personnalise (ex. "chef-patissier") dote des bonnes permissions ouvre les +> memes cas, sans changement de code. Les 5 roles seed sont un point de depart, +> pas une liste fermee. --- ## 3. Diagramme de cas d'utilisation -Mermaid ne fournit pas de type `usecase` natif. La representation ci-dessous -utilise un `flowchart` : les acteurs sont des noeuds a gauche, les cas -d'utilisation sont des noeuds arrondis regroupes par sous-systeme, et les -fleches portent les relations (`<>`, `<>`) la ou elles -ont du sens. +Mermaid ne fournit pas de type `usecase` natif. La representation utilise un +`flowchart` : les acteurs sont a gauche, les cas d'utilisation regroupes par +sous-systeme. La permission qui conditionne chaque cas back-office est precisee +en section 4. ```mermaid flowchart LR %% Acteurs - Client(("Client
borne kiosk")) - Admin(("Administration
role admin")) - Prep(("Preparation
role preparation")) - Accueil(("Accueil
role accueil")) + Customer(("Customer
borne kiosk
non authentifie")) + Admin(("Admin
role admin")) + Manager(("Manager
role manager")) + Kitchen(("Kitchen
role kitchen
lecture seule")) + Counter(("Counter
role counter")) + Drive(("Drive
role drive")) %% Sous-systeme Borne client - subgraph BORNE["Borne client - Bloc 1"] + subgraph BORNE["Borne client - Bloc 1 (public)"] UC1(["Consulter le catalogue"]) - UC2(["Composer un menu"]) - UC3(["Passer une commande"]) - UC4(["Saisir le numero de retrait"]) - UC5(["Recevoir la confirmation"]) - UC6(["Payer la commande"]) + UC2(["Composer le panier"]) + UC3(["Consulter les allergenes"]) + UC4(["Passer une commande"]) + UC5(["Saisir le numero de retrait"]) + UC6(["Recevoir la confirmation"]) end - %% Sous-systeme Back-office - subgraph BACK["Back-office - Bloc 2"] - UC10(["Gerer le catalogue
categories, produits, menus"]) - UC11(["Gerer les utilisateurs et roles"]) - UC12(["Consulter les statistiques"]) - UC20(["Consulter la file de preparation"]) - UC21(["Faire avancer une commande"]) - UC30(["Saisir une commande
comptoir ou drive"]) - UC31(["Remettre la commande au client"]) - UC40(["S'authentifier"]) + %% Sous-systeme Operations commande + subgraph OPS["Operations commande - back-office"] + UC10(["Saisir une commande
comptoir / drive"]) + UC11(["Consulter la file de preparation"]) + UC12(["Remettre la commande"]) + UC13(["Annuler une commande"]) end - %% Relations Client - Client --> UC1 - Client --> UC2 - Client --> UC3 - Client --> UC6 - Client --> UC5 + %% Sous-systeme Catalogue + subgraph CAT["Catalogue - back-office"] + UC20(["Gerer produits"]) + UC21(["Gerer menus et slots"]) + UC22(["Gerer categories"]) + UC23(["Gerer ingredients,
compositions, allergenes"]) + end - %% include / extend cote borne - UC3 -. include .-> UC4 - UC3 -. include .-> UC6 + %% Sous-systeme Stock + subgraph STK["Stock - back-office"] + UC30(["Consulter le stock"]) + UC31(["Compter l'inventaire"]) + UC32(["Reapprovisionner"]) + end + + %% Sous-systeme Administration + subgraph ADM["Administration - back-office"] + UC40(["Gerer les utilisateurs"]) + UC41(["Gerer roles et permissions"]) + UC42(["Consulter les statistiques"]) + end + + %% Transverse + UC50(["S'authentifier"]) + UC51(["Se deconnecter"]) + + %% Relations Customer + Customer --> UC1 + Customer --> UC2 + Customer --> UC4 + Customer --> UC6 UC2 -. include .-> UC1 - UC3 -. extend .-> UC2 + UC2 -. extend .-> UC3 + UC4 -. include .-> UC5 + UC4 -. include .-> UC2 - %% Relations Administration + %% Relations Counter / Drive (operations commande + stock) + Counter --> UC10 + Counter --> UC11 + Counter --> UC12 + Counter --> UC13 + Drive --> UC10 + Drive --> UC11 + Drive --> UC12 + Drive --> UC13 + UC10 -. include .-> UC1 + + %% Kitchen (lecture seule) + Kitchen --> UC11 + + %% Stock (kitchen / counter / drive / manager / admin) + Kitchen --> UC30 + Kitchen --> UC31 + Counter --> UC30 + Counter --> UC31 + Drive --> UC30 + Drive --> UC31 + Manager --> UC30 + Manager --> UC31 + Manager --> UC32 + + %% Catalogue (manager + admin) + Manager --> UC20 + Manager --> UC21 + Manager --> UC22 + Manager --> UC23 + Admin --> UC20 + Admin --> UC21 + Admin --> UC22 + Admin --> UC23 + + %% Administration (admin) + stats (manager + admin) Admin --> UC40 - Admin --> UC10 + Admin --> UC41 + Admin --> UC42 + Admin --> UC30 + Admin --> UC31 + Admin --> UC32 Admin --> UC11 - Admin --> UC12 + Admin --> UC13 + Manager --> UC42 - %% Relations Preparation - Prep --> UC40 - Prep --> UC20 - Prep --> UC21 - - %% Relations Accueil - Accueil --> UC40 - Accueil --> UC30 - Accueil --> UC31 - UC30 -. include .-> UC1 - - %% Authentification mutualisee - UC10 -. include .-> UC40 - UC11 -. include .-> UC40 - UC20 -. include .-> UC40 - UC30 -. include .-> UC40 + %% Authentification mutualisee (tout cas back-office) + UC10 -. include .-> UC50 + UC11 -. include .-> UC50 + UC20 -. include .-> UC50 + UC30 -. include .-> UC50 + UC40 -. include .-> UC50 + UC42 -. include .-> UC50 ``` --- ## 4. Description des cas d'utilisation -### 4.1 Acteur Client (borne kiosk) +### 4.1 Acteur Customer (borne kiosk, non authentifie) -| Cas | Description | Entites manipulees | +| Cas | Operation MCT | Description | Entites manipulees | +|---|---|---|---| +| Consulter le catalogue | 3.1 LOAD_CATALOGUE | Parcourir categories, produits et menus disponibles, charges via `GET /api/categories`, `/api/products`, `/api/menus` (ou JSON fallback). | `category`, `product`, `menu`, `menu_slot`, `menu_slot_option` | +| Composer le panier | 3.2 COMPOSE_CART | Ajouter produits a la carte ou menus ; remplir les slots d'un menu (`order_item_selection`), choisir le format Normal/Maxi, ajouter/retirer des ingredients (`order_item_modifier`). Panier volatil cote front, aucun ecrit BDD a ce stade. | `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `product_ingredient` | +| Consulter les allergenes | (derive de 3.1) | Afficher en modal les allergenes d'un produit, **calcules** par jointure `product_ingredient -> ingredient_allergen -> allergen` (INCO 1169/2011). Etend la composition. | `allergen`, `ingredient_allergen`, `product_ingredient` | +| Passer une commande | 3.3 CREATE_ORDER | Valider le panier et saisir le numero de retrait. Creation atomique : INSERT `customer_order` (`pending_payment` puis `paid` dans la meme transaction), `order_item` + selections + modifiers snapshotes, decrement du stock + `stock_movement`. | `customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`, `ingredient`, `stock_movement` | +| Saisir le numero de retrait | (inclus dans 3.3) | Renseigner le numero qui identifie le client. Tient lieu de paiement (cadre RNCP). Cas inclus par "Passer une commande". | `customer_order.order_number` | +| Recevoir la confirmation | 3.4 DISPLAY_CONFIRMATION | Afficher l'ecran de confirmation avec le numero, apres reponse `201` (statut `paid`). La borne se reinitialise. | `customer_order` | + +### 4.2 Acteurs Counter et Drive (roles `counter`, `drive`) + +| Cas | Operation MCT | Permission | Description | Entites | +|---|---|---|---|---| +| Saisir une commande comptoir/drive | 4.1 CREATE_COUNTER_ORDER | `order.create` | Composer une commande pour un client au comptoir (`counter`) ou au drive (`drive`). Logique identique a CREATE_ORDER ; `source` auto-tague depuis `role.order_source`. Numero `C-`/`D-YYYY-MM-DD-NNN`. | `customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`, `ingredient`, `stock_movement` | +| Consulter la file de preparation | 5.1 LIST_ORDERS_DISPLAY | `order.read` | Voir les commandes `paid` triees par `paid_at` croissant, filtrees par `role_visible_source` (counter voit kiosk+counter ; drive voit drive). Couleur KDS = `now - paid_at`. | `customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`, `role_visible_source` | +| Remettre la commande | 6.1 DELIVER_ORDER | `order.deliver` | Geste unique `paid -> delivered`, `delivered_at = NOW()`. | `customer_order` | +| Annuler une commande | 7.1 CANCEL_ORDER | `order.cancel` | Transition vers `cancelled` depuis `pending_payment`/`paid`, `cancelled_at = NOW()`. Re-credit du stock si `paid`. | `customer_order`, `ingredient`, `stock_movement` | + +### 4.3 Acteur Kitchen (role `kitchen`, lecture seule) + +| Cas | Operation MCT | Permission | Description | Entites | +|---|---|---|---|---| +| Consulter la file de preparation | 5.1 LIST_ORDERS_DISPLAY | `order.read` | Voir toutes les sources (kiosk, counter, drive) en lecture seule. **Aucune transition de statut** : le KDS est un affichage visuel. | `customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`, `role_visible_source` | + +### 4.4 Stock (Kitchen, Counter, Drive, Manager, Admin) + +| Cas | Operation MCT | Permission | Description | Entites | +|---|---|---|---|---| +| Consulter le stock | 9.3 READ_STOCK | `stock.read` | Lister les ingredients avec stock courant ; alerte rupture calculee a l'affichage (`stock_quantity <= low_stock_threshold`). | `ingredient`, `stock_movement` | +| Compter l'inventaire | 9.2 INVENTORY_COUNT | `stock.count` | Saisir un comptage physique ; le systeme enregistre l'ecart (`inventory_correction`). Inclut les equipiers (kitchen/counter/drive). | `ingredient`, `stock_movement` | +| Reapprovisionner | 9.1 RESTOCK | `stock.manage` | Enregistrer une livraison en conditionnements (`+= N * pack_size`). Reserve manager/admin. | `ingredient`, `stock_movement` | + +### 4.5 Catalogue (Manager, Admin) + +| Cas | Operation MCT | Permissions | Description | Entites | +|---|---|---|---|---| +| Gerer produits | 8.1-8.3 CREATE/UPDATE/DELETE_PRODUCT | `product.create`/`update` (manager+admin), `product.delete` (admin seul) | CRUD produits (nom, prix, `vat_rate`, image, dispo). La suppression physique est reservee a `admin` et bloquee si reference (FK RESTRICT). | `product`, `category` | +| Gerer menus et slots | 8.4-8.6 CREATE/UPDATE/DELETE_MENU | `menu.create`/`update` (manager+admin), `menu.delete` (admin seul) | CRUD menus avec leur configuration de slots (`menu_slot`, `menu_slot_option`) et le burger fixe. | `menu`, `menu_slot`, `menu_slot_option`, `product` | +| Gerer categories | 8.7 MANAGE_CATEGORY | `category.manage` (manager+admin) | CRUD categories ; desactivation `is_active=0`. | `category` | +| Gerer ingredients, compositions, allergenes | 8.8 MANAGE_INGREDIENT | `ingredient.manage` (manager+admin) | CRUD `ingredient` ; composition `product_ingredient` (quantites Normal/Maxi, retirable/ajoutable, supplement) ; mapping `ingredient_allergen` (14 allergenes UE). | `ingredient`, `product_ingredient`, `ingredient_allergen`, `allergen` | + +### 4.6 Administration (role `admin`) + Stats (Manager, Admin) + +| Cas | Operation MCT | Permissions | Description | Entites | +|---|---|---|---|---| +| Gerer les utilisateurs | 10.1-10.3 CREATE/UPDATE/DEACTIVATE_USER | `user.create`/`update`/`deactivate` (admin) ; `user.read` (admin+manager) | CRUD comptes back-office avec hash argon2id ; desactivation sans suppression (historique preserve). | `user`, `role` | +| Gerer roles et permissions | 10.4 MANAGE_RBAC | `role.manage` (admin) | Editer la matrice `role_permission`, creer/modifier des roles personnalises (`default_route`, `order_source`), regler `role_visible_source`. Permissions statiques (declarees en migration). | `role`, `permission`, `role_permission`, `role_visible_source` | +| Consulter les statistiques | 11.1 READ_STATS | `stats.read` (admin+manager) | Agregats par `service_day` (coupure 10h), top produits, taux d'annulation, temps moyen de remise `delivered_at - paid_at`, repartition par `source`/`service_mode`. | `customer_order`, `order_item` | + +### 4.7 Cas transverses - Authentification + +| Cas | Operation MCT | Description | |---|---|---| -| Consulter le catalogue | Parcourir les categories, produits et menus disponibles. Charges via `GET /api/categories`, `/api/products`, `/api/menus` (ou JSON fallback). | `categorie`, `produit`, `menu` | -| Composer un menu | Choisir burger + accompagnement + boisson + sauce, regler les options de taille (normale / grande) et de personnalisation. Etend "Passer une commande" car un menu compose est une variante d'item au panier. | `menu`, `menu_produit`, `produit` | -| Passer une commande | Valider le panier, declencher la creation de la commande composee. Inclut la saisie du numero de retrait et le paiement. | `commande`, `ligne_commande` | -| Saisir le numero de retrait | Renseigner le numero qui identifie le client au comptoir. Cas inclus par "Passer une commande". | `commande.numero` | -| Payer la commande | Regler la commande une fois le panier compose et valide. Materialise la transition `pending_payment -> paid` de `state-commande.md`. Cas inclus par "Passer une commande". | `commande.statut`, `commande.paye_a` | -| Recevoir la confirmation | Afficher l'ecran de confirmation avec le numero, apres paiement. | `commande` | - -> Note de coherence : le cycle de vie comporte deux phases successives, la -> composition de la commande puis son paiement (regle metier confirmee). Le cas -> "Payer la commande" est retenu cote Client et materialise la transition -> `pending_payment -> paid` de l'ENUM `statut` -> (`dictionary.md` 3.5, `state-commande.md`). - -### 4.2 Acteur Administration (role admin) - -| Cas | Description | Entites manipulees | -|---|---|---| -| Gerer le catalogue | CRUD sur categories, produits et menus (libelles, prix, images, disponibilite, composition de menu). | `categorie`, `produit`, `menu`, `menu_produit` | -| Gerer les utilisateurs et roles | CRUD sur les comptes back-office et leurs roles ; consultation de la matrice de permissions. | `user`, `role`, `permission`, `role_permission` | -| Consulter les statistiques | Voir les commandes du jour de service, le chiffre d'affaires, les produits les plus commandes. | `commande`, `ligne_commande` | - -### 4.3 Acteur Preparation (role preparation, ex-Cuisine) - -| Cas | Description | Entites manipulees | -|---|---|---| -| Consulter la file de preparation | Afficher les commandes a preparer triees par heure de livraison croissante, tous canaux confondus. | `commande`, `ligne_commande` | -| Faire avancer une commande | Declarer une commande "preparee", ce qui declenche une transition de statut (voir `state-commande.md`). | `commande.statut` | - -### 4.4 Acteur Accueil (role accueil, recouvre Caisse) - -| Cas | Description | Entites manipulees | -|---|---|---| -| Saisir une commande | Creer une commande pour un client au comptoir (`counter`) ou au drive (`drive`), en consultant le catalogue. | `commande`, `ligne_commande`, `produit`, `menu` | -| Remettre la commande au client | Declarer une commande "livree" au moment de la remise physique. | `commande.statut` | - -### 4.5 Cas transverse - S'authentifier - -Tous les acteurs du back-office (Administration, Preparation, Accueil) passent -par "S'authentifier" avant d'acceder a leurs cas. Modelise comme cas inclus -(`<>`) par chaque cas back-office pour eviter de surcharger le -diagramme. Le Client de la borne n'est pas authentifie (canal `kiosk` public). +| S'authentifier | 12.1 AUTHENTICATE_USER | Tous les roles back-office passent par ce cas avant d'acceder a leurs cas (relation `<>`). Verification argon2id, regeneration de session (anti-fixation), `is_active=1` requis, redirection vers `role.default_route`. Le Customer du kiosk n'est pas authentifie. | +| Se deconnecter | 12.2 LOGOUT_USER | Destruction de session (`session_destroy()`) sur clic ou expiration (idle 4h / absolu 10h). | --- @@ -188,35 +261,32 @@ diagramme. Le Client de la borne n'est pas authentifie (canal `kiosk` public). | Relation | Type | Justification | |---|---|---| | Passer une commande -> Saisir le numero de retrait | include | La saisie du numero fait partie integrante de toute validation de commande. | -| Passer une commande -> Payer la commande | include | Le paiement suit la composition du panier et fait partie integrante du parcours (phase 2 du cycle de vie). | -| Composer un menu -> Consulter le catalogue | include | Composer un menu suppose de parcourir les produits eligibles a chaque slot. | -| Passer une commande -> Composer un menu | extend | Le menu est un cas optionnel : une commande peut ne contenir que des produits a la carte. La composition etend le parcours seulement si le client choisit un menu. | -| Saisir une commande (Accueil) -> Consulter le catalogue | include | L'equipier consulte le catalogue pour saisir au comptoir / drive. | -| Cas back-office -> S'authentifier | include | Acces conditionne a une session authentifiee. | +| Passer une commande -> Composer le panier | include | Une commande resulte d'un panier compose. | +| Composer le panier -> Consulter le catalogue | include | Composer suppose de parcourir les produits eligibles (a la carte ou par slot). | +| Composer le panier -> Consulter les allergenes | extend | La consultation des allergenes est un cas optionnel declenche a la demande du client sur un produit. | +| Saisir une commande (Counter/Drive) -> Consulter le catalogue | include | L'equipier consulte le catalogue pour saisir au comptoir/drive. | +| Cas back-office -> S'authentifier | include | Acces conditionne a une session authentifiee detenant la permission requise. | --- -## 6. Incoherences remontees vers les autres livrables +## 6. Points resolus par rapport au v0.1 -Ces ecarts entre les sources sont signales pour arbitrage de l'auteur (la -modelisation finale releve de sa decision, mantra de validation humaine). +Les incoherences que le v0.1 remontait pour arbitrage sont desormais tranchees +par le modele v0.2 (`dictionary.md`, `mct.md`). -1. **ENUM `statut` et phase de paiement (tranche)** - Le dictionnaire (`dictionary.md` 3.5) definit - `statut ENUM('pending_payment','paid','preparing','ready','delivered','cancelled')` - avec un paiement explicite. La regle metier confirmee fixe deux phases - successives, la composition de la commande puis son paiement. Le cas - "Payer la commande" est donc retenu cote Client et materialise la transition - `pending_payment -> paid`. Cet ecart est tranche : la machine canonique de - `state-commande.md` fait foi. - -2. **Acteur "Caisse" absent du RBAC** - Aucun role `caisse` n'existe (`PROJECT_CONTEXT.md` section 7 : `admin`, - `preparation`, `accueil`). La fonction d'encaissement de la consigne a ete - rattachee a l'acteur **Accueil**. A confirmer. - -3. **"Manager" vs "Admin"** - La consigne parle de "Manager/Admin" ; le brief ne connait que `admin`. Les - deux ont ete fusionnes en un acteur **Administration**. A confirmer si un - role manager intermediaire est souhaite (le dictionnaire 3.8 mentionne un - role `manager` extensible, non present dans le scope section 7). +1. **Acteur "Caisse"** : ecarte. L'encaissement est atomique a la creation + (saisie du numero = substitut de paiement, `mct.md` section 13) et realise + par le Customer (kiosk) ou par `counter`/`drive` (back-office). Aucun role + `caisse` n'est necessaire. +2. **"Manager" vs "Admin"** : scindes en deux roles distincts. `manager` gere le + catalogue (create/update), le stock/reappro et les stats ; `admin` ajoute les + suppressions catalogue, la gestion des utilisateurs et le RBAC. +3. **"Accueil" unique** : scinde en `counter` et `drive`, car le tag `source` et + le filtre `role_visible_source` different selon le canal. +4. **Machine a etats** : alignee sur les 4 etats du `dictionary.md` 3.10 + (`pending_payment -> paid -> delivered` + `cancelled`). Plus de `preparing` / + `ready` : la cuisine (`kitchen`) est en lecture seule, la remise est un geste + unique (`counter`/`drive`). +5. **Modele permission-driven** : chaque cas back-office est rattache a sa + permission (catalogue de 23 codes fige, `dictionary.md` 3.17). Le diagramme + reflete la matrice seed ; le gardien effectif reste la permission.