feat(catalog) : M6 — Catalogue produits (ERP-197 → ERP-203) (#154)
Auto Tag Develop / tag (push) Successful in 11s

Module **M6 — Catalogue produits** (ERP-197 → ERP-203), pile consolidée en une seule MR vers `develop` pour un CI unique.

Contenu (commits) :
- ERP-197 — permissions `catalog.products.*` + sidebar + 3 miroirs RBAC
- ERP-198 — migration schéma M6 (storage_type, product, jonctions, type PRODUIT)
- ERP-199 — entités Product + StorageType + repositories + contrat de sérialisation
- ERP-200 — ProductProvider + ProductProcessor (unicité code, RG-6.03/05/06, normalisation)
- ERP-201 — référentiel StorageType exposé (filtre site) + seed Figma + catégories PRODUIT
- ERP-202 — export XLSX du catalogue produits (filtres liste)
- ERP-203 — tests PHPUnit RG-6.01→6.10 + capture du contrat JSON produit
- fix review M6 — default jsonb mort (states) + constante préfixe storage-type de test

Remplace et clôt les MR #148, #149, #150, #151, #152, #153 (commits intégralement inclus ici).

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #154
This commit was merged in pull request #154.
This commit is contained in:
2026-06-25 12:50:14 +00:00
parent fdd4394e99
commit 4207a4ae12
33 changed files with 3859 additions and 9 deletions
@@ -575,6 +575,47 @@ final class ColumnCommentsCatalog
'status' => 'Cycle de vie (ERP-193) : DRAFT (« En attente », pesee enregistree sans contrepartie/immat) ou VALIDATED (« Terminée », valide avec numero). chk_wt_status. Defaut DRAFT.',
'deleted_at' => 'Horodatage du soft-delete technique — prepare mais non expose par l API au M5 (§ 2.13). Null = ligne active.',
] + self::timestampableBlamableComments(),
// M6 Catalog (ERP-199) — tables desormais mappees par les entites
// Product / StorageType : schema:update (test) les recree sans COMMENT
// -> app:apply-column-comments les rejoue depuis ce catalogue. Strings
// identiques aux COMMENT de la migration Version20260625110000 (ERP-198).
'storage_type' => [
'_table' => 'Referentiel des types de stockage (PROVISOIRE, en attente liste Aurore) — Boisseau, Cellule, Tas, Cuve melasse… (RG-6.06). Lecture seule au M6.',
'id' => 'Identifiant interne auto-incremente.',
'code' => 'Code stable MAJUSCULE du type de stockage (ex. TAS, CUVE_MELASSE). Unique (uq_storage_type_code).',
'label' => 'Libelle FR affiche du type de stockage (ex. « Cuve melasse »).',
],
'storage_type_site' => [
'_table' => 'Jointure M2M storage_type <-> site (Sites) — sites sur lesquels un type de stockage est disponible (alimente le filtrage du multi-select par site, RG-6.06).',
'storage_type_id' => 'FK -> storage_type.id, ON DELETE CASCADE — type de stockage disponible.',
'site_id' => 'FK -> site.id, ON DELETE CASCADE — site ou le type de stockage est disponible.',
],
'product' => [
'_table' => 'Produits du catalogue (M6 Catalog) — etat Achat/Vendu/Autre, sites de disponibilite, categorie produit, types de stockage.',
'id' => 'Identifiant interne auto-incremente.',
'code' => 'Code produit (= « Numero » de la liste), saisi, unique global parmi les actifs (RG-6.01). Index partiel uq_product_code_active. Normalise serveur (trim/UPPER).',
'name' => 'Nom du produit (≤ 255). Normalise serveur (trim).',
'states' => 'Etats du produit (JSON) : sous-ensemble non vide de PURCHASE|SALE|OTHER, multi-select (RG-6.02, chk_product_states_not_empty). Pilote les champs conditionnels.',
'manufactured' => '« Fabrique » : saisi uniquement si states contient SALE, sinon force false serveur (RG-6.03).',
'contains_molasses' => '« Contient de la melasse » : saisi uniquement si states contient SALE, sinon force false serveur (RG-6.03).',
'category_id' => 'Categorie produit (FK -> category.id, ON DELETE RESTRICT) — type PRODUIT, obligatoire, validee applicativement (RG-6.05).',
'deleted_at' => 'Horodatage du soft-delete technique — non expose au M6 ; la liste exclut les produits supprimes (§ 2.7). Null = ligne active.',
] + self::timestampableBlamableComments(),
'product_site' => [
'_table' => 'Jointure M2M product <-> site (Sites) — sites de disponibilite du produit (>= 1 obligatoire, RG-6.04).',
'product_id' => 'FK -> product.id, ON DELETE CASCADE — produit concerne.',
'site_id' => 'FK -> site.id, ON DELETE RESTRICT — site de disponibilite rattache au produit.',
],
'product_storage_type' => [
'_table' => 'Jointure M2M product <-> storage_type — types de stockage du produit (>= 1 obligatoire, filtres par les sites selectionnes, RG-6.06).',
'product_id' => 'FK -> product.id, ON DELETE CASCADE — produit concerne.',
'storage_type_id' => 'FK -> storage_type.id, ON DELETE RESTRICT — type de stockage rattache au produit.',
],
];
}