docs(uml): add security sequence + v0.2 drift fixes (sequence, state, use-cases)

This commit is contained in:
Imugiii 2026-06-15 08:04:13 +00:00
parent d305a095fc
commit fae5c23722
4 changed files with 622 additions and 287 deletions

View file

@ -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.

View file

@ -1,8 +1,8 @@
# Diagramme de sequence - Passer une commande (borne client) # Diagramme de sequence - Passer une commande (borne client)
**Phase UML** : P1 - Conception, complement UML (apres MCD) **Phase UML** : P1 - Conception, complement UML (apres MCD)
**Statut** : v0.1 **Statut** : v0.2 - prod-like, creation atomique (create + pay)
**Date** : 2026-05-21 **Date** : 2026-06-11
**Branche** : `feat/p1-conception` **Branche** : `feat/p1-conception`
**Auteur methodologie** : BYAN **Auteur methodologie** : BYAN
@ -12,18 +12,24 @@
Ce document decrit le **flux temporel** du parcours "passer une commande" cote Ce document decrit le **flux temporel** du parcours "passer une commande" cote
**Client sur la borne kiosk** : navigation dans les categories, selection d'un **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 produit ou composition d'un menu (slots + format Normal/Maxi + modifiers
numero de retrait, paiement, puis confirmation. 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 Le diagramme reste au niveau conceptuel / logique. Il nomme les echanges entre
entre participants sans detailler l'implementation PHP (controllers, models) participants sans detailler l'implementation PHP ni le SQL exact. Il complete le
ni le SQL exact. Il complete le cas d'utilisation "Passer une commande" de cas d'utilisation "Passer une commande" de `docs/uml/use-cases.md` (4.1), la
`docs/uml/use-cases.md` et la machine a etats de `docs/uml/state-commande.md`. machine a etats de `docs/uml/state-commande.md` (T1/T2) et l'operation
`CREATE_ORDER` du `docs/merise/mct.md` (3.3).
**Sources** : **Sources** :
- `docs/PROJECT_CONTEXT.md` section 2 (processus metier), section 7 (endpoints API) - `docs/PROJECT_CONTEXT.md` section 2 (processus metier), section 7 (endpoints API)
- `docs/merise/dictionary.md` (`commande`, `ligne_commande`, `menu`, `produit`) - `docs/merise/dictionary.md` 3.10-3.13 (`customer_order`, `order_item`, `order_item_selection`, `order_item_modifier`)
- `docs/uml/state-commande.md` (transitions `pending_payment -> paid`) - `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 alt Produit a la carte
Client->>Borne: selectionner un produit 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 Borne->>Borne: ajouter la ligne au panier local
else Composition d'un menu else Composition d'un menu
Client->>Borne: selectionner un menu Client->>Borne: selectionner un menu
Borne->>API: GET /api/menus (composition du menu) Borne->>API: GET /api/menus (slots + options eligibles)
API->>BDD: lire menu et composition API->>BDD: lire menu, menu_slot, menu_slot_option
BDD-->>API: menu + produits par role BDD-->>API: menu + slots + options
API-->>Borne: composition (JSON) API-->>Borne: composition (JSON)
Borne-->>Client: afficher les choix par slot (burger, accompagnement, boisson, sauce) Borne-->>Client: afficher les slots (boisson, accompagnement, sauce) + format Normal/Maxi
Client->>Borne: choisir chaque composant + tailles Client->>Borne: choisir chaque slot + format + modifiers du burger
Borne->>Borne: ajouter la ligne menu au panier local Borne->>Borne: ajouter la ligne menu au panier local
end end
@ -94,36 +102,35 @@ sequenceDiagram
Borne-->>Client: panier mis a jour Borne-->>Client: panier mis a jour
end 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: valider la commande
Client->>Borne: saisir le numero de retrait Client->>Borne: saisir le numero de retrait
Borne->>Borne: valider le panier (au moins 1 ligne) Borne->>Borne: valider le panier (au moins 1 ligne, numero non vide)
Borne->>API: POST /api/orders (lignes + mode_consommation + numero) Borne->>API: POST /api/orders (lignes + selections + modifiers + service_mode + numero)
API->>API: recalculer les totaux cote serveur API->>API: recalculer les totaux cote serveur (HT / TVA / TTC, taux par produit)
API->>BDD: creer la commande (statut pending_payment) API->>BDD: BEGIN transaction
API->>BDD: creer les lignes (snapshot libelle + prix) API->>BDD: INSERT customer_order (status pending_payment, source kiosk)
BDD-->>API: commande persistee {id, numero, statut: pending_payment} API->>BDD: INSERT order_item (snapshot libelle + prix + vat_rate)
API-->>Borne: 201 Created {id, numero, statut: pending_payment, total} API->>BDD: INSERT order_item_selection (par slot de menu rempli)
Borne-->>Client: afficher le total a regler 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 API-->>Borne: 201 Created {id, order_number, status: paid, total_ttc}
Borne->>API: POST /api/orders/{id}/pay Borne-->>Client: ecran de confirmation avec le numero de retrait
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
Note over Client,BDD: Cas d'erreur Note over Client,BDD: Cas d'erreur
alt Panier vide ou donnees invalides alt Panier vide, produit indisponible ou donnees invalides
API-->>Borne: 4xx {error: code, message} API->>BDD: ROLLBACK (si transaction entamee)
API-->>Borne: 4xx {error: {code, message}}
Borne-->>Client: message d'erreur, retour au panier Borne-->>Client: message d'erreur, retour au panier
end end
``` ```
@ -132,27 +139,31 @@ sequenceDiagram
## 4. Notes de modelisation ## 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 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 utilisateur. L'API recalcule les totaux a la reception du `POST /api/orders` a
partir des prix en base, puis fige les snapshots partir des prix en base (HT, TVA ligne par ligne via `vat_rate`, TTC), puis fige
(`prix_unitaire_ttc_cents_snapshot`, `libelle_snapshot` dans `ligne_commande`, les snapshots (`unit_price_cents_snapshot`, `vat_rate_snapshot`,
voir `dictionary.md` 3.6). Le total affiche par le client n'est pas considere `label_snapshot` sur `order_item`, voir `dictionary.md` 3.11). Le total affiche
comme la source de verite : ceci limite la falsification du prix cote client. 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 Le parcours materialise les transitions T1 et T2 de
`docs/uml/state-commande.md`, en deux phases successives conformes a la regle `docs/uml/state-commande.md` dans **un seul appel et une seule transaction** :
metier :
- `POST /api/orders` cree la commande composee en `pending_payment` (T1). - `POST /api/orders` cree la commande en `pending_payment` (T1) puis la fait
- `POST /api/orders/{id}/pay` enregistre le paiement et fait passer la commande passer a `paid` (T2) avant le `COMMIT`. `paid_at` est renseigne.
a `paid` (T2), avec l'horodatage `paye_a`. - 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 : Le statut `pending_payment` n'est donc pas observable en dehors de la
composer la commande, puis la payer. transaction (coherent avec `mct.md` section 13).
### 4.3 Panier local jusqu'a la validation ### 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 `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 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) les lectures (phases 1 a 2). La validation et la creation (phase 4) requierent
requierent l'API ; sans elle, la commande n'est ni persistee ni payee. Ce cas l'API ; sans elle, la commande n'est ni persistee ni payee. Ce cas degrade
degrade n'est pas detaille dans le diagramme nominal ci-dessus. 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 | | 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 | | 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 | Oui : `categorie`, `produit`, `menu`, `menu_produit`, `commande`, `ligne_commande` | | 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), valeurs ENUM anglaises | | Statuts utilises coherents avec `state-commande.md` | Oui : `pending_payment` puis `paid` (T1, T2), atomiques |
| Format de reponse JSON | Coherent avec `PROJECT_CONTEXT.md` section 7 (`{data, error}`) et la reponse `{id, number, status}` du POST orders | | 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 ## 6. Arbitrage tranche
La phase de paiement est integree au flux conformement a la regle metier des La phase de paiement separee du v0.1 (`POST /api/orders/{id}/pay`) est supprimee :
deux phases (composer puis payer). La sequence suit la machine canonique de la creation et le passage a `paid` sont atomiques dans `POST /api/orders`,
`state-commande.md` : creation en `pending_payment` (T1) puis paiement vers conformement au MCT v0.2 (3.3) et a la regle metier (saisie du numero = substitut
`paid` (T2), avec des valeurs ENUM en anglais. Point a confirmer au MCT : de paiement). Le decrement de stock et la journalisation `stock_movement` sont
l'endpoint de paiement (`POST /api/orders/{id}/pay`) doit etre reporte dans la inclus dans la meme transaction, garantissant la coherence stock/commande. Les
section 7 du brief s'il n'y figure pas encore. 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).

