Files
Starseed/docs/specs/_RETEX-M1-pour-M2.md
T
matthieu d97b9ce6d0
Auto Tag Develop / tag (push) Successful in 11s
feat(technique) : module Technique + taxonomie categories prestataires (#89)
## M3 — Ticket 1.1 : module Technique + taxonomie catégories prestataires

Prérequis de tout le M3 (répertoire prestataires). Spec : `docs/specs/M3-prestataires/spec-back.md` § 2.1 + § 2.4.

### Contenu
- **Nouveau module `Technique`** (`src/Module/Technique/TechniqueModule.php`) : `ID=technique`, `LABEL=Technique`, `REQUIRED=false`, `permissions()` (5 codes `technique.providers.*` : view / manage / accounting.view / accounting.manage / archive).
- Activation dans `config/modules.php` → `/api/modules` expose `technique`.
- **Layer front** `frontend/modules/technique/` (auto-détecté).
- **Seed taxonomie PRESTATAIRE** : nouveau `CategoryType` (code `PRESTATAIRE` / label `Prestataire`) + 3 catégories (Maintenance industrielle, Nettoyage, Transport).
  - Migration racine idempotente `Version20260612080000` (`ON CONFLICT DO NOTHING` + `NOT EXISTS`, jonction M2M `category_category_type` — schéma courant, pas l'ancien `category_type_id`).
  - Fixtures `CategoryTypeFixtures` / `CategoryFixtures` étendues (survivent au purger `db-reset`).

### Critères d'acceptation 
- [x] Module + permissions déclarées (`app:sync-permissions` → 5 codes en base)
- [x] `TechniqueModule::class` dans `config/modules.php`
- [x] Layer front
- [x] Seed CategoryType PRESTATAIRE (migration + fixture idempotente)
- [x] ≥ 3 catégories PRESTATAIRE
- [x] `GET /api/categories?typeCode=PRESTATAIRE` filtre correctement

### Tests
- `TechniqueModuleTest` : identité + jeu de 5 permissions figé.
- `CategoryPrestataireSeedTest` : `?typeCode=PRESTATAIRE` ne renvoie QUE le type PRESTATAIRE + pagination Hydra préservée.
- `make test` : **589 tests OK** · `php-cs-fixer` : 0 correction · `make db-reset` : type + 3 catégories présents, idempotent.

### Hors-périmètre (tickets M3 suivants)
Section sidebar « Technique », personas RBAC E2E, et entités `Provider*` (l'écran `/providers` n'existe pas encore → pas de lien mort introduit ici).

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #89
2026-06-12 14:19:14 +00:00

7.1 KiB
Raw Blame History

RETEX M1 (Clients) → à appliquer pour M2 (Fournisseurs)

But : éviter de reproduire en M2 les erreurs de contrat de sérialisation qui ont bloqué M1. ~80 % des frictions M1 venaient du contrat API (sérialisation / groupes / sous-ressources), pas du métier. À lire AVANT de rédiger spec-back.md et spec-front.md du M2, et à garder ouvert pendant la rédaction.


0. TL;DR (les 3 erreurs à ne jamais refaire)

  1. Affirmer qu'un champ est « embarqué » sans vérifier les 3 maillons de sérialisation. En M1 : Category.code annoncé dans client:read, détail annoncé embarquant contacts/adresses/ribs → faux dans le code. Résultat : colonnes liste vides, onglets détail impossibles à peupler.
  2. Livrer des sous-ressources en POST-only (pas de GetCollection, pas d'embed) → le front ne peut pas lister les enfants de l'agrégat.
  3. Écrire la spec/les tickets sur une intention, pas sur le contrat réel. Le docblock Client décrivait un embed jamais implémenté.

1. Contrat de sérialisation : les 3 maillons obligatoires

Pour chaque champ affiché (liste OU détail), la spec back doit prouver les trois maillons. Si un seul manque → le champ sort en quasi-IRI (@id/@type seulement) ou pas du tout.

Maillon Question Exemple M1 raté
(a) Groupe sur la propriété #[Groups([...])] contient-il un read-group ? Supplier::$addresses sans groupe → jamais sérialisé
(b) Groupe dans le normalizationContext de l'opération l'opération (GetCollection/Get) liste-t-elle ce groupe ? GetCollection en ['client:read','default:read']
(c) Read-group de l'entité imbriquée dans le contexte parent pour embarquer les champs d'une relation (catégorie, site…), le contexte parent inclut-il category:read / site:read ? Category.codecategory:read, absent du contexte client → pas de code

Règle de rédaction : dans spec-back.md, faire un tableau « champ → groupe propriété → groupe(s) à ajouter au contexte de chaque opération » pour la liste ET le détail. Inclure explicitement les relations imbriquées (ex. catégories d'une adresse, sites d'une adresse).

2. Collections enfant d'un agrégat : décider embed vs GetCollection, et câbler en ENTIER

Décision à acter dès la spec back pour chaque sous-collection (contacts, adresses, RIB, lignes…) :

  • Embed dans le détail (recommandé pour un agrégat DDD) : poser #[Groups(['<root>:item:read'])] sur la propriété + ajouter au normalizationContext du Get racine les read-groups des entités enfant et de leurs relations imbriquées. 1 requête, cohérent avec un composable useX(id). Réservé aux ensembles bornés (ne viole pas la règle n°13 : elle vise les collections exposées, pas un embed borné d'item).
  • GetCollection sous-ressource : /<root>/{id}/children paginé. À réserver aux collections potentiellement volumineuses. Si choisi, créer l'opération (pas seulement POST).

Anti-pattern M1 : sous-ressources avec POST + Get unitaire seulement → aucun moyen de lister (ids non découvrables). Interdit.

3. Vérifier le contrat sur l'API RÉELLE avant d'écrire les tickets front

Le blocage M1 (codes/sites/sous-collections) aurait été vu en 5 min. À mettre dans la definition of done de la spec back :

Créer un enregistrement de test, appeler GET /api/<resource> (liste) ET GET /api/<resource>/{id} (détail), coller la réponse JSON réelle dans la spec. Toute donnée affichée par le front doit apparaître dans ce JSON collé.

4. La spec décrit le RÉEL, pas l'intention

  • Bannir les « devrait être embarqué », « est exposé » non vérifiés. Décrire ce qui existe (ou ce qui sera livré dans le ticket, en le marquant clairement « à livrer »).
  • Si un docblock/commentaire existant contredit le code, le corriger, pas le recopier.

5. Réutiliser les acquis M1 (ne pas réinventer)

  • Taxonomie ERP-78 : si M2 catégorise les fournisseurs, repartir du modèle type unique + code stable (slug MAJUSCULE auto-généré, NOT NULL, figé, lecture seule category:read), filtrage métier via ?categoryCode=. Réutiliser le contrat partagé CategoryInterface (pas d'import inter-module).
  • Front : usePaginatedList (listes), composants Malio*, useApi(), formatPhoneFR, blocs réutilisables (Contact/Adresse), pattern de blocs dynamiques + modal de confirmation.
  • Archive : flag is_archived distinct de deleted_at (soft delete). Restauration → gérer le 409 homonyme.
  • Normalisation = serveur (UPPERCASE nom société, Capitalize noms, lowercase email, téléphone en chiffres). Le front envoie la saisie, réaffiche la valeur normalisée renvoyée. À documenter dans la spec.
  • Gating fin + mode strict PATCH : PATCH par groupe de sérialisation ; tout champ hors-permission dans le payload = 403 sur l'intégralité (pas de filtrage silencieux). Spécifier la matrice rôle × onglet.

6. Règles ABSOLUES transverses à rappeler dans la spec M2

  • Pagination obligatoire (règle n°13) sur toute GetCollection ; échappatoire ?pagination=false réservée aux selects de référentiels bornés.
  • COMMENT ON COLUMN (règle n°12) sur chaque colonne créée/modifiée (sinon make test casse). Helper standard pour les colonnes Timestampable/Blamable.
  • Timestampable + Blamable sur toute nouvelle entité métier (4 colonnes + trait) ; garde-fou archi.
  • #[Auditable] sur les entités métier ; #[AuditIgnore] sur les champs sensibles (équivalents BIC/IBAN/secret).
  • declare(strict_types=1); partout ; commentaires FR, code EN.
  • Routes front à plat (pas de préfixe module), état tableau jamais dans l'URL.
  • 3 miroirs RBAC à toucher ensemble : config/sidebar.php, frontend/tests/e2e/_fixtures/personas.ts, SeedE2ECommand.php.
  • Communication inter-module uniquement via Shared/Domain/Contract/ ou domain events — jamais d'import direct.

7. Fixtures & seed dès le départ

M1 a subi un aller-retour (ERP-68) faute de fixtures alignées. Pour M2 : prévoir dès la spec un seed de fournisseurs démo couvrant tous les cas des règles métier (relations, catégories codées, archivés, cas comptables) + comptes de rôles démo, pour vérifier le gating et le golden path sans bricolage.

8. Mini-checklist de relecture de la spec M2 (avant de la déclarer prête)

  • Chaque champ affiché (liste + détail) a ses 3 maillons de sérialisation documentés (propriété, contexte opération, relations imbriquées).
  • Chaque sous-collection a une décision embed vs GetCollection explicite et complètement câblée (pas de POST-only).
  • Réponses JSON réelles (liste + détail) collées dans la spec back.
  • Matrice RBAC rôle × écran × onglet + mode strict PATCH spécifiés.
  • Pagination, COMMENT ON COLUMN, Timestampable/Blamable, Audit, routes à plat : rappelés.
  • Réutilisations M1 identifiées (taxonomie code, usePaginatedList, blocs, archive, normalisation).
  • Seed/fixtures démo planifiés.