release: dev -> main v0.2.0 #93

Merged
Corentin merged 96 commits from dev into main 2026-06-23 10:09:58 +02:00
8 changed files with 225 additions and 0 deletions
Showing only changes of commit 32f9baacce - Show all commits

18
docs/domaines/README.md Normal file
View file

@ -0,0 +1,18 @@
# Documentation par domaine
Une fiche par domaine fonctionnel livre : **perimetre**, **ce qui est livre** (code +
routes), **regles metier** (RG-T* de `docs/merise/mlt.md`), **decisions** (renvoi
`docs/adr/`), **tables**. Vue d'ensemble : `docs/ARCHITECTURE.md`.
**Auteur : BYAN** (formalisation ; arbitrage et validation par l'auteur).
| Domaine | Fiche | Statut |
|---|---|---|
| Authentification & sessions | [auth.md](auth.md) | Livre (P2) |
| Catalogue (categories, produits, menus) | [catalogue.md](catalogue.md) | Livre (P3) |
| Stock & recettes (ingredients) | [stock-recettes.md](stock-recettes.md) | Livre (P3) |
| Comptes utilisateurs | [users.md](users.md) | Livre (P3) |
| RBAC (roles & permissions) | [rbac.md](rbac.md) | Livre (P3) |
| Statistiques | [stats.md](stats.md) | Livre (P3, KPIs vente differes P4) |
| Borne (kiosk) | [borne.md](borne.md) | Front P5 (API au swap P4) |
| Commande | — | P4 (schema pret, workflow a venir) |

29
docs/domaines/auth.md Normal file
View file

@ -0,0 +1,29 @@
# Domaine — Authentification & sessions
## Perimetre
Connexion back-office, deconnexion, reinitialisation de mot de passe, garde de session,
PIN d'action sensible. Pas d'auth cote borne (front public).
## Ce qui est livre
- `App\Auth\AuthService` (login 12.1 / logout 12.2), `PasswordResetService` (12.3).
- `SessionManager` (seul a toucher `$_SESSION`/cookie, mode test memoire), `SessionGuard`
(RG-6/RG-T02 : idle 4h, absolu 10h, `is_active`), `Csrf` (jeton synchroniseur).
- `PasswordHasher` (argon2id + leurre de timing), `PinVerifier`, `PinThrottle`,
`ThrottlePolicy` (backoff degressif).
- Controleurs `AuthController`, `PasswordResetController`, `ProfileController` (set-PIN
self-service), `MeController` (`/api/me`).
## Regles metier
- RG-6 / RG-T02 : session valide (idle + absolu + compte actif) sinon 302 `/login`.
- RG-8 / RG-9 : throttle login par compte + par IP (`login_throttle`), backoff degressif.
- RG-T13 : PIN d'action sensible (voir [users](users.md), [rbac](rbac.md), stock).
- Anti-enumeration : reponses neutres (reset, login) ; leurre de timing argon2id.
## Decisions
[ADR-0001](../adr/0001-php-from-scratch-sans-composer.md) (from scratch),
[ADR-0004](../adr/0004-pin-action-sensible-audit.md) (PIN),
[ADR-0005](../adr/0005-throttle-pin-separe-du-login.md) (throttle PIN).
## Tables
`user`, `login_throttle`, `pin_throttle`, `audit_log` (login + pin.failed). Detail :
`docs/merise/mlt.md` section 12 + 22.

31
docs/domaines/borne.md Normal file
View file

@ -0,0 +1,31 @@
# Domaine — Borne (kiosk)
## Perimetre
Front client tactile (Bloc 1) : parcours welcome -> categories -> produit -> panier ->
confirmation. HTML/CSS/JS vanilla, servi en statique par Apache.
## Ce qui est livre
- Pages : `index`, `categories`, `products`, `product`, `cart`, `payment`,
`confirmation` (`src/public/borne/`).
- JS modules ES6 (`assets/js/`) : `data.js` (chargement, point de swap P4), `state.js`
(panier), `page-*.js`, `nav.js`, et `allergens.js` (modale generale 14 INCO sur carte
et fiche).
- Donnees : JSON statiques (`data/`) en P5 ; basculent sur `/api/*` DB-backed au swap P4.
## Regles metier / conventions
- Allergenes : info **generale** (les 14 INCO, reglement UE 1169/2011), pas un calcul
par produit (mapping `ingredient_allergen` differe).
- CSP-safe pour le code projet : pas de script inline ajoute (donnees via `data-*`,
`addEventListener`). Source allergenes = liste fixe `data/allergens.json`, se branchera
sur `/api/allergens` au swap P4.
## Tests
Harnais front `node:test` + jsdom (`tests/js/allergens.test.js`) : 14 INCO, bouton "i",
ouverture/fermeture (bouton/overlay/Echap), idempotence. Job CI `js-tests` (Node 20).
## Decisions
Swap point P5 -> API au P4 (cf. `data.js` + journaux). Modele = app self-hostable
([ADR-0009](../adr/0009-compose-standalone-et-prod-gitignore.md)).
## Tables (au swap P4)
`category`, `product`, `menu` + `allergen` (lecture). Aujourd'hui : JSON statiques.

View file

@ -0,0 +1,29 @@
# Domaine — Catalogue (categories, produits, menus)
## Perimetre
CRUD des categories, produits et menus composes (borne de base + slots). Base du
catalogue consomme par la borne.
## Ce qui est livre
- Repositories : `CategoryRepository`, `ProductRepository`, `MenuRepository`.
- Controleurs : `CategoryController` (`category.manage`), `ProductController`
(`product.read/create/update/delete`), `MenuController` (`menu.read/create/update/delete`).
- Menus composes : burger de base + `menu_slot` / `menu_slot_option`, editeur slots en
JS vanilla CSP-safe (champ cache `slots_json`), reecriture delete-and-reinsert en tx.
## Regles metier
- RG-T16 (allowlist colonnes), RG-T18 (validation serveur bornee : prix > 0, TVA dans
{55,100}, etc.), RG-T15 (sorties echappees).
- Produit : PIN equipier + audit UNIQUEMENT si prix ou TVA change (mlt 8.2 RG-4) ;
suppression = PIN + audit (8.3). Menu : suppression = PIN + audit (8.6).
- Pas de suppression dure si reference (FK RESTRICT depuis order_item / menu / selection)
-> 409, alternative = desactivation (`is_available`).
## Decisions
[ADR-0002](../adr/0002-back-office-mvc-rendu-serveur.md) (MVC serveur),
[ADR-0006](../adr/0006-http-409-conflit-422-validation.md) (409/422),
[ADR-0004](../adr/0004-pin-action-sensible-audit.md) (PIN + audit).
## Tables
`category`, `product`, `menu`, `menu_slot`, `menu_slot_option`. Detail :
`docs/merise/mlt.md` section 8.

30
docs/domaines/rbac.md Normal file
View file

@ -0,0 +1,30 @@
# Domaine — RBAC (roles & permissions)
## Perimetre
Gestion des roles et de la matrice role/permission (mlt 10.4 MANAGE_RBAC), permission
`role.manage`. Catalogue de permissions fige au seed (lecture seule).
## Ce qui est livre
- `RoleRepository` (App\Auth) : roles (CRUD, code immuable), permissions (lecture),
matrice (`permissionIdsFor`/`permissionCodesFor`, `setPermissions` tx +
`replacePermissions` raw), `role_visible_source` (`setVisibleSources` / raw).
- `RoleController` (`role.manage`) : index, create/store (role custom RG-4), edit/update
(champs role + matrice + sources visibles en UNE transaction). Vues `admin/roles/{index,form}`.
- Matrice soumise en champs **scalaires** (`perm_<id>`, `source_<enum>`) : `Request::formBody`
ne garde que les scalaires (pas de `name[]`, pas de JS).
## Regles metier
- RG-6 (mlt 10.4) : PIN equipier + `audit_log` (`role.manage`) dans une transaction ;
`details` JSON = **diff** des codes de permission (ajoutes/retires), calcule avant la
reecriture delete-and-reinsert.
- `Authorizer::can` recharge les permissions a chaque verification (effet immediat).
- Garde-fous anti-lockout : le role `admin` conserve `role.manage` ET reste actif ;
`code` immuable apres creation ; `order_source` borne a l'ENUM ; code dupli -> 409.
## Decisions
[ADR-0004](../adr/0004-pin-action-sensible-audit.md) (PIN + audit),
[ADR-0006](../adr/0006-http-409-conflit-422-validation.md) (409).
## Tables
`role`, `permission`, `role_permission`, `role_visible_source`, `audit_log`. Detail :
`docs/merise/mlt.md` section 10.4.

26
docs/domaines/stats.md Normal file
View file

@ -0,0 +1,26 @@
# Domaine — Statistiques
## Perimetre
Tableau de bord de pilotage (mlt domaine 11), permission `stats.read`. Landing par
defaut du role manager.
## Ce qui est livre
- `StatsRepository` : `counts()` (compteurs catalogue : produits/menus/categories/
ingredients, total + actifs/disponibles), `stockHealth()` (repartition des ingredients
actifs par bande RG-T21 + liste d'alerte triee du plus critique).
- `StatsController` (`stats.read`) -> `/admin/stats` + vue `admin/stats/index` (cartes
KPI + table d'alerte stock) + lien nav "Pilotage".
## Regles metier / perimetre
- KPIs sur les **donnees disponibles** en P3 : sante catalogue + stock. **Ferme le 404**
du landing manager (`role.default_route = /admin/stats`).
- KPIs de **vente** (CA, volumes, `service_day`) = **P4** : ils dependent du domaine
commande (encore en schema seul).
- Sante stock = reutilise `IngredientRepository::stockBand` (source unique RG-T21).
## Decisions
[ADR-0003](../adr/0003-stock-pourcentage-dispo-calculee.md) (bandes RG-T21).
## Tables
Lecture seule : `product`, `menu`, `category`, `ingredient` (compteurs + bandes).
KPIs vente (P4) : `customer_order`, `order_item`. Detail : `docs/merise/mlt.md` section 11.

View file

@ -0,0 +1,31 @@
# Domaine — Stock & recettes (ingredients)
## Perimetre
Gestion des ingredients, du stock (reappro + inventaire), des mouvements de stock, et de
la composition des produits (recettes). Sous-tend la disponibilite produit calculee.
## Ce qui est livre
- `IngredientRepository` : CRUD, stock %/bande calcules, `restock` (tx), `inventoryCount`
(tx, ecrit une ligne meme a delta=0, RG-3), `movements` (borne), `isReferenced`.
- `IngredientController` : CRUD (`ingredient.manage`, sans PIN), RESTOCK (`stock.manage`,
sans PIN), INVENTORY_COUNT (`stock.count` + PIN), mouvements (`stock.read`).
- `ProductRepository` : composition (`product_ingredient`), `setComposition`
(delete-and-reinsert tx), `isOrderable` (RG-T21), `autoUnavailableIds`.
- Editeur de recette (`ProductController::recipeForm/saveRecipe`, `ingredient.manage`).
## Regles metier
- RG-T13 : INVENTORY_COUNT seule action sensible du stock (PIN equipier) ; succes ->
`stock_movement.user_id`, **sans** `audit_log` (RG-T14 : le mouvement EST la trace).
RESTOCK et CRUD ingredient ne sont PAS sensibles.
- RG-T22 : echec PIN inventaire -> `pin.failed` + throttle dans une transaction.
- RG-T21 : disponibilite produit calculee (cf. [ADR-0003](../adr/0003-stock-pourcentage-dispo-calculee.md)).
- FK : `product_ingredient`/`stock_movement` RESTRICT sur l'ingredient (hard-delete -> 409) ;
`product_ingredient.product_id` CASCADE (trace du nombre de lignes a la suppression, dette #27).
## Decisions
[ADR-0003](../adr/0003-stock-pourcentage-dispo-calculee.md) (stock % + RG-T21),
[ADR-0004](../adr/0004-pin-action-sensible-audit.md) / RG-T14 (attribution sans double-journal).
## Tables
`ingredient`, `product_ingredient`, `stock_movement`, `allergen`, `ingredient_allergen`
(mapping differe). Detail : `docs/merise/mlt.md` sections 8.8 + 9.

31
docs/domaines/users.md Normal file
View file

@ -0,0 +1,31 @@
# Domaine — Comptes utilisateurs
## Perimetre
Gestion des comptes back-office (mlt domaine 10.1-10.3 + 10.5) : creation, edition,
desactivation, reinitialisation de PIN, effacement RGPD.
## Ce qui est livre
- `UserRepository` (App\Auth) : all (JOIN role) / find / emailExists / activeRoleExists /
create / update (allowlist) / setPasswordHash / clearPin / deactivate / anonymise /
activeAdminCount / isAdmin.
- `UserController` : index (`user.read`), create/store (`user.create`), edit/update
(`user.update`), deactivate (`user.deactivate`), reset-pin, erase-PII. Vues
`admin/users/{index,form,confirm}`.
## Regles metier
- RG-T13/14 : **toutes** les mutations sont sensibles -> PIN equipier + `audit_log`
(`user.create/update/deactivate/erase_pii`) dans la meme transaction ; `details` JSON =
noms de champs / role (pas de PII). Throttle RG-T22.
- RG-T16 : allowlist (email/prenom/nom/role_id/is_active) ; `is_active` pose serveur a
la creation. Unicite email -> 409.
- Self-protection : pas d'auto-desactivation (403 SELF_DEACTIVATION) ; on ne retire pas
le statut du **dernier admin actif** (update/deactivate/erase) ; effacement deja fait -> 409.
## Decisions
[ADR-0004](../adr/0004-pin-action-sensible-audit.md) (PIN + audit),
[ADR-0007](../adr/0007-rgpd-anonymisation-tombstone.md) (anonymisation RGPD),
[ADR-0006](../adr/0006-http-409-conflit-422-validation.md) (409/422).
## Tables
`user` (+ `anonymized_at` pour RGPD), `audit_log`, `role` (FK). Detail :
`docs/merise/mlt.md` section 10.1-10.5.