` que n'importe quel visiteur voit dans le HTML.
+- Pour acceder a la colonne `image_path` en DB, un attaquant doit deja avoir une breche DB (SQLi, credentials voles). A ce stade il a deja toutes les donnees metier (commandes, password_hash, etc.) ; connaitre `/uploads/produits/` est l'info la moins critique de la DB.
+
+#### Les vrais risques securite filesystem (traites par ailleurs)
+
+1. **Path traversal a l'upload** : valider que le nom de fichier upload passe par `basename()` + regex `^[a-z0-9_-]+\.(jpg|png|webp)$` cote service admin.
+2. **MIME type spoof** : verifier le vrai MIME via `finfo_file()` (extension `.jpg` ne suffit pas). Desactiver l'execution PHP dans `/uploads/` via Apache (`php_flag engine off` + `FilesMatch .(php|phtml|phar)$ deny`).
+3. **Stockage hors-webroot pour les fichiers sensibles** : pas applicable au catalogue public, mais regle de principe pour PDF de facturation, exports stats, etc.
+4. **Validation taille** : `UPLOAD_MAX_SIZE_MB` dans `.env` + verification PHP cote upload.
+5. **Nom non-predictible pour fichiers sensibles** : UUID au lieu du nom metier si l'image contient des donnees sensibles. Pas applicable a un catalogue public.
+
+#### Sources
+
+- OWASP File Upload Cheat Sheet (section "Filesystem storage")
+- MariaDB Knowledge Base - LONGBLOB performance considerations
+- Apache HTTP Server documentation - `mod_xsendfile` et serving static content
+
---
-## 6. A faire au prochain sprint (MCD)
+## 5. A faire au prochain sprint (MCD)
- Tracer le MCD avec les cardinalites precises (entites + associations + roles + cardinalites
min/max)
diff --git a/docs/merise/mcd.md b/docs/merise/mcd.md
new file mode 100644
index 0000000..1bdbc8e
--- /dev/null
+++ b/docs/merise/mcd.md
@@ -0,0 +1,309 @@
+# Modele Conceptuel des Donnees (MCD) - Wakdo
+
+**Phase Merise** : P1 - Conception, etape 2 (apres dictionnaire de donnees)
+**Statut** : v0.1
+**Date** : 2026-04-30
+**Branche** : `feat/p1-conception`
+
+---
+
+## 1. Objet du document
+
+Le MCD (Modele Conceptuel des Donnees) formalise les **entites** du domaine
+Wakdo, leurs **associations** et les **cardinalites** qui regissent ces
+associations. Il est la traduction normalisee du dictionnaire de donnees, et
+sert de base au MLD (Modele Logique des Donnees) qui produira le schema
+relationnel.
+
+A la difference du dictionnaire (qui detaille les attributs et types), le MCD
+focalise sur la structure relationnelle : combien de X pour un Y, est-ce
+obligatoire, peut-il y avoir des relations sans participants ?
+
+**Sources** :
+- `docs/merise/dictionary.md` (entites + attributs identifies)
+- `docs/PROJECT_CONTEXT.md` (regles metier : composition menu, parcours commande, RBAC)
+- `docs/merise/_sources/` (donnees ecole : 9 categories + 53 produits + 13 menus)
+
+---
+
+## 2. Notation Merise utilisee
+
+### Cardinalites au pied de l'association (style francais)
+
+A chaque extremite d'une association, la cardinalite `(min, max)` precise
+combien de fois une instance de l'entite participe a l'association.
+
+```
+ENTITE_A (min,max) ----[ ASSOCIATION ]---- (min,max) ENTITE_B
+```
+
+| Notation | Lecture | Exemple |
+|---|---|---|
+| `(0,1)` | Optionnel, au plus 1 | Un user a (0,1) avatar |
+| `(1,1)` | Obligatoire, exactement 1 | Un produit appartient a (1,1) categorie |
+| `(0,N)` | Optionnel, illimite | Une categorie regroupe (0,N) produits |
+| `(1,N)` | Obligatoire au moins 1, illimite | Une commande contient (1,N) lignes |
+
+Lecture francaise : "une instance de l'entite-source participe au moins MIN
+fois et au plus MAX fois a l'association".
+
+### Convention nommage des associations
+
+Verbe a l'infinitif au sens metier, ex : `regroupe`, `compose`, `contient`,
+`refere`, `a_pour_role`, `possede`.
+
+Les associations qui portent des attributs (= relations N-N enrichies)
+deviennent des **entites associatives** au MLD (table de jointure avec
+colonnes propres).
+
+---
+
+## 3. Vue d'ensemble (diagramme global)
+
+Diagramme entites-relations dessine dans draw.io, exporte en SVG. Les
+sources `.drawio` editables sont dans `_diagrams/`. Cardinalites Merise
+`(min,max)` notees directement sur les associations. Recapitulatif des
+cardinalites en section 5.
+
+
+
+*Source : [`_diagrams/mcd-global.drawio`](_diagrams/mcd-global.drawio)*
+
+> **A regenerer** : le diagramme global doit etre mis a jour pour inclure l'entite `COMMANDE_EVENT` (11 entites au total) et l'attribut `source` sur `COMMANDE`. Le SVG actuel reflete l'etat anterieur a ces decisions.
+
+### Lecture rapide
+
+- Une `CATEGORIE` regroupe `(0,N)` `PRODUIT` ou `MENU` ; un `PRODUIT` ou un
+ `MENU` appartient a `(1,1)` categorie (chacun cote sa categorie de
+ rattachement).
+- Un `MENU` est compose de `(1,N)` produits (un menu sans composition n'a pas
+ de sens metier) ; un `PRODUIT` peut faire partie de `(0,N)` menus
+ (independance).
+- Une `COMMANDE` contient `(1,N)` `LIGNE_COMMANDE` (commande vide impossible) ;
+ une ligne appartient a `(1,1)` commande.
+- Une `LIGNE_COMMANDE` refere `(0,1)` `PRODUIT` OU `(0,1)` `MENU` selon le
+ discriminateur `type_item` (polymorphisme).
+- Une `COMMANDE` est journalisee par `(1,N)` `COMMANDE_EVENT` (au moins 1 event `CREATED`, append-only).
+- Un `USER` declenche `(0,N)` `COMMANDE_EVENT` (NULL possible si event auto-kiosk).
+- Un `USER` a `(1,1)` `ROLE` (un user sans role ne peut pas se connecter) ;
+ un `ROLE` peut etre porte par `(0,N)` users.
+- Un `ROLE` possede `(0,N)` `PERMISSION` via `ROLE_PERMISSION` (matrice N-N).
+
+---
+
+## 4. Decomposition par sous-domaine
+
+Le modele est segmente en 3 sous-domaines pour faciliter la lecture et
+l'analyse :
+
+1. **Catalogue** : produits, menus, categories, composition
+2. **Commande** : commande, lignes, references polymorphiques
+3. **RBAC** : users, roles, permissions, mapping
+
+### 4.1 Sous-domaine Catalogue
+
+
+
+*Source : [`_diagrams/mcd-catalogue.drawio`](_diagrams/mcd-catalogue.drawio)*
+
+#### Justification des cardinalites
+
+| Cote | Cardinalite | Justification |
+|---|---|---|
+| Categorie -> Produit | `(0,N)` cote Categorie | Une categorie peut etre creee a vide (ex : "petit dejeuner" ajoutee sans produit initial). Maximum illimite (au moins 53 produits dans la source actuelle, repartis sur 9 categories). |
+| Categorie -> Produit | `(1,1)` cote Produit | Tout produit doit etre rattache a une categorie pour etre affiche sur la borne. Pas de produit "orphelin". |
+| Categorie -> Menu | `(0,N)` cote Categorie | Toutes les categories sauf "menus" ont 0 menu. La categorie "menus" en a 13. |
+| Categorie -> Menu | `(1,1)` cote Menu | Tout menu est rattache a la categorie "menus" (par construction du modele). Un menu sans categorie ne s'affiche pas sur la borne. |
+| Menu -> MenuProduit | `(1,N)` cote Menu | Un menu doit avoir au moins 1 produit dans sa composition. Sans composition, le menu est invendable. |
+| Produit -> MenuProduit | `(0,N)` cote Produit | Un produit peut exister independamment des menus (vente a la carte uniquement). Inversement, un produit peut entrer dans plusieurs menus differents (ex : frites moyennes presentes dans plusieurs combos). |
+
+#### Note sur l'entite associative `MENU_PRODUIT`
+
+`MENU_PRODUIT` est une entite associative : la relation N-N entre `MENU` et
+`PRODUIT` porte deux attributs metier (`role` et `position`). Au MLD, elle
+deviendra une table de jointure avec PK composite `(menu_id, produit_id)`.
+
+### 4.2 Sous-domaine Commande
+
+
+
+*Source : [`_diagrams/mcd-commande.drawio`](_diagrams/mcd-commande.drawio)*
+
+> **A regenerer** : le diagramme `mcd-commande.drawio` doit etre mis a jour pour inclure l'entite `COMMANDE_EVENT` (cf. section 4.2.bis ci-dessous) et l'attribut `source` sur `COMMANDE`. Le SVG actuel reflete l'etat anterieur a ces decisions.
+
+#### Justification des cardinalites
+
+| Cote | Cardinalite | Justification |
+|---|---|---|
+| Commande -> LigneCommande | `(1,N)` cote Commande | Une commande sans aucune ligne n'a pas de sens metier. Au moment de la validation (passage de `pending_payment` a `paid`), au moins 1 ligne est presente. |
+| Commande -> LigneCommande | `(1,1)` cote LigneCommande | Toute ligne appartient a exactement une commande. ON DELETE CASCADE (si commande supprimee, lignes aussi). |
+| LigneCommande -> Produit | `(0,N)` cote Produit | Un produit peut etre commande des centaines de fois. Maximum non borne. |
+| LigneCommande -> Produit | `(0,1)` cote LigneCommande | Selon `type_item` : si `'produit'` -> 1 produit reference ; si `'menu'` -> 0 (la colonne `produit_id` est NULL). |
+| LigneCommande -> Menu | `(0,N)` cote Menu | Symetrique de Produit. |
+| LigneCommande -> Menu | `(0,1)` cote LigneCommande | Selon `type_item` : si `'menu'` -> 1 menu reference ; si `'produit'` -> 0. |
+| Commande -> CommandeEvent | `(1,N)` cote Commande | Toute commande a au moins 1 event (CREATED) ; en pratique 5-8 events sur tout son cycle de vie. |
+| Commande -> CommandeEvent | `(1,1)` cote CommandeEvent | Chaque event appartient a exactement une commande. ON DELETE CASCADE. |
+| User -> CommandeEvent | `(0,N)` cote User | Un equipier peut declencher 0 a N events (un nouveau user n'a encore rien fait). |
+| User -> CommandeEvent | `(0,1)` cote CommandeEvent | NULL si event auto (kiosk paye via CB sans equipier) ou si le user a ete supprime (ON DELETE SET NULL preserve l'audit). |
+
+#### Note sur le polymorphisme
+
+La cardinalite `(0,1)` cote LigneCommande pour les deux associations
+`refere_si_type_produit` et `refere_si_type_menu` reflete le polymorphisme :
+**exactement une** des deux references est non-nulle, l'autre est nulle.
+Cette regle d'exclusivite est a renforcer au MLD via une contrainte CHECK
+SQL ou une regle applicative :
+
+```sql
+CHECK (
+ (type_item = 'produit' AND produit_id IS NOT NULL AND menu_id IS NULL)
+ OR (type_item = 'menu' AND menu_id IS NOT NULL AND produit_id IS NULL)
+)
+```
+
+Voir `docs/notes/polymorphic-fk-snapshots.md` pour le detail du choix de
+modelisation polymorphique.
+
+#### 4.2.bis Journal d'audit `COMMANDE_EVENT` (event sourcing simplifie)
+
+`COMMANDE_EVENT` est une entite append-only qui journalise chaque changement d'etat d'une commande, avec l'auteur de la transition (un `USER` ou NULL si auto). Pattern event sourcing simplifie (cf. note 10 du dictionnaire).
+
+Trois proprietes invariantes :
+
+1. **Append-only** : aucun UPDATE / DELETE applicatif sur `commande_event`. Garantie d'integrite de l'audit.
+2. **Lien fort a la commande** : `ON DELETE CASCADE` cote `commande_id` (si la commande disparait, son journal aussi).
+3. **Lien faible a l'user** : `ON DELETE SET NULL` cote `user_id` (si un equipier est supprime, les events restent avec `user_id = NULL` ; l'audit reste consultable, seule l'attribution individuelle est perdue).
+
+La contrainte croisee `(source, mode_consommation)` introduite par l'attribut `source` sur `COMMANDE` (cf. dictionnaire note 8) est verifiee au MLT lors de la creation de la commande, pas au MCD.
+
+### 4.3 Sous-domaine RBAC
+
+
+
+*Source : [`_diagrams/mcd-rbac.drawio`](_diagrams/mcd-rbac.drawio)*
+
+#### Justification des cardinalites
+
+| Cote | Cardinalite | Justification |
+|---|---|---|
+| User -> Role | `(1,1)` cote User | Un user doit avoir un role pour acceder au back-office. Pas de connexion sans role. |
+| User -> Role | `(0,N)` cote Role | Un role peut exister sans aucun user (ex : nouveau role cree dans l'UI admin avant d'etre assigne). |
+| Role -> Permission (via ROLE_PERMISSION) | `(0,N)` cote Role | Un role peut avoir 0 permission (role "vide" ou "en construction") jusqu'a toutes les permissions (admin). |
+| Role -> Permission (via ROLE_PERMISSION) | `(0,N)` cote Permission | Une permission peut etre assignee a 0 role (permission declaree mais pas encore distribuee) ou a plusieurs roles. |
+
+#### Note sur le modele RBAC
+
+Le RBAC retenu est **dynamique cote roles** (creables/modifiables via UI
+admin) et **statique cote permissions** (declarees en migration, liees au
+code applicatif). Voir `docs/notes/rbac-roles-permissions.md` pour le detail
+du choix.
+
+---
+
+## 5. Recapitulatif global des cardinalites
+
+| # | Association | Cote A | Cardinalite A | Cote B | Cardinalite B |
+|---|---|---|---|---|---|
+| 1 | regroupe (Categorie -> Produit) | Categorie | (0,N) | Produit | (1,1) |
+| 2 | regroupe (Categorie -> Menu) | Categorie | (0,N) | Menu | (1,1) |
+| 3 | compose (Menu <-> Produit via MenuProduit) | Menu | (1,N) | Produit | (0,N) |
+| 4 | contient (Commande -> LigneCommande) | Commande | (1,N) | LigneCommande | (1,1) |
+| 5 | refere_si_type_produit (LigneCommande -> Produit) | LigneCommande | (0,1) | Produit | (0,N) |
+| 6 | refere_si_type_menu (LigneCommande -> Menu) | LigneCommande | (0,1) | Menu | (0,N) |
+| 7 | journalise (Commande -> CommandeEvent) | Commande | (1,N) | CommandeEvent | (1,1) |
+| 8 | declenche (User -> CommandeEvent) | User | (0,N) | CommandeEvent | (0,1) |
+| 9 | a_pour_role (User -> Role) | User | (1,1) | Role | (0,N) |
+| 10 | possede (Role <-> Permission via RolePermission) | Role | (0,N) | Permission | (0,N) |
+
+---
+
+## 6. Cross-validation avec le dictionnaire de donnees
+
+Verification que chaque entite du dictionnaire est presente dans le MCD et
+inversement.
+
+| Entite dictionnaire (section 3) | Presente dans MCD ? | Sous-domaine |
+|---|---|---|
+| `categorie` (3.1) | Oui | Catalogue |
+| `produit` (3.2) | Oui | Catalogue |
+| `menu` (3.3) | Oui | Catalogue |
+| `menu_produit` (3.4) | Oui (entite associative) | Catalogue |
+| `commande` (3.5) | Oui | Commande |
+| `ligne_commande` (3.6) | Oui | Commande |
+| `commande_event` (3.7) | Oui (journal d'audit) | Commande |
+| `user` (3.8) | Oui | RBAC |
+| `role` (3.9) | Oui | RBAC |
+| `permission` (3.10) | Oui | RBAC |
+| `role_permission` (3.11) | Oui (entite associative) | RBAC |
+
+Coherence : 100%. Aucune entite du dictionnaire n'est absente du MCD, aucune
+entite du MCD n'est en plus du dictionnaire.
+
+---
+
+## 7. Decisions reportees au MLD
+
+Le MCD reste au niveau conceptuel. Les decisions suivantes sont reportees au
+MLD (modele logique des donnees, etape suivante) :
+
+1. **Resolution des entites associatives en tables** : `MENU_PRODUIT` et
+ `ROLE_PERMISSION` deviendront des tables de jointure avec PK composite.
+2. **Choix des PK techniques vs metier** : pour les entites principales, PK
+ technique `id INT UNSIGNED AUTO_INCREMENT`. Pour `commande`, garder
+ un identifiant metier `numero` UNIQUE en plus de l'id technique.
+3. **Index techniques** : non discutes au MCD (couche logique). Au MLD :
+ index sur les FK, sur les colonnes `est_actif`/`est_disponible`, sur les
+ colonnes utilisees dans les `WHERE`/`ORDER BY` frequents (`created_at`,
+ `statut`).
+4. **Contraintes CHECK SQL** : la regle d'exclusivite polymorphique sur
+ `LIGNE_COMMANDE` sera materialisee via CHECK en MariaDB 10.2+, ou via
+ trigger sur versions anteriures.
+5. **Triggers / vues** : non identifies au MCD. A evaluer au MLD pour les
+ denormalisations utiles (vue `commande_avec_total` ?).
+
+---
+
+## 8. Coherence avec le MCT
+
+Le MCT (Modele Conceptuel des Traitements) decrira les operations metier qui
+manipulent ces entites :
+
+- **Composer panier** (kiosk) : creation de COMMANDE statut `pending_payment` + insertion COMMANDE_EVENT `CREATED`
+- **Valider payment** : transition COMMANDE statut `pending_payment` -> `paid` + insertion COMMANDE_EVENT `PAID`
+- **Preparer commande** (cuisine) : transition `paid` -> `preparing` + insertion COMMANDE_EVENT `PREPARING_STARTED`
+- **Marquer pret** : transition `preparing` -> `ready` + insertion COMMANDE_EVENT `READY`
+- **Remettre client** : transition `ready` -> `delivered` + insertion COMMANDE_EVENT `DELIVERED`
+- **Annuler** : transition vers `cancelled` (depuis tout statut sauf `delivered`) + insertion COMMANDE_EVENT `CANCELLED`
+- **CRUD admin** : operations sur PRODUIT, MENU, CATEGORIE, USER, ROLE
+
+Cross-validation MCD <-> MCT (mantra #34) : verifier au MCT que chaque
+entite du MCD participe a au moins un traitement, et que chaque traitement
+manipule des entites existantes du MCD.
+
+Pre-validation rapide (intuitive, a re-valider au MCT) :
+
+| Entite MCD | Au moins 1 traitement attendu ? |
+|---|---|
+| Categorie | Oui (CRUD admin) |
+| Produit | Oui (CRUD admin + ajout panier) |
+| Menu | Oui (CRUD admin + ajout panier) |
+| MenuProduit | Oui (composition menu admin) |
+| Commande | Oui (cycle de vie complet) |
+| LigneCommande | Oui (creation panier) |
+| CommandeEvent | Oui (insere a chaque transition de statut) |
+| User | Oui (CRUD admin + login + declenchement events) |
+| Role | Oui (CRUD admin + assignation) |
+| Permission | Oui (consultation + assignation matrice) |
+| RolePermission | Oui (matrice admin) |
+
+---
+
+## 9. A faire au prochain sprint (MCT)
+
+- Lister exhaustivement les operations metier (acteurs, evenements
+ declencheurs, regles de declenchement)
+- Modeliser les flux entre acteurs (client kiosk, equipier comptoir, equipier
+ cuisine, equipier drive, manager, admin)
+- Identifier les synchronisations (ex : passage de `paid` a `preparing` peut
+ etre manuel cuisine ou automatique selon volume)
+- Cross-valider MCD <-> MCT exhaustivement
diff --git a/docs/merise/mct.md b/docs/merise/mct.md
new file mode 100644
index 0000000..4df7ae5
--- /dev/null
+++ b/docs/merise/mct.md
@@ -0,0 +1,598 @@
+# Modele Conceptuel des Traitements (MCT) - Wakdo
+
+**Phase Merise** : P1 - Conception, etape 3 (apres MCD)
+**Statut** : v0.1
+**Date** : 2026-05-21
+**Branche** : `feat/p1-conception`
+**Auteur methodologie** : BYAN
+
+---
+
+## 1. Objet du document
+
+Le MCT (Modele Conceptuel des Traitements) decrit les **operations metier** du domaine Wakdo
+sous la forme canonique Merise : **evenement declencheur -> operation -> resultat emis**.
+
+Il repond a la question : que se passe-t-il dans le domaine, et quand ?
+Il ne repond pas a la question : qui fait quoi, sur quel poste, dans quel ordre organisationnel
+(cette dimension est volontairement omise - le MOT est saute, raccourci agile assume, coheret
+avec le cadre RNCP solo).
+
+Le MCT couvre :
+- Le parcours commande de bout en bout (borne kiosk, comptoir, drive)
+- La gestion du catalogue (manager/admin)
+- La gestion des utilisateurs et roles (admin)
+- La connexion au back-office (tous acteurs back)
+
+**Acteurs identifies** :
+
+| Acteur | Code | Interface |
+|--------|------|-----------|
+| Client (borne) | CLIENT | Kiosk tactile (public, non authentifie) |
+| Accueil | ACCUEIL | Back-office, role `accueil` |
+| Preparation (cuisine) | CUISINE | Back-office, role `preparation` |
+| Manager / Administrateur | ADMIN | Back-office, role `admin` |
+| Systeme | SYS | Logique interne API / PHP |
+
+**Cross-reference MCD** : chaque operation manipule des entites du MCD (section 9). Le MCT est
+construit en coherence avec la machine a etats de `commande.statut` :
+
+```
+pending_payment -> paid -> preparing -> ready -> delivered
+ | | | |
+ +-------------+-----------+----------+-> cancelled (depuis tout etat non remis)
+```
+
+---
+
+## 2. Conventions de representation
+
+### Format d'une operation
+
+```
+[EVENEMENT(S) DECLENCHEUR(S)]
+ |
+ | [REGLE DE SYNCHRONISATION / CONDITION]
+ v
+ ( OPERATION )
+ |
+ v
+[RESULTAT(S) EMIS]
+```
+
+**Synchronisations** :
+- `ET` : tous les evenements doivent etre presents simultanement pour declencher l'operation
+- `OU` : l'un quelconque des evenements suffit
+
+**Conditions** : exprimees entre crochets `[condition]` sur l'arc entrant.
+
+### Notation textuelle adoptee
+
+Pour chaque operation, le document presente :
+- **Evenement(s) declencheur(s)** : ce qui arrive et provoque l'operation
+- **Acteur(s)** : qui est a l'origine (OU qui valide)
+- **Synchronisation** : `ET` / `OU` si plusieurs evenements, condition
+- **Operation** : nom de l'operation, description de ce qu'elle fait
+- **Entites MCD touchees** : lecture (R) ou ecriture (W) sur les entites du MCD
+- **Resultat(s)** : ce qui est emis ou produit a l'issue de l'operation
+
+---
+
+## 3. Domaine 1 - Parcours commande (borne kiosk)
+
+Ce domaine couvre le cycle de vie d'une commande initiee depuis la borne client.
+
+### 3.1 Charger le catalogue
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | Le client ouvre la borne (connexion au kiosk) |
+| **Acteur** | CLIENT |
+| **Synchronisation** | Aucune (evenement unique) |
+| **Condition** | La borne est en service (dans la plage horaire 10h00-01h00) |
+| **Operation** | CHARGER_CATALOGUE |
+| **Description** | Recuperation de la liste des categories actives, des produits disponibles et des menus disponibles pour affichage sur la borne |
+| **Entites MCD** | R : `categorie` (est_actif=1), `produit` (est_disponible=1), `menu` (est_disponible=1), `menu_produit` |
+| **Resultat** | Catalogue charge, borne affiche la page d'accueil |
+
+---
+
+### 3.2 Composer le panier
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | Le client selectionne un produit ou un menu sur la borne |
+| **Acteur** | CLIENT |
+| **Synchronisation** | Evenement repetable (OU : ajout produit, ajout menu, modification quantite, suppression item) |
+| **Condition** | Le produit ou menu selectionne est disponible (`est_disponible=1`) |
+| **Operation** | COMPOSER_PANIER |
+| **Description** | Construction du panier en memoire : ajout d'un article (produit unitaire ou menu), avec eventuellement une option de taille (+0,50 EUR sur accompagnements et boissons), recalcul du total TTC. Le panier est une structure volatile cote client ; aucune ecriture en BDD a ce stade. |
+| **Entites MCD** | R : `produit`, `menu`, `menu_produit` - W : aucune (etat volatile front) |
+| **Resultat** | Panier mis a jour, total recalcule, affichage recapitulatif |
+
+---
+
+### 3.3 Valider et passer la commande
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenements declencheurs** | 1. Client confirme le panier (appui sur "Valider") ET 2. Client saisit son numero de commande |
+| **Acteur** | CLIENT |
+| **Synchronisation** | ET (les deux actions sont requises) |
+| **Condition** | Le panier contient au moins 1 article. Le numero saisi est non vide. |
+| **Operation** | PASSER_COMMANDE |
+| **Description** | Creation de la commande en base : insertion d'une ligne `commande` avec statut `pending_payment`, snapshot du total HT/TVA/TTC au taux en vigueur, source `kiosk`. Creation des lignes `ligne_commande` avec snapshot des libelles et prix. Le systeme genere le numero de commande au format `K-YYYY-MM-DD-NNN`. Le client saisit ensuite son numero de commande (substitut de paiement dans le cadre RNCP) : la commande passe au statut `paid`. La transition `pending_payment -> paid` est atomique dans cette operation. |
+| **Entites MCD** | R : `produit`, `menu` (snapshot prix/libelle) - W : `commande` (INSERT statut `pending_payment`, puis UPDATE statut `paid`), `ligne_commande` (INSERT N lignes), `commande_event` (INSERT 2 events : `CREATED` user_id=NULL puis `PAID` user_id=NULL — kiosk = auto-validation, pas d'equipier) |
+| **Resultat** | Commande creee (statut `paid` en fin d'operation), numero affiche au client, evenement COMMANDE_CREEE emis vers le domaine preparation |
+
+---
+
+### 3.4 Confirmer la commande au client
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | COMMANDE_CREEE (retour API 201 apres PASSER_COMMANDE) |
+| **Acteur** | SYS |
+| **Synchronisation** | Aucune |
+| **Condition** | La reponse API contient un id, un numero et un statut `paid` (la transition `pending_payment -> paid` s'est executee dans PASSER_COMMANDE) |
+| **Operation** | AFFICHER_CONFIRMATION |
+| **Description** | Affichage de l'ecran de confirmation sur la borne avec le numero de commande. La borne se reinitialise ensuite pour le client suivant. |
+| **Entites MCD** | R : aucune nouvelle lecture BDD (les donnees sont dans la reponse API) |
+| **Resultat** | Ecran de confirmation affiche, borne disponible pour la commande suivante |
+
+---
+
+## 4. Domaine 2 - Parcours commande (comptoir et drive)
+
+Ce domaine couvre la saisie manuelle d'une commande par un equipier accueil pour un client
+au comptoir ou au drive.
+
+### 4.1 Saisir une commande manuelle
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'equipier accueil initie une nouvelle commande depuis le back-office |
+| **Acteur** | ACCUEIL |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur est authentifie et possede la permission `commande.create`. La source est `comptoir` ou `drive`. |
+| **Operation** | SAISIR_COMMANDE_MANUELLE |
+| **Description** | Composition du panier via le back-office : selection de produits et menus, choix du mode de consommation, choix de la source (`comptoir` ou `drive`). Logique identique a PASSER_COMMANDE cote kiosk, a la difference que l'acteur est un equipier authentifie. La transition `pending_payment -> paid` est atomique dans cette operation (l'equipier valide le paiement du client). |
+| **Entites MCD** | R : `produit`, `menu`, `menu_produit` - W : `commande` (INSERT statut `pending_payment`, puis UPDATE statut `paid`, source `comptoir` ou `drive`), `ligne_commande` (INSERT), `commande_event` (INSERT 2 events : `CREATED` user_id=acteur puis `PAID` user_id=acteur) |
+| **Resultat** | Commande creee (statut `paid` en fin d'operation), numero imprime ou annonce au client |
+
+---
+
+## 5. Domaine 3 - Preparation (cuisine)
+
+### 5.1 Consulter les commandes a preparer
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'equipier cuisine accede a sa vue ou rafraichit la liste |
+| **Acteur** | CUISINE |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur est authentifie et possede la permission `commande.read`. |
+| **Operation** | LISTER_COMMANDES_A_PREPARER |
+| **Description** | Lecture des commandes de statut `paid` triees par `created_at` croissant (heure de passage croissante, tous canaux confondus). Affichage du numero, du contenu (lignes avec libelle snapshot), et de la source (kiosk/comptoir/drive). |
+| **Entites MCD** | R : `commande` (statut=`paid`), `ligne_commande` |
+| **Resultat** | Liste des commandes en attente de preparation affichee, triee par heure croissante |
+
+---
+
+### 5.2 Marquer une commande en preparation
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'equipier cuisine clique sur "Prendre en charge" pour une commande |
+| **Acteur** | CUISINE |
+| **Synchronisation** | Aucune |
+| **Condition** | La commande est au statut `paid`. L'acteur possede la permission `commande.update`. |
+| **Operation** | MARQUER_EN_PREPARATION |
+| **Description** | Transition de statut `paid` -> `preparing` sur la commande. Mise a jour de `updated_at`. La commande disparait de la file "a preparer" et passe dans la file "en preparation". |
+| **Entites MCD** | W : `commande` (UPDATE statut `paid` -> `preparing`), `commande_event` (INSERT event `PREPARING_STARTED` user_id=acteur) |
+| **Resultat** | Commande au statut `preparing`, evenement COMMANDE_EN_PREPARATION emis |
+
+---
+
+### 5.3 Marquer une commande prete
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'equipier cuisine clique sur "Pret" pour une commande en preparation |
+| **Acteur** | CUISINE |
+| **Synchronisation** | Aucune |
+| **Condition** | La commande est au statut `preparing`. L'acteur possede la permission `commande.update`. |
+| **Operation** | MARQUER_PRETE |
+| **Description** | Transition de statut `preparing` -> `ready`. Mise a jour de `updated_at`. La commande est desormais visible pour l'accueil qui peut la remettre au client. |
+| **Entites MCD** | W : `commande` (UPDATE statut `preparing` -> `ready`), `commande_event` (INSERT event `READY` user_id=acteur) |
+| **Resultat** | Commande au statut `ready`, evenement COMMANDE_PRETE emis vers l'accueil |
+
+---
+
+## 6. Domaine 4 - Remise au client (accueil)
+
+### 6.1 Consulter les commandes pretes
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'equipier accueil accede a sa vue ou rafraichit la liste |
+| **Acteur** | ACCUEIL |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur est authentifie et possede la permission `commande.read`. |
+| **Operation** | LISTER_COMMANDES_PRETES |
+| **Description** | Lecture des commandes de statut `ready`. Affichage du numero de commande, contenu, source. |
+| **Entites MCD** | R : `commande` (statut=`ready`), `ligne_commande` |
+| **Resultat** | Liste des commandes pretes affichee |
+
+---
+
+### 6.2 Declarer une commande livree
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenements declencheurs** | 1. La commande est au statut `ready` ET 2. L'equipier accueil clique sur "Livree" |
+| **Acteur** | ACCUEIL |
+| **Synchronisation** | ET |
+| **Condition** | La commande est au statut `ready`. L'acteur possede la permission `commande.update`. |
+| **Operation** | DECLARER_LIVREE |
+| **Description** | Transition de statut `ready` -> `delivered`. Fin du cycle de vie de la commande. La commande passe en historique. |
+| **Entites MCD** | W : `commande` (UPDATE statut `ready` -> `delivered`), `commande_event` (INSERT event `DELIVERED` user_id=acteur) |
+| **Resultat** | Commande au statut `delivered`, cycle de vie termine |
+
+---
+
+## 7. Domaine 5 - Annulation
+
+### 7.1 Annuler une commande
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | Un acteur autorise demande l'annulation d'une commande |
+| **Acteur** | ACCUEIL ou ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | La commande est dans un statut annulable : `pending_payment`, `paid`, `preparing` ou `ready`. Seuls les statuts finaux `delivered` et `cancelled` ne peuvent pas transitionner vers `cancelled` : une commande reste annulable tant qu'elle n'a pas ete remise au client (modification, annulation ou remboursement). L'acteur possede la permission `commande.cancel`. |
+| **Operation** | ANNULER_COMMANDE |
+| **Description** | Transition du statut courant vers `cancelled`. Mise a jour de `updated_at`. La commande reste en base pour l'historique et les stats (pas de suppression physique). |
+| **Entites MCD** | W : `commande` (UPDATE statut -> `cancelled`), `commande_event` (INSERT event `CANCELLED` user_id=acteur, `payload` peut contenir la raison) |
+| **Resultat** | Commande au statut `cancelled`, visible dans l'historique admin |
+
+---
+
+## 8. Domaine 6 - Gestion du catalogue (admin)
+
+### 8.1 Creer un produit
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de creation de produit |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `produit.create`. La categorie ciblee existe et est active. Le libelle est non vide. Le prix est strictement positif. |
+| **Operation** | CREER_PRODUIT |
+| **Description** | Insertion d'un nouveau produit en base avec sa categorie, son libelle, son prix en centimes, son image (upload optionnel). `est_disponible` a `1` par defaut. |
+| **Entites MCD** | R : `categorie` (validation FK) - W : `produit` (INSERT) |
+| **Resultat** | Produit cree, retour a la liste des produits |
+
+---
+
+### 8.2 Modifier un produit
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de modification d'un produit existant |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `produit.update`. Le produit existe. Les nouvelles valeurs respectent les contraintes (prix > 0, libelle non vide). |
+| **Operation** | MODIFIER_PRODUIT |
+| **Description** | Mise a jour des colonnes modifiables (`libelle`, `description`, `prix_ttc_cents`, `image_path`, `est_disponible`, `ordre`, `categorie_id`). Les snapshots deja stockes dans `ligne_commande` ne sont pas affectes (integrite historique garantie par le design). |
+| **Entites MCD** | W : `produit` (UPDATE) |
+| **Resultat** | Produit mis a jour, liste produits rafraichie |
+
+---
+
+### 8.3 Supprimer un produit
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin confirme la suppression d'un produit |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `produit.delete`. Le produit n'est pas compose dans un menu actif (FK `menu_produit.produit_id` avec ON DELETE RESTRICT). Verification prealable requise. |
+| **Operation** | SUPPRIMER_PRODUIT |
+| **Description** | Suppression physique du produit si aucune contrainte FK ne bloque. Si le produit est reference dans un menu, la suppression est bloquee (RESTRICT en base). La consequence metier est que l'admin doit d'abord retirer le produit de tous les menus qui le contiennent. |
+| **Entites MCD** | W : `produit` (DELETE - bloque si reference dans `menu_produit`) |
+| **Resultat** | Produit supprime OU erreur "produit utilise dans un menu" |
+
+---
+
+### 8.4 Creer un menu
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de creation de menu avec sa composition |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `menu.create`. Le libelle est non vide. Le prix est strictement positif. Au moins un produit de role `burger` est associe (contrainte metier : un menu sans burger n'a pas de sens). |
+| **Operation** | CREER_MENU |
+| **Description** | Insertion du menu (`menu`) puis insertion des lignes de composition (`menu_produit`) : pour chaque produit selectionne, un enregistrement avec son role (burger, accompagnement, boisson, sauce) et sa position. |
+| **Entites MCD** | R : `produit` (validation des composants), `categorie` - W : `menu` (INSERT), `menu_produit` (INSERT N lignes) |
+| **Resultat** | Menu cree avec sa composition, visible sur la borne |
+
+---
+
+### 8.5 Modifier un menu
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de modification d'un menu existant |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `menu.update`. Le menu existe. La composition modifiee conserve au moins un produit de role `burger`. |
+| **Operation** | MODIFIER_MENU |
+| **Description** | Mise a jour des colonnes du menu. Si la composition est modifiee : suppression de toutes les lignes `menu_produit` pour ce menu puis reinsertion (pattern delete-and-reinsert, plus simple que le diff ligne a ligne). Les snapshots deja commandes ne sont pas affectes. |
+| **Entites MCD** | W : `menu` (UPDATE), `menu_produit` (DELETE + INSERT) |
+| **Resultat** | Menu mis a jour |
+
+---
+
+### 8.6 Supprimer un menu
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin confirme la suppression d'un menu |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `menu.delete`. La suppression d'un menu ne bloque pas les `ligne_commande` historiques (FK avec ON DELETE RESTRICT sur `ligne_commande.menu_id`). Verification prealable requise. |
+| **Operation** | SUPPRIMER_MENU |
+| **Description** | Suppression en cascade des lignes `menu_produit` (ON DELETE CASCADE), puis suppression du menu si aucune `ligne_commande` historique ne le reference. |
+| **Entites MCD** | W : `menu_produit` (DELETE CASCADE), `menu` (DELETE - bloque si reference dans `ligne_commande`) |
+| **Resultat** | Menu supprime OU erreur "menu present dans des commandes historiques" |
+
+---
+
+### 8.7 Gerer les categories
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin cree, modifie ou desactive une categorie |
+| **Acteur** | ADMIN |
+| **Synchronisation** | OU (create, update, desactivation) |
+| **Condition** | L'acteur possede la permission `categorie.manage`. Pour une desactivation : les produits et menus de la categorie sont desactives en cascade applicative (pas de FK CASCADE ici, logique PHP). |
+| **Operation** | GERER_CATEGORIE |
+| **Description** | CRUD sur l'entite `categorie`. La desactivation d'une categorie (`est_actif=0`) masque ses produits de la borne sans suppression physique. La suppression physique est bloquee si des produits ou menus y sont rattaches (ON DELETE RESTRICT). |
+| **Entites MCD** | W : `categorie` (INSERT / UPDATE / DELETE conditionnel) |
+| **Resultat** | Categorie creee / mise a jour / desactivee |
+
+---
+
+## 9. Domaine 7 - Gestion des utilisateurs et roles (admin)
+
+### 9.1 Creer un utilisateur
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de creation d'utilisateur |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `user.create`. L'email n'existe pas deja en base. Un role valide est selectionne. |
+| **Operation** | CREER_USER |
+| **Description** | Insertion de l'utilisateur avec hash du mot de passe (argon2id). L'email est unique. Le `role_id` est obligatoire (FK NOT NULL). |
+| **Entites MCD** | R : `role` (validation FK) - W : `user` (INSERT) |
+| **Resultat** | Utilisateur cree, peut se connecter au back-office |
+
+---
+
+### 9.2 Modifier un utilisateur
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin soumet le formulaire de modification |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `user.update`. L'utilisateur existe. Si le mot de passe est fourni, il est rehache. |
+| **Operation** | MODIFIER_USER |
+| **Description** | Mise a jour des champs modifiables (`nom`, `prenom`, `email`, `role_id`, `est_actif`). Si un nouveau mot de passe est saisi, il remplace le hash existant. |
+| **Entites MCD** | W : `user` (UPDATE) |
+| **Resultat** | Utilisateur mis a jour |
+
+---
+
+### 9.3 Desactiver un utilisateur
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin clique sur "Desactiver" pour un utilisateur |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `user.update`. L'admin ne peut pas se desactiver lui-meme (protection applicative). |
+| **Operation** | DESACTIVER_USER |
+| **Description** | Mise a jour de `est_actif=0`. La session active de l'utilisateur est invalidee au prochain acces (verification `est_actif` dans le middleware d'authentification). L'utilisateur n'est pas supprime, son historique reste tracable. |
+| **Entites MCD** | W : `user` (UPDATE est_actif=0) |
+| **Resultat** | Utilisateur desactive, acces back-office bloque |
+
+---
+
+### 9.4 Gerer la matrice role-permissions
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'admin modifie l'assignation des permissions pour un role |
+| **Acteur** | ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'acteur possede la permission `role.manage`. Les permissions selectionnees existent en base. |
+| **Operation** | GERER_MATRICE_RBAC |
+| **Description** | Mise a jour de la table `role_permission` pour un role donne : suppression des anciennes assignations et insertion des nouvelles (pattern delete-and-reinsert). Les permissions elles-memes sont statiques (declarees en migration, non modifiables via UI). |
+| **Entites MCD** | R : `role`, `permission` - W : `role_permission` (DELETE + INSERT) |
+| **Resultat** | Matrice RBAC mise a jour, prise en effet au prochain acces des utilisateurs portant ce role |
+
+---
+
+## 10. Domaine 8 - Authentification back-office
+
+### 10.1 Se connecter au back-office
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | Un acteur soumet le formulaire de connexion |
+| **Acteur** | ACCUEIL / CUISINE / ADMIN |
+| **Synchronisation** | Aucune |
+| **Condition** | L'email existe en base. Le mot de passe correspond au hash argon2id. L'utilisateur est actif (`est_actif=1`). |
+| **Operation** | AUTHENTIFIER_USER |
+| **Description** | Verification des identifiants. Si valides : regeneration de l'identifiant de session (protection contre la fixation de session), stockage du `user_id` et du `role_id` en session, mise a jour de `last_login_at`. Idle timeout : 4h. Absolute timeout : 10h. |
+| **Entites MCD** | R : `user` (verification), `role` (chargement permissions) - W : `user` (UPDATE last_login_at) |
+| **Resultat** | Session ouverte, redirection vers la vue correspondant au role |
+
+---
+
+### 10.2 Se deconnecter du back-office
+
+| Champ | Valeur |
+|-------|--------|
+| **Evenement declencheur** | L'acteur clique sur "Deconnexion" ou la session expire |
+| **Acteur** | ACCUEIL / CUISINE / ADMIN / SYS (expiration) |
+| **Synchronisation** | OU |
+| **Condition** | Une session valide est ouverte |
+| **Operation** | DECONNECTER_USER |
+| **Description** | Destruction de la session PHP (`session_destroy()`). La session est supprimee cote serveur. Le cookie de session est invalide. |
+| **Entites MCD** | Aucune ecriture en base (la gestion de session est en PHP natif, hors BDD pour MVP) |
+| **Resultat** | Session detruite, redirection vers la page de connexion |
+
+---
+
+## 11. Machine a etats de commande.statut
+
+Synthese des transitions couvertes par les operations du MCT.
+
+```
+ [CLIENT / ACCUEIL]
+ PASSER_COMMANDE
+ SAISIR_COMMANDE_MANUELLE
+ |
+ v
+ [ pending_payment ] (commande composee, paiement en attente)
+ |
+ [CLIENT / ACCUEIL] paiement confirme
+ (atomique dans PASSER_COMMANDE / SAISIR_COMMANDE_MANUELLE)
+ |
+ v
+ [ paid ]
+ |
+ [CUISINE] MARQUER_EN_PREPARATION
+ |
+ v
+ [ preparing ]
+ |
+ [CUISINE] MARQUER_PRETE
+ |
+ v
+ [ ready ]
+ |
+ [ACCUEIL] DECLARER_LIVREE
+ |
+ v
+ [ delivered ] (terminal, non annulable)
+
+
+ Depuis pending_payment / paid / preparing / ready :
+ [ACCUEIL ou ADMIN] ANNULER_COMMANDE
+ |
+ v
+ [ cancelled ] (terminal)
+```
+
+**Note sur la transition `pending_payment -> paid`** : dans le cadre RNCP, le paiement est
+remplace par la saisie du numero de commande par le client (borne) ou par la validation de
+l'equipier (comptoir/drive). La transition est atomique au sein des operations PASSER_COMMANDE
+et SAISIR_COMMANDE_MANUELLE. Le statut `pending_payment` est visible en base le temps de la
+transaction, et le statut final stocke est `paid`. Ce decoupage en deux etats reflete la
+semantique metier (le client compose SA commande, PUIS il paie) et preserve la capacite
+d'evolution vers un paiement reel sans migration destructive.
+
+---
+
+## 12. Tableau de synthese des operations
+
+| # | Operation | Domaine | Acteur | Entites W | Entites R |
+|---|-----------|---------|--------|-----------|-----------|
+| 1 | CHARGER_CATALOGUE | Commande kiosk | CLIENT | - | categorie, produit, menu, menu_produit |
+| 2 | COMPOSER_PANIER | Commande kiosk | CLIENT | - (volatile) | produit, menu, menu_produit |
+| 3 | PASSER_COMMANDE | Commande kiosk | CLIENT | commande, ligne_commande, commande_event | produit, menu |
+| 4 | AFFICHER_CONFIRMATION | Commande kiosk | SYS | - | - |
+| 5 | SAISIR_COMMANDE_MANUELLE | Commande comptoir/drive | ACCUEIL | commande, ligne_commande, commande_event | produit, menu, menu_produit |
+| 6 | LISTER_COMMANDES_A_PREPARER | Preparation | CUISINE | - | commande, ligne_commande |
+| 7 | MARQUER_EN_PREPARATION | Preparation | CUISINE | commande, commande_event | - |
+| 8 | MARQUER_PRETE | Preparation | CUISINE | commande, commande_event | - |
+| 9 | LISTER_COMMANDES_PRETES | Remise client | ACCUEIL | - | commande, ligne_commande |
+| 10 | DECLARER_LIVREE | Remise client | ACCUEIL | commande, commande_event | - |
+| 11 | ANNULER_COMMANDE | Annulation | ACCUEIL / ADMIN | commande, commande_event | - |
+| 12 | CREER_PRODUIT | Catalogue | ADMIN | produit | categorie |
+| 13 | MODIFIER_PRODUIT | Catalogue | ADMIN | produit | - |
+| 14 | SUPPRIMER_PRODUIT | Catalogue | ADMIN | produit | menu_produit |
+| 15 | CREER_MENU | Catalogue | ADMIN | menu, menu_produit | produit, categorie |
+| 16 | MODIFIER_MENU | Catalogue | ADMIN | menu, menu_produit | - |
+| 17 | SUPPRIMER_MENU | Catalogue | ADMIN | menu_produit, menu | ligne_commande |
+| 18 | GERER_CATEGORIE | Catalogue | ADMIN | categorie | produit, menu |
+| 19 | CREER_USER | RBAC | ADMIN | user | role |
+| 20 | MODIFIER_USER | RBAC | ADMIN | user | - |
+| 21 | DESACTIVER_USER | RBAC | ADMIN | user | - |
+| 22 | GERER_MATRICE_RBAC | RBAC | ADMIN | role_permission | role, permission |
+| 23 | AUTHENTIFIER_USER | Auth | ALL BACK | user | user, role |
+| 24 | DECONNECTER_USER | Auth | ALL BACK | - | - |
+
+**Total : 24 operations** couvrant la totalite du cycle de vie metier Wakdo.
+
+---
+
+## 13. Cross-validation MCT -> MCD (mantra #34)
+
+Verification que chaque entite du MCD participe a au moins une operation du MCT.
+
+| Entite MCD | Operations qui la lisent | Operations qui l'ecrivent | Couverture |
+|------------|--------------------------|--------------------------|------------|
+| `categorie` | 1, 12, 15, 18 | 18 | OK |
+| `produit` | 1, 2, 3, 5, 12, 14 | 12, 13, 14 | OK |
+| `menu` | 1, 2, 3, 5, 15, 17 | 15, 16, 17 | OK |
+| `menu_produit` | 1, 2, 5, 14 | 15, 16, 17 | OK |
+| `commande` | 6, 9 | 3, 5, 7, 8, 10, 11 | OK |
+| `ligne_commande` | 6, 9, 17 | 3, 5 | OK |
+| `commande_event` | - (lecture via SELECT historique non listee comme operation) | 3, 5, 7, 8, 10, 11 | OK |
+| `user` | 23 | 19, 20, 21, 23 | OK |
+| `role` | 19, 22, 23 | 22 | OK |
+| `permission` | 22 | - (statique, migration) | OK (*) |
+| `role_permission` | - | 22 | OK |
+
+(*) `permission` est en lecture seule via les operations MCT : ses valeurs sont declarees en
+migration SQL et ne sont pas modifiables via UI (RBAC statique cote permissions, dynamique
+cote roles). Cette decision est documentee dans le MCD section 4.3.
+
+**Conclusion** : 11/11 entites couvertes. Coherence MCT <-> MCD validee.
+
+---
+
+## 14. Points d'incoherence detectes et signalement
+
+Les points suivants necessite une attention ou une decision de l'auteur :
+
+### 14.1 Divergence `commande.statut` entre dictionnaire et PROJECT_CONTEXT - RESOLUE
+
+- **Machine canonique retenue** : `pending_payment -> paid -> preparing -> ready -> delivered` (transitions nominales) ; `cancelled` atteignable depuis tout etat non remis (`pending_payment`, `paid`, `preparing`, `ready`), pour couvrir modification, annulation et remboursement client.
+- **Arbitrage** : la regle metier confirmee impose deux phases successives : le client compose sa commande (statut `pending_payment`), puis il paie (statut `paid`). PROJECT_CONTEXT utilisait un terme `pending` simplifie qui ne refletait pas cette distinction. La machine canonique du dictionnaire est la source de verite. La transition `pending_payment -> paid` est atomique dans les operations PASSER_COMMANDE et SAISIR_COMMANDE_MANUELLE dans le cadre RNCP (substitut de paiement = saisie du numero). Ce point est considere comme clos.
+
+### 14.2 Absence d'acteur `user` lie a `commande` - RESOLUE (2026-05-28)
+
+**Decision actee** : pas de colonne `user_id` directe sur `commande`, mais une table d'audit dediee `commande_event` (cf. dictionnaire 3.7, MCD 4.2.bis). Pattern event sourcing simplifie. Chaque operation qui modifie `commande.statut` insere une ligne dans `commande_event` avec l'utilisateur a l'origine de la transition (NULL si auto-validation kiosk). Tracabilite complete sans denormalisation lourde sur `commande`.
+
+### 14.3 Colonne `source` absente de `commande` dans le dictionnaire - RESOLUE (2026-05-28)
+
+**Decision actee** : ajout d'une colonne `source ENUM('kiosk','comptoir','drive')` sur `commande`, en plus de `mode_consommation`. Les deux dimensions sont **distinctes** :
+- `source` = canal de saisie (kiosk / comptoir / drive) - input
+- `mode_consommation` = mode de consommation fiscal (sur_place / a_emporter / drive) - output
+
+Contrainte croisee : `source = drive` implique `mode_consommation = drive` (verifiee au MLT lors de la creation de commande). Pour `kiosk` et `comptoir`, les deux dimensions sont independantes. Documente dans le dictionnaire notes 8 et 9.
+
+### 14.4 Stats et `service_day`
+
+PROJECT_CONTEXT documente une logique `service_day` (section 2). Le MCT ne couvre pas
+l'agregation des stats (cron 04h30). Ce traitement est volontairement hors scope MCT (c'est
+un traitement technique automatise, pas un traitement metier interactif). Il sera documente
+dans le MLT (section cron).
diff --git a/docs/merise/mld.md b/docs/merise/mld.md
new file mode 100644
index 0000000..8e49cd4
--- /dev/null
+++ b/docs/merise/mld.md
@@ -0,0 +1,525 @@
+# Modele Logique des Donnees (MLD) - Wakdo
+
+**Phase Merise** : P1 - Conception, etape 5 (apres MCD, MCT, MLT)
+**Statut** : v0.1
+**Date** : 2026-05-28
+**Branche** : `feat/p1-conception`
+**Auteur methodologie** : BYAN
+
+---
+
+## 1. Objet du document
+
+Le MLD transcrit le MCD en schema relationnel formel : 1 entite -> 1 table, chaque association traduite selon sa cardinalite, contraintes referentielles materialisees, index dimensionnes pour les acces frequents.
+
+C'est l'etape qui transforme la modelisation conceptuelle en specification implementable. Le DDL SQL (`db/migrations/0001_init_schema.sql`) sera derive directement de ce document a P2.
+
+**Source** : `dictionary.md` (types et contraintes par attribut), `mcd.md` (entites + cardinalites + decisions reportees), `mct.md` (operations + entites manipulees), `mlt.md` (regles de gestion + transitions + protection concurrence).
+
+**Cibles** :
+- MariaDB 11.4 LTS (cf. `docker-compose.yml` service `wakdo-db`)
+- Engine InnoDB (ACID, FKs, row-level locking, CHECK depuis 10.2.1)
+- Charset `utf8mb4` collation `utf8mb4_unicode_ci`
+
+---
+
+## 2. Conventions de notation
+
+### Notation relationnelle
+
+```
+TABLE_NAME (col1, col2, #col_fk, [col_optionnelle])
+
+ PK : col1
+ UK : col2
+ FK : col_fk -> AUTRE_TABLE(id)
+```
+
+| Symbole | Signification |
+|---|---|
+| `col` | Colonne NOT NULL |
+| `[col]` | Colonne nullable |
+| `#col` | Colonne FK (sans le diese : non-FK) |
+
+Cette notation reste proche de l'usage Merise francais (UNIRIS, ouvrages Nanci/Espinasse) : la cle primaire est soulignee dans les documents classiques, ici on prefixe par `PK` pour la portabilite ASCII.
+
+### Types
+
+Les types SQL exacts sont definis dans `dictionary.md` section 2 (Conventions generales) et reprecises dans chaque section de cette MLD. Conventions retenues :
+
+- `INT UNSIGNED AUTO_INCREMENT` pour toutes les PK techniques
+- `INT UNSIGNED` pour tous les montants en centimes (anti-FLOAT cf. dictionary note 1)
+- `VARCHAR(