docs(merise): translate prose to French (project ASCII convention), code identifiers unchanged
This commit is contained in:
parent
79d8ad9985
commit
aa6b386f3d
5 changed files with 2063 additions and 2063 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,97 +1,97 @@
|
||||||
# Conceptual Data Model (MCD) — Wakdo
|
# Modele Conceptuel de Donnees (MCD) — Wakdo
|
||||||
|
|
||||||
**Merise phase** : P1 - Conception, step 2 (data dictionary first, mantra #33)
|
**Phase Merise** : P1 - Conception, etape 2 (data dictionary first, mantra #33)
|
||||||
**Version** : v0.2 — prod-like, 21 entities (19 prod-like + security-by-design layer)
|
**Version** : v0.2 — prod-like, 21 entites (19 prod-like + couche security-by-design)
|
||||||
**Date** : 2026-06-04 (security-by-design additions 2026-06-11)
|
**Date** : 2026-06-04 (ajouts security-by-design 2026-06-11)
|
||||||
**Branch** : `feat/p1-conception`
|
**Branche** : `feat/p1-conception`
|
||||||
**Status** : prod-like — all D1-D8 + stock decisions applied (see `docs/notes/revue-alignement-p1.md` §7); security-by-design layer (audit_log + accountability/auth columns) in progress
|
**Statut** : prod-like — toutes les decisions D1-D8 + stock appliquees (voir `docs/notes/revue-alignement-p1.md` §7) ; couche security-by-design (audit_log + colonnes imputabilite/auth) en cours
|
||||||
**Author** : BYAN (methodology layer)
|
**Auteur** : BYAN (couche methodologie)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Purpose of this document
|
## 1. Objectif de ce document
|
||||||
|
|
||||||
The MCD (Modele Conceptuel des Donnees) formalises the **entities** of the Wakdo domain,
|
Le MCD (Modele Conceptuel des Donnees) formalise les **entites** du domaine Wakdo,
|
||||||
their **associations**, and the **cardinalities** governing those associations.
|
leurs **associations**, et les **cardinalites** qui regissent ces associations.
|
||||||
It is the normalised translation of the data dictionary, and serves as the basis for the
|
C'est la traduction normalisee du dictionnaire de donnees, et il sert de base au
|
||||||
MLD (relational mapping).
|
MLD (mapping relationnel).
|
||||||
|
|
||||||
Unlike the dictionary (which details attributes and types), the MCD focuses on relational
|
Contrairement au dictionnaire (qui detaille les attributs et les types), le MCD se concentre sur la
|
||||||
structure: how many X per Y, whether participation is mandatory, whether associations carry
|
structure relationnelle : combien de X par Y, si la participation est obligatoire, si les associations portent
|
||||||
their own attributes.
|
leurs propres attributs.
|
||||||
|
|
||||||
**Sources**:
|
**Sources** :
|
||||||
- `docs/merise/dictionary.md` (v0.2 — 21 entities, source of truth for all names, types, ENUMs)
|
- `docs/merise/dictionary.md` (v0.2 — 21 entites, source de verite pour tous les noms, types, ENUMs)
|
||||||
- `docs/notes/revue-alignement-p1.md` §7 (decision table D1-D8 + stock)
|
- `docs/notes/revue-alignement-p1.md` §7 (table de decisions D1-D8 + stock)
|
||||||
- `docs/PROJECT_CONTEXT.md` (business rules: menu composition, order flow, RBAC, service modes)
|
- `docs/PROJECT_CONTEXT.md` (regles metier : composition de menu, flux de commande, RBAC, modes de service)
|
||||||
- `docs/merise/_sources/` (school data: 9 categories, 53 products, 13 menus)
|
- `docs/merise/_sources/` (donnees de l'ecole : 9 categories, 53 produits, 13 menus)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Merise notation used
|
## 2. Notation Merise utilisee
|
||||||
|
|
||||||
### Cardinalities at the association foot (French Merise style)
|
### Cardinalites au pied de l'association (style Merise francais)
|
||||||
|
|
||||||
At each end of an association, the cardinality `(min,max)` states how many times an
|
A chaque extremite d'une association, la cardinalite `(min,max)` indique combien de fois une
|
||||||
instance of the entity participates in the association.
|
instance de l'entite participe a l'association.
|
||||||
|
|
||||||
```
|
```
|
||||||
ENTITY_A (min,max) ----[ ASSOCIATION ]---- (min,max) ENTITY_B
|
ENTITY_A (min,max) ----[ ASSOCIATION ]---- (min,max) ENTITY_B
|
||||||
```
|
```
|
||||||
|
|
||||||
| Notation | Reading | Example |
|
| Notation | Lecture | Exemple |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `(0,1)` | Optional, at most 1 | A stock_movement links to (0,1) customer_order |
|
| `(0,1)` | Optionnel, au plus 1 | Un stock_movement est lie a (0,1) customer_order |
|
||||||
| `(1,1)` | Mandatory, exactly 1 | A product belongs to (1,1) category |
|
| `(1,1)` | Obligatoire, exactement 1 | Un product appartient a (1,1) category |
|
||||||
| `(0,N)` | Optional, unbounded | A category groups (0,N) products |
|
| `(0,N)` | Optionnel, non borne | Une category regroupe (0,N) products |
|
||||||
| `(1,N)` | At least 1, unbounded | An order contains (1,N) order_items |
|
| `(1,N)` | Au moins 1, non borne | Une commande contient (1,N) order_items |
|
||||||
|
|
||||||
Reading: "one instance of the source entity participates at least MIN times and at most
|
Lecture : "une instance de l'entite source participe au moins MIN fois et au plus
|
||||||
MAX times in the association".
|
MAX fois a l'association".
|
||||||
|
|
||||||
### Association naming convention
|
### Convention de nommage des associations
|
||||||
|
|
||||||
Active verb in business terms, e.g.: `groups`, `anchors`, `defines_slot`, `contains`,
|
Verbe d'action en termes metier, par exemple : `groups`, `anchors`, `defines_slot`, `contains`,
|
||||||
`references_product`, `references_menu`, `fills_slot`, `modifies_ingredient`, `logs`,
|
`references_product`, `references_menu`, `fills_slot`, `modifies_ingredient`, `logs`,
|
||||||
`holds`, `grants`, `filters_source`, `decrements`.
|
`holds`, `grants`, `filters_source`, `decrements`.
|
||||||
|
|
||||||
N-N associations that carry their own attributes become **associative entities** in the MLD
|
Les associations N-N qui portent leurs propres attributs deviennent des **entites associatives** dans le MLD
|
||||||
(join table with own columns).
|
(table de jointure avec colonnes propres).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Decomposition by sub-domain
|
## 3. Decomposition par sous-domaine
|
||||||
|
|
||||||
The 21-entity model is split into 4 sub-domains for readability. Beyond approximately
|
Le modele de 21 entites est divise en 4 sous-domaines pour la lisibilite. Au-dela d'environ
|
||||||
5 entities, a single flat diagram becomes difficult to read; decomposition is the standard
|
5 entites, un diagramme plat unique devient difficile a lire ; la decomposition est la pratique
|
||||||
Merise practice for models of this size.
|
Merise standard pour les modeles de cette taille.
|
||||||
|
|
||||||
| Sub-domain | Entities | Count |
|
| Sous-domaine | Entites | Nombre |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Catalogue | category, product, menu, menu_slot, menu_slot_option | 5 |
|
| Catalogue | category, product, menu, menu_slot, menu_slot_option | 5 |
|
||||||
| Ingredients & Stock | ingredient, product_ingredient, allergen, ingredient_allergen, stock_movement | 5 |
|
| Ingredients & Stock | ingredient, product_ingredient, allergen, ingredient_allergen, stock_movement | 5 |
|
||||||
| Order | customer_order, order_item, order_item_selection, order_item_modifier | 4 |
|
| Order | customer_order, order_item, order_item_selection, order_item_modifier | 4 |
|
||||||
| RBAC & Audit | user, role, role_visible_source, permission, role_permission, audit_log, login_throttle | 7 |
|
| RBAC & Audit | user, role, role_visible_source, permission, role_permission, audit_log, login_throttle | 7 |
|
||||||
|
|
||||||
> **Security-by-design layer (2026-06-11)**: `audit_log` (entity 20) is a cross-cutting,
|
> **Couche security-by-design (2026-06-11)** : `audit_log` (entite 20) est un journal transverse,
|
||||||
> append-only log of sensitive actions; it is placed in the RBAC & Audit sub-domain because
|
> append-only des actions sensibles ; il est place dans le sous-domaine RBAC & Audit parce que
|
||||||
> its references (`actor_user_id`, `actor_role_id`) are RBAC entities. `login_throttle`
|
> ses references (`actor_user_id`, `actor_role_id`) sont des entites RBAC. `login_throttle`
|
||||||
> (entity 21) is a per-source-IP brute-force throttle, keyed by IP and carrying no FK; it sits
|
> (entite 21) est un throttle anti-brute-force par IP source, indexe par IP et ne portant aucune FK ; il se situe
|
||||||
> in the same sub-domain because it guards the authentication path. New columns on existing
|
> dans le meme sous-domaine parce qu'il protege le chemin d'authentification. Nouvelles colonnes sur des entites existantes :
|
||||||
> entities: `user` auth-lifecycle + `pin_hash` + `anonymized_at`, `customer_order.acting_user_id`
|
> `user` cycle de vie auth + `pin_hash` + `anonymized_at`, `customer_order.acting_user_id`
|
||||||
> + `idempotency_key`. See dictionary note 13.
|
> + `idempotency_key`. Voir note 13 du dictionnaire.
|
||||||
|
|
||||||
**Note on the absence of a global diagram**: a single 21-entity ER diagram would be
|
**Note sur l'absence d'un diagramme global** : un unique diagramme ER de 21 entites serait
|
||||||
unreadable and unmaintainable. The sub-domain decomposition below is the intentional
|
illisible et impossible a maintenir. La decomposition par sous-domaine ci-dessous est le choix
|
||||||
structural choice. Each sub-domain is a Mermaid `erDiagram` (authoritative, rendered
|
structurel intentionnel. Chaque sous-domaine est un `erDiagram` Mermaid (faisant autorite, rendu
|
||||||
natively) with a portable SVG render in `docs/merise/_diagrams/`; see section 11 for the
|
nativement) avec un rendu SVG portable dans `docs/merise/_diagrams/` ; voir la section 11 pour les
|
||||||
sources and regeneration command.
|
sources et la commande de regeneration.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Sub-domain: Catalogue
|
## 4. Sous-domaine : Catalogue
|
||||||
|
|
||||||
### 4.1 Mermaid entity-relationship diagram
|
### 4.1 Diagramme entite-relation Mermaid
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
|
|
@ -147,30 +147,30 @@ erDiagram
|
||||||
product ||--o{ menu_slot_option : "is_eligible_for"
|
product ||--o{ menu_slot_option : "is_eligible_for"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 Association cardinalities
|
### 4.2 Cardinalites des associations
|
||||||
|
|
||||||
| # | Association | Side A | Cardinality A | Side B | Cardinality B | Justification |
|
| # | Association | Cote A | Cardinalite A | Cote B | Cardinalite B | Justification |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| C1 | groups (product) | category | (0,N) | product | (1,1) | A category can exist with no products yet (created empty). A product must belong to exactly one category to appear on the kiosk. |
|
| C1 | groups (product) | category | (0,N) | product | (1,1) | Une categorie peut exister sans aucun produit pour l'instant (creee vide). Un produit doit appartenir a exactement une categorie pour apparaitre sur la borne. |
|
||||||
| C2 | groups (menu) | category | (0,N) | menu | (1,1) | Same rationale as C1 for menus. All 13 menus belong to the `menus` category. |
|
| C2 | groups (menu) | category | (0,N) | menu | (1,1) | Meme raisonnement que C1 pour les menus. Les 13 menus appartiennent a la categorie `menus`. |
|
||||||
| C3 | anchors | menu | (1,1) | product | (0,N) | Each menu is built around exactly one fixed burger product (`burger_product_id`). A product may anchor 0 or more menus (a burger not used in a menu yet; or a popular burger anchoring several formats). |
|
| C3 | anchors | menu | (1,1) | product | (0,N) | Chaque menu est construit autour d'exactement un produit burger fixe (`burger_product_id`). Un produit peut ancrer 0 ou plusieurs menus (un burger pas encore utilise dans un menu ; ou un burger populaire ancrant plusieurs formats). |
|
||||||
| C4 | defines_slot | menu | (1,N) | menu_slot | (1,1) | A menu must define at least one slot (drink, side, sauce) to have customisable composition. A slot belongs to exactly one menu. |
|
| C4 | defines_slot | menu | (1,N) | menu_slot | (1,1) | Un menu doit definir au moins un slot (boisson, accompagnement, sauce) pour avoir une composition personnalisable. Un slot appartient a exactement un menu. |
|
||||||
| C5 | lists | menu_slot | (1,N) | menu_slot_option | (1,1) | A slot must list at least one eligible product (otherwise the customer cannot fill it). Each option row belongs to exactly one slot. |
|
| C5 | lists | menu_slot | (1,N) | menu_slot_option | (1,1) | Un slot doit lister au moins un produit eligible (sinon le client ne peut pas le remplir). Chaque ligne d'option appartient a exactement un slot. |
|
||||||
| C6 | is_eligible_for | product | (0,N) | menu_slot_option | (1,1) | A product may be eligible for any number of slots across all menus, or none if it is only sold a la carte. Each option row references exactly one product. |
|
| C6 | is_eligible_for | product | (0,N) | menu_slot_option | (1,1) | Un produit peut etre eligible pour un nombre quelconque de slots a travers tous les menus, ou aucun s'il n'est vendu qu'a la carte. Chaque ligne d'option reference exactement un produit. |
|
||||||
|
|
||||||
### 4.3 Notes on the Catalogue sub-domain
|
### 4.3 Notes sur le sous-domaine Catalogue
|
||||||
|
|
||||||
**`menu_slot` vs category filter**: the explicit eligibility list `menu_slot_option(menu_slot_id, product_id)` was chosen over a category-based filter (`menu_slot.category_id`). Rationale: a product added to the `drinks` category should not automatically appear in every drink slot of every menu. The explicit list avoids accidental eligibility when the catalogue grows (see dictionary note 11).
|
**`menu_slot` vs filtre par categorie** : la liste d'eligibilite explicite `menu_slot_option(menu_slot_id, product_id)` a ete choisie plutot qu'un filtre base sur la categorie (`menu_slot.category_id`). Raisonnement : un produit ajoute a la categorie `drinks` ne devrait pas apparaitre automatiquement dans chaque slot boisson de chaque menu. La liste explicite evite une eligibilite accidentelle quand le catalogue s'agrandit (voir note 11 du dictionnaire).
|
||||||
|
|
||||||
**`menu.burger_product_id` as anchor**: the menu references a specific burger product, not a generic slot. This allows the ingredient configurator (sub-domain Ingredients & Stock) to resolve which ingredients are modifiable for a menu line, via `menu -> burger_product_id -> product_ingredient`.
|
**`menu.burger_product_id` comme ancre** : le menu reference un produit burger specifique, pas un slot generique. Cela permet au configurateur d'ingredients (sous-domaine Ingredients & Stock) de resoudre quels ingredients sont modifiables pour une ligne de menu, via `menu -> burger_product_id -> product_ingredient`.
|
||||||
|
|
||||||
**Normal / Maxi format**: two prices (`price_normal_cents`, `price_maxi_cents`) on `menu`; format recorded at `order_item.format`. No individual slot-level price differential is stored (see dictionary note 7).
|
**Format Normal / Maxi** : deux prix (`price_normal_cents`, `price_maxi_cents`) sur `menu` ; format enregistre au niveau de `order_item.format`. Aucun differentiel de prix au niveau du slot individuel n'est stocke (voir note 7 du dictionnaire).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Sub-domain: Ingredients & Stock
|
## 5. Sous-domaine : Ingredients & Stock
|
||||||
|
|
||||||
### 5.1 Mermaid entity-relationship diagram
|
### 5.1 Diagramme entite-relation Mermaid
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
|
|
@ -236,35 +236,35 @@ erDiagram
|
||||||
user |o--o{ stock_movement : "logs"
|
user |o--o{ stock_movement : "logs"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 Association cardinalities
|
### 5.2 Cardinalites des associations
|
||||||
|
|
||||||
| # | Association | Side A | Cardinality A | Side B | Cardinality B | Justification |
|
| # | Association | Cote A | Cardinalite A | Cote B | Cardinalite B | Justification |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| I1 | is_composed_of | product | (0,N) | product_ingredient | (1,1) | A product may have no ingredients entered in the system yet (catalogue row exists before recipe is entered). A recipe row belongs to exactly one product. |
|
| I1 | is_composed_of | product | (0,N) | product_ingredient | (1,1) | Un produit peut n'avoir aucun ingredient encore saisi dans le systeme (la ligne de catalogue existe avant que la recette ne soit saisie). Une ligne de recette appartient a exactement un produit. |
|
||||||
| I2 | appears_in | ingredient | (1,N) | product_ingredient | (1,1) | An ingredient in active use appears in at least one product recipe. Each recipe row references exactly one ingredient. Newly created ingredients with no recipe row yet are modelled as (0,N) from a pure structural standpoint; the business rule of (1,N) applies to ingredients in production use. |
|
| I2 | appears_in | ingredient | (1,N) | product_ingredient | (1,1) | Un ingredient en usage actif apparait dans au moins une recette de produit. Chaque ligne de recette reference exactement un ingredient. Les ingredients nouvellement crees sans ligne de recette sont modelises en (0,N) d'un point de vue purement structurel ; la regle metier de (1,N) s'applique aux ingredients en usage de production. |
|
||||||
| I3 | contains (allergens) | ingredient | (0,N) | ingredient_allergen | (1,1) | An ingredient may contain no regulated allergens (e.g., pure salt). Each allergen-link row belongs to one ingredient. |
|
| I3 | contains (allergens) | ingredient | (0,N) | ingredient_allergen | (1,1) | Un ingredient peut ne contenir aucun allergene reglemente (par exemple, du sel pur). Chaque ligne de lien d'allergene appartient a un ingredient. |
|
||||||
| I4 | is_present_in | allergen | (0,N) | ingredient_allergen | (1,1) | An allergen may initially have no linked ingredients (seed: allergen catalogue is complete before recipe data is entered). Each link row references one allergen. |
|
| I4 | is_present_in | allergen | (0,N) | ingredient_allergen | (1,1) | Un allergene peut initialement n'avoir aucun ingredient lie (seed : le catalogue d'allergenes est complet avant que les donnees de recette ne soient saisies). Chaque ligne de lien reference un allergene. |
|
||||||
| I5 | decrements | ingredient | (0,N) | stock_movement | (1,1) | All movements affect exactly one ingredient. An ingredient may have no stock movement rows yet if it was recently created and no orders have been placed. Each movement row references exactly one ingredient. |
|
| I5 | decrements | ingredient | (0,N) | stock_movement | (1,1) | Tous les mouvements affectent exactement un ingredient. Un ingredient peut n'avoir encore aucune ligne de mouvement de stock s'il a ete cree recemment et qu'aucune commande n'a ete passee. Chaque ligne de mouvement reference exactement un ingredient. |
|
||||||
| I6 | triggers | customer_order | (0,1) | stock_movement | (0,N) | A `sale` or `cancellation` movement references the originating order. A `restock` or `inventory_correction` has no order (NULL). A given order triggers movements across all its ingredients; an order still `pending_payment` has triggered no movement yet. |
|
| I6 | triggers | customer_order | (0,1) | stock_movement | (0,N) | Un mouvement `sale` ou `cancellation` reference la commande d'origine. Un `restock` ou `inventory_correction` n'a pas de commande (NULL). Une commande donnee declenche des mouvements sur tous ses ingredients ; une commande encore `pending_payment` n'a declenche aucun mouvement. |
|
||||||
| I7 | logs | user | (0,1) | stock_movement | (0,N) | Automated sale decrements have no user (NULL). Manual restocks and corrections are attributed to a user. A user may log any number of movements. |
|
| I7 | logs | user | (0,1) | stock_movement | (0,N) | Les decrements de vente automatises n'ont pas d'utilisateur (NULL). Les reapprovisionnements et corrections manuels sont attribues a un utilisateur. Un utilisateur peut journaliser un nombre quelconque de mouvements. |
|
||||||
|
|
||||||
### 5.3 Notes on the Ingredients & Stock sub-domain
|
### 5.3 Notes sur le sous-domaine Ingredients & Stock
|
||||||
|
|
||||||
**`product_ingredient` as an associative entity**: the N-N association between `product` and `ingredient` carries five attributes (`quantity_normal`, `quantity_maxi`, `is_removable`, `is_addable`, `extra_price_cents`). It becomes a join table in the MLD with composite PK `(product_id, ingredient_id)`.
|
**`product_ingredient` comme entite associative** : l'association N-N entre `product` et `ingredient` porte cinq attributs (`quantity_normal`, `quantity_maxi`, `is_removable`, `is_addable`, `extra_price_cents`). Elle devient une table de jointure dans le MLD avec une PK composite `(product_id, ingredient_id)`.
|
||||||
|
|
||||||
**`ingredient_allergen` as a pure join table**: no own attributes. The allergen set for a product is computed at query time by joining `product_ingredient -> ingredient_allergen -> allergen`; no manual per-product entry is needed.
|
**`ingredient_allergen` comme table de jointure pure** : aucun attribut propre. L'ensemble des allergenes d'un produit est calcule au moment de la requete en joignant `product_ingredient -> ingredient_allergen -> allergen` ; aucune saisie manuelle par produit n'est necessaire.
|
||||||
|
|
||||||
**`stock_movement` immutability**: this table is append-only. No UPDATE or DELETE is permitted at application layer. Corrections are new rows with `movement_type = 'inventory_correction'` and a signed `delta`.
|
**Immuabilite de `stock_movement`** : cette table est append-only. Aucun UPDATE ni DELETE n'est autorise au niveau applicatif. Les corrections sont de nouvelles lignes avec `movement_type = 'inventory_correction'` et un `delta` signe.
|
||||||
|
|
||||||
**Percentage-based stock model**: stock health is anchored on a per-ingredient `stock_capacity` (the 100% reference, `CHECK > 0`). `stock_quantity` is signed and may go negative when sales outrun counted stock; the system does not block an order on a low stock read. `stock_pct = ROUND(stock_quantity / stock_capacity * 100)` is computed, not stored. Two percentage thresholds drive a three-band behaviour: `low_stock_pct` (warning band, default 10%) and `critical_stock_pct` (auto-out-of-stock floor, default 5%), with the table-level invariant `critical_stock_pct < low_stock_pct`. Above the low band is normal; between critical and low the product stays orderable and a manager alert is raised (the manager either pulls the product via `product.is_available = 0` or restocks to clear the alert); at or below the critical band the product auto-goes out-of-stock (computed, see below).
|
**Modele de stock base sur les pourcentages** : la sante du stock est ancree sur une `stock_capacity` par ingredient (la reference 100%, `CHECK > 0`). `stock_quantity` est signe et peut devenir negatif quand les ventes depassent le stock compte ; le systeme ne bloque pas une commande sur une lecture de stock bas. `stock_pct = ROUND(stock_quantity / stock_capacity * 100)` est calcule, pas stocke. Deux seuils en pourcentage pilotent un comportement a trois bandes : `low_stock_pct` (bande d'alerte, defaut 10%) et `critical_stock_pct` (plancher de mise en rupture automatique, defaut 5%), avec l'invariant au niveau de la table `critical_stock_pct < low_stock_pct`. Au-dessus de la bande d'alerte, c'est normal ; entre critique et bas, le produit reste commandable et une alerte manager est levee (le manager soit retire le produit via `product.is_available = 0`, soit reapprovisionne pour lever l'alerte) ; au niveau ou en dessous de la bande critique, le produit passe automatiquement en rupture (calcule, voir ci-dessous).
|
||||||
|
|
||||||
**Computed product availability (rule RG-T21, see `mlt.md`)**: effective orderability is derived, not stored. A product is orderable when `product.is_available = 1` AND each non-removable (`is_removable = 0`) ingredient in its `product_ingredient` has `stock_quantity > stock_capacity * critical_stock_pct / 100`. A required ingredient reaching the critical band makes the product auto-out-of-stock with no write and no cascade; a manual pull (`product.is_available = 0`) is a hard override; restock above the critical band makes the product orderable again on its own. A removable/optional ingredient at the critical band does not block the product (only its add-on becomes unavailable). The dashboard distinguishes a manual pull from a stock-driven OOS.
|
**Disponibilite produit calculee (regle RG-T21, voir `mlt.md`)** : la commandabilite effective est derivee, pas stockee. Un produit est commandable quand `product.is_available = 1` ET que chaque ingredient non retirable (`is_removable = 0`) de son `product_ingredient` a `stock_quantity > stock_capacity * critical_stock_pct / 100`. Un ingredient requis atteignant la bande critique met le produit en rupture automatique sans ecriture et sans cascade ; un retrait manuel (`product.is_available = 0`) est une surcharge forte ; un reapprovisionnement au-dessus de la bande critique rend le produit commandable a nouveau de lui-meme. Un ingredient retirable/optionnel a la bande critique ne bloque pas le produit (seul son supplement devient indisponible). Le tableau de bord distingue un retrait manuel d'une rupture pilotee par le stock.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Sub-domain: Order
|
## 6. Sous-domaine : Order
|
||||||
|
|
||||||
### 6.1 Mermaid entity-relationship diagram
|
### 6.1 Diagramme entite-relation Mermaid
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
|
|
@ -336,55 +336,55 @@ erDiagram
|
||||||
ingredient ||--o{ order_item_modifier : "modified_by"
|
ingredient ||--o{ order_item_modifier : "modified_by"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6.2 Association cardinalities
|
### 6.2 Cardinalites des associations
|
||||||
|
|
||||||
| # | Association | Side A | Cardinality A | Side B | Cardinality B | Justification |
|
| # | Association | Cote A | Cardinalite A | Cote B | Cardinalite B | Justification |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| O1 | contains | customer_order | (1,N) | order_item | (1,1) | An order without at least one line has no business meaning. A line belongs to exactly one order. ON DELETE CASCADE: if the order is purged, its lines go with it. |
|
| O1 | contains | customer_order | (1,N) | order_item | (1,1) | Une commande sans au moins une ligne n'a aucun sens metier. Une ligne appartient a exactement une commande. ON DELETE CASCADE : si la commande est purgee, ses lignes partent avec elle. |
|
||||||
| O2 | references_product | order_item | (0,1) | product | (0,N) | When `item_type = 'product'`, `product_id` is non-null (1 product referenced). When `item_type = 'menu'`, `product_id` is NULL (0). A product may appear in any number of order lines across history. |
|
| O2 | references_product | order_item | (0,1) | product | (0,N) | Quand `item_type = 'product'`, `product_id` est non nul (1 produit reference). Quand `item_type = 'menu'`, `product_id` est NULL (0). Un produit peut apparaitre dans un nombre quelconque de lignes de commande a travers l'historique. |
|
||||||
| O3 | references_menu | order_item | (0,1) | menu | (0,N) | Symmetric to O2 for the menu discriminator branch. Exactly one of O2/O3 is active per line (CHECK constraint in MLD). |
|
| O3 | references_menu | order_item | (0,1) | menu | (0,N) | Symetrique a O2 pour la branche du discriminateur menu. Exactement un de O2/O3 est actif par ligne (contrainte CHECK dans le MLD). |
|
||||||
| O4 | fills_slot | order_item | (0,N) | order_item_selection | (1,1) | A `menu`-type order line has one selection per slot (typically 2-3). A `product`-type line has no selections (0). Each selection row belongs to exactly one order line. |
|
| O4 | fills_slot | order_item | (0,N) | order_item_selection | (1,1) | Une ligne de commande de type `menu` a une selection par slot (typiquement 2-3). Une ligne de type `product` n'a aucune selection (0). Chaque ligne de selection appartient a exactement une ligne de commande. |
|
||||||
| O5 | slot_filled_by | menu_slot | (0,N) | order_item_selection | (1,1) | A slot definition may have been chosen many times across historical orders (0,N). Each selection row references exactly one slot. ON DELETE RESTRICT: preserves historical records if the slot definition is later changed. |
|
| O5 | slot_filled_by | menu_slot | (0,N) | order_item_selection | (1,1) | Une definition de slot peut avoir ete choisie de nombreuses fois a travers les commandes historiques (0,N). Chaque ligne de selection reference exactement un slot. ON DELETE RESTRICT : preserve les enregistrements historiques si la definition de slot est modifiee ulterieurement. |
|
||||||
| O6 | chosen_for_slot | product | (0,N) | order_item_selection | (1,1) | A product may have been selected for many slot choices across history. Each selection references one product. |
|
| O6 | chosen_for_slot | product | (0,N) | order_item_selection | (1,1) | Un produit peut avoir ete selectionne pour de nombreux choix de slot a travers l'historique. Chaque selection reference un produit. |
|
||||||
| O7 | modifies_ingredient | order_item | (0,N) | order_item_modifier | (1,1) | An order line may have any number of ingredient modifications (remove onion, add cheese). Each modifier row belongs to one order line. |
|
| O7 | modifies_ingredient | order_item | (0,N) | order_item_modifier | (1,1) | Une ligne de commande peut avoir un nombre quelconque de modifications d'ingredients (retirer l'oignon, ajouter du fromage). Chaque ligne de modificateur appartient a une ligne de commande. |
|
||||||
| O8 | modified_by | ingredient | (0,N) | order_item_modifier | (1,1) | An ingredient may have been modified in many order lines across history. Each modifier references one ingredient. |
|
| O8 | modified_by | ingredient | (0,N) | order_item_modifier | (1,1) | Un ingredient peut avoir ete modifie dans de nombreuses lignes de commande a travers l'historique. Chaque modificateur reference un ingredient. |
|
||||||
|
|
||||||
### 6.3 Notes on the Order sub-domain
|
### 6.3 Notes sur le sous-domaine Order
|
||||||
|
|
||||||
**Polymorphism on `order_item`**: each line references either a `product` or a `menu` (not both, not neither). The discriminator `item_type` ENUM drives which FK is populated. The mutual exclusivity is enforced by a CHECK constraint in the MLD. This pattern (2 nullable FKs + discriminator + CHECK) is a standard relational approach to single-table inheritance without a separate table per type.
|
**Polymorphisme sur `order_item`** : chaque ligne reference soit un `product`, soit un `menu` (ni les deux, ni aucun). Le discriminateur `item_type` ENUM pilote quelle FK est renseignee. L'exclusivite mutuelle est imposee par une contrainte CHECK dans le MLD. Ce pattern (2 FK nullables + discriminateur + CHECK) est une approche relationnelle standard de l'heritage en table unique sans table separee par type.
|
||||||
|
|
||||||
**`order_item_selection` (menu slot choices)**: captures which product the customer chose for each slot of a menu line. One row per slot filled. Used for KPI analysis (most popular drink/side combinations). The `label_snapshot` preserves the product name at transaction time.
|
**`order_item_selection` (choix de slot de menu)** : capture quel produit le client a choisi pour chaque slot d'une ligne de menu. Une ligne par slot rempli. Utilise pour l'analyse de KPI (combinaisons boisson/accompagnement les plus populaires). Le `label_snapshot` preserve le nom du produit au moment de la transaction.
|
||||||
|
|
||||||
**`order_item_modifier` (ingredient modifications)**: attaches to an `order_item` regardless of whether the line is a standalone product or a menu. For a menu line, the modifiable product is the fixed burger, resolved via `order_item.menu_id -> menu.burger_product_id` (see dictionary note 10). No additional FK column is needed on `order_item_modifier`.
|
**`order_item_modifier` (modifications d'ingredients)** : se rattache a un `order_item` que la ligne soit un produit autonome ou un menu. Pour une ligne de menu, le produit modifiable est le burger fixe, resolu via `order_item.menu_id -> menu.burger_product_id` (voir note 10 du dictionnaire). Aucune colonne FK supplementaire n'est necessaire sur `order_item_modifier`.
|
||||||
|
|
||||||
**Price snapshots**: `label_snapshot`, `unit_price_cents_snapshot`, and `vat_rate_snapshot` on `order_item` preserve the state at transaction time. If a product is later renamed or repriced, historical order data remains consistent. ON DELETE RESTRICT on `product_id` and `menu_id` is a secondary safeguard.
|
**Snapshots de prix** : `label_snapshot`, `unit_price_cents_snapshot`, et `vat_rate_snapshot` sur `order_item` preservent l'etat au moment de la transaction. Si un produit est ulterieurement renomme ou retarife, les donnees de commande historiques restent coherentes. ON DELETE RESTRICT sur `product_id` et `menu_id` est une protection secondaire.
|
||||||
|
|
||||||
**`service_day` computation** (KPI grouping): not stored as a column. Computed at query time:
|
**Calcul de `service_day`** (regroupement KPI) : non stocke comme colonne. Calcule au moment de la requete :
|
||||||
```sql
|
```sql
|
||||||
CASE WHEN HOUR(created_at) < 10 THEN DATE(created_at) - INTERVAL 1 DAY ELSE DATE(created_at) END
|
CASE WHEN HOUR(created_at) < 10 THEN DATE(created_at) - INTERVAL 1 DAY ELSE DATE(created_at) END
|
||||||
```
|
```
|
||||||
Cutoff: 10:00. The generated-column formula with `INTERVAL 4 HOUR 30 MINUTE` from the v0.1 MLD
|
Seuil : 10:00. La formule de colonne generee avec `INTERVAL 4 HOUR 30 MINUTE` du MLD v0.1
|
||||||
was incorrect and is dropped (decision D6, `revue-alignement-p1.md` §7).
|
etait incorrecte et est abandonnee (decision D6, `revue-alignement-p1.md` §7).
|
||||||
|
|
||||||
**`source = 'drive' => service_mode = 'drive'`**: cross-constraint. A drive-channel order can
|
**`source = 'drive' => service_mode = 'drive'`** : contrainte croisee. Une commande du canal drive ne peut
|
||||||
only have `service_mode = 'drive'`. Enforced at application layer (and optionally as a CHECK in
|
avoir que `service_mode = 'drive'`. Imposee au niveau applicatif (et optionnellement comme CHECK dans
|
||||||
the MLD).
|
le MLD).
|
||||||
|
|
||||||
**4-state machine** (`pending_payment -> paid -> delivered` + `cancelled`):
|
**Machine a 4 etats** (`pending_payment -> paid -> delivered` + `cancelled`) :
|
||||||
`preparing` and `ready` are dropped (decision D4, `revue-alignement-p1.md` §7). KPI timing is
|
`preparing` et `ready` sont abandonnes (decision D4, `revue-alignement-p1.md` §7). Le timing KPI est
|
||||||
`delivered_at - paid_at`; KDS colour coding is computed from `NOW() - paid_at`.
|
`delivered_at - paid_at` ; le codage couleur KDS est calcule a partir de `NOW() - paid_at`.
|
||||||
|
|
||||||
**Security-by-design columns (2026-06-11)**: `idempotency_key` (client UUID, UNIQUE)
|
**Colonnes security-by-design (2026-06-11)** : `idempotency_key` (UUID client, UNIQUE)
|
||||||
deduplicates a retried `POST /api/orders`. `acting_user_id` (FK -> `user`, ON DELETE SET NULL)
|
deduplique un `POST /api/orders` rejoue. `acting_user_id` (FK -> `user`, ON DELETE SET NULL)
|
||||||
records the counter/drive staff who took the order under PIN; NULL for anonymous kiosk orders.
|
enregistre l'employe de comptoir/drive qui a pris la commande sous PIN ; NULL pour les commandes anonymes de la borne.
|
||||||
This adds a `customer_order |o--o| user : "taken_by"` association (cardinality: an order is
|
Cela ajoute une association `customer_order |o--o| user : "taken_by"` (cardinalite : une commande est
|
||||||
taken by (0,1) user; a user takes (0,N) orders). See dictionary note 13.
|
prise par (0,1) user ; un user prend (0,N) commandes). Voir note 13 du dictionnaire.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Sub-domain: RBAC
|
## 7. Sous-domaine : RBAC
|
||||||
|
|
||||||
### 7.1 Mermaid entity-relationship diagram
|
### 7.1 Diagramme entite-relation Mermaid
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
|
|
@ -453,175 +453,175 @@ erDiagram
|
||||||
role |o--o{ audit_log : "context_of"
|
role |o--o{ audit_log : "context_of"
|
||||||
```
|
```
|
||||||
|
|
||||||
> `login_throttle` is a standalone entity with no association: it is keyed by source IP
|
> `login_throttle` est une entite autonome sans association : elle est indexee par IP source
|
||||||
> (`ip_address UNIQUE`), not by a modelled actor, so it carries no FK and connects to no
|
> (`ip_address UNIQUE`), pas par un acteur modelise, donc elle ne porte aucune FK et ne se connecte a aucune
|
||||||
> other entity in the diagram.
|
> autre entite du diagramme.
|
||||||
|
|
||||||
### 7.2 Association cardinalities
|
### 7.2 Cardinalites des associations
|
||||||
|
|
||||||
| # | Association | Side A | Cardinality A | Side B | Cardinality B | Justification |
|
| # | Association | Cote A | Cardinalite A | Cote B | Cardinalite B | Justification |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| R1 | holds | user | (1,1) | role | (0,N) | A user must have exactly one role to access the back-office. A role may have no current users (created but not yet assigned). ON DELETE RESTRICT on `role_id`: a role cannot be deleted while users hold it. |
|
| R1 | holds | user | (1,1) | role | (0,N) | Un utilisateur doit avoir exactement un role pour acceder au back-office. Un role peut n'avoir aucun utilisateur actuel (cree mais pas encore assigne). ON DELETE RESTRICT sur `role_id` : un role ne peut etre supprime tant que des utilisateurs le detiennent. |
|
||||||
| R2 | sees_source | role | (0,N) | role_visible_source | (1,1) | A role may see 0 or more order sources on the preparation dashboard (admin/manager use a global view with no source filter). Each visibility row belongs to exactly one role. |
|
| R2 | sees_source | role | (0,N) | role_visible_source | (1,1) | Un role peut voir 0 ou plusieurs sources de commande sur le tableau de bord de preparation (admin/manager utilisent une vue globale sans filtre de source). Chaque ligne de visibilite appartient a exactement un role. |
|
||||||
| R3 | grants | role | (0,N) | role_permission | (1,1) | A role may have no permissions (a newly created role before assignment) or many. Each mapping row belongs to one role. |
|
| R3 | grants | role | (0,N) | role_permission | (1,1) | Un role peut n'avoir aucune permission (un role nouvellement cree avant assignation) ou plusieurs. Chaque ligne de mapping appartient a un role. |
|
||||||
| R4 | granted_to | permission | (0,N) | role_permission | (1,1) | A permission may be granted to no roles yet (declared at seed, not yet distributed) or to several. Each mapping row references one permission. |
|
| R4 | granted_to | permission | (0,N) | role_permission | (1,1) | Une permission peut n'etre encore accordee a aucun role (declaree au seed, pas encore distribuee) ou a plusieurs. Chaque ligne de mapping reference une permission. |
|
||||||
| R5 | performs | user | (0,1) | audit_log | (0,N) | A sensitive action captured under PIN records its acting user; automated/non-attributable entries carry NULL. A user may have logged any number of actions. ON DELETE SET NULL preserves the trail on user anonymisation/removal. |
|
| R5 | performs | user | (0,1) | audit_log | (0,N) | Une action sensible capturee sous PIN enregistre son utilisateur agissant ; les entrees automatisees/non attribuables portent NULL. Un utilisateur peut avoir journalise un nombre quelconque d'actions. ON DELETE SET NULL preserve la trace lors de l'anonymisation/suppression de l'utilisateur. |
|
||||||
| R6 | context_of | role | (0,1) | audit_log | (0,N) | Each audit row may denormalise the actor's role at action time (NULL allowed). A role may be the context of many audit rows. ON DELETE SET NULL preserves the trail. |
|
| R6 | context_of | role | (0,1) | audit_log | (0,N) | Chaque ligne d'audit peut denormaliser le role de l'acteur au moment de l'action (NULL autorise). Un role peut etre le contexte de nombreuses lignes d'audit. ON DELETE SET NULL preserve la trace. |
|
||||||
|
|
||||||
### 7.3 Notes on the RBAC sub-domain
|
### 7.3 Notes sur le sous-domaine RBAC
|
||||||
|
|
||||||
**RBAC architecture**: roles are dynamic (creatable and modifiable via admin UI). Permissions are static (declared in migration, tied to application code). Application code tests permissions, not role names: adding a new role with the right permissions requires no code change (permission-driven, per Sandhu/NIST RBAC model — decision D4, `revue-alignement-p1.md` §7).
|
**Architecture RBAC** : les roles sont dynamiques (creables et modifiables via l'UI admin). Les permissions sont statiques (declarees en migration, liees au code applicatif). Le code applicatif teste les permissions, pas les noms de role : ajouter un nouveau role avec les bonnes permissions ne necessite aucun changement de code (permission-driven, selon le modele RBAC Sandhu/NIST — decision D4, `revue-alignement-p1.md` §7).
|
||||||
|
|
||||||
**`role.order_source`**: when a counter or drive staff member creates an order, the `source` column on `customer_order` is automatically populated from their role's `order_source`. NULL for admin and manager (they can create on behalf of any channel).
|
**`role.order_source`** : quand un employe de comptoir ou de drive cree une commande, la colonne `source` sur `customer_order` est automatiquement renseignee a partir de l'`order_source` de son role. NULL pour admin et manager (ils peuvent creer pour le compte de n'importe quel canal).
|
||||||
|
|
||||||
**`role.default_route`**: the landing screen for each role, stored in the database. Front-end routing reads this value at login; no role name is hardcoded in routing logic.
|
**`role.default_route`** : l'ecran d'arrivee pour chaque role, stocke en base de donnees. Le routage front-end lit cette valeur au login ; aucun nom de role n'est code en dur dans la logique de routage.
|
||||||
|
|
||||||
**`role_visible_source`**: a pure join table linking a role to the set of order sources visible on the preparation dashboard. A `kitchen` role sees all three sources; a `counter` role sees `kiosk` and `counter`; a `drive` role sees only `drive`.
|
**`role_visible_source`** : une table de jointure pure liant un role a l'ensemble des sources de commande visibles sur le tableau de bord de preparation. Un role `kitchen` voit les trois sources ; un role `counter` voit `kiosk` et `counter` ; un role `drive` ne voit que `drive`.
|
||||||
|
|
||||||
**`role_permission`** and **`role_visible_source`** both use composite PKs. ON DELETE CASCADE on both FKs of `role_permission` (deleting a role or a permission removes its mappings). ON DELETE CASCADE on `role_id` of `role_visible_source`.
|
**`role_permission`** et **`role_visible_source`** utilisent tous deux des PK composites. ON DELETE CASCADE sur les deux FK de `role_permission` (supprimer un role ou une permission retire ses mappings). ON DELETE CASCADE sur le `role_id` de `role_visible_source`.
|
||||||
|
|
||||||
**Seed roles** (5 roles, frozen at DDL; extendable without code change):
|
**Roles de seed** (5 roles, figes au DDL ; extensibles sans changement de code) :
|
||||||
`admin`, `manager`, `kitchen`, `counter`, `drive`.
|
`admin`, `manager`, `kitchen`, `counter`, `drive`.
|
||||||
|
|
||||||
**`audit_log` (security-by-design)**: append-only log of sensitive actions, immutable like
|
**`audit_log` (security-by-design)** : journal append-only des actions sensibles, immuable comme
|
||||||
`stock_movement`. Both FKs (`actor_user_id`, `actor_role_id`) are nullable with ON DELETE
|
`stock_movement`. Les deux FK (`actor_user_id`, `actor_role_id`) sont nullables avec ON DELETE
|
||||||
SET NULL, so the trail survives user anonymisation (RGPD) and role removal. The `actor_role_id`
|
SET NULL, de sorte que la trace survit a l'anonymisation de l'utilisateur (RGPD) et a la suppression de role. Le `actor_role_id`
|
||||||
is denormalised on purpose: even if the user is later anonymised, the role context of the
|
est denormalise a dessein : meme si l'utilisateur est ulterieurement anonymise, le contexte de role de
|
||||||
action is preserved. It carries no PII (the `details` JSON stores changed field names, not
|
l'action est preserve. Il ne porte aucune PII (le JSON `details` stocke les noms des champs modifies, pas les
|
||||||
values for user-targeted actions). See dictionary 3.20 and note 13.
|
valeurs pour les actions ciblant un utilisateur). Voir dictionnaire 3.20 et note 13.
|
||||||
|
|
||||||
**`login_throttle` (security-by-design)**: per-source-IP brute-force throttle, complementing
|
**`login_throttle` (security-by-design)** : throttle anti-brute-force par IP source, complementant
|
||||||
the per-account counter already on `user` (`failed_login_attempts` / `lockout_until`). One row
|
le compteur par compte deja present sur `user` (`failed_login_attempts` / `lockout_until`). Une ligne
|
||||||
per IP (`ip_address VARCHAR(45) UNIQUE`, 45 chars to hold a full IPv6 literal), upserted on each
|
par IP (`ip_address VARCHAR(45) UNIQUE`, 45 caracteres pour contenir un litteral IPv6 complet), upsertee a chaque
|
||||||
failed login: `failed_attempts` counts consecutive failures from this IP in the current window,
|
echec de login : `failed_attempts` compte les echecs consecutifs depuis cette IP dans la fenetre courante,
|
||||||
`window_started_at` marks the start of that window (which resets when expired), `lockout_until`
|
`window_started_at` marque le debut de cette fenetre (qui se reinitialise a son expiration), `lockout_until`
|
||||||
holds the end of the degressive backoff (NULL = not throttled), `last_attempt_at` the timestamp
|
contient la fin du backoff degressif (NULL = non throttle), `last_attempt_at` l'horodatage
|
||||||
of the last failed attempt. It has no FK (an IP is not a modelled entity) and no association. A
|
de la derniere tentative echouee. Elle n'a aucune FK (une IP n'est pas une entite modelisee) et aucune association. Un
|
||||||
daily cron purges rows with no active lockout whose `last_attempt_at` is older than 24h. See
|
cron quotidien purge les lignes sans lockout actif dont le `last_attempt_at` est plus ancien que 24h. Voir
|
||||||
dictionary 3.21 and note 13.
|
dictionnaire 3.21 et note 13.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Cross-validation MCD <-> dictionary
|
## 8. Validation croisee MCD <-> dictionnaire
|
||||||
|
|
||||||
Verification that all 21 dictionary entities appear in the MCD and vice versa.
|
Verification que les 21 entites du dictionnaire apparaissent dans le MCD et reciproquement.
|
||||||
|
|
||||||
| # | Dictionary entity (section 3) | Sub-domain in MCD | Present |
|
| # | Entite du dictionnaire (section 3) | Sous-domaine dans le MCD | Presente |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | `category` (3.1) | Catalogue | Yes |
|
| 1 | `category` (3.1) | Catalogue | Oui |
|
||||||
| 2 | `product` (3.2) | Catalogue + Ingredients + Order | Yes |
|
| 2 | `product` (3.2) | Catalogue + Ingredients + Order | Oui |
|
||||||
| 3 | `menu` (3.3) | Catalogue + Order | Yes |
|
| 3 | `menu` (3.3) | Catalogue + Order | Oui |
|
||||||
| 4 | `menu_slot` (3.4) | Catalogue + Order | Yes |
|
| 4 | `menu_slot` (3.4) | Catalogue + Order | Oui |
|
||||||
| 5 | `menu_slot_option` (3.5) | Catalogue | Yes |
|
| 5 | `menu_slot_option` (3.5) | Catalogue | Oui |
|
||||||
| 6 | `ingredient` (3.6) | Ingredients + Order | Yes |
|
| 6 | `ingredient` (3.6) | Ingredients + Order | Oui |
|
||||||
| 7 | `product_ingredient` (3.7) | Ingredients | Yes |
|
| 7 | `product_ingredient` (3.7) | Ingredients | Oui |
|
||||||
| 8 | `allergen` (3.8) | Ingredients | Yes |
|
| 8 | `allergen` (3.8) | Ingredients | Oui |
|
||||||
| 9 | `ingredient_allergen` (3.9) | Ingredients | Yes |
|
| 9 | `ingredient_allergen` (3.9) | Ingredients | Oui |
|
||||||
| 10 | `customer_order` (3.10) | Order | Yes |
|
| 10 | `customer_order` (3.10) | Order | Oui |
|
||||||
| 11 | `order_item` (3.11) | Order | Yes |
|
| 11 | `order_item` (3.11) | Order | Oui |
|
||||||
| 12 | `order_item_selection` (3.12) | Order | Yes |
|
| 12 | `order_item_selection` (3.12) | Order | Oui |
|
||||||
| 13 | `order_item_modifier` (3.13) | Order | Yes |
|
| 13 | `order_item_modifier` (3.13) | Order | Oui |
|
||||||
| 14 | `user` (3.14) | RBAC | Yes |
|
| 14 | `user` (3.14) | RBAC | Oui |
|
||||||
| 15 | `role` (3.15) | RBAC | Yes |
|
| 15 | `role` (3.15) | RBAC | Oui |
|
||||||
| 16 | `role_visible_source` (3.16) | RBAC | Yes |
|
| 16 | `role_visible_source` (3.16) | RBAC | Oui |
|
||||||
| 17 | `permission` (3.17) | RBAC | Yes |
|
| 17 | `permission` (3.17) | RBAC | Oui |
|
||||||
| 18 | `role_permission` (3.18) | RBAC | Yes |
|
| 18 | `role_permission` (3.18) | RBAC | Oui |
|
||||||
| 19 | `stock_movement` (3.19) | Ingredients & Stock | Yes |
|
| 19 | `stock_movement` (3.19) | Ingredients & Stock | Oui |
|
||||||
| 20 | `audit_log` (3.20) | RBAC & Audit | Yes |
|
| 20 | `audit_log` (3.20) | RBAC & Audit | Oui |
|
||||||
| 21 | `login_throttle` (3.21) | RBAC & Audit | Yes |
|
| 21 | `login_throttle` (3.21) | RBAC & Audit | Oui |
|
||||||
|
|
||||||
**Result**: 21/21 entities traced (19 prod-like + `audit_log` and `login_throttle`
|
**Resultat** : 21/21 entites tracees (19 prod-like + `audit_log` et `login_throttle`
|
||||||
security-by-design). No entity from the dictionary is absent from the MCD. No entity in the MCD
|
security-by-design). Aucune entite du dictionnaire n'est absente du MCD. Aucune entite du MCD
|
||||||
falls outside the dictionary.
|
ne tombe en dehors du dictionnaire.
|
||||||
|
|
||||||
**Entities appearing in multiple sub-domains** (cross-domain shared entities):
|
**Entites apparaissant dans plusieurs sous-domaines** (entites partagees inter-domaines) :
|
||||||
- `product`: Catalogue (sold item, slot eligibility) + Ingredients (recipe) + Order (line reference, slot choice)
|
- `product` : Catalogue (article vendu, eligibilite de slot) + Ingredients (recette) + Order (reference de ligne, choix de slot)
|
||||||
- `menu`: Catalogue (definition, slots) + Order (line reference)
|
- `menu` : Catalogue (definition, slots) + Order (reference de ligne)
|
||||||
- `menu_slot`: Catalogue (slot definition) + Order (slot choices via `order_item_selection`)
|
- `menu_slot` : Catalogue (definition de slot) + Order (choix de slot via `order_item_selection`)
|
||||||
- `ingredient`: Ingredients (recipe, stock) + Order (modifiers)
|
- `ingredient` : Ingredients (recette, stock) + Order (modificateurs)
|
||||||
- `customer_order`: Order (order lifecycle) + Ingredients (stock movement trigger) + RBAC & Audit (taken_by staff via `acting_user_id`)
|
- `customer_order` : Order (cycle de vie de la commande) + Ingredients (declencheur de mouvement de stock) + RBAC & Audit (employe taken_by via `acting_user_id`)
|
||||||
- `user`: RBAC (authentication) + Ingredients (stock movement author) + Order (`acting_user_id` on counter/drive orders) + Audit (actor of `audit_log`)
|
- `user` : RBAC (authentification) + Ingredients (auteur de mouvement de stock) + Order (`acting_user_id` sur les commandes comptoir/drive) + Audit (acteur de `audit_log`)
|
||||||
- `role`: RBAC (permissions, visible sources) + Audit (denormalised `actor_role_id` context on `audit_log`)
|
- `role` : RBAC (permissions, sources visibles) + Audit (contexte `actor_role_id` denormalise sur `audit_log`)
|
||||||
|
|
||||||
This is expected in a normalised model. The sub-domain split is for readability; the actual
|
C'est attendu dans un modele normalise. La division par sous-domaine est pour la lisibilite ; le schema
|
||||||
relational schema is a unified graph.
|
relationnel reel est un graphe unifie.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Decisions deferred to the MLD
|
## 9. Decisions reportees au MLD
|
||||||
|
|
||||||
The MCD remains at the conceptual level. The following decisions are deferred to the MLD:
|
Le MCD reste au niveau conceptuel. Les decisions suivantes sont reportees au MLD :
|
||||||
|
|
||||||
1. **Resolution of associative entities into tables**: `product_ingredient`, `menu_slot_option`,
|
1. **Resolution des entites associatives en tables** : `product_ingredient`, `menu_slot_option`,
|
||||||
`ingredient_allergen`, `role_visible_source`, `role_permission` become join tables with
|
`ingredient_allergen`, `role_visible_source`, `role_permission` deviennent des tables de jointure avec
|
||||||
composite PKs.
|
des PK composites.
|
||||||
2. **Technical PK vs business identifier**: `id INT UNSIGNED AUTO_INCREMENT` on all main entities.
|
2. **PK technique vs identifiant metier** : `id INT UNSIGNED AUTO_INCREMENT` sur toutes les entites principales.
|
||||||
`customer_order` additionally carries `order_number VARCHAR(20) UNIQUE` (human-readable,
|
`customer_order` porte en plus `order_number VARCHAR(20) UNIQUE` (lisible par un humain,
|
||||||
format `K/C/D-YYYY-MM-DD-NNN` per channel).
|
format `K/C/D-YYYY-MM-DD-NNN` par canal).
|
||||||
3. **ON DELETE rules**: CASCADE vs RESTRICT vs SET NULL. Detailed in the MLD.
|
3. **Regles ON DELETE** : CASCADE vs RESTRICT vs SET NULL. Detaillees dans le MLD.
|
||||||
4. **CHECK constraints**: polymorphism exclusivity on `order_item`, cross-constraint
|
4. **Contraintes CHECK** : exclusivite de polymorphisme sur `order_item`, contrainte croisee
|
||||||
`source/service_mode` on `customer_order`, arithmetic invariant on totals.
|
`source/service_mode` sur `customer_order`, invariant arithmetique sur les totaux.
|
||||||
5. **Indexes**: not discussed at MCD level. Defined in the MLD for frequent query patterns.
|
5. **Index** : non discutes au niveau MCD. Definis dans le MLD pour les patterns de requete frequents.
|
||||||
6. **`service_day` formula**: applicative CASE expression, not a stored generated column.
|
6. **Formule `service_day`** : expression applicative CASE, pas une colonne generee stockee.
|
||||||
Documented in the MLD.
|
Documentee dans le MLD.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. MCD <-> MCT coherence (mantra #34)
|
## 10. Coherence MCD <-> MCT (mantra #34)
|
||||||
|
|
||||||
Pre-validation: each entity participates in at least one treatment.
|
Pre-validation : chaque entite participe a au moins un traitement.
|
||||||
|
|
||||||
| Entity | Expected treatment(s) |
|
| Entite | Traitement(s) attendu(s) |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `category` | Admin CRUD |
|
| `category` | CRUD admin |
|
||||||
| `product` | Admin CRUD + kiosk cart add |
|
| `product` | CRUD admin + ajout au panier borne |
|
||||||
| `menu` | Admin CRUD + kiosk cart add |
|
| `menu` | CRUD admin + ajout au panier borne |
|
||||||
| `menu_slot` | Admin CRUD (menu composition) |
|
| `menu_slot` | CRUD admin (composition de menu) |
|
||||||
| `menu_slot_option` | Admin CRUD (slot eligibility management) |
|
| `menu_slot_option` | CRUD admin (gestion de l'eligibilite des slots) |
|
||||||
| `ingredient` | Admin CRUD + stock movements |
|
| `ingredient` | CRUD admin + mouvements de stock |
|
||||||
| `product_ingredient` | Admin recipe management |
|
| `product_ingredient` | Gestion des recettes admin |
|
||||||
| `allergen` | Admin CRUD (seed: read-only catalogue) |
|
| `allergen` | CRUD admin (seed : catalogue en lecture seule) |
|
||||||
| `ingredient_allergen` | Admin allergen mapping |
|
| `ingredient_allergen` | Mapping des allergenes admin |
|
||||||
| `customer_order` | Full order lifecycle (create -> pay -> deliver / cancel) |
|
| `customer_order` | Cycle de vie complet de la commande (create -> pay -> deliver / cancel) |
|
||||||
| `order_item` | Cart building, line creation at validation |
|
| `order_item` | Construction du panier, creation de ligne a la validation |
|
||||||
| `order_item_selection` | Menu slot selection during cart building |
|
| `order_item_selection` | Selection de slot de menu pendant la construction du panier |
|
||||||
| `order_item_modifier` | Ingredient modification during cart building |
|
| `order_item_modifier` | Modification d'ingredient pendant la construction du panier |
|
||||||
| `user` | Admin CRUD + login |
|
| `user` | CRUD admin + login |
|
||||||
| `role` | Admin CRUD + user assignment |
|
| `role` | CRUD admin + assignation d'utilisateur |
|
||||||
| `role_visible_source` | Admin role configuration |
|
| `role_visible_source` | Configuration de role admin |
|
||||||
| `permission` | Admin permission matrix management |
|
| `permission` | Gestion de la matrice de permissions admin |
|
||||||
| `role_permission` | Admin permission matrix management |
|
| `role_permission` | Gestion de la matrice de permissions admin |
|
||||||
| `stock_movement` | Automatic at `paid` transition; manual restock and inventory correction |
|
| `stock_movement` | Automatique a la transition `paid` ; reapprovisionnement manuel et correction d'inventaire |
|
||||||
| `audit_log` | Written by sensitive operations: UPDATE/DELETE product/menu (8.2/8.3/8.6), CANCEL_ORDER (7.1), RESTOCK/INVENTORY_COUNT (9.1/9.2), user ops (10.1-10.3), MANAGE_RBAC (10.4), and failed/successful logins (12.1) |
|
| `audit_log` | Ecrit par les operations sensibles : UPDATE/DELETE product/menu (8.2/8.3/8.6), CANCEL_ORDER (7.1), RESTOCK/INVENTORY_COUNT (9.1/9.2), operations utilisateur (10.1-10.3), MANAGE_RBAC (10.4), et logins echoues/reussis (12.1) |
|
||||||
| `login_throttle` | Read and written by AUTHENTICATE_USER (12.1): per-source-IP throttle upserted on each failed login, read to enforce the backoff window, purged by a daily cron |
|
| `login_throttle` | Lu et ecrit par AUTHENTICATE_USER (12.1) : throttle par IP source upserte a chaque echec de login, lu pour imposer la fenetre de backoff, purge par un cron quotidien |
|
||||||
|
|
||||||
Cross-validation MCD <-> MCT (mantra #34) to be completed exhaustively in `mct.md`
|
La validation croisee MCD <-> MCT (mantra #34) sera completee de maniere exhaustive dans `mct.md`
|
||||||
once the MCT incorporates the security-by-design operations (PIN-gated sensitive actions,
|
une fois que le MCT integrera les operations security-by-design (actions sensibles protegees par PIN,
|
||||||
audit writes, reset/lockout, anonymisation). The treatment-layer additions are tracked there.
|
ecritures d'audit, reset/lockout, anonymisation). Les ajouts de la couche traitements y sont suivis.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Diagram sources and regeneration
|
## 11. Sources des diagrammes et regeneration
|
||||||
|
|
||||||
The authoritative graphical model is the set of Mermaid `erDiagram` blocks in sections 4-7,
|
Le modele graphique faisant autorite est l'ensemble des blocs `erDiagram` Mermaid des sections 4-7,
|
||||||
one per sub-domain. They render natively on Forgejo and GitHub. The MCD is decomposed by
|
un par sous-domaine. Ils s'affichent nativement sur Forgejo et GitHub. Le MCD est decompose par
|
||||||
sub-domain on purpose: a single 21-entity diagram cannot be laid out without crossing
|
sous-domaine a dessein : un unique diagramme de 21 entites ne peut etre dispose sans croisement de
|
||||||
relationship lines (intrinsic planarity limit, and `erDiagram` offers no manual layout
|
lignes de relation (limite de planarite intrinseque, et `erDiagram` n'offre aucun controle de mise en page
|
||||||
control). Each sub-domain stays at 5-8 entities, which auto-layout handles cleanly. The
|
manuel). Chaque sous-domaine reste a 5-8 entites, ce que la mise en page automatique gere proprement. La
|
||||||
integrated view across sub-domains is the cross-validation table in section 8.
|
vue integree a travers les sous-domaines est la table de validation croisee de la section 8.
|
||||||
|
|
||||||
Portable SVG renders live in `docs/merise/_diagrams/` (for PDF export / offline viewing):
|
Des rendus SVG portables se trouvent dans `docs/merise/_diagrams/` (pour l'export PDF / consultation hors ligne) :
|
||||||
|
|
||||||
| Sub-domain | Source | Render |
|
| Sous-domaine | Source | Rendu |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Catalogue | `mcd-catalogue.mmd` | `mcd-catalogue.svg` |
|
| Catalogue | `mcd-catalogue.mmd` | `mcd-catalogue.svg` |
|
||||||
| Ingredients & Stock | `mcd-ingredients-stock.mmd` | `mcd-ingredients-stock.svg` |
|
| Ingredients & Stock | `mcd-ingredients-stock.mmd` | `mcd-ingredients-stock.svg` |
|
||||||
| Order | `mcd-order.mmd` | `mcd-order.svg` |
|
| Order | `mcd-order.mmd` | `mcd-order.svg` |
|
||||||
| RBAC | `mcd-rbac.mmd` | `mcd-rbac.svg` |
|
| RBAC | `mcd-rbac.mmd` | `mcd-rbac.svg` |
|
||||||
|
|
||||||
The `.mmd` files are extracted from the `erDiagram` blocks above; the `.svg` are produced by
|
Les fichiers `.mmd` sont extraits des blocs `erDiagram` ci-dessus ; les `.svg` sont produits par
|
||||||
`make docs-render` (mmdc). If a block here changes, re-extract the matching `.mmd` and re-run
|
`make docs-render` (mmdc). Si un bloc ici change, re-extraire le `.mmd` correspondant et relancer
|
||||||
`make docs-render`. The legacy v0.1 `.drawio` sources have been removed: drawio gave manual
|
`make docs-render`. Les anciennes sources `.drawio` v0.1 ont ete supprimees : drawio offrait un controle de mise en page
|
||||||
layout control but required hand-editing and did not render in the Markdown previews, whereas
|
manuel mais necessitait une edition a la main et ne s'affichait pas dans les apercus Markdown, alors que
|
||||||
the decomposed Mermaid blocks are version-controlled, render everywhere, and stay in sync with
|
les blocs Mermaid decomposes sont versionnes, s'affichent partout, et restent synchronises avec
|
||||||
this document.
|
ce document.
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,44 @@
|
||||||
# Model of Conceptual Treatments (MCT) — Wakdo
|
# Modele Conceptuel des Traitements (MCT) — Wakdo
|
||||||
|
|
||||||
**Merise phase** : P1 - Conception, step 3 (after MCD)
|
**Phase Merise** : P1 - Conception, etape 3 (apres le MCD)
|
||||||
**Version** : v0.2 — prod-like, 4-state machine (+ security-by-design layer 2026-06-11)
|
**Version** : v0.2 — prod-like, machine a 4 etats (+ couche security-by-design 2026-06-11)
|
||||||
**Date** : 2026-06-04 (security-by-design additions 2026-06-11)
|
**Date** : 2026-06-04 (ajouts security-by-design 2026-06-11)
|
||||||
**Branch** : `feat/p1-conception`
|
**Branche** : `feat/p1-conception`
|
||||||
**Status** : prod-like — all D1-D8 + stock decisions applied (see `docs/notes/revue-alignement-p1.md` §7); security-by-design ops added (ERASE_USER_PII, RESET_PASSWORD, PIN-gated sensitive set, audit_log writes, auth throttling) — 28 operations
|
**Statut** : prod-like — toutes les decisions D1-D8 + stock appliquees (voir `docs/notes/revue-alignement-p1.md` §7) ; operations security-by-design ajoutees (ERASE_USER_PII, RESET_PASSWORD, ensemble sensible protege par PIN, ecritures audit_log, throttling d'authentification) — 28 operations
|
||||||
**Author** : BYAN (methodology layer)
|
**Auteur** : BYAN (couche methodologie)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Purpose
|
## 1. Objectif
|
||||||
|
|
||||||
The MCT (Model of Conceptual Treatments) describes the **business operations** of the Wakdo
|
Le MCT (Modele Conceptuel des Traitements) decrit les **operations metier** du domaine
|
||||||
domain in the canonical Merise form: **triggering event -> operation -> emitted result**.
|
Wakdo sous la forme canonique Merise : **evenement declencheur -> operation -> resultat emis**.
|
||||||
|
|
||||||
It answers the question: what happens in the domain, and when?
|
Il repond a la question : que se passe-t-il dans le domaine, et quand ?
|
||||||
It does not answer: who does what, on which workstation, in which organisational order
|
Il ne repond pas a : qui fait quoi, sur quel poste de travail, dans quel ordre organisationnel
|
||||||
(the MOT level is intentionally skipped — agile shortcut, consistent with the solo RNCP
|
(le niveau MOT est volontairement saute — raccourci agile, coherent avec le cadre RNCP
|
||||||
framework).
|
solo).
|
||||||
|
|
||||||
The MCT covers:
|
Le MCT couvre :
|
||||||
- The order lifecycle end-to-end (kiosk, counter, drive)
|
- Le cycle de vie de la commande de bout en bout (kiosk, comptoir, drive)
|
||||||
- Catalogue management (manager / admin)
|
- La gestion du catalogue (manager / admin)
|
||||||
- User and role management (admin)
|
- La gestion des utilisateurs et des roles (admin)
|
||||||
- Back-office authentication (all back-office actors)
|
- L'authentification back-office (tous les acteurs back-office)
|
||||||
|
|
||||||
**Identified actors**:
|
**Acteurs identifies** :
|
||||||
|
|
||||||
| Actor | Code | Interface |
|
| Acteur | Code | Interface |
|
||||||
|-------|------|-----------|
|
|-------|------|-----------|
|
||||||
| Customer (kiosk) | CUSTOMER | Touch kiosk (public, unauthenticated) |
|
| Client (kiosk) | CUSTOMER | Borne tactile (public, non authentifie) |
|
||||||
| Counter staff | COUNTER | Back-office, role `counter` |
|
| Personnel comptoir | COUNTER | Back-office, role `counter` |
|
||||||
| Drive staff | DRIVE | Back-office, role `drive` |
|
| Personnel drive | DRIVE | Back-office, role `drive` |
|
||||||
| Kitchen staff | KITCHEN | Back-office, role `kitchen` (read-only on orders) |
|
| Personnel cuisine | KITCHEN | Back-office, role `kitchen` (lecture seule sur les commandes) |
|
||||||
| Manager | MANAGER | Back-office, role `manager` |
|
| Manager | MANAGER | Back-office, role `manager` |
|
||||||
| Administrator | ADMIN | Back-office, role `admin` |
|
| Administrateur | ADMIN | Back-office, role `admin` |
|
||||||
| System | SYS | Internal API / PHP logic |
|
| Systeme | SYS | API interne / logique PHP |
|
||||||
|
|
||||||
**MCD cross-reference**: each operation references entities from the MCD (section 14).
|
**Reference croisee MCD** : chaque operation reference des entites du MCD (section 14).
|
||||||
The MCT is consistent with the `customer_order.status` state machine:
|
Le MCT est coherent avec la machine a etats de `customer_order.status` :
|
||||||
|
|
||||||
```
|
```
|
||||||
pending_payment -> paid -> delivered
|
pending_payment -> paid -> delivered
|
||||||
|
|
@ -46,32 +46,32 @@ pending_payment -> paid -> delivered
|
||||||
+--------------+-----------> cancelled (from any non-terminal state)
|
+--------------+-----------> cancelled (from any non-terminal state)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Dropped states** (compared to v0.1): `preparing` and `ready` are removed.
|
**Etats supprimes** (par rapport a v0.1) : `preparing` et `ready` sont retires.
|
||||||
Rationale: in a fast-food context the kitchen display (KDS) is a visual system; staff read
|
Justification : dans un contexte fast-food, l'affichage cuisine (KDS) est un systeme visuel ;
|
||||||
the ticket and act. The single staff gesture is "deliver". KPI is total time
|
le personnel lit le ticket et agit. L'unique geste du personnel est « delivrer ». Le KPI est
|
||||||
`delivered_at - paid_at` (SLA approx. 10 min). KDS colour coding is computed from
|
le temps total `delivered_at - paid_at` (SLA approx. 10 min). Le code couleur du KDS est calcule a partir de
|
||||||
`now - paid_at`; no additional stored state is required.
|
`now - paid_at` ; aucun etat stocke supplementaire n'est requis.
|
||||||
|
|
||||||
**Dropped operations** (compared to v0.1): `MARK_IN_PREPARATION` (`MARQUER_EN_PREPARATION`)
|
**Operations supprimees** (par rapport a v0.1) : `MARK_IN_PREPARATION` (`MARQUER_EN_PREPARATION`)
|
||||||
and `MARK_READY` (`MARQUER_PRETE`) are removed because their intermediate states no longer
|
et `MARK_READY` (`MARQUER_PRETE`) sont retirees car leurs etats intermediaires n'existent plus.
|
||||||
exist. `DELIVER_ORDER` becomes the sole status-advancing action for counter/drive staff.
|
`DELIVER_ORDER` devient la seule action faisant avancer le statut pour le personnel comptoir/drive.
|
||||||
|
|
||||||
**Security-by-design layer (2026-06-11)**: two operations are added — `RESET_PASSWORD` (12.3)
|
**Couche security-by-design (2026-06-11)** : deux operations sont ajoutees — `RESET_PASSWORD` (12.3)
|
||||||
and `ERASE_USER_PII` (10.5, RGPD anonymisation). A subset of operations is **PIN-gated**:
|
et `ERASE_USER_PII` (10.5, anonymisation RGPD). Un sous-ensemble d'operations est **protege par PIN** :
|
||||||
back-office sessions stay shared per workstation, but a per-staff PIN re-authorises the
|
les sessions back-office restent partagees par poste de travail, mais un PIN par membre du personnel
|
||||||
sensitive set — `CANCEL_ORDER` (7.1), `UPDATE_PRODUCT`/`DELETE_PRODUCT` (8.2/8.3),
|
re-autorise l'ensemble sensible — `CANCEL_ORDER` (7.1), `UPDATE_PRODUCT`/`DELETE_PRODUCT` (8.2/8.3),
|
||||||
`DELETE_MENU` (8.6), `INVENTORY_COUNT` (9.2), user management (10.1-10.3), `MANAGE_RBAC`
|
`DELETE_MENU` (8.6), `INVENTORY_COUNT` (9.2), gestion des utilisateurs (10.1-10.3), `MANAGE_RBAC`
|
||||||
(10.4), `ERASE_USER_PII` (10.5). These non-stock actions append an immutable `audit_log` row
|
(10.4), `ERASE_USER_PII` (10.5). Ces actions hors stock ajoutent une ligne `audit_log` immuable
|
||||||
(actor, action, target); stock actions record attribution in `stock_movement`. The treatment
|
(acteur, action, cible) ; les actions de stock enregistrent l'attribution dans `stock_movement`. La logique
|
||||||
logic (PIN, audit, throttling, idempotency, atomic stock decrement, computed product
|
de traitement (PIN, audit, throttling, idempotence, decrement atomique du stock, disponibilite produit
|
||||||
availability) is specified in `mlt.md` (rules RG-T13-T21). This adds entities 20 `audit_log`
|
calculee) est specifiee dans `mlt.md` (regles RG-T13-T21). Cela ajoute les entites 20 `audit_log`
|
||||||
and 21 `login_throttle` to the model.
|
et 21 `login_throttle` au modele.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Representation conventions
|
## 2. Conventions de representation
|
||||||
|
|
||||||
### Operation format
|
### Format des operations
|
||||||
|
|
||||||
```
|
```
|
||||||
[TRIGGERING EVENT(S)]
|
[TRIGGERING EVENT(S)]
|
||||||
|
|
@ -84,467 +84,467 @@ and 21 `login_throttle` to the model.
|
||||||
[EMITTED RESULT(S)]
|
[EMITTED RESULT(S)]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Synchronisations**:
|
**Synchronisations** :
|
||||||
- `AND`: all events must be present simultaneously to trigger the operation.
|
- `AND` : tous les evenements doivent etre presents simultanement pour declencher l'operation.
|
||||||
- `OR`: any one of the events is sufficient.
|
- `OR` : l'un quelconque des evenements suffit.
|
||||||
|
|
||||||
**Conditions**: expressed in square brackets `[condition]` on the incoming arc.
|
**Conditions** : exprimees entre crochets `[condition]` sur l'arc entrant.
|
||||||
|
|
||||||
### Textual notation
|
### Notation textuelle
|
||||||
|
|
||||||
For each operation the document provides:
|
Pour chaque operation, le document fournit :
|
||||||
- **Triggering event(s)**: what occurs and causes the operation.
|
- **Evenement(s) declencheur(s)** : ce qui survient et provoque l'operation.
|
||||||
- **Actor(s)**: who initiates (or validates).
|
- **Acteur(s)** : qui initie (ou valide).
|
||||||
- **Synchronisation**: `AND` / `OR` if multiple events, plus condition.
|
- **Synchronisation** : `AND` / `OR` si plusieurs evenements, plus la condition.
|
||||||
- **Operation**: name and description of what it does.
|
- **Operation** : nom et description de ce qu'elle fait.
|
||||||
- **MCD entities touched**: read (R) or write (W).
|
- **Entites MCD touchees** : lecture (R) ou ecriture (W).
|
||||||
- **Result(s)**: what is emitted or produced.
|
- **Resultat(s)** : ce qui est emis ou produit.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Domain 1 — Order lifecycle (kiosk)
|
## 3. Domaine 1 — Cycle de vie de la commande (kiosk)
|
||||||
|
|
||||||
### 3.1 LOAD_CATALOGUE
|
### 3.1 LOAD_CATALOGUE
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Customer opens the kiosk (connection to the kiosk endpoint) |
|
| **Evenement declencheur** | Le client ouvre le kiosk (connexion a l'endpoint du kiosk) |
|
||||||
| **Actor** | CUSTOMER |
|
| **Acteur** | CUSTOMER |
|
||||||
| **Synchronisation** | None (single event) |
|
| **Synchronisation** | Aucune (evenement unique) |
|
||||||
| **Condition** | The kiosk is in service (within business hours 10:00-01:00) |
|
| **Condition** | Le kiosk est en service (dans les horaires d'ouverture 10:00-01:00) |
|
||||||
| **Operation** | LOAD_CATALOGUE |
|
| **Operation** | LOAD_CATALOGUE |
|
||||||
| **Description** | Retrieval of active categories, available products, and available menus (with their slots and eligible options) for display on the kiosk screen. Product availability is COMPUTED: a product is orderable only if its `is_available` flag is set AND each non-removable (`is_removable=0`) ingredient in its `product_ingredient` is above the critical band (`stock_quantity > stock_capacity * critical_stock_pct/100`). See rule RG-T21 in `mlt.md`. |
|
| **Description** | Recuperation des categories actives, des produits disponibles et des menus disponibles (avec leurs slots et options eligibles) pour affichage sur l'ecran du kiosk. La disponibilite des produits est CALCULEE : un produit est commandable seulement si son flag `is_available` est positionne ET que chaque ingredient non retirable (`is_removable=0`) de son `product_ingredient` est au-dessus de la bande critique (`stock_quantity > stock_capacity * critical_stock_pct/100`). Voir la regle RG-T21 dans `mlt.md`. |
|
||||||
| **MCD entities** | R: `category` (is_active=1), `product` (is_available=1), `menu` (is_available=1), `menu_slot`, `menu_slot_option`, `ingredient` (is_active=1), `allergen`, `ingredient_allergen` |
|
| **Entites MCD** | R: `category` (is_active=1), `product` (is_available=1), `menu` (is_available=1), `menu_slot`, `menu_slot_option`, `ingredient` (is_active=1), `allergen`, `ingredient_allergen` |
|
||||||
| **Result** | Catalogue loaded; kiosk displays the home screen |
|
| **Resultat** | Catalogue charge ; le kiosk affiche l'ecran d'accueil |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.2 COMPOSE_CART
|
### 3.2 COMPOSE_CART
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Customer selects a product or a menu on the kiosk |
|
| **Evenement declencheur** | Le client selectionne un produit ou un menu sur le kiosk |
|
||||||
| **Actor** | CUSTOMER |
|
| **Acteur** | CUSTOMER |
|
||||||
| **Synchronisation** | Repeatable event (OR: add product, add menu, change quantity, remove item, choose menu slot, choose format Normal/Maxi, add/remove ingredient modifier) |
|
| **Synchronisation** | Evenement repetable (OR : ajouter produit, ajouter menu, modifier quantite, retirer un article, choisir un slot de menu, choisir le format Normal/Maxi, ajouter/retirer un modificateur d'ingredient) |
|
||||||
| **Condition** | The selected product or menu has `is_available=1` |
|
| **Condition** | Le produit ou le menu selectionne a `is_available=1` |
|
||||||
| **Operation** | COMPOSE_CART |
|
| **Operation** | COMPOSE_CART |
|
||||||
| **Description** | In-memory cart construction: add an item (standalone product or menu), select slot products (`order_item_selection`), optionally modify ingredients (`order_item_modifier`), choose Normal or Maxi format for menus, recalculate TTC total. The cart is a volatile client-side structure; no database write at this stage. |
|
| **Description** | Construction du panier en memoire : ajouter un article (produit autonome ou menu), selectionner les produits des slots (`order_item_selection`), modifier optionnellement les ingredients (`order_item_modifier`), choisir le format Normal ou Maxi pour les menus, recalculer le total TTC. Le panier est une structure volatile cote client ; aucune ecriture en base a ce stade. |
|
||||||
| **MCD entities** | R: `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `product_ingredient` — W: none (volatile front-end state) |
|
| **Entites MCD** | R: `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `product_ingredient` — W: aucune (etat volatile front-end) |
|
||||||
| **Result** | Cart updated, total recalculated, summary displayed |
|
| **Resultat** | Panier mis a jour, total recalcule, recapitulatif affiche |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.3 CREATE_ORDER
|
### 3.3 CREATE_ORDER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering events** | 1. Customer confirms cart (presses "Validate") AND 2. Customer enters their order number (RNCP payment substitute) |
|
| **Evenements declencheurs** | 1. Le client confirme le panier (appuie sur « Valider ») AND 2. Le client saisit son numero de commande (substitut de paiement RNCP) |
|
||||||
| **Actor** | CUSTOMER |
|
| **Acteur** | CUSTOMER |
|
||||||
| **Synchronisation** | AND (both actions required) |
|
| **Synchronisation** | AND (les deux actions requises) |
|
||||||
| **Condition** | Cart contains at least 1 item. The order number entered is non-empty. |
|
| **Condition** | Le panier contient au moins 1 article. Le numero de commande saisi est non vide. |
|
||||||
| **Operation** | CREATE_ORDER |
|
| **Operation** | CREATE_ORDER |
|
||||||
| **Description** | Atomic order creation: INSERT `customer_order` with status `pending_payment`, source `kiosk`, snapshot of HT/VAT/TTC totals (computed line by line using `vat_rate` snapshotted per item). INSERT `order_item` lines with `label_snapshot`, `unit_price_cents_snapshot`, `vat_rate_snapshot`. INSERT `order_item_selection` for each slot filled in a menu item. INSERT `order_item_modifier` for each ingredient modification. Decrement `ingredient.stock_quantity` for each ingredient consumed (adjusted by modifiers: remove => no decrement; add => extra decrement); INSERT one `stock_movement` row of type `sale` per affected ingredient unit. Stock decrements and order insert are within the same transaction. After the customer enters their order number, the status transitions `pending_payment -> paid` within the same transaction; `paid_at` is set. The system generates the order number in format `K-YYYY-MM-DD-NNN`. |
|
| **Description** | Creation atomique de la commande : INSERT `customer_order` avec statut `pending_payment`, source `kiosk`, snapshot des totaux HT/TVA/TTC (calcules ligne par ligne en utilisant `vat_rate` snapshote par article). INSERT des lignes `order_item` avec `label_snapshot`, `unit_price_cents_snapshot`, `vat_rate_snapshot`. INSERT `order_item_selection` pour chaque slot rempli dans un article de menu. INSERT `order_item_modifier` pour chaque modification d'ingredient. Decrement de `ingredient.stock_quantity` pour chaque ingredient consomme (ajuste par les modificateurs : retrait => pas de decrement ; ajout => decrement supplementaire) ; INSERT d'une ligne `stock_movement` de type `sale` par unite d'ingredient affectee. Les decrements de stock et l'insertion de la commande sont dans la meme transaction. Apres que le client a saisi son numero de commande, le statut passe `pending_payment -> paid` dans la meme transaction ; `paid_at` est positionne. Le systeme genere le numero de commande au format `K-YYYY-MM-DD-NNN`. |
|
||||||
| **MCD entities** | R: `product`, `menu`, `ingredient`, `product_ingredient` (snapshot) — W: `customer_order` (INSERT status `pending_payment` then UPDATE status `paid`, `paid_at`), `order_item` (INSERT N lines), `order_item_selection` (INSERT per menu slot chosen), `order_item_modifier` (INSERT per modification), `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `sale` per unit) |
|
| **Entites MCD** | R: `product`, `menu`, `ingredient`, `product_ingredient` (snapshot) — W: `customer_order` (INSERT status `pending_payment` then UPDATE status `paid`, `paid_at`), `order_item` (INSERT N lines), `order_item_selection` (INSERT per menu slot chosen), `order_item_modifier` (INSERT per modification), `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `sale` per unit) |
|
||||||
| **Result** | Order created (status `paid` at end of operation), order number displayed to customer, logical event ORDER_CREATED emitted toward the preparation domain |
|
| **Resultat** | Commande creee (statut `paid` en fin d'operation), numero de commande affiche au client, evenement logique ORDER_CREATED emis vers le domaine de preparation |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.4 DISPLAY_CONFIRMATION
|
### 3.4 DISPLAY_CONFIRMATION
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | ORDER_CREATED (API response 201 after CREATE_ORDER) |
|
| **Evenement declencheur** | ORDER_CREATED (reponse API 201 apres CREATE_ORDER) |
|
||||||
| **Actor** | SYS |
|
| **Acteur** | SYS |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | API response contains an id, an order_number and status `paid` |
|
| **Condition** | La reponse API contient un id, un order_number et le statut `paid` |
|
||||||
| **Operation** | DISPLAY_CONFIRMATION |
|
| **Operation** | DISPLAY_CONFIRMATION |
|
||||||
| **Description** | Display of the confirmation screen on the kiosk with the order number. The kiosk then resets for the next customer. |
|
| **Description** | Affichage de l'ecran de confirmation sur le kiosk avec le numero de commande. Le kiosk se reinitialise ensuite pour le client suivant. |
|
||||||
| **MCD entities** | R: none (data is in the API response) |
|
| **Entites MCD** | R: aucune (les donnees sont dans la reponse API) |
|
||||||
| **Result** | Confirmation screen displayed; kiosk available for next order |
|
| **Resultat** | Ecran de confirmation affiche ; kiosk disponible pour la commande suivante |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Domain 2 — Order lifecycle (counter and drive)
|
## 4. Domaine 2 — Cycle de vie de la commande (comptoir et drive)
|
||||||
|
|
||||||
### 4.1 CREATE_COUNTER_ORDER
|
### 4.1 CREATE_COUNTER_ORDER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | A counter or drive staff member initiates a new order from the back-office |
|
| **Evenement declencheur** | Un membre du personnel comptoir ou drive initie une nouvelle commande depuis le back-office |
|
||||||
| **Actor** | COUNTER or DRIVE |
|
| **Acteur** | COUNTER ou DRIVE |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | The actor is authenticated and holds permission `order.create`. The `source` is `counter` or `drive` (auto-tagged from `role.order_source`). |
|
| **Condition** | L'acteur est authentifie et detient la permission `order.create`. La `source` est `counter` ou `drive` (auto-taggee depuis `role.order_source`). |
|
||||||
| **Operation** | CREATE_COUNTER_ORDER |
|
| **Operation** | CREATE_COUNTER_ORDER |
|
||||||
| **Description** | Manual order composition via the back-office: select products and menus, choose service mode (`dine_in`/`takeaway`/`drive`), fill menu slots, add ingredient modifiers. Identical creation logic to CREATE_ORDER (snapshot, stock decrement in same transaction, atomic `pending_payment -> paid` transition). The `source` is auto-tagged from `role.order_source` (counter -> `counter`, drive -> `drive`). Order number format: `C-YYYY-MM-DD-NNN` (counter) or `D-YYYY-MM-DD-NNN` (drive). Cross-constraint: if `source = 'drive'` then `service_mode = 'drive'` (verified at creation). |
|
| **Description** | Composition manuelle de la commande via le back-office : selectionner produits et menus, choisir le mode de service (`dine_in`/`takeaway`/`drive`), remplir les slots de menu, ajouter des modificateurs d'ingredient. Logique de creation identique a CREATE_ORDER (snapshot, decrement de stock dans la meme transaction, transition atomique `pending_payment -> paid`). La `source` est auto-taggee depuis `role.order_source` (counter -> `counter`, drive -> `drive`). Format du numero de commande : `C-YYYY-MM-DD-NNN` (comptoir) ou `D-YYYY-MM-DD-NNN` (drive). Contrainte croisee : si `source = 'drive'` alors `service_mode = 'drive'` (verifie a la creation). |
|
||||||
| **MCD entities** | R: `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `product_ingredient` — W: `customer_order` (INSERT status `pending_payment` then UPDATE status `paid`, `paid_at`), `order_item`, `order_item_selection`, `order_item_modifier`, `ingredient` (stock decrement), `stock_movement` (INSERT type `sale`) |
|
| **Entites MCD** | R: `product`, `menu`, `menu_slot`, `menu_slot_option`, `ingredient`, `product_ingredient` — W: `customer_order` (INSERT status `pending_payment` then UPDATE status `paid`, `paid_at`), `order_item`, `order_item_selection`, `order_item_modifier`, `ingredient` (stock decrement), `stock_movement` (INSERT type `sale`) |
|
||||||
| **Result** | Order created (status `paid`), order number communicated to customer |
|
| **Resultat** | Commande creee (statut `paid`), numero de commande communique au client |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Domain 3 — Preparation display (kitchen)
|
## 5. Domaine 3 — Affichage de preparation (cuisine)
|
||||||
|
|
||||||
### 5.1 LIST_ORDERS_DISPLAY
|
### 5.1 LIST_ORDERS_DISPLAY
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Kitchen staff accesses or refreshes the preparation display |
|
| **Evenement declencheur** | Le personnel cuisine accede a l'affichage de preparation ou le rafraichit |
|
||||||
| **Actor** | KITCHEN (or COUNTER, DRIVE, ADMIN) |
|
| **Acteur** | KITCHEN (ou COUNTER, DRIVE, ADMIN) |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | The actor is authenticated and holds permission `order.read`. |
|
| **Condition** | L'acteur est authentifie et detient la permission `order.read`. |
|
||||||
| **Operation** | LIST_ORDERS_DISPLAY |
|
| **Operation** | LIST_ORDERS_DISPLAY |
|
||||||
| **Description** | Read `customer_order` rows with status `paid`, filtered by sources visible to the actor's role (from `role_visible_source`): kitchen sees all sources; counter sees kiosk+counter; drive sees drive. Orders are sorted by `paid_at` ascending (oldest first). For each order, display: order number, source, content (`order_item` with `label_snapshot`, `quantity`, format, slot selections, ingredient modifiers). KDS colour is computed from `now - paid_at` against the SLA threshold (approx. 10 min), not stored. Kitchen staff performs no status transition — this is a read-only operation. |
|
| **Description** | Lecture des lignes `customer_order` avec statut `paid`, filtrees par les sources visibles selon le role de l'acteur (depuis `role_visible_source`) : la cuisine voit toutes les sources ; le comptoir voit kiosk+counter ; le drive voit drive. Les commandes sont triees par `paid_at` ascendant (les plus anciennes en premier). Pour chaque commande, afficher : numero de commande, source, contenu (`order_item` avec `label_snapshot`, `quantity`, format, selections de slots, modificateurs d'ingredient). La couleur KDS est calculee a partir de `now - paid_at` par rapport au seuil de SLA (approx. 10 min), non stockee. Le personnel cuisine n'effectue aucune transition de statut — c'est une operation en lecture seule. |
|
||||||
| **MCD entities** | R: `customer_order` (status=`paid`), `order_item`, `order_item_selection`, `order_item_modifier`, `role_visible_source` |
|
| **Entites MCD** | R: `customer_order` (status=`paid`), `order_item`, `order_item_selection`, `order_item_modifier`, `role_visible_source` |
|
||||||
| **Result** | Preparation display list shown, sorted by payment time ascending |
|
| **Resultat** | Liste d'affichage de preparation montree, triee par heure de paiement ascendante |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Domain 4 — Delivery to customer
|
## 6. Domaine 4 — Livraison au client
|
||||||
|
|
||||||
### 6.1 DELIVER_ORDER
|
### 6.1 DELIVER_ORDER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering events** | 1. The order is at status `paid` AND 2. Counter or drive staff clicks "Delivered" |
|
| **Evenements declencheurs** | 1. La commande est au statut `paid` AND 2. Le personnel comptoir ou drive clique sur « Livre » |
|
||||||
| **Actor** | COUNTER or DRIVE |
|
| **Acteur** | COUNTER ou DRIVE |
|
||||||
| **Synchronisation** | AND |
|
| **Synchronisation** | AND |
|
||||||
| **Condition** | The order has status `paid`. The actor holds permission `order.deliver`. The actor's role is consistent with the order source (counter staff handles kiosk+counter orders; drive staff handles drive orders — filtered by role_visible_source). |
|
| **Condition** | La commande a le statut `paid`. L'acteur detient la permission `order.deliver`. Le role de l'acteur est coherent avec la source de la commande (le personnel comptoir traite les commandes kiosk+counter ; le personnel drive traite les commandes drive — filtre par role_visible_source). |
|
||||||
| **Operation** | DELIVER_ORDER |
|
| **Operation** | DELIVER_ORDER |
|
||||||
| **Description** | Single-gesture transition `paid -> delivered`. Sets `delivered_at = NOW()`. The order moves to history. This operation replaces the v0.1 two-step sequence (mark-ready then deliver); the kitchen's visual confirmation (KDS) is sufficient before this action. |
|
| **Description** | Transition en geste unique `paid -> delivered`. Positionne `delivered_at = NOW()`. La commande passe en historique. Cette operation remplace la sequence en deux etapes de v0.1 (marquer-prete puis livrer) ; la confirmation visuelle de la cuisine (KDS) suffit avant cette action. |
|
||||||
| **MCD entities** | W: `customer_order` (UPDATE status `paid` -> `delivered`, `delivered_at = NOW()`) |
|
| **Entites MCD** | W: `customer_order` (UPDATE status `paid` -> `delivered`, `delivered_at = NOW()`) |
|
||||||
| **Result** | Order at status `delivered`, lifecycle complete |
|
| **Resultat** | Commande au statut `delivered`, cycle de vie complet |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Domain 5 — Cancellation
|
## 7. Domaine 5 — Annulation
|
||||||
|
|
||||||
### 7.1 CANCEL_ORDER
|
### 7.1 CANCEL_ORDER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | An authorised actor requests cancellation of an order |
|
| **Evenement declencheur** | Un acteur autorise demande l'annulation d'une commande |
|
||||||
| **Actor** | COUNTER, DRIVE, or ADMIN |
|
| **Acteur** | COUNTER, DRIVE ou ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | The order exists. `customer_order.status` is in `['pending_payment', 'paid']`. Terminal statuses `delivered` and `cancelled` cannot transition to `cancelled`. The actor holds permission `order.cancel`. |
|
| **Condition** | La commande existe. `customer_order.status` est dans `['pending_payment', 'paid']`. Les statuts terminaux `delivered` et `cancelled` ne peuvent pas transiter vers `cancelled`. L'acteur detient la permission `order.cancel`. |
|
||||||
| **Operation** | CANCEL_ORDER |
|
| **Operation** | CANCEL_ORDER |
|
||||||
| **Description** | Transition from current status to `cancelled`. Sets `cancelled_at = NOW()`. The order is retained in the database for history and stats (no physical deletion). If the current status is `paid`, stock is re-credited: for each ingredient consumed by the order (accounting for modifiers), `ingredient.stock_quantity` is incremented; one `stock_movement` row of type `cancellation` is inserted per affected ingredient unit. Stock re-credit and status update are within the same transaction. |
|
| **Description** | Transition du statut courant vers `cancelled`. Positionne `cancelled_at = NOW()`. La commande est conservee en base pour l'historique et les stats (pas de suppression physique). Si le statut courant est `paid`, le stock est recredite : pour chaque ingredient consomme par la commande (en tenant compte des modificateurs), `ingredient.stock_quantity` est incremente ; une ligne `stock_movement` de type `cancellation` est inseree par unite d'ingredient affectee. Le recredit du stock et la mise a jour du statut sont dans la meme transaction. |
|
||||||
| **MCD entities** | R: `order_item`, `order_item_modifier`, `ingredient`, `product_ingredient` — W: `customer_order` (UPDATE status -> `cancelled`, `cancelled_at = NOW()`), `ingredient` (UPDATE stock_quantity, conditional on status `paid`), `stock_movement` (INSERT type `cancellation`, conditional on status `paid`) |
|
| **Entites MCD** | R: `order_item`, `order_item_modifier`, `ingredient`, `product_ingredient` — W: `customer_order` (UPDATE status -> `cancelled`, `cancelled_at = NOW()`), `ingredient` (UPDATE stock_quantity, conditional on status `paid`), `stock_movement` (INSERT type `cancellation`, conditional on status `paid`) |
|
||||||
| **Result** | Order at status `cancelled`, visible in admin history |
|
| **Resultat** | Commande au statut `cancelled`, visible dans l'historique admin |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Domain 6 — Catalogue management
|
## 8. Domaine 6 — Gestion du catalogue
|
||||||
|
|
||||||
### 8.1 CREATE_PRODUCT
|
### 8.1 CREATE_PRODUCT
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager submits the product creation form |
|
| **Evenement declencheur** | L'admin ou le manager soumet le formulaire de creation de produit |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `product.create`. Target category exists and `is_active=1`. `name` is non-empty. `price_cents > 0`. |
|
| **Condition** | L'acteur detient la permission `product.create`. La categorie cible existe et `is_active=1`. `name` est non vide. `price_cents > 0`. |
|
||||||
| **Operation** | CREATE_PRODUCT |
|
| **Operation** | CREATE_PRODUCT |
|
||||||
| **Description** | INSERT a new `product` with its category, name, price in cents, VAT rate in per-mille (`vat_rate`: 100=10%, 55=5.5%, default 100), optional image path. `is_available=1` by default. |
|
| **Description** | INSERT d'un nouveau `product` avec sa categorie, son nom, son prix en centimes, son taux de TVA en pour-mille (`vat_rate` : 100=10%, 55=5.5%, defaut 100), chemin d'image optionnel. `is_available=1` par defaut. |
|
||||||
| **MCD entities** | R: `category` (FK validation) — W: `product` (INSERT) |
|
| **Entites MCD** | R: `category` (FK validation) — W: `product` (INSERT) |
|
||||||
| **Result** | Product created, redirect to product list |
|
| **Resultat** | Produit cree, redirection vers la liste des produits |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.2 UPDATE_PRODUCT
|
### 8.2 UPDATE_PRODUCT
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager submits the product update form |
|
| **Evenement declencheur** | L'admin ou le manager soumet le formulaire de modification de produit |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `product.update`. Product exists. New values respect constraints (`price_cents > 0`, non-empty name). |
|
| **Condition** | L'acteur detient la permission `product.update`. Le produit existe. Les nouvelles valeurs respectent les contraintes (`price_cents > 0`, nom non vide). |
|
||||||
| **Operation** | UPDATE_PRODUCT |
|
| **Operation** | UPDATE_PRODUCT |
|
||||||
| **Description** | UPDATE modifiable columns (`name`, `description`, `price_cents`, `vat_rate`, `image_path`, `is_available`, `display_order`, `category_id`). Snapshots already stored in `order_item` are not affected (historical integrity guaranteed by design). |
|
| **Description** | UPDATE des colonnes modifiables (`name`, `description`, `price_cents`, `vat_rate`, `image_path`, `is_available`, `display_order`, `category_id`). Les snapshots deja stockes dans `order_item` ne sont pas affectes (integrite historique garantie par conception). |
|
||||||
| **MCD entities** | W: `product` (UPDATE) |
|
| **Entites MCD** | W: `product` (UPDATE) |
|
||||||
| **Result** | Product updated, product list refreshed |
|
| **Resultat** | Produit mis a jour, liste des produits rafraichie |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.3 DELETE_PRODUCT
|
### 8.3 DELETE_PRODUCT
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin confirms deletion of a product |
|
| **Evenement declencheur** | L'admin confirme la suppression d'un produit |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `product.delete`. Product is not a slot option in any `menu_slot_option` (FK `ON DELETE RESTRICT`). Product is not referenced in any `order_item` historical line (FK `ON DELETE RESTRICT`). Preliminary check required. |
|
| **Condition** | L'acteur detient la permission `product.delete`. Le produit n'est option de slot dans aucun `menu_slot_option` (FK `ON DELETE RESTRICT`). Le produit n'est reference dans aucune ligne historique `order_item` (FK `ON DELETE RESTRICT`). Verification prealable requise. |
|
||||||
| **Operation** | DELETE_PRODUCT |
|
| **Operation** | DELETE_PRODUCT |
|
||||||
| **Description** | Physical deletion of the product if no FK constraint blocks. If the product is referenced in a menu slot or historical order line, deletion is blocked. The recommended alternative is to deactivate (`is_available=0`). Also blocks if the product is the `burger_product_id` of any `menu`. |
|
| **Description** | Suppression physique du produit si aucune contrainte FK ne la bloque. Si le produit est reference dans un slot de menu ou une ligne de commande historique, la suppression est bloquee. L'alternative recommandee est de le desactiver (`is_available=0`). Bloque egalement si le produit est le `burger_product_id` d'un `menu`. |
|
||||||
| **MCD entities** | W: `product` (DELETE — blocked if referenced in `menu_slot_option`, `order_item`, or `menu.burger_product_id`) |
|
| **Entites MCD** | W: `product` (DELETE — blocked if referenced in `menu_slot_option`, `order_item`, or `menu.burger_product_id`) |
|
||||||
| **Result** | Product deleted OR error "product in use" |
|
| **Resultat** | Produit supprime OU erreur « produit utilise » |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.4 CREATE_MENU
|
### 8.4 CREATE_MENU
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager submits the menu creation form with its slot configuration |
|
| **Evenement declencheur** | L'admin ou le manager soumet le formulaire de creation de menu avec sa configuration de slots |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `menu.create`. `name` is non-empty. `price_normal_cents > 0`, `price_maxi_cents > 0`. `burger_product_id` references an existing product. At least one slot is defined with at least one option. |
|
| **Condition** | L'acteur detient la permission `menu.create`. `name` est non vide. `price_normal_cents > 0`, `price_maxi_cents > 0`. `burger_product_id` reference un produit existant. Au moins un slot est defini avec au moins une option. |
|
||||||
| **Operation** | CREATE_MENU |
|
| **Operation** | CREATE_MENU |
|
||||||
| **Description** | Transaction: INSERT `menu` (with `burger_product_id`, `price_normal_cents`, `price_maxi_cents`), then INSERT `menu_slot` rows (one per slot: drink, side, sauce...), then INSERT `menu_slot_option` rows (eligible products per slot). |
|
| **Description** | Transaction : INSERT `menu` (avec `burger_product_id`, `price_normal_cents`, `price_maxi_cents`), puis INSERT des lignes `menu_slot` (une par slot : boisson, accompagnement, sauce...), puis INSERT des lignes `menu_slot_option` (produits eligibles par slot). |
|
||||||
| **MCD entities** | R: `product` (burger FK validation, slot options validation), `category` — W: `menu` (INSERT), `menu_slot` (INSERT), `menu_slot_option` (INSERT) |
|
| **Entites MCD** | R: `product` (burger FK validation, slot options validation), `category` — W: `menu` (INSERT), `menu_slot` (INSERT), `menu_slot_option` (INSERT) |
|
||||||
| **Result** | Menu created with its slot configuration, visible on the kiosk |
|
| **Resultat** | Menu cree avec sa configuration de slots, visible sur le kiosk |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.5 UPDATE_MENU
|
### 8.5 UPDATE_MENU
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager submits the menu update form |
|
| **Evenement declencheur** | L'admin ou le manager soumet le formulaire de modification de menu |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `menu.update`. Menu exists. Updated configuration preserves at least one slot with at least one option. |
|
| **Condition** | L'acteur detient la permission `menu.update`. Le menu existe. La configuration mise a jour preserve au moins un slot avec au moins une option. |
|
||||||
| **Operation** | UPDATE_MENU |
|
| **Operation** | UPDATE_MENU |
|
||||||
| **Description** | UPDATE `menu` columns. If slot configuration is modified: DELETE all `menu_slot_option` rows for this menu's slots, DELETE `menu_slot` rows, then re-INSERT (delete-and-reinsert pattern, atomic in transaction). Snapshots in `order_item` are not affected. |
|
| **Description** | UPDATE des colonnes `menu`. Si la configuration des slots est modifiee : DELETE de toutes les lignes `menu_slot_option` pour les slots de ce menu, DELETE des lignes `menu_slot`, puis re-INSERT (pattern delete-and-reinsert, atomique en transaction). Les snapshots dans `order_item` ne sont pas affectes. |
|
||||||
| **MCD entities** | W: `menu` (UPDATE), `menu_slot` (DELETE + INSERT), `menu_slot_option` (DELETE + INSERT) |
|
| **Entites MCD** | W: `menu` (UPDATE), `menu_slot` (DELETE + INSERT), `menu_slot_option` (DELETE + INSERT) |
|
||||||
| **Result** | Menu updated |
|
| **Resultat** | Menu mis a jour |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.6 DELETE_MENU
|
### 8.6 DELETE_MENU
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin confirms deletion of a menu |
|
| **Evenement declencheur** | L'admin confirme la suppression d'un menu |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `menu.delete`. Menu is not referenced in any `order_item` historical line (FK `ON DELETE RESTRICT`). Preliminary check required. |
|
| **Condition** | L'acteur detient la permission `menu.delete`. Le menu n'est reference dans aucune ligne historique `order_item` (FK `ON DELETE RESTRICT`). Verification prealable requise. |
|
||||||
| **Operation** | DELETE_MENU |
|
| **Operation** | DELETE_MENU |
|
||||||
| **Description** | If no `order_item` references this menu: DELETE `menu_slot_option` (CASCADE from `menu_slot`), DELETE `menu_slot` (CASCADE from `menu`), DELETE `menu`. If historical references exist, propose deactivation (`is_available=0`) instead. |
|
| **Description** | Si aucun `order_item` ne reference ce menu : DELETE `menu_slot_option` (CASCADE from `menu_slot`), DELETE `menu_slot` (CASCADE from `menu`), DELETE `menu`. Si des references historiques existent, proposer la desactivation (`is_available=0`) a la place. |
|
||||||
| **MCD entities** | W: `menu_slot_option` (DELETE CASCADE), `menu_slot` (DELETE CASCADE), `menu` (DELETE — blocked if referenced in `order_item`) |
|
| **Entites MCD** | W: `menu_slot_option` (DELETE CASCADE), `menu_slot` (DELETE CASCADE), `menu` (DELETE — blocked if referenced in `order_item`) |
|
||||||
| **Result** | Menu deleted OR error "menu present in historical orders" |
|
| **Resultat** | Menu supprime OU erreur « menu present dans des commandes historiques » |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.7 MANAGE_CATEGORY
|
### 8.7 MANAGE_CATEGORY
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager creates, updates, or deactivates a category |
|
| **Evenement declencheur** | L'admin ou le manager cree, modifie ou desactive une categorie |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | OR (create, update, deactivation) |
|
| **Synchronisation** | OR (creation, modification, desactivation) |
|
||||||
| **Condition** | Actor holds permission `category.manage`. For deactivation: products and menus in the category are not auto-deactivated in DB (no CASCADE on `is_active`); the application layer proposes deactivating child products/menus. |
|
| **Condition** | L'acteur detient la permission `category.manage`. Pour la desactivation : les produits et menus de la categorie ne sont pas auto-desactives en base (pas de CASCADE sur `is_active`) ; la couche applicative propose de desactiver les produits/menus enfants. |
|
||||||
| **Operation** | MANAGE_CATEGORY |
|
| **Operation** | MANAGE_CATEGORY |
|
||||||
| **Description** | CRUD on `category`. Deactivation (`is_active=0`) hides the category and its products from the kiosk without physical deletion. Physical deletion is blocked if products or menus reference this category (FK `ON DELETE RESTRICT`). |
|
| **Description** | CRUD sur `category`. La desactivation (`is_active=0`) masque la categorie et ses produits du kiosk sans suppression physique. La suppression physique est bloquee si des produits ou des menus referencent cette categorie (FK `ON DELETE RESTRICT`). |
|
||||||
| **MCD entities** | W: `category` (INSERT / UPDATE / conditional DELETE) |
|
| **Entites MCD** | W: `category` (INSERT / UPDATE / conditional DELETE) |
|
||||||
| **Result** | Category created / updated / deactivated |
|
| **Resultat** | Categorie creee / modifiee / desactivee |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 8.8 MANAGE_INGREDIENT
|
### 8.8 MANAGE_INGREDIENT
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin or manager creates, updates, or deactivates an ingredient; or manages product composition (`product_ingredient`) or allergen mapping (`ingredient_allergen`) |
|
| **Evenement declencheur** | L'admin ou le manager cree, modifie ou desactive un ingredient ; ou gere la composition produit (`product_ingredient`) ou le mapping allergene (`ingredient_allergen`) |
|
||||||
| **Actor** | ADMIN or MANAGER |
|
| **Acteur** | ADMIN ou MANAGER |
|
||||||
| **Synchronisation** | OR (create ingredient, update ingredient, update composition, update allergen mapping) |
|
| **Synchronisation** | OR (creer ingredient, modifier ingredient, modifier composition, modifier mapping allergene) |
|
||||||
| **Condition** | Actor holds permission `ingredient.manage`. |
|
| **Condition** | L'acteur detient la permission `ingredient.manage`. |
|
||||||
| **Operation** | MANAGE_INGREDIENT |
|
| **Operation** | MANAGE_INGREDIENT |
|
||||||
| **Description** | CRUD on `ingredient` (name, unit, pack_size, pack_label, stock_capacity, low_stock_pct, critical_stock_pct, is_active). Manage `product_ingredient` composition (quantity_normal, quantity_maxi, is_removable, is_addable, extra_price_cents) for any product. Manage `ingredient_allergen` mapping (14 EU regulated allergens). Deactivating an ingredient (`is_active=0`) hides it from the configurator without deletion. Physical deletion of `ingredient` is blocked if referenced in `product_ingredient` (FK `ON DELETE RESTRICT`) or `stock_movement` (FK `ON DELETE RESTRICT`). |
|
| **Description** | CRUD sur `ingredient` (name, unit, pack_size, pack_label, stock_capacity, low_stock_pct, critical_stock_pct, is_active). Gestion de la composition `product_ingredient` (quantity_normal, quantity_maxi, is_removable, is_addable, extra_price_cents) pour tout produit. Gestion du mapping `ingredient_allergen` (14 allergenes reglementes UE). Desactiver un ingredient (`is_active=0`) le masque du configurateur sans suppression. La suppression physique de `ingredient` est bloquee s'il est reference dans `product_ingredient` (FK `ON DELETE RESTRICT`) ou `stock_movement` (FK `ON DELETE RESTRICT`). |
|
||||||
| **MCD entities** | R: `product` (FK validation), `allergen` (FK validation) — W: `ingredient` (INSERT/UPDATE/DELETE conditional), `product_ingredient` (INSERT/UPDATE/DELETE), `ingredient_allergen` (INSERT/DELETE) |
|
| **Entites MCD** | R: `product` (FK validation), `allergen` (FK validation) — W: `ingredient` (INSERT/UPDATE/DELETE conditional), `product_ingredient` (INSERT/UPDATE/DELETE), `ingredient_allergen` (INSERT/DELETE) |
|
||||||
| **Result** | Ingredient / composition / allergen mapping updated |
|
| **Resultat** | Ingredient / composition / mapping allergene mis a jour |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Domain 7 — Stock management
|
## 9. Domaine 7 — Gestion du stock
|
||||||
|
|
||||||
### 9.1 RESTOCK
|
### 9.1 RESTOCK
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Manager or admin records a delivery of ingredient packs |
|
| **Evenement declencheur** | Le manager ou l'admin enregistre une livraison de packs d'ingredient |
|
||||||
| **Actor** | MANAGER or ADMIN |
|
| **Acteur** | MANAGER ou ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `stock.manage`. Ingredient exists and `is_active=1`. Number of packs `N >= 1`. |
|
| **Condition** | L'acteur detient la permission `stock.manage`. L'ingredient existe et `is_active=1`. Nombre de packs `N >= 1`. |
|
||||||
| **Operation** | RESTOCK |
|
| **Operation** | RESTOCK |
|
||||||
| **Description** | UPDATE `ingredient.stock_quantity += N * pack_size`. INSERT one `stock_movement` row: type `restock`, delta `+= N * pack_size`, `user_id` of the actor, optional `note` (e.g. delivery reference). Both writes are in the same transaction. |
|
| **Description** | UPDATE `ingredient.stock_quantity += N * pack_size`. INSERT d'une ligne `stock_movement` : type `restock`, delta `+= N * pack_size`, `user_id` de l'acteur, `note` optionnelle (ex. reference de livraison). Les deux ecritures sont dans la meme transaction. |
|
||||||
| **MCD entities** | R: `ingredient` — W: `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `restock`) |
|
| **Entites MCD** | R: `ingredient` — W: `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `restock`) |
|
||||||
| **Result** | Stock incremented, movement logged |
|
| **Resultat** | Stock incremente, mouvement journalise |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 9.2 INVENTORY_COUNT
|
### 9.2 INVENTORY_COUNT
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | A staff member or manager records the result of a physical inventory count |
|
| **Evenement declencheur** | Un membre du personnel ou un manager enregistre le resultat d'un inventaire physique |
|
||||||
| **Actor** | KITCHEN, COUNTER, DRIVE, MANAGER, or ADMIN |
|
| **Acteur** | KITCHEN, COUNTER, DRIVE, MANAGER ou ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `stock.count`. Ingredient exists. Physical count `actual_quantity >= 0`. |
|
| **Condition** | L'acteur detient la permission `stock.count`. L'ingredient existe. Comptage physique `actual_quantity >= 0`. |
|
||||||
| **Operation** | INVENTORY_COUNT |
|
| **Operation** | INVENTORY_COUNT |
|
||||||
| **Description** | Compute `delta = actual_quantity - ingredient.stock_quantity` (may be negative or positive). UPDATE `ingredient.stock_quantity = actual_quantity`. INSERT one `stock_movement` row: type `inventory_correction`, delta = computed discrepancy, `user_id` of the actor, optional `note`. Both writes in the same transaction. |
|
| **Description** | Calcul de `delta = actual_quantity - ingredient.stock_quantity` (peut etre negatif ou positif). UPDATE `ingredient.stock_quantity = actual_quantity`. INSERT d'une ligne `stock_movement` : type `inventory_correction`, delta = ecart calcule, `user_id` de l'acteur, `note` optionnelle. Les deux ecritures dans la meme transaction. |
|
||||||
| **MCD entities** | R: `ingredient` (read current stock_quantity) — W: `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `inventory_correction`) |
|
| **Entites MCD** | R: `ingredient` (read current stock_quantity) — W: `ingredient` (UPDATE stock_quantity), `stock_movement` (INSERT type `inventory_correction`) |
|
||||||
| **Result** | Stock reconciled to physical count, discrepancy logged |
|
| **Resultat** | Stock reconcilie au comptage physique, ecart journalise |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 9.3 READ_STOCK
|
### 9.3 READ_STOCK
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | An authorised actor accesses the stock view |
|
| **Evenement declencheur** | Un acteur autorise accede a la vue du stock |
|
||||||
| **Actor** | KITCHEN, COUNTER, DRIVE, MANAGER, or ADMIN |
|
| **Acteur** | KITCHEN, COUNTER, DRIVE, MANAGER ou ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `stock.read`. |
|
| **Condition** | L'acteur detient la permission `stock.read`. |
|
||||||
| **Operation** | READ_STOCK |
|
| **Operation** | READ_STOCK |
|
||||||
| **Description** | Read `ingredient` list with current `stock_quantity`, `stock_capacity`, computed `stock_pct`, `low_stock_pct`, `critical_stock_pct`, `pack_size`, `pack_label`. Stock bands computed at display time: `low_stock` when `stock_quantity <= stock_capacity * low_stock_pct/100`, `critical_stock` when `stock_quantity <= stock_capacity * critical_stock_pct/100`. Optional: read `stock_movement` history for a given ingredient, filtered by date range. |
|
| **Description** | Lecture de la liste `ingredient` avec le `stock_quantity` courant, `stock_capacity`, `stock_pct` calcule, `low_stock_pct`, `critical_stock_pct`, `pack_size`, `pack_label`. Bandes de stock calculees au moment de l'affichage : `low_stock` lorsque `stock_quantity <= stock_capacity * low_stock_pct/100`, `critical_stock` lorsque `stock_quantity <= stock_capacity * critical_stock_pct/100`. Optionnel : lecture de l'historique `stock_movement` pour un ingredient donne, filtre par plage de dates. |
|
||||||
| **MCD entities** | R: `ingredient`, `stock_movement` (optional history) |
|
| **Entites MCD** | R: `ingredient`, `stock_movement` (optional history) |
|
||||||
| **Result** | Stock list displayed with low-stock indicators |
|
| **Resultat** | Liste du stock affichee avec indicateurs de stock bas |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Domain 8 — User and role management (admin)
|
## 10. Domaine 8 — Gestion des utilisateurs et des roles (admin)
|
||||||
|
|
||||||
### 10.1 CREATE_USER
|
### 10.1 CREATE_USER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin submits the user creation form |
|
| **Evenement declencheur** | L'admin soumet le formulaire de creation d'utilisateur |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `user.create`. Email does not already exist in `user.email` (UNIQUE constraint). A valid and active `role_id` is selected. |
|
| **Condition** | L'acteur detient la permission `user.create`. L'email n'existe pas deja dans `user.email` (contrainte UNIQUE). Un `role_id` valide et actif est selectionne. |
|
||||||
| **Operation** | CREATE_USER |
|
| **Operation** | CREATE_USER |
|
||||||
| **Description** | INSERT user with argon2id password hash. Email is unique. `role_id` is mandatory (FK NOT NULL). `is_active=1` by default. `last_login_at=NULL` at creation. |
|
| **Description** | INSERT de l'utilisateur avec un hash de mot de passe argon2id. L'email est unique. `role_id` est obligatoire (FK NOT NULL). `is_active=1` par defaut. `last_login_at=NULL` a la creation. |
|
||||||
| **MCD entities** | R: `role` (FK validation) — W: `user` (INSERT) |
|
| **Entites MCD** | R: `role` (FK validation) — W: `user` (INSERT) |
|
||||||
| **Result** | User created, can log into the back-office |
|
| **Resultat** | Utilisateur cree, peut se connecter au back-office |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10.2 UPDATE_USER
|
### 10.2 UPDATE_USER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin submits the user update form |
|
| **Evenement declencheur** | L'admin soumet le formulaire de modification d'utilisateur |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `user.update`. User exists. If a new password is provided, it is re-hashed. |
|
| **Condition** | L'acteur detient la permission `user.update`. L'utilisateur existe. Si un nouveau mot de passe est fourni, il est re-hashe. |
|
||||||
| **Operation** | UPDATE_USER |
|
| **Operation** | UPDATE_USER |
|
||||||
| **Description** | UPDATE modifiable fields (`first_name`, `last_name`, `email`, `role_id`, `is_active`). If a new password is supplied, it replaces the existing hash (argon2id rehash). |
|
| **Description** | UPDATE des champs modifiables (`first_name`, `last_name`, `email`, `role_id`, `is_active`). Si un nouveau mot de passe est fourni, il remplace le hash existant (rehash argon2id). |
|
||||||
| **MCD entities** | W: `user` (UPDATE) |
|
| **Entites MCD** | W: `user` (UPDATE) |
|
||||||
| **Result** | User updated |
|
| **Resultat** | Utilisateur mis a jour |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10.3 DEACTIVATE_USER
|
### 10.3 DEACTIVATE_USER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin clicks "Deactivate" for a user |
|
| **Evenement declencheur** | L'admin clique sur « Desactiver » pour un utilisateur |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `user.deactivate`. Admin cannot deactivate their own account (application-level protection). |
|
| **Condition** | L'acteur detient la permission `user.deactivate`. L'admin ne peut pas desactiver son propre compte (protection au niveau applicatif). |
|
||||||
| **Operation** | DEACTIVATE_USER |
|
| **Operation** | DEACTIVATE_USER |
|
||||||
| **Description** | UPDATE `is_active=0`. The user's active session is invalidated on next access (middleware checks `is_active=1` on each authenticated request). User is not deleted; history remains traceable. |
|
| **Description** | UPDATE `is_active=0`. La session active de l'utilisateur est invalidee au prochain acces (le middleware verifie `is_active=1` a chaque requete authentifiee). L'utilisateur n'est pas supprime ; l'historique reste tracable. |
|
||||||
| **MCD entities** | W: `user` (UPDATE is_active=0) |
|
| **Entites MCD** | W: `user` (UPDATE is_active=0) |
|
||||||
| **Result** | User deactivated, back-office access blocked |
|
| **Resultat** | Utilisateur desactive, acces back-office bloque |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10.4 MANAGE_RBAC
|
### 10.4 MANAGE_RBAC
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Admin modifies permission assignments for a role, or creates / updates a custom role |
|
| **Evenement declencheur** | L'admin modifie les affectations de permissions pour un role, ou cree / modifie un role personnalise |
|
||||||
| **Actor** | ADMIN |
|
| **Acteur** | ADMIN |
|
||||||
| **Synchronisation** | OR (update role permissions, create custom role, update role attributes) |
|
| **Synchronisation** | OR (modifier les permissions du role, creer un role personnalise, modifier les attributs du role) |
|
||||||
| **Condition** | Actor holds permission `role.manage`. Selected permissions exist in the `permission` catalogue. |
|
| **Condition** | L'acteur detient la permission `role.manage`. Les permissions selectionnees existent dans le catalogue `permission`. |
|
||||||
| **Operation** | MANAGE_RBAC |
|
| **Operation** | MANAGE_RBAC |
|
||||||
| **Description** | Update `role_permission` for a given role: DELETE existing assignments, INSERT new ones (delete-and-reinsert, atomic in transaction). Permissions themselves are static (declared in migration, not modifiable via UI). Also covers: CREATE/UPDATE custom `role` (code, label, description, default_route, order_source), UPDATE `role_visible_source` (visible dashboard sources for the role). RBAC architecture rule: application code tests permissions, not role names — adding a new role with correct permissions requires no code change. |
|
| **Description** | Mise a jour de `role_permission` pour un role donne : DELETE des affectations existantes, INSERT des nouvelles (delete-and-reinsert, atomique en transaction). Les permissions elles-memes sont statiques (declarees en migration, non modifiables via l'UI). Couvre egalement : CREATE/UPDATE d'un `role` personnalise (code, label, description, default_route, order_source), UPDATE de `role_visible_source` (sources de tableau de bord visibles pour le role). Regle d'architecture RBAC : le code applicatif teste les permissions, pas les noms de role — ajouter un nouveau role avec les bonnes permissions ne requiert aucun changement de code. |
|
||||||
| **MCD entities** | R: `role`, `permission` — W: `role_permission` (DELETE + INSERT), `role` (INSERT/UPDATE for custom roles), `role_visible_source` (INSERT/DELETE) |
|
| **Entites MCD** | R: `role`, `permission` — W: `role_permission` (DELETE + INSERT), `role` (INSERT/UPDATE for custom roles), `role_visible_source` (INSERT/DELETE) |
|
||||||
| **Result** | RBAC matrix updated, effective immediately for new requests of users bearing this role |
|
| **Resultat** | Matrice RBAC mise a jour, effective immediatement pour les nouvelles requetes des utilisateurs porteurs de ce role |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10.5 ERASE_USER_PII (security-by-design)
|
### 10.5 ERASE_USER_PII (security-by-design)
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | A RGPD erasure request is processed for a back-office user |
|
| **Evenement declencheur** | Une demande d'effacement RGPD est traitee pour un utilisateur back-office |
|
||||||
| **Actor** | ADMIN (PIN-gated) |
|
| **Acteur** | ADMIN (protege par PIN) |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `user.update` and has re-authorised via PIN. Target user exists and is not already anonymised. |
|
| **Condition** | L'acteur detient la permission `user.update` et s'est re-autorise via PIN. L'utilisateur cible existe et n'est pas deja anonymise. |
|
||||||
| **Operation** | ERASE_USER_PII |
|
| **Operation** | ERASE_USER_PII |
|
||||||
| **Description** | RGPD right-to-erasure honoured by **anonymisation**, not physical deletion: PII (`email`, `first_name`, `last_name`) is cleared/replaced by a non-identifying placeholder, credentials invalidated, `anonymized_at` set. The row persists so referential links (`stock_movement`, `customer_order`, `audit_log`) stay valid and resolve to an anonymised principal. See `mlt.md` 10.5 and dictionary note 13. |
|
| **Description** | Le droit a l'effacement RGPD est honore par **anonymisation**, non par suppression physique : les PII (`email`, `first_name`, `last_name`) sont effacees/remplacees par un placeholder non identifiant, les identifiants invalides, `anonymized_at` positionne. La ligne persiste afin que les liens referentiels (`stock_movement`, `customer_order`, `audit_log`) restent valides et se resolvent vers un principal anonymise. Voir `mlt.md` 10.5 et la note 13 du dictionnaire. |
|
||||||
| **MCD entities** | W: `user` (UPDATE — PII cleared, `anonymized_at` set), `audit_log` (INSERT) |
|
| **Entites MCD** | W: `user` (UPDATE — PII cleared, `anonymized_at` set), `audit_log` (INSERT) |
|
||||||
| **Result** | User anonymised; PII removed; accountability links preserved; one `audit_log` row recorded |
|
| **Resultat** | Utilisateur anonymise ; PII supprimees ; liens d'imputabilite preserves ; une ligne `audit_log` enregistree |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Domain 9 — Stats and KPI
|
## 11. Domaine 9 — Stats et KPI
|
||||||
|
|
||||||
### 11.1 READ_STATS
|
### 11.1 READ_STATS
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Manager or admin accesses the stats dashboard |
|
| **Evenement declencheur** | Le manager ou l'admin accede au tableau de bord des stats |
|
||||||
| **Actor** | MANAGER or ADMIN |
|
| **Acteur** | MANAGER ou ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Actor holds permission `stats.read`. |
|
| **Condition** | L'acteur detient la permission `stats.read`. |
|
||||||
| **Operation** | READ_STATS |
|
| **Operation** | READ_STATS |
|
||||||
| **Description** | Aggregate queries on `customer_order` and `order_item`. Key aggregations: order count and revenue (TTC) by `service_day` (computed with CASE WHEN HOUR(created_at) < 10 THEN DATE(created_at) - INTERVAL 1 DAY ELSE DATE(created_at) END; cutoff at 10:00); top products by `label_snapshot` COUNT in `order_item`; cancellation rate; average delivery time `delivered_at - paid_at`; breakdown by `source` and `service_mode`. Queries exclude cancelled orders from revenue sums but include them in volume counts. No additional stored column for `service_day`; computation at query time. |
|
| **Description** | Requetes d'agregation sur `customer_order` et `order_item`. Agregations cles : nombre de commandes et chiffre d'affaires (TTC) par `service_day` (calcule avec CASE WHEN HOUR(created_at) < 10 THEN DATE(created_at) - INTERVAL 1 DAY ELSE DATE(created_at) END ; coupure a 10:00) ; top produits par COUNT de `label_snapshot` dans `order_item` ; taux d'annulation ; temps de livraison moyen `delivered_at - paid_at` ; ventilation par `source` et `service_mode`. Les requetes excluent les commandes annulees des sommes de chiffre d'affaires mais les incluent dans les comptages de volume. Pas de colonne stockee supplementaire pour `service_day` ; calcul au moment de la requete. |
|
||||||
| **MCD entities** | R: `customer_order`, `order_item` |
|
| **Entites MCD** | R: `customer_order`, `order_item` |
|
||||||
| **Result** | Stats dashboard displayed |
|
| **Resultat** | Tableau de bord des stats affiche |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Domain 10 — Back-office authentication
|
## 12. Domaine 10 — Authentification back-office
|
||||||
|
|
||||||
### 12.1 AUTHENTICATE_USER
|
### 12.1 AUTHENTICATE_USER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | An actor submits the login form |
|
| **Evenement declencheur** | Un acteur soumet le formulaire de connexion |
|
||||||
| **Actor** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN |
|
| **Acteur** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN |
|
||||||
| **Synchronisation** | None |
|
| **Synchronisation** | Aucune |
|
||||||
| **Condition** | Account not in a throttling window (`lockout_until`). Email exists in database. Password matches argon2id hash. User `is_active=1`. |
|
| **Condition** | Le compte n'est pas dans une fenetre de throttling (`lockout_until`). L'email existe en base. Le mot de passe correspond au hash argon2id. L'utilisateur `is_active=1`. |
|
||||||
| **Operation** | AUTHENTICATE_USER |
|
| **Operation** | AUTHENTICATE_USER |
|
||||||
| **Description** | Credential verification. If valid: session ID regeneration (protection against session fixation), storage of `user_id` and `role_id` in session, UPDATE `last_login_at`, reset of the login failure counter. On failure: increment `failed_login_attempts` and apply a degressive backoff (`lockout_until`), enumeration-safe generic error. Idle timeout: 4h. Absolute timeout: 10h. Redirect to `role.default_route`. See `mlt.md` 12.1. |
|
| **Description** | Verification des identifiants. Si valide : regeneration de l'ID de session (protection contre la fixation de session), stockage de `user_id` et `role_id` en session, UPDATE `last_login_at`, remise a zero du compteur d'echecs de connexion. En cas d'echec : incrementation de `failed_login_attempts` et application d'un backoff degressif (`lockout_until`), erreur generique resistante a l'enumeration. Idle timeout : 4h. Absolute timeout : 10h. Redirection vers `role.default_route`. Voir `mlt.md` 12.1. |
|
||||||
| **MCD entities** | R: `user` (verification), `role` (load permissions, default_route), `role_permission`, `login_throttle` (the per-IP throttle gate) — W: `user` (UPDATE last_login_at, `failed_login_attempts`, `lockout_until`), `login_throttle` (upsert `failed_attempts`/`lockout_until` on failure, clear on success), `audit_log` (INSERT login success/failure) |
|
| **Entites MCD** | R: `user` (verification), `role` (load permissions, default_route), `role_permission`, `login_throttle` (the per-IP throttle gate) — W: `user` (UPDATE last_login_at, `failed_login_attempts`, `lockout_until`), `login_throttle` (upsert `failed_attempts`/`lockout_until` on failure, clear on success), `audit_log` (INSERT login success/failure) |
|
||||||
| **Result** | Session opened, redirect to role-specific default view; or throttled failure logged |
|
| **Resultat** | Session ouverte, redirection vers la vue par defaut specifique au role ; ou echec throttle journalise |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 12.2 LOGOUT_USER
|
### 12.2 LOGOUT_USER
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | Actor clicks "Logout" OR session expires |
|
| **Evenement declencheur** | L'acteur clique sur « Deconnexion » OU la session expire |
|
||||||
| **Actor** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN / SYS (expiry) |
|
| **Acteur** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN / SYS (expiration) |
|
||||||
| **Synchronisation** | OR |
|
| **Synchronisation** | OR |
|
||||||
| **Condition** | A valid session is open |
|
| **Condition** | Une session valide est ouverte |
|
||||||
| **Operation** | LOGOUT_USER |
|
| **Operation** | LOGOUT_USER |
|
||||||
| **Description** | PHP session destruction (`session_destroy()`). Session deleted server-side. Session cookie invalidated. |
|
| **Description** | Destruction de la session PHP (`session_destroy()`). Session supprimee cote serveur. Cookie de session invalide. |
|
||||||
| **MCD entities** | No database write (session management is in PHP native, outside DB for this project) |
|
| **Entites MCD** | Aucune ecriture en base (la gestion des sessions est en PHP natif, hors base pour ce projet) |
|
||||||
| **Result** | Session destroyed, redirect to login page |
|
| **Resultat** | Session detruite, redirection vers la page de connexion |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 12.3 RESET_PASSWORD (security-by-design)
|
### 12.3 RESET_PASSWORD (security-by-design)
|
||||||
|
|
||||||
| Field | Value |
|
| Champ | Valeur |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Triggering event** | A user requests a password reset, then confirms it via the emailed link |
|
| **Evenement declencheur** | Un utilisateur demande une reinitialisation de mot de passe, puis la confirme via le lien envoye par email |
|
||||||
| **Actor** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN |
|
| **Acteur** | COUNTER / DRIVE / KITCHEN / MANAGER / ADMIN |
|
||||||
| **Synchronisation** | Sequential two-phase: request, then confirm |
|
| **Synchronisation** | Sequentielle en deux phases : demande, puis confirmation |
|
||||||
| **Condition** | Request: the submitted email is processed enumeration-safely (same neutral response whether or not it exists). Confirm: a valid, non-expired token is presented. |
|
| **Condition** | Demande : l'email soumis est traite de maniere resistante a l'enumeration (meme reponse neutre qu'il existe ou non). Confirmation : un token valide et non expire est presente. |
|
||||||
| **Operation** | RESET_PASSWORD |
|
| **Operation** | RESET_PASSWORD |
|
||||||
| **Description** | Request phase generates a random token, stores its hash + expiry, and e-mails the raw token once. Confirm phase validates the token hash + expiry, replaces `password_hash` (argon2id), clears the token, and resets the login failure counter. See `mlt.md` 12.3. |
|
| **Description** | La phase de demande genere un token aleatoire, stocke son hash + expiration, et envoie le token brut une seule fois par email. La phase de confirmation valide le hash du token + expiration, remplace `password_hash` (argon2id), efface le token et remet a zero le compteur d'echecs de connexion. Voir `mlt.md` 12.3. |
|
||||||
| **MCD entities** | W: `user` (UPDATE `password_reset_token_hash` + `password_reset_expires_at` on request; UPDATE `password_hash`, clear token, reset `failed_login_attempts`/`lockout_until` on confirm), `audit_log` (INSERT) |
|
| **Entites MCD** | W: `user` (UPDATE `password_reset_token_hash` + `password_reset_expires_at` on request; UPDATE `password_hash`, clear token, reset `failed_login_attempts`/`lockout_until` on confirm), `audit_log` (INSERT) |
|
||||||
| **Result** | Password reset via a one-time, time-bound token; one `audit_log` row recorded |
|
| **Resultat** | Mot de passe reinitialise via un token a usage unique et a duree limitee ; une ligne `audit_log` enregistree |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. State machine — customer_order.status
|
## 13. Machine a etats — customer_order.status
|
||||||
|
|
||||||
Summary of transitions covered by MCT operations.
|
Recapitulatif des transitions couvertes par les operations MCT.
|
||||||
|
|
||||||
```
|
```
|
||||||
[CUSTOMER / COUNTER / DRIVE]
|
[CUSTOMER / COUNTER / DRIVE]
|
||||||
|
|
@ -573,20 +573,20 @@ Summary of transitions covered by MCT operations.
|
||||||
[ cancelled ] (terminal)
|
[ cancelled ] (terminal)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note on the `pending_payment -> paid` transition**: in the RNCP context, payment is
|
**Note sur la transition `pending_payment -> paid`** : dans le contexte RNCP, le paiement est
|
||||||
replaced by the customer entering their order number (kiosk) or by staff validation
|
remplace par la saisie du numero de commande par le client (kiosk) ou par la validation du personnel
|
||||||
(counter/drive). The transition is atomic within CREATE_ORDER and CREATE_COUNTER_ORDER.
|
(comptoir/drive). La transition est atomique au sein de CREATE_ORDER et CREATE_COUNTER_ORDER.
|
||||||
The `pending_payment` status is not observable outside the transaction.
|
Le statut `pending_payment` n'est pas observable en dehors de la transaction.
|
||||||
|
|
||||||
**Dropped from v0.1**: `preparing` and `ready` states; `MARK_IN_PREPARATION` and `MARK_READY`
|
**Supprime de v0.1** : etats `preparing` et `ready` ; operations `MARK_IN_PREPARATION` et `MARK_READY`.
|
||||||
operations. Kitchen staff have a read-only view of `paid` orders (LIST_ORDERS_DISPLAY). The
|
Le personnel cuisine a une vue en lecture seule des commandes `paid` (LIST_ORDERS_DISPLAY). L'unique
|
||||||
single delivery action (DELIVER_ORDER) collapses the v0.1 three-step sequence into one gesture.
|
action de livraison (DELIVER_ORDER) condense la sequence en trois etapes de v0.1 en un seul geste.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Operations summary table
|
## 14. Tableau recapitulatif des operations
|
||||||
|
|
||||||
| # | Operation | Domain | Actor | W Entities | R Entities |
|
| # | Operation | Domaine | Acteur | Entites W | Entites R |
|
||||||
|---|-----------|--------|-------|------------|------------|
|
|---|-----------|--------|-------|------------|------------|
|
||||||
| 1 | LOAD_CATALOGUE | Order kiosk | CUSTOMER | — | category, product, menu, menu_slot, menu_slot_option, ingredient, allergen, ingredient_allergen |
|
| 1 | LOAD_CATALOGUE | Order kiosk | CUSTOMER | — | category, product, menu, menu_slot, menu_slot_option, ingredient, allergen, ingredient_allergen |
|
||||||
| 2 | COMPOSE_CART | Order kiosk | CUSTOMER | — (volatile) | product, menu, menu_slot, menu_slot_option, ingredient, product_ingredient |
|
| 2 | COMPOSE_CART | Order kiosk | CUSTOMER | — (volatile) | product, menu, menu_slot, menu_slot_option, ingredient, product_ingredient |
|
||||||
|
|
@ -617,22 +617,22 @@ single delivery action (DELIVER_ORDER) collapses the v0.1 three-step sequence in
|
||||||
| 27 | ERASE_USER_PII | RBAC | ADMIN | user, audit_log | user |
|
| 27 | ERASE_USER_PII | RBAC | ADMIN | user, audit_log | user |
|
||||||
| 28 | RESET_PASSWORD | Auth | ALL BACK | user, audit_log | user |
|
| 28 | RESET_PASSWORD | Auth | ALL BACK | user, audit_log | user |
|
||||||
|
|
||||||
**Total: 28 operations** (26 prod-like + `ERASE_USER_PII` and `RESET_PASSWORD` from the
|
**Total : 28 operations** (26 prod-like + `ERASE_USER_PII` et `RESET_PASSWORD` de la
|
||||||
security-by-design layer).
|
couche security-by-design).
|
||||||
|
|
||||||
**Audit log writes (security-by-design)**: the sensitive operations 7.1 (cancel), 8.2/8.3
|
**Ecritures du journal d'audit (security-by-design)** : les operations sensibles 7.1 (annulation), 8.2/8.3
|
||||||
(product update/delete), 8.6 (menu delete), 10.1-10.5 (user/RBAC/erasure) and 12.1 (login)
|
(modification/suppression de produit), 8.6 (suppression de menu), 10.1-10.5 (utilisateur/RBAC/effacement) et 12.1 (connexion)
|
||||||
also write an `audit_log` row (W entity not repeated per row above to keep the table legible).
|
ecrivent egalement une ligne `audit_log` (entite W non repetee par ligne ci-dessus pour garder le tableau lisible).
|
||||||
Stock operations 9.1/9.2 record their attribution via `stock_movement.user_id`. PIN-gated set
|
Les operations de stock 9.1/9.2 enregistrent leur attribution via `stock_movement.user_id`. Ensemble protege par PIN
|
||||||
per `mlt.md` RG-T13.
|
selon `mlt.md` RG-T13.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 15. MCT -> MCD cross-validation (mantra #34)
|
## 15. Verification croisee MCT -> MCD (mantra #34)
|
||||||
|
|
||||||
Verification that each MCD entity participates in at least one MCT operation.
|
Verification que chaque entite MCD participe a au moins une operation MCT.
|
||||||
|
|
||||||
| MCD entity | Operations that read | Operations that write | Coverage |
|
| Entite MCD | Operations en lecture | Operations en ecriture | Couverture |
|
||||||
|------------|---------------------|----------------------|----------|
|
|------------|---------------------|----------------------|----------|
|
||||||
| `category` | 1, 9, 12, 15 | 15 | OK |
|
| `category` | 1, 9, 12, 15 | 15 | OK |
|
||||||
| `product` | 1, 2, 3, 5, 9, 11, 12 | 9, 10, 11 | OK |
|
| `product` | 1, 2, 3, 5, 9, 11, 12 | 9, 10, 11 | OK |
|
||||||
|
|
@ -653,20 +653,20 @@ Verification that each MCD entity participates in at least one MCT operation.
|
||||||
| `permission` | 23 | — (static seed) | OK (*) |
|
| `permission` | 23 | — (static seed) | OK (*) |
|
||||||
| `role_permission` | 25 | 23 | OK |
|
| `role_permission` | 25 | 23 | OK |
|
||||||
| `stock_movement` | 19 | 3, 5, 8, 17, 18 | OK |
|
| `stock_movement` | 19 | 3, 5, 8, 17, 18 | OK |
|
||||||
| `audit_log` | (admin audit view) | 8, 10, 11, 14, 20, 21, 22, 23, 25, 27, 28 | OK |
|
| `audit_log` | (vue d'audit admin) | 8, 10, 11, 14, 20, 21, 22, 23, 25, 27, 28 | OK |
|
||||||
| `login_throttle` | 25 | 25 | OK |
|
| `login_throttle` | 25 | 25 | OK |
|
||||||
|
|
||||||
(*) `allergen` and `permission` are read-only at the MCT level: their values are declared
|
(*) `allergen` et `permission` sont en lecture seule au niveau MCT : leurs valeurs sont declarees
|
||||||
in seed migrations and are not modifiable via the UI. `allergen` is managed indirectly
|
dans les migrations de seed et ne sont pas modifiables via l'UI. `allergen` est gere indirectement
|
||||||
via `ingredient_allergen` in MANAGE_INGREDIENT.
|
via `ingredient_allergen` dans MANAGE_INGREDIENT.
|
||||||
|
|
||||||
(**) `audit_log` (entity 20, security-by-design) is write-mostly: it is appended by the
|
(**) `audit_log` (entite 20, security-by-design) est principalement en ecriture : il est ajoute par les
|
||||||
sensitive operations above and read through an admin audit view (a dedicated read operation
|
operations sensibles ci-dessus et lu via une vue d'audit admin (une operation de lecture dediee
|
||||||
can be formalised when the audit UI is specified at P3).
|
peut etre formalisee lorsque l'UI d'audit sera specifiee en P3).
|
||||||
|
|
||||||
(***) `login_throttle` (entity 21, security-by-design) is the per-source-IP brute-force
|
(***) `login_throttle` (entite 21, security-by-design) est le verrou de throttling anti-force-brute par IP source :
|
||||||
throttle gate: it is read AND written (upserted) by `AUTHENTICATE_USER` (25). Its daily purge
|
il est lu ET ecrit (upserte) par `AUTHENTICATE_USER` (25). Sa purge quotidienne
|
||||||
of stale rows is a cron, documented in `mlt.md`, outside MCT operation scope.
|
des lignes obsoletes est un cron, documente dans `mlt.md`, hors du perimetre des operations MCT.
|
||||||
|
|
||||||
**Conclusion**: 21/21 entities covered (19 prod-like + `audit_log` + `login_throttle`). MCT <-> MCD consistency
|
**Conclusion** : 21/21 entites couvertes (19 prod-like + `audit_log` + `login_throttle`). Coherence MCT <-> MCD
|
||||||
validated.
|
validee.
|
||||||
|
|
|
||||||
1012
docs/merise/mld.md
1012
docs/merise/mld.md
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue