feat(catalog) : M6 — Catalogue produits (ERP-197 → ERP-203) #154

Merged
matthieu merged 9 commits from feat/erp-203-tests-product-rg into develop 2026-06-25 12:50:16 +00:00

9 Commits

Author SHA1 Message Date
malio 1ee48555bd Merge branch 'develop' into feat/erp-203-tests-product-rg
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m50s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 5m33s
2026-06-25 12:43:22 +00:00
Matthieu 30e7839615 fix(catalog) : retours review M6 — default jsonb mort (states) + constante prefixe storage-type de test
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 49s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m23s
2026-06-25 14:20:52 +02:00
Matthieu 5dc5e703e3 test(catalog) : ERP-203 — tests PHPUnit RG-6.01→6.10 + capture du contrat JSON produit
Couvre les RG produit (M6) en tests fonctionnels API et capture le contrat de
serialisation reel (DoD spec-back § 4.0.bis).

Tests (tests/Module/Catalog/Api/) :
- AbstractProductApiTestCase : base commune (productType find-or-create PRODUIT,
  productCategory, seedStorageType par sites, validProductPayload, dump DoD).
- ProductSerializationContractTest : POST reel + GET liste/detail ; category en
  objet embarque, sites/storageTypes en tableaux d'objets, states en tableau,
  manufactured/containsMolasses booleens presents. Dump regenerable via
  PRODUCT_DOD_DUMP=1.
- ProductCodeUniquenessTest (RG-6.01) : 409 doublon actif, collision sur forme
  normalisee, reutilisation d'un code soft-deleted (index partiel).
- ProductStatesValidationTest (RG-6.02) : >=1 etat requis, valeur hors enum 422.
- ProductConditionalFieldsTest (RG-6.03) : manufactured/containsMolasses forces
  false sans SALE (POST et PATCH), conserves avec SALE.
- ProductCategoryTypeTest (RG-6.05) : 422 si categorie non-PRODUIT.
- ProductStorageTypeBySiteTest (RG-6.06) : 422 si storageType hors sites choisis.
- ProductRBACMatrixTest : Admin 200/201 ; Bureau/Compta/Commerciale/Usine 403
  (view + manage) ; view lit mais ne gere pas.

Fix contrat de serialisation : le getter containsMolasses() ne respectait pas la
convention d'accesseur (get/is/has) -> le serialiseur n'exposait PAS le champ
dans le JSON, alors que le DoD l'exige. Renomme en isContainsMolasses() (+ unique
appelant dans ProductExportController). Defaut capte par le test de contrat.

Spec : JSON reel colle dans spec-back § 4.0.bis (remplace l'esquisse). Le contrat
reel est plus riche : la liste porte deja sites/storageTypes embarques, category
embarque categoryTypes + audit, createdBy/updatedBy en IRI, sites avec adresse.

make test vert (897 tests) ; php-cs-fixer conforme.
2026-06-25 12:53:57 +02:00
Matthieu cbc445a539 feat(catalog) : ERP-202 — export XLSX du catalogue produits (filtres liste) 2026-06-25 11:54:50 +02:00
Matthieu 8644ad79ce feat(catalog) : ERP-201 — référentiel StorageType exposé (filtre site) + seed Figma + catégories PRODUIT
Endpoint StorageType (lecture seule) :
- StorageTypeProvider : tri label ASC, filtre ?siteId[]= (EXISTS corrélé, RG-6.06),
  pagination Hydra + échappatoire ?pagination=false (référentiel borné) ;
- createListQueryBuilder ajouté au repository (interface + impl) ;
- provider câblé sur GetCollection + Get de l'entité StorageType.

Seed (fixtures idempotentes par lookup code, miroir CategoryTypeFixtures) :
- StorageTypeFixtures : 10 types Figma (PROVISOIRE HP-M6-02), rattachés aux 3 sites
  (86/17/82) via le contrat Shared SiteProviderInterface (pas d'import inter-module) ;
- CategoryTypeFixtures : ajout du type PRODUIT (réaligne dev/test sur le seed migration ERP-198) ;
- CategoryFixtures : 4 catégories PRODUIT de démo (Céréales, Oléagineux, Aliments du bétail, Engrais).

Fix dette ERP-198/199 : mapping ORM de product.states aligné sur la colonne JSONB
de la migration (options jsonb=true). Sans ça, schema:update tentait ALTER states
TYPE JSON et cassait le CHECK jsonb_array_length -> make db-reset / test-db-setup en échec.

make db-reset OK (fixtures idempotentes, données vérifiées), make test vert (873), php-cs-fixer OK.
2026-06-25 11:41:40 +02:00
Matthieu ac86500266 feat(catalog) : ERP-200 — ProductProvider + ProductProcessor (unicité code, RG-6.03/05/06, normalisation)
Provider de lecture (liste paginée Hydra filtrée + item) :
- exclut les produits soft-deleted (RG-6.09), tri name ASC ;
- filtres ?search (code+name), ?categoryId/?categoryCode, ?state (JSONB @>), ?siteId[] (EXISTS) ;
- Get item : 404 sur soft-deleted (non exposé au M6, § 2.7) ;
- pagination obligatoire via Paginator ORM (règle n°13), échappatoire ?pagination=false.

Processor d'écriture (POST/PATCH) :
- normalisation serveur code trim+UPPER, name trim (RG-6.07, ProductFieldNormalizer) ;
- RG-6.03 : manufactured/containsMolasses forcés false si states sans SALE ;
- RG-6.01 : unicité globale du code parmi les actifs -> 409 (pré-check + filet anti-race index partiel), propertyPath code côté front.

Entité Product : Assert\Callback RG-6.05 (catégorie de type PRODUIT) et RG-6.06
(types de stockage disponibles sur au moins un site choisi), atPath pour mapping
inline 422 ; constantes d'états.

Repository : createListQueryBuilder (filtres + eager-load category/sites/storageTypes)
+ existsActiveByCode déjà en place.

make test vert (873 tests), php-cs-fixer OK.
2026-06-25 11:16:03 +02:00
Matthieu bc14e3893b feat(catalog) : ERP-199 — entités Product + StorageType + repositories + contrat de sérialisation
Entité Product (#[Auditable], TimestampableBlamable, soft-delete préparé non
exposé) et référentiel StorageType (lecture seule, provisoire) dans le module
Catalog, avec le contrat de sérialisation posé une fois (read-groups par
propriété affichée — RETEX M1→M5, 3 maillons spec § 4.0).

- Product : code (unique global RG-6.01), name, states (json multi-select
  PURCHASE/SALE/OTHER ≥1, RG-6.02), manufactured/containsMolasses (RG-6.03),
  category ManyToOne (PRODUIT, RG-6.05), sites + storageTypes ManyToMany (≥1).
  Messages FR sur toutes les contraintes, Length calée colonnes. Opérations
  Get/GetCollection (.view) + Post/Patch (.manage), pas de Delete. Provider/
  Processor référencés (implémentés en ERP-200).
- StorageType : code/label + sites ManyToMany (filtrage par site, ERP-201).
  Référentiel statique → whitelist EntitiesAreTimestampableBlamableTest.
- Repositories Product/StorageType (interfaces Domain + impl Doctrine).
- Validation états via Assert\Choice(multiple) plutôt qu'Assert\All (seul
  Choice est géré par EntityConstraintsHaveFrenchMessageTest).
- Garde-fous schema:update : 5 tables M6 ajoutées à ColumnCommentsCatalog,
  index partiel uq_product_code_active rejoué dans makefile test-db-setup.
- i18n audit.entity.catalog_product.
2026-06-25 10:44:34 +02:00
Matthieu 5409c79d1d feat(catalog) : ERP-198 — migration schéma M6 (storage_type, product, jonctions, type PRODUIT) 2026-06-25 10:20:58 +02:00
Matthieu e9f8b0bc45 feat(catalog) : ERP-197 — permissions catalog.products.* + item sidebar + 3 miroirs RBAC
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m53s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m31s
- CatalogModule::permissions() : ajout catalog.products.view / .manage (admin-only, C7)
- config/sidebar.php : item « Catalogue produit » (/admin/products) sous « Repertoire transporteurs » (section Administration)
- personas.ts + SeedE2ECommand.php : persona admin gagne view/manage + lien products (3 miroirs RBAC alignes)
- i18n : cle sidebar.catalog.products
2026-06-25 09:50:29 +02:00