View file

@ -1,8 +1,8 @@
# Diagramme d'etats-transitions - Commande # Diagramme d'etats-transitions - Commande
**Phase UML** : P1 - Conception, complement UML (apres MCD) **Phase UML** : P1 - Conception, complement UML (apres MCD)
**Statut** : v0.1 **Statut** : v0.2 - prod-like, machine a 4 etats
**Date** : 2026-05-21 **Date** : 2026-06-11
**Branche** : `feat/p1-conception` **Branche** : `feat/p1-conception`
**Auteur methodologie** : BYAN **Auteur methodologie** : BYAN
@ -10,38 +10,41 @@
## 1. Objet du document ## 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 Il decrit les etats possibles d'une commande, les transitions autorisees entre
ces etats, les **evenements** qui les declenchent et les **gardes** (conditions) ces etats, les **evenements** qui les declenchent et les **gardes** (conditions)
qui les conditionnent. qui les conditionnent.
Il complete le MCD (`docs/merise/mcd.md` section 9, qui esquisse le cycle de Il complete le MCD (`docs/merise/mcd.md`, cycle de vie de la commande), le
vie) et le dictionnaire (`docs/merise/dictionary.md` 3.5, qui declare l'ENUM). 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 ## 2. Source de verite et regle metier
La regle metier confirmee fixe deux phases successives dans le cycle de vie Le modele v0.2 (prod-like) reduit la machine a **quatre etats**. La regle metier
d'une commande : le client **compose** sa commande, **puis** il **paie**. Une distingue la **composition payee** de la **remise** : une commande est creee et
fois payee, la commande entre en preparation. Le paiement fait partie integrante payee en une operation atomique (la saisie du numero de retrait tient lieu de
du cycle. Les valeurs d'etat sont en anglais et alignees sur l'ENUM du paiement dans le cadre RNCP), puis elle est remise au client en un geste unique.
dictionnaire.
| Source | Valeurs de statut | | Source | Valeurs de statut |
|---|---| |---|---|
| `dictionary.md` 3.5 (ENUM SQL) | `pending_payment`, `paid`, `preparing`, `ready`, `delivered`, `cancelled` | | `dictionary.md` 3.10 (ENUM SQL) | `pending_payment`, `paid`, `delivered`, `cancelled` |
| Regle metier confirmee | composer -> payer -> preparer -> pret -> remettre | | `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. > Le dictionnaire (`dictionary.md` 3.10) et la machine ci-dessous partagent la
Elle suit l'ENUM du dictionnaire et la regle metier des deux phases : > 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. **Etats supprimes par rapport au v0.1** : `preparing` et `ready`. En contexte
- `paid` : paiement effectue ; la commande peut entrer en file de preparation. 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
> Le dictionnaire (`dictionary.md` 3.5) et la machine ci-dessous partagent la valeur metier proportionnelle. La cuisine est en **lecture seule** ; la remise
> meme ENUM, ce qui maintient la coherence entre le modele de donnees et le (`DELIVER_ORDER`) est le geste unique qui fait avancer le statut. Le KPI est le
> modele d'etats (cross-validation, mantra #34). 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 | | 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) | | 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 peut entrer en file de preparation. | Client (paiement) ou Accueil | | Payee | `paid` | Paiement effectue ; la commande entre en file de preparation (lecture seule cuisine). | Client (kiosk) ou Counter/Drive |
| En preparation | `preparing` | Prise en charge par la Preparation, en cuisine. | Preparation | | Livree | `delivered` | Remise effectuee au client. Etat **final**. | Counter ou Drive |
| Prete | `ready` | Preparation terminee, prete au comptoir. | Preparation | | Annulee | `cancelled` | Commande abandonnee ou annulee avant remise. Etat **final**. | Counter, Drive ou Admin |
| Livree | `delivered` | Remise effectuee au client. Etat **final**. | Accueil |
| Annulee | `cancelled` | Commande abandonnee ou annulee. Etat **final**. | Client, Accueil ou Administration | `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 ```mermaid
stateDiagram-v2 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 --> paid : payer\n[atomique dans CREATE_ORDER / CREATE_COUNTER_ORDER\nsaisie du numero = substitut de paiement]
pending_payment --> cancelled : abandonner\n[avant paiement] pending_payment --> cancelled : annuler\n[avant remise]
paid --> preparing : prendre en charge\n[acteur Preparation, file triee par heure croissante] paid --> delivered : remettre au client\n[acteur Counter / Drive, geste unique]
paid --> cancelled : annuler\n[Accueil ou Administration] paid --> cancelled : annuler\n[Counter / Drive / Admin, re-credit du stock]
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]
delivered --> [*] delivered --> [*]
cancelled --> [*] cancelled --> [*]
@ -86,29 +87,29 @@ stateDiagram-v2
| # | De | Vers | Evenement declencheur | Garde (condition) | Acteur | | # | 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 | | 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 de la commande | La commande contient au moins une `ligne_commande` ; le paiement aboutit | Client / Accueil | | 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 | Client / Accueil | | T3 | `pending_payment` | `cancelled` | Abandon avant paiement | Commande pas encore payee | Counter / Drive / Admin |
| T4 | `paid` | `preparing` | Prise en charge en file | La commande est la plus ancienne non traitee (tri par heure de livraison croissante) | Preparation | | 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 preparation | Decision operationnelle | Accueil / Administration | | T5 | `paid` | `cancelled` | Annulation avant remise | L'acteur detient `order.cancel` ; le stock consomme est re-credite (`stock_movement` type `cancellation`) | Counter / Drive / Admin |
| 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 |
### Invariants de la machine a etats ### Invariants de la machine a etats
- `delivered` et `cancelled` sont des etats **finaux** : aucune transition n'en - `delivered` et `cancelled` sont des etats **finaux** : aucune transition n'en
sort. sort.
- Aucune transition ne revient en arriere (pas de `preparing -> paid`). Une - Aucune transition ne revient en arriere. Une erreur operationnelle se traite
erreur operationnelle se traite par annulation puis nouvelle commande, pour par annulation puis nouvelle commande, pour preserver l'integrite de
preserver l'integrite de l'historique et des snapshots de prix. l'historique et des snapshots (`label_snapshot`, `unit_price_cents_snapshot`,
- La transition vers `cancelled` est possible depuis tous les etats **sauf** `vat_rate_snapshot` sur `order_item`).
`delivered` (une commande remise ne s'annule pas dans ce modele). Ceci est - La transition vers `cancelled` est possible depuis `pending_payment` et
coherent avec `mcd.md` section 9 : "Annuler : transition vers `cancelled` `paid`, mais pas depuis `delivered` (une commande remise ne s'annule pas dans
(depuis tout statut sauf `delivered`)". ce modele). Coherent avec `mct.md` 7.1 (`CANCEL_ORDER`).
- `paye_a` (DATETIME, `dictionary.md` 3.5) est renseigne au moment de la - `paid_at` (DATETIME, `dictionary.md` 3.10) est renseigne a la transition T2.
transition T2 (`pending_payment -> paid`) et reste NULL avant. `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 | | Verification | Resultat |
|---|---| |---|---|
| Tous les etats du diagramme existent dans l'ENUM `dictionary.md` 3.5 | Oui (6 valeurs, toutes utilisees) | | 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 `mcd.md` 9 | Respectee (T5, T7, T9 ; pas de transition depuis `delivered`) | | La regle "annulation possible sauf depuis delivered" de `mct.md` 7.1 | Respectee (T3, T5 ; 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) | | Transition `paid -> delivered` en geste unique de `mct.md` 6.1 | Couvert par T4 (`DELIVER_ORDER`) |
| Acteurs de `use-cases.md` | Preparation declenche T4/T6/T7 ; Accueil declenche T8/T9 ; Administration peut annuler | | 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 ## 7. Arbitrage tranche
La divergence historique entre l'ENUM du dictionnaire et un parcours sans La machine retenue est reduite a quatre etats (Decision 4,
paiement est resolue par la regle metier confirmee : le cycle de vie comporte `docs/notes/revue-alignement-p1.md` section 7) :
deux phases successives, la composition de la commande puis son paiement. Le
paiement fait partie integrante du cycle.
La machine canonique retenue est donc :
``` ```
pending_payment -> paid -> preparing -> ready -> delivered pending_payment -> paid -> delivered
(cancelled atteignable depuis pending_payment, paid, preparing) | |
+------------+--------> cancelled (depuis pending_payment ou paid)
``` ```
Cette machine est la source de verite partagee par `dictionary.md` 3.5, **Note sur la transition `pending_payment -> paid`** : dans le cadre RNCP, le
`use-cases.md` (cas "Payer la commande" cote Client) et paiement est remplace par la saisie du numero de commande (kiosk) ou par la
`sequence-passer-commande.md` (etape paiement entre validation du panier et validation de l'equipier (counter/drive). La transition est **atomique** dans
confirmation). La colonne `paye_a` est renseignee a la transition T2. A `CREATE_ORDER` et `CREATE_COUNTER_ORDER` : le statut `pending_payment` n'est pas
revalider lors du MCT. 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).

View file

@ -1,8 +1,8 @@
# Diagramme de cas d'utilisation - Wakdo # Diagramme de cas d'utilisation - Wakdo
**Phase UML** : P1 - Conception, complement UML (apres MCD) **Phase UML** : P1 - Conception, complement UML (apres MCD)
**Statut** : v0.1 **Statut** : v0.2 - prod-like, 5 roles RBAC + catalogue de 23 permissions
**Date** : 2026-05-21 **Date** : 2026-06-11
**Branche** : `feat/p1-conception` **Branche** : `feat/p1-conception`
**Auteur methodologie** : BYAN **Auteur methodologie** : BYAN
@ -12,174 +12,247 @@
Ce document recense les **cas d'utilisation** de Wakdo, c'est-a-dire les 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 fonctionnalites observables du systeme du point de vue de ses acteurs. Il
complete le MCD (`docs/merise/mcd.md`) et le dictionnaire complete le MCD (`docs/merise/mcd.md`), le dictionnaire
(`docs/merise/dictionary.md`) en passant de la vue **donnees** a la vue (`docs/merise/dictionary.md`) et le MCT (`docs/merise/mct.md`, 26 operations) en
**usages**. 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 Le diagramme reste au niveau conceptuel : il identifie qui fait quoi, sans
l'endpoint qui realisera chaque cas, mais identifie qui fait quoi. 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** : **Sources** :
- `docs/PROJECT_CONTEXT.md` sections 2 (acteurs, processus), 7 (scope RBAC) - `docs/PROJECT_CONTEXT.md` sections 2 (acteurs, processus), 7 (scope back-office)
- `docs/merise/dictionary.md` (entites `commande`, `role`, `user`) - `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 ## 2. Acteurs - perimetre et challenge de pertinence
Le brief (`PROJECT_CONTEXT.md` section 2 et section 7) definit les acteurs Le brief initial (`PROJECT_CONTEXT.md` section 2) decrivait quatre acteurs
metier. Avant de les retenir, chaque acteur propose dans la consigne initiale metier (Client, Accueil, Preparation, Administration) adosses a 3 roles RBAC. Le
est confronte au perimetre reel du projet. 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. | | **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**. |
| **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. | | **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. |
| **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`. | | **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`). |
| **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. | | **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". |
| **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". | | **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 ### 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) 1. **Customer** (borne kiosk, non authentifie)
2. **Administration** (role `admin`) 2. **Admin** (role `admin`)
3. **Preparation** (role `preparation`, ex-"Cuisine") 3. **Manager** (role `manager`)
4. **Accueil** (role `accueil`, recouvre le besoin "Caisse") 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. > Regle RBAC permission-driven (`dictionary.md` 3.15) : les rattachements
> Les cas d'usage des employes (Administration, Preparation, Accueil) sont > acteur -> cas ci-dessous refletent la **matrice de permissions par defaut au
> couverts directement ici. Cette decision suit le mantra du Rasoir d'Ockham > seed**. Le gardien reel est la permission, pas le nom du role : un role
> (#37) : on evite une couche de modelisation redondante tant qu'aucun besoin > personnalise (ex. "chef-patissier") dote des bonnes permissions ouvre les
> ne la justifie. > 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 ## 3. Diagramme de cas d'utilisation
Mermaid ne fournit pas de type `usecase` natif. La representation ci-dessous Mermaid ne fournit pas de type `usecase` natif. La representation utilise un
utilise un `flowchart` : les acteurs sont des noeuds a gauche, les cas `flowchart` : les acteurs sont a gauche, les cas d'utilisation regroupes par
d'utilisation sont des noeuds arrondis regroupes par sous-systeme, et les sous-systeme. La permission qui conditionne chaque cas back-office est precisee
fleches portent les relations (`<<include>>`, `<<extend>>`) la ou elles en section 4.
ont du sens.
```mermaid ```mermaid
flowchart LR flowchart LR
%% Acteurs %% Acteurs
Client(("Client<br/>borne kiosk")) Customer(("Customer<br/>borne kiosk<br/>non authentifie"))
Admin(("Administration<br/>role admin")) Admin(("Admin<br/>role admin"))
Prep(("Preparation<br/>role preparation")) Manager(("Manager<br/>role manager"))
Accueil(("Accueil<br/>role accueil")) Kitchen(("Kitchen<br/>role kitchen<br/>lecture seule"))
Counter(("Counter<br/>role counter"))
Drive(("Drive<br/>role drive"))
%% Sous-systeme Borne client %% Sous-systeme Borne client
subgraph BORNE["Borne client - Bloc 1"] subgraph BORNE["Borne client - Bloc 1 (public)"]
UC1(["Consulter le catalogue"]) UC1(["Consulter le catalogue"])
UC2(["Composer un menu"]) UC2(["Composer le panier"])
UC3(["Passer une commande"]) UC3(["Consulter les allergenes"])
UC4(["Saisir le numero de retrait"]) UC4(["Passer une commande"])
UC5(["Recevoir la confirmation"]) UC5(["Saisir le numero de retrait"])
UC6(["Payer la commande"]) UC6(["Recevoir la confirmation"])
end end
%% Sous-systeme Back-office %% Sous-systeme Operations commande
subgraph BACK["Back-office - Bloc 2"] subgraph OPS["Operations commande - back-office"]
UC10(["Gerer le catalogue<br/>categories, produits, menus"]) UC10(["Saisir une commande<br/>comptoir / drive"])
UC11(["Gerer les utilisateurs et roles"]) UC11(["Consulter la file de preparation"])
UC12(["Consulter les statistiques"]) UC12(["Remettre la commande"])
UC20(["Consulter la file de preparation"]) UC13(["Annuler une commande"])
UC21(["Faire avancer une commande"])
UC30(["Saisir une commande<br/>comptoir ou drive"])
UC31(["Remettre la commande au client"])
UC40(["S'authentifier"])
end end
%% Relations Client %% Sous-systeme Catalogue
Client --> UC1 subgraph CAT["Catalogue - back-office"]
Client --> UC2 UC20(["Gerer produits"])
Client --> UC3 UC21(["Gerer menus et slots"])
Client --> UC6 UC22(["Gerer categories"])
Client --> UC5 UC23(["Gerer ingredients,<br/>compositions, allergenes"])
end
%% include / extend cote borne %% Sous-systeme Stock
UC3 -. include .-> UC4 subgraph STK["Stock - back-office"]
UC3 -. include .-> UC6 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 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 --> UC40
Admin --> UC10 Admin --> UC41
Admin --> UC42
Admin --> UC30
Admin --> UC31
Admin --> UC32
Admin --> UC11 Admin --> UC11
Admin --> UC12 Admin --> UC13
Manager --> UC42
%% Relations Preparation %% Authentification mutualisee (tout cas back-office)
Prep --> UC40 UC10 -. include .-> UC50
Prep --> UC20 UC11 -. include .-> UC50
Prep --> UC21 UC20 -. include .-> UC50
UC30 -. include .-> UC50
%% Relations Accueil UC40 -. include .-> UC50
Accueil --> UC40 UC42 -. include .-> UC50
Accueil --> UC30
Accueil --> UC31
UC30 -. include .-> UC1
%% Authentification mutualisee
UC10 -. include .-> UC40
UC11 -. include .-> UC40
UC20 -. include .-> UC40
UC30 -. include .-> UC40
``` ```
--- ---
## 4. Description des cas d'utilisation ## 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` | | S'authentifier | 12.1 AUTHENTICATE_USER | Tous les roles back-office passent par ce cas avant d'acceder a leurs cas (relation `<<include>>`). Verification argon2id, regeneration de session (anti-fixation), `is_active=1` requis, redirection vers `role.default_route`. Le Customer du kiosk n'est pas authentifie. |
| 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` | | Se deconnecter | 12.2 LOGOUT_USER | Destruction de session (`session_destroy()`) sur clic ou expiration (idle 4h / absolu 10h). |
| 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
(`<<include>>`) par chaque cas back-office pour eviter de surcharger le
diagramme. Le Client de la borne n'est pas authentifie (canal `kiosk` public).
--- ---
@ -188,35 +261,32 @@ diagramme. Le Client de la borne n'est pas authentifie (canal `kiosk` public).
| Relation | Type | Justification | | 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 -> 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). | | Passer une commande -> Composer le panier | include | Une commande resulte d'un panier compose. |
| Composer un menu -> Consulter le catalogue | include | Composer un menu suppose de parcourir les produits eligibles a chaque slot. | | Composer le panier -> Consulter le catalogue | include | Composer suppose de parcourir les produits eligibles (a la carte ou par 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. | | 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 (Accueil) -> Consulter le catalogue | include | L'equipier consulte le catalogue pour saisir au comptoir / drive. | | 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. | | 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 Les incoherences que le v0.1 remontait pour arbitrage sont desormais tranchees
modelisation finale releve de sa decision, mantra de validation humaine). par le modele v0.2 (`dictionary.md`, `mct.md`).
1. **ENUM `statut` et phase de paiement (tranche)** 1. **Acteur "Caisse"** : ecarte. L'encaissement est atomique a la creation
Le dictionnaire (`dictionary.md` 3.5) definit (saisie du numero = substitut de paiement, `mct.md` section 13) et realise
`statut ENUM('pending_payment','paid','preparing','ready','delivered','cancelled')` par le Customer (kiosk) ou par `counter`/`drive` (back-office). Aucun role
avec un paiement explicite. La regle metier confirmee fixe deux phases `caisse` n'est necessaire.
successives, la composition de la commande puis son paiement. Le cas 2. **"Manager" vs "Admin"** : scindes en deux roles distincts. `manager` gere le
"Payer la commande" est donc retenu cote Client et materialise la transition catalogue (create/update), le stock/reappro et les stats ; `admin` ajoute les
`pending_payment -> paid`. Cet ecart est tranche : la machine canonique de suppressions catalogue, la gestion des utilisateurs et le RBAC.
`state-commande.md` fait foi. 3. **"Accueil" unique** : scinde en `counter` et `drive`, car le tag `source` et
le filtre `role_visible_source` different selon le canal.
2. **Acteur "Caisse" absent du RBAC** 4. **Machine a etats** : alignee sur les 4 etats du `dictionary.md` 3.10
Aucun role `caisse` n'existe (`PROJECT_CONTEXT.md` section 7 : `admin`, (`pending_payment -> paid -> delivered` + `cancelled`). Plus de `preparing` /
`preparation`, `accueil`). La fonction d'encaissement de la consigne a ete `ready` : la cuisine (`kitchen`) est en lecture seule, la remise est un geste
rattachee a l'acteur **Accueil**. A confirmer. unique (`counter`/`drive`).
5. **Modele permission-driven** : chaque cas back-office est rattache a sa
3. **"Manager" vs "Admin"** permission (catalogue de 23 codes fige, `dictionary.md` 3.17). Le diagramme
La consigne parle de "Manager/Admin" ; le brief ne connait que `admin`. Les reflete la matrice seed ; le gardien effectif reste la permission.
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).