## 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
7.1 KiB
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.mdetspec-front.mddu M2, et à garder ouvert pendant la rédaction.
0. TL;DR (les 3 erreurs à ne jamais refaire)
- Affirmer qu'un champ est « embarqué » sans vérifier les 3 maillons de sérialisation. En M1 :
Category.codeannoncé dansclient:read, détail annoncé embarquant contacts/adresses/ribs → faux dans le code. Résultat : colonnes liste vides, onglets détail impossibles à peupler. - 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. - Écrire la spec/les tickets sur une intention, pas sur le contrat réel. Le docblock
Clientdé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.code ∈ category: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 aunormalizationContextduGetracine les read-groups des entités enfant et de leurs relations imbriquées. 1 requête, cohérent avec un composableuseX(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}/childrenpaginé. À 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) ETGET /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 +
codestable (slug MAJUSCULE auto-généré, NOT NULL, figé, lecture seulecategory:read), filtrage métier via?categoryCode=. Réutiliser le contrat partagéCategoryInterface(pas d'import inter-module). - Front :
usePaginatedList(listes), composantsMalio*,useApi(),formatPhoneFR, blocs réutilisables (Contact/Adresse), pattern de blocs dynamiques + modal de confirmation. - Archive : flag
is_archiveddistinct dedeleted_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=falseréservée aux selects de référentiels bornés. COMMENT ON COLUMN(règle n°12) sur chaque colonne créée/modifiée (sinonmake testcasse). 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.