From 6f2aedc699e4975b428cfe8a4dbf89d1533ebcf2 Mon Sep 17 00:00:00 2001 From: Corentin JOGUET Date: Wed, 24 Jun 2026 12:37:54 +0200 Subject: [PATCH] chore(borne): bascule allergenes sur /api/allergens + menage donnees/docs (#103) --- docs/api/conventions.md | 25 +++-- docs/design/maquette-vs-build.md | 49 ++++++---- src/app/Catalogue/AllergenRepository.php | 7 +- src/app/Controllers/CatalogueController.php | 9 +- src/public/borne/assets/js/allergens.js | 6 +- src/public/borne/assets/js/data.js | 28 +++--- src/public/borne/categories.html | 15 ++- src/public/borne/data/README.md | 28 ++---- src/public/borne/data/allergens.json | 16 ---- src/public/borne/data/categories.json | 11 --- src/public/borne/data/produits.json | 86 ----------------- src/public/borne/products.html | 4 +- tests/Integration/AllergenReadDbTest.php | 9 +- tests/Support/FakeCatalogueDatabase.php | 12 +++ .../Catalogue/CatalogueControllerTest.php | 26 +++++ tests/js/allergens.test.js | 96 +++++++++++-------- 16 files changed, 185 insertions(+), 242 deletions(-) delete mode 100644 src/public/borne/data/allergens.json delete mode 100644 src/public/borne/data/categories.json delete mode 100644 src/public/borne/data/produits.json diff --git a/docs/api/conventions.md b/docs/api/conventions.md index 3f0ad61..dddf2d7 100644 --- a/docs/api/conventions.md +++ b/docs/api/conventions.md @@ -51,10 +51,9 @@ Code de reference : routes dans `src/public/admin/index.php`, controleurs dans | Famille | Prefixe | Rendu | Authentification | Exemple | |---|---|---|---|---| | Pages back-office | aucun | HTML (vue serveur + `layout.php`) | session admin | `/login`, `/forgot_password` | -| API REST | `/api/` | JSON (enveloppe section 7) | selon la ressource (section 10) | `/api/health`, `/api/categories` (prevu) | +| API REST | `/api/` | JSON (enveloppe section 7) | selon la ressource (section 10) | `/api/health`, `/api/categories` (livre) | -La borne (kiosk) consommera l'API REST `/api/*` (P4). En attendant, elle lit un repli JSON -statique sous `src/public/borne/data/` (voir section 8.3). +La borne (kiosk) consomme l'API REST `/api/*` en lecture pour le catalogue (voir section 8.3). --- @@ -107,7 +106,7 @@ is_active) et d'`Authorizer` (RG-T03, permissions rechargees depuis la base). Re si la session est absente, expiree ou le compte desactive. Les autorisations par operation (et le PIN des actions sensibles, RG-T13) se cablent quand les operations existent (P3). -### 5.2 API kiosk - lecture catalogue + commande (prevu P4, public) +### 5.2 API kiosk - lecture catalogue + commande (livre, public) La borne est publique (aucune session) ; cf. `mlt.md` CREATE_ORDER, declencheur kiosk. @@ -270,18 +269,16 @@ Codes specifiques nommes par le MLT, en surcharge du socle : `CANNOT_CANCEL_IN_S `INVALID_TRANSITION` (409) pour l'annulation (`mlt.md` 7.1, `security-sequence.md`). Meme format d'enveloppe. -### 8.3 Divergence connue : repli JSON de la borne +### 8.3 Nommage borne vs canonique : le rapprochement dans data.js -Le repli statique de la borne (`src/public/borne/data/categories.json`, `produits.json`) provient -des sources de l'ecole et porte un nommage different et heterogene (`title`/`nom`, `prix`, `image`, -`type`). Ce contrat est fige par le brief ecole et consomme tel quel par le JS de la borne via -`data.js`. +Le front de la borne attend un nommage historique heterogene issu des sources de l'ecole +(`title`/`nom`, `prix`, `image`, `type`). L'API sert la forme canonique de 8.1 +(`/api/categories`, `/api/products`, `/api/menus`, `/api/allergens`). Le rapprochement se fait +en un point unique : la couche `data.js`, qui deballe l'enveloppe `{ data }` et mappe la forme +canonique vers ce que la borne attend. Les anciens fichiers JSON statiques sous +`src/public/borne/data/` ont ete retires. -La convention canonique reste celle de 8.1. Le rapprochement se fait en un point unique : la couche -`data.js` (bascule prevue en P4). Quand l'API exposera `/api/categories` et `/api/products`, elle -servira la forme canonique ; `data.js` mappera vers ce que la borne attend. - -| Repli borne | Canonique API / dictionnaire | +| Forme borne | Canonique API / dictionnaire | |---|---| | `title` (categorie) | `name` | | `nom` (produit) | `name` | diff --git a/docs/design/maquette-vs-build.md b/docs/design/maquette-vs-build.md index 26d642e..71e3aa5 100644 --- a/docs/design/maquette-vs-build.md +++ b/docs/design/maquette-vs-build.md @@ -23,9 +23,12 @@ Accueil -> Remerciement ``` -Le kiosk construit, lui, eclate cet ecran unique en **pages distinctes** et n'a -pas de panneau de commande persistant. C'est l'origine du sentiment "ca ne -correspond pas". +Le kiosk construit a desormais rejoint ce paradigme : l'ecran de commande +(`products.html`) porte un **panneau de commande persistant** a droite, les options +produit et le composeur de menu s'ouvrent **en modale** par-dessus la grille, et le +**chevalet** (saisie du numero de table) s'ouvre en modale au paiement sur place. Les +pages intermediaires `product.html` et `cart.html` du premier jet ont ete retirees. +Cette note garde la trace de la decomposition maquette -> code et des ecarts resorbes. ## 2. Decomposition ecran par ecran @@ -87,25 +90,29 @@ correspond pas". | Maquette | Kiosk construit | Verdict | |----------|-----------------|---------| | 1. Accueil sur place / a emporter | `index.html` | conforme | -| 2 + 6. Ecran de commande unique (bandeau + grille + **panneau persistant**) | eclate en `categories.html` -> `products.html` -> `cart.html` | divergence structurante : multi-pages, et **pas de panneau de commande persistant** | +| 2 + 6. Ecran de commande unique (bandeau + grille + **panneau persistant**) | `products.html` : bandeau categories (`category-strip.js`) + grille + **panneau de commande persistant** a droite (`order-panel.js`) | conforme | | (pas de page categories separee) | `categories.html` plein ecran "Que souhaitez-vous commander ?" | ecran **ajoute** (la maquette met les categories en bandeau) | -| 3-5. Composeur menu = **assistant modal en etapes** | `page-product-menu.js` = composition **libre** | divergence (le refactor "consommer les slots /api/menus" est deja en file P4) | -| 8. Modale d'option produit (taille + quantite) | `product.html` (page) | divergence : page au lieu de modale | -| 9. Ecran **chevalet** dedie (saisie numero) | numero gere par l'API (chunk 1a), affiche en confirmation | manquant cote ecran | +| 3-5. Composeur menu = **assistant modal en etapes** | `page-product-menu.js` : composeur **modal pilote par les slots** de `/api/menus/{id}` (format Maxi puis 1 etape par slot) | conforme | +| 8. Modale d'option produit (taille + quantite) | `product-options.js` : **modale** d'options (taille R4 + stepper de quantite) au-dessus de la grille | conforme | +| 9. Ecran **chevalet** dedie (saisie numero) | **modale chevalet** au paiement sur place (`page-payment.js`), numero pose via l'API ; rappele en confirmation | conforme | | (aucun ecran de paiement) | `payment.html` "Carte bancaire / Especes" | ecran **ajoute** par le build | | 10. Remerciement | `confirmation.html` | conforme | -## 4. Ecarts structurants (le fond du sujet) +## 4. Ecarts structurants (resorbes) -1. **Paradigme inverse.** Maquette = **mono-ecran** (un plan de commande avec - categories en bandeau et un panneau recapitulatif persistant a droite, modales - par-dessus). Build = **multi-pages** classiques (categories -> produits -> - produit -> panier). C'est l'ecart structurant principal. -2. **Panneau de commande lateral absent.** La piece centrale de la maquette - (numero de commande, lignes editables avec corbeille, TOTAL ttc, Abandon / - Payer, visible en permanence) n'est pas presente dans le build. -3. **Composition de menu.** Maquette = assistant modal en etapes ; build = - composition libre cote client (`page-product-menu.js`). +Les ecarts structurants du premier jet ont ete realignes sur la maquette : + +1. **Paradigme.** L'ecran de commande (`products.html`) suit le plan mono-ecran de + la maquette : categories en bandeau (`category-strip.js`), grille produits, et + panneau recapitulatif persistant a droite ; les options et le composeur de menu + s'ouvrent en modale par-dessus. Les pages `product.html` et `cart.html` du + premier jet ont ete retirees. +2. **Panneau de commande lateral.** La piece centrale de la maquette (numero de + commande, lignes editables avec quantite et retrait, TOTAL ttc, Abandon / Payer) + est rendue par `order-panel.js`, visible en permanence sur l'ecran de commande. +3. **Composition de menu.** Le composeur (`page-product-menu.js`) est un assistant + modal en etapes pilote par les slots de `/api/menus/{id}` (format Maxi puis une + etape par slot), conforme a l'enchainement de la maquette. ## 5. Rebrand McDonald's -> Wakdo @@ -116,6 +123,8 @@ note n'est donc pas le rebrand mais la **structure** des ecrans. ## 6. Suite -Re-alignement du kiosk sur la maquette (panneau persistant + bandeau categories + -composeur en modale) = chantier UI conduit via un cycle FD dedie. Backlog des -divergences = section 3 ci-dessus. +Le re-alignement du kiosk sur la maquette (panneau persistant + bandeau categories ++ composeur en modale + chevalet en modale) est livre. La borne lit le catalogue +via l'API REST (`/api/categories|products|menus|allergens`). Reste a faire : la +generation dynamique de l'ecran categories depuis `GET /api/categories` (section 3, +ecran categories) et le polissage visuel du rebrand Wakdo. diff --git a/src/app/Catalogue/AllergenRepository.php b/src/app/Catalogue/AllergenRepository.php index 7b916dd..4419162 100644 --- a/src/app/Catalogue/AllergenRepository.php +++ b/src/app/Catalogue/AllergenRepository.php @@ -9,8 +9,9 @@ use App\Core\DatabaseInterface; /** * Lecture des allergenes a declaration obligatoire (INCO) : info GENERALE (les 14 * categories), pas un calcul par produit (le mapping ingredient_allergen reste - * differe). Sert l'endpoint public anonyme /api/allergens. Le schema ne porte que - * code + name ; les descriptions riches restent cote borne (data/allergens.json). + * differe). Sert l'endpoint public anonyme /api/allergens. Le schema porte + * code + name + description ; la description (texte INCO seede) est exposee par + * l'API et consommee par la borne via /api/allergens. * * Non `final` : seam de test (sous-classe -> double sans base). */ @@ -27,6 +28,6 @@ class AllergenRepository */ public function all(): array { - return $this->db->fetchAll('SELECT id, code, name FROM allergen ORDER BY id'); + return $this->db->fetchAll('SELECT id, code, name, description FROM allergen ORDER BY id'); } } diff --git a/src/app/Controllers/CatalogueController.php b/src/app/Controllers/CatalogueController.php index 746e791..e7021ca 100644 --- a/src/app/Controllers/CatalogueController.php +++ b/src/app/Controllers/CatalogueController.php @@ -187,14 +187,15 @@ class CatalogueController extends Controller /** * @param array $row - * @return array{id: int, code: string, name: string} + * @return array{id: int, code: string, name: string, description: ?string} */ private function presentAllergen(array $row): array { return [ - 'id' => (int) ($row['id'] ?? 0), - 'code' => (string) ($row['code'] ?? ''), - 'name' => (string) ($row['name'] ?? ''), + 'id' => (int) ($row['id'] ?? 0), + 'code' => (string) ($row['code'] ?? ''), + 'name' => (string) ($row['name'] ?? ''), + 'description' => $this->nullableString($row['description'] ?? null), ]; } diff --git a/src/public/borne/assets/js/allergens.js b/src/public/borne/assets/js/allergens.js index 62f1244..43c4eb1 100644 --- a/src/public/borne/assets/js/allergens.js +++ b/src/public/borne/assets/js/allergens.js @@ -7,9 +7,9 @@ * * CSP 'self' : aucun script inline, aucun handler inline. Le DOM est construit par * l'API (createElement/textContent) ; textContent neutralise toute injection. - * Les donnees viennent de data.js (loadAllergens) : liste fixe en P5, /api/allergens - * au swap P4. openAllergenModal prend la liste en parametre pour rester independant - * de la couche de chargement (et testable sans fetch). + * Les donnees viennent de data.js (loadAllergens), qui lit /api/allergens. + * openAllergenModal prend la liste en parametre pour rester independant de la + * couche de chargement (et testable sans fetch). */ const OVERLAY_CLASS = 'allergen-modal-overlay'; diff --git a/src/public/borne/assets/js/data.js b/src/public/borne/assets/js/data.js index bf13b4d..175814c 100644 --- a/src/public/borne/assets/js/data.js +++ b/src/public/borne/assets/js/data.js @@ -10,18 +10,17 @@ * indexe par slug de categorie ; menus glisses sous la cle 'menus'). Les signatures * publiques et les formes de retour sont inchangees -> les pages n'ont pas bouge. * - * Les allergenes restent un repli statique (data/allergens.json) : leur bascule - * sur /api/allergens est un chunk ulterieur. + * Les allergenes sont desormais lus depuis /api/allergens (id/code/name/description), + * comme les autres collections catalogue : le repli statique a ete retire. */ const CATEGORIES_URL = '/api/categories'; const PRODUCTS_URL = '/api/products'; const MENUS_URL = '/api/menus'; -/* Liste fixe des 14 allergenes INCO (info generale, modale borne). L'endpoint - * /api/allergens existe desormais (id/code/name), mais la borne garde ce JSON - * statique : il porte les DESCRIPTIONS riches, absentes du schema allergen. Bascule - * possible si les descriptions sont ajoutees cote API. */ -const ALLERGENS_URL = 'data/allergens.json'; +/* Les 14 allergenes INCO (info generale, modale borne). L'endpoint /api/allergens + * porte id/code/name/description (la description INCO est seede en base) -> la borne + * la consomme via l'API, comme les autres collections catalogue. */ +const ALLERGENS_URL = '/api/allergens'; /* Memoisation par PROMESSE (pas par resultat) : N appelants concurrents au meme * chargement partagent UNE seule requete reseau (evite les fetch /api/* redondants @@ -148,17 +147,16 @@ export async function loadMenu(id) { } /** - * Fetches and caches the 14 INCO allergens (general info modal). Repli statique : - * la reponse est un tableau nu (pas d'enveloppe), conserve tel quel. - * @returns {Promise} + * Fetches and caches the 14 INCO allergens (general info modal). Consomme + * /api/allergens (enveloppe { data }, forme canonique id/code/name/description) et + * ramene chaque entree a la forme borne { id, name, description } attendue par la + * modale (allergens.js) ; le champ `code` n'est pas utilise cote borne. + * @returns {Promise>} */ export function loadAllergens() { if (!_allergensPromise) { - _allergensPromise = fetch(ALLERGENS_URL) - .then(res => { - if (!res.ok) throw new Error(`Failed to load allergens: HTTP ${res.status}`); - return res.json(); - }) + _allergensPromise = fetchCollection(ALLERGENS_URL) + .then(rows => rows.map(a => ({ id: a.id, name: a.name, description: a.description ?? null }))) .catch(e => { _allergensPromise = null; throw e; }); } return _allergensPromise; diff --git a/src/public/borne/categories.html b/src/public/borne/categories.html index e7bea2e..61ffe61 100644 --- a/src/public/borne/categories.html +++ b/src/public/borne/categories.html @@ -13,11 +13,11 @@