corentin_wakdo/docs/journal/2026-06-25--audit-remediation-et-features-94-105.md

13 KiB

2026-06-25 — Synthese : CD prod, SMTP reel, durcissement borne, POS comptoir/drive, dashboard stock (#94-#105)

Auteur : BYAN. Retrospective de synthese couvrant douze PR mergees apres la session du 2026-06-18 (front login + amorce P4 commande). Elles se regroupent en quatre fils : mise en production reelle (#94-#97), finition du parcours borne client (#98, #99, #101, #102, #103), et refonte de la saisie comptoir/drive et de la page stock cote back-office (#100, #104, #105). Entree descriptive : on decrit ce qui est livre, pas ce qui est promis.


Ce qui a ete livre (PR mergees)

PR Bloc Objet
#94 CD Deploiement push-based vers Vision (prod) + preuve de version dans GET /api/health
#95 CD Modeles versionnes docker-compose.prod.yml.example + .env.prod.example
#96 Auth Envoi reel de l'email de reset via relais SMTP (Brevo) — client SMTP maison
#97 CD Passage des variables SMTP/MAIL au conteneur wakdo-app (correctif #96)
#98 Borne Menu Maxi agrandit la boisson en 50cl + transport du format choisi
#99 Borne Produit/menu en rupture de stock rendu non commandable (RG-T21)
#100 Back-office Refonte saisie comptoir/drive : prix, verrou du mode, navigation, file
#101 Borne Panier unique = panneau persistant (retrait de cart.html et product.html)
#102 Borne Confirmation avant l'abandon de la commande
#103 Borne Bascule des allergenes sur /api/allergens + menage des donnees/docs statiques
#104 Back-office Saisie comptoir/drive en POS tactile a tuiles (refonte de #100)
#105 Back-office Page Stock en tableau de bord (alertes + reapprovisionnement en avant)

Bloc 1 — Mise en production reelle (#94, #95, #96, #97)

Ce qui a ete fait

  • CD push-based (#94) : .forgejo/workflows/deploy.yml ouvre, sur push main, une session SSH vers Vision (l'hote de prod). scripts/deploy.sh y recupere main en fast-forward, ecrit un marqueur de version (src/VERSION : SHA + date), journalise une ligne dans deploy.log, puis reconstruit et recree la stack. GET /api/health expose desormais version et deployed_at, lus depuis ce marqueur : c'est la preuve cote app qu'un deploiement a bien repris le dernier commit. Doc : docs/architecture/deployment.md.
  • Modeles de prod versionnes (#95) : docker-compose.prod.yml.example et .env.prod.example entrent au depot comme gabarits. Le fichier reel reste gitignore (specifique a l'hote : Traefik, reseau externe), conformement a ADR-0009 ; le .example documente la forme attendue.
  • SMTP reel (#96, #97) : la reinitialisation de mot de passe envoyait jusque-la un mail inerte. Un client SMTP maison (SmtpClient, SmtpMailer, transport via flux PHP StreamSmtpTransport derriere l'interface SmtpTransport) parle a un relais reel (Brevo en l'occurrence). PasswordResetController s'y branche. #97 corrige un oubli : les variables SMTP/MAIL n'etaient pas transmises au conteneur wakdo-app (declarees dans les deux fichiers compose).

Pourquoi — decisions et alternatives

  • Decision : CD par SSH, pas par Docker-in-CI. Le runner Forgejo (sur Stark) n'a pas acces au socket Docker, par choix de securite : un job CI ne pilote pas Docker sur son hote. Le deploiement vers Vision se fait donc par SSH avec une forced command cote serveur. Alternative ecartee : donner le socket Docker au runner — rejetee pour la surface d'attaque. C'est le prolongement de la decision E2E-CI du 2026-06-18 (meme contrainte de socket).
  • Decision : client SMTP maison plutot qu'une bibliotheque. ADR-0001 fige le projet sans Composer ni dependance tierce ; un client SMTP minimal (EHLO/AUTH/MAIL/RCPT/DATA sur flux) reste coherent avec cette contrainte et reste testable via un faux transport (FakeSmtpTransport). Alternative ecartee : mail() de PHP — sans relais authentifie, la delivrabilite est aleatoire et la configuration sort du depot.
  • Decision : marqueur de version dans src/VERSION lu a chaud. Le marqueur est sous le mount du code (./src -> /var/www/html), donc relu sans rebuild. Cela donne une preuve de deploiement observable de l'exterieur sans instrumentation supplementaire.

Criteres RNCP couverts

  • Bloc 3 - deploiement : chaine de livraison continue tracable (deploy.yml, deploy.sh, deployment.md), preuve de version exposee.
  • Bloc 1 - securite : separation runner/prod sans socket Docker ; secrets de prod hors du depot (gabarits .example seulement).

Bloc 2 — Finition du parcours borne client (#98, #99, #101, #102, #103)

Ce qui a ete fait

  • Menu Maxi -> boisson 50cl (#98) : choisir le format Maxi d'un menu fait passer la boisson de 33cl a 50cl. La migration 0007_product_size_variant et le seed 0006_drink_maxi_variant portent la variante en base ; le format choisi est transporte jusqu'au paiement (checkout.js, page-product-menu.js). Tests OrderRepository + tests JS du composeur.
  • Rupture non commandable (#99, RG-T21) : un produit (ou un menu dont un composant requis) sous le seuil critique de stock est marque indisponible et non ajoutable a la borne ; le serveur refuse aussi la creation cote OrderRepository (defense en profondeur, le client n'est pas seul juge). Visuel d'indisponibilite cote style.css.
  • Panier unique = panneau persistant (#101) : suppression des pages cart.html et product.html ; le panier devient un panneau lateral persistant (order-panel.js) present sur les pages au lieu d'une page dediee. Le net du diff est negatif (-784 lignes) : c'est une simplification de l'architecture front borne.
  • Confirmation d'abandon (#102) : abandonner la commande ouvre une modale de confirmation (confirm-modal.js) au lieu de vider le panier au premier clic.
  • Allergenes via API (#103) : la borne lisait des fichiers JSON statiques (allergens.json, categories.json, produits.json) ; elle consomme desormais /api/allergens (et le catalogue par API). Les JSON statiques et la doc afferente sont retires (menage). docs/api/conventions.md et docs/design/maquette-vs-build.md mis a jour.

Pourquoi — decisions et alternatives

  • Decision : rupture controlee cote serveur ET cote client (#99). L'indisponibilite est calculee au plus pres de la verite (RG-T21, ADR-0003) ; le client la reflete pour l'UX, mais OrderRepository revalide a la creation. Alternative ecartee : masquer cote client seulement — laisse une fenetre ou une commande forgee passerait.
  • Decision : panier persistant plutot que page panier (#101). Sur une borne, l'aller- retour vers une page panier ajoute une etape ; un panneau visible en permanence reduit la navigation. Le retrait de deux pages reduit aussi la surface a maintenir.
  • Decision : source de verite unique pour les donnees borne (#103). Les JSON statiques dupliquaient le catalogue de la base ; ils pouvaient diverger silencieusement. Passer par l'API supprime la duplication et fait converger borne et back-office sur le meme modele.

Criteres RNCP couverts

  • Bloc 2 - front client : parcours borne complet (composeur, panier, abandon, indisponibilite) sur donnees reelles par API.
  • Bloc 1 - regles metier : RG-T21 (disponibilite calculee) appliquee de bout en bout.

Bloc 3 — Saisie comptoir/drive en POS tactile (#100 puis #104)

Ce qui a ete fait

  • #100 a d'abord refondu la saisie comptoir/drive en tant que formulaire enrichi : prix affiches, verrou du mode de service au canal drive, navigation, file des commandes recentes. CounterOrderController derive la source (counter/drive) du chemin de la requete ; ajout de la liste des commandes recentes par canal.
  • #104 a ensuite remplace ce formulaire-liste par un POS tactile a tuiles facon borne : onglets categories en haut, grille de tuiles produits/menus, panneau commande persistant a droite. Pense pour la tablette (grandes cibles, un tap = ajout). Le panier est construit cote client (counter-order.js, CSP 'self', vanilla, zero handler inline) a partir d'un script JSON inerte, puis serialise dans un champ cache items_json. Le serveur revalide la forme (RG-T18), recalcule les prix (RG-T16) et resout les modificateurs : les prix cote client sont indicatifs.

Pourquoi — decisions et alternatives

  • Decision : POS a tuiles, reutilisant l'UX borne (#104). Cf. ADR-0011. L'equipier comptoir et le client borne font le meme geste (choisir des produits, composer) ; un meme paradigme tuiles+panneau reduit l'apprentissage et reutilise les patterns deja eprouves. Alternative ecartee : garder le formulaire-liste (#100) — moins adapte au tactile et a la rapidite attendue d'une caisse.
  • Decision : serveur seul juge des prix et de la composition. Le client propose, le serveur fige (RG-T16). Coherent avec le reste du domaine commande.
  • Decision : un controleur pour deux canaux, source derivee du chemin. Le decoupage par chemin (/drive... vs /counter...) plutot que par parametre rend les deux canaux etanches : un equipier ne peut pas requalifier sa commande en falsifiant un champ.

Comment — points techniques cles

  • CounterOrderController : source() lue depuis le chemin ; store() decode items_json, revalide, delegue a createStaffOrder (commande creee directement paid, encaissement immediat, sans PIN — la permission order.create suffit). Repli legacy qty_<id> accepte quand items_json est absent (degradation sans JS).
  • counter-order.js : construction des onglets/grille/panneau, modale de composition pour produits a modificateurs et menus, serialisation a la soumission.

Criteres RNCP couverts

  • Bloc 2 - back-office : ecran de caisse pour equipiers non-techniques (zero jargon), CSP-safe sans framework front.
  • Bloc 1 - regles metier : recalcul/revalidation serveur (RG-T16, RG-T18), etancheite des canaux.

Bloc 4 — Page Stock en tableau de bord (#105)

Ce qui a ete fait

La page d'accueil Ingredients/Stock, jugee trop chargee et opaque, est refondue en tableau de bord. Elle porte desormais : un bandeau expliquant le lien stock -> disponibilite borne (un ingredient requis sous le seuil critique rend indisponibles les produits qui l'utilisent, RG-T21) ; un resume comptant les ingredients critiques / en alerte / au-dessus du seuil ; une section "A reapprovisionner" mettant en avant les ingredients bas (critiques d'abord) avec barre de niveau et bouton de reapprovisionnement direct. La liste complete passe au second plan et le CRUD est relegue. Les sous-pages (reappro, inventaire, mouvements, creation) restent inchangees. index() expose des compteurs par etat, calcules cote serveur a partir de stock_band deja resolu par le depot, pour garder la vue declarative et la valeur testable.

Pourquoi — decisions et alternatives

Cf. ADR-0012. Decision : dashboard oriente action plutot que liste-CRUD. Le metier quotidien d'un equipier stock est de voir vite ce qui manque et de reapprovisionner, pas d'editer des fiches. Mettre les alertes et le bouton de reappro en avant aligne l'ecran sur ce geste. Alternative ecartee : garder une liste exhaustive triable — exhaustive mais muette sur l'urgence.

Criteres RNCP couverts

  • Bloc 2 - back-office : ergonomie orientee tache pour utilisateur non-technique.
  • Bloc 1 - regles metier : lien stock -> disponibilite (RG-T21) rendu explicite a l'ecran.

Verifications

A la cloture du lot : suite JS 135 tests verte (verifiee en local), suites PHPUnit et PHPStan niveau 6 vertes en CI Forgejo, php -l propre. Chaque PR est passee par la CI Forgejo (checks requis) avant merge natif.

Points d'amelioration conscients

  • #100 puis #104 : la refonte POS (#104) remplace une premiere refonte (#100) du meme ecran a quelques jours d'intervalle. Le formulaire enrichi de #100 a servi de palier avant le pivot tactile ; l'iteration est assumee plutot que masquee.
  • SMTP : le client maison couvre le cas d'usage reset (un destinataire, texte). Il n'est pas un agent mail generaliste ; tout besoin plus large (pieces jointes, files) serait a reevaluer.

Liens vers artefacts

  • Commits : 8c5d942 (#94) -> 03ef99d (#105).
  • ADR associes : docs/adr/0011-pos-tactile-tuiles-comptoir-drive.md, docs/adr/0012-page-stock-tableau-de-bord.md.
  • Fichiers principaux : .forgejo/workflows/deploy.yml, scripts/deploy.sh, src/app/Controllers/HealthController.php, src/app/Auth/SmtpClient.php, src/app/Controllers/CounterOrderController.php, src/public/admin/assets/js/counter-order.js, src/app/Controllers/IngredientController.php, src/app/Views/admin/ingredients/index.php.