feat(catalog) : M7 — entité Storage + repository + contrat de sérialisation (ERP-212) #164

Merged
tristan merged 1 commits from feat/erp-212-entite-storage into develop 2026-06-30 05:59:57 +00:00
Owner

M7 · ERP-212 (1.3) — Entité Storage + repository + contrat de sérialisation

⚠️ MR empilée sur feat/erp-211-migration-storage (#163), elle-même sur feat/erp-210-… (#162). À merger dans l'ordre 210 → 211 → 212 ; rebaser sur develop au fil des intégrations.

Mappe l'entité Storage (jumelle de Product M6) avec ses relations ManyToOne, le getter virtuel displayName, les contraintes FR et les read-groups de sérialisation.

Livré

  • Storage.php : #[Auditable], TimestampableInterface/BlamableInterface + trait. site / storageType ManyToOne (NOT NULL, ON DELETE RESTRICT, #[Assert\NotNull] FR). numero string(50) (NotBlank + Length(max:50) FR). states JSONB (Count(min:1) + Choice(multiple) sur {RECEPTION, PRODUCTION, TRIAGE} FR). deletedAt non exposé.
  • RG-7.05getDisplayName(): string = trim(label.' '.numero), #[Groups(['storage:read'])], non persisté.
  • Contrat de sérialisationstorage:read sur chaque champ ; site/storageType embarqués via site:read/storage_type:read ; createdAt/updatedAt via default:read. Écriture storage:write.
  • ApiResourceGetCollection/Get (catalog.storages.view) + Post/Patch (catalog.storages.manage, collectDenormalizationErrors). Pas de Delete (§ 2.8). Provider/Processor référencés (StorageProvider/StorageProcessor, créés en ERP-213::class non résolu au boot, aucun endpoint frappé ici).
  • RepositoryStorageRepositoryInterface (Domain) + DoctrineStorageRepository (Infrastructure).

Décisions / dérivations (au-delà de l'énoncé, requises pour make test vert)

  • Assert\Choice(multiple: true) retenu au lieu de Assert\All([Choice]) du prompt : Assert\All n'est pas mappé par EntityConstraintsHaveFrenchMessageTest et le ferait échouer. Équivalent fonctionnel, miroir exact de Product::states.
  • i18n catalog_storage ajouté à audit.entity (entité #[Auditable]AuditableEntitiesHaveI18nLabelTest).
  • Entrée storage dans ColumnCommentsCatalog : la table est désormais mappée → en TEST schema:update strip les COMMENT, app:apply-column-comments les rejoue (sinon ColumnsHaveSqlCommentTest casse).
  • makefile test-db-setup : ré-application de l'index partiel uq_storage_site_type_numero_active après schema:update (parité product/supplier/…).
  • states mappé type:'json', options:['jsonb'=>true] pour coller à la colonne JSONB + CHECK (évite l'ALTER TYPE JSON qui casserait jsonb_array_length).

Vérifications

  • doctrine:schema:validate : mapping correct ; seul « drift » = index partiel non exprimable en ORM + comments (rejoués) — identique à product/category, aucune dérive jsonb/type/index.
  • make test : EntitiesAreTimestampableBlamableTest (conforme sans whitelist), EntityConstraintsHaveFrenchMessageTest, AuditableEntitiesHaveI18nLabelTest, ColumnsHaveSqlCommentTest, CollectionsArePaginatedTest verts. Échec résiduel = flaky JWT connu (Technique, passe en isolation).
  • make php-cs-fixer-allow-risky : 0 fichier à corriger.
## M7 · ERP-212 (1.3) — Entité `Storage` + repository + contrat de sérialisation > ⚠️ **MR empilée** sur `feat/erp-211-migration-storage` (#163), elle-même sur `feat/erp-210-…` (#162). À merger dans l'ordre 210 → 211 → 212 ; rebaser sur `develop` au fil des intégrations. Mappe l'entité `Storage` (jumelle de `Product` M6) avec ses relations ManyToOne, le getter virtuel `displayName`, les contraintes FR et les read-groups de sérialisation. ### Livré - **`Storage.php`** : `#[Auditable]`, `TimestampableInterface`/`BlamableInterface` + trait. `site` / `storageType` ManyToOne (NOT NULL, ON DELETE RESTRICT, `#[Assert\NotNull]` FR). `numero` string(50) (`NotBlank` + `Length(max:50)` FR). `states` JSONB (`Count(min:1)` + `Choice(multiple)` sur `{RECEPTION, PRODUCTION, TRIAGE}` FR). `deletedAt` non exposé. - **RG-7.05** — `getDisplayName(): string` = `trim(label.' '.numero)`, `#[Groups(['storage:read'])]`, non persisté. - **Contrat de sérialisation** — `storage:read` sur chaque champ ; `site`/`storageType` embarqués via `site:read`/`storage_type:read` ; `createdAt`/`updatedAt` via `default:read`. Écriture `storage:write`. - **ApiResource** — `GetCollection`/`Get` (`catalog.storages.view`) + `Post`/`Patch` (`catalog.storages.manage`, `collectDenormalizationErrors`). Pas de `Delete` (§ 2.8). Provider/Processor référencés (`StorageProvider`/`StorageProcessor`, **créés en ERP-213** — `::class` non résolu au boot, aucun endpoint frappé ici). - **Repository** — `StorageRepositoryInterface` (Domain) + `DoctrineStorageRepository` (Infrastructure). ### Décisions / dérivations (au-delà de l'énoncé, requises pour `make test` vert) - **`Assert\Choice(multiple: true)`** retenu au lieu de `Assert\All([Choice])` du prompt : `Assert\All` n'est pas mappé par `EntityConstraintsHaveFrenchMessageTest` et le ferait échouer. Équivalent fonctionnel, miroir exact de `Product::states`. - **i18n `catalog_storage`** ajouté à `audit.entity` (entité `#[Auditable]` → `AuditableEntitiesHaveI18nLabelTest`). - **Entrée `storage` dans `ColumnCommentsCatalog`** : la table est désormais mappée → en TEST `schema:update` strip les COMMENT, `app:apply-column-comments` les rejoue (sinon `ColumnsHaveSqlCommentTest` casse). - **`makefile` test-db-setup** : ré-application de l'index partiel `uq_storage_site_type_numero_active` après `schema:update` (parité product/supplier/…). - `states` mappé `type:'json', options:['jsonb'=>true]` pour coller à la colonne JSONB + CHECK (évite l'ALTER TYPE JSON qui casserait `jsonb_array_length`). ### Vérifications - `doctrine:schema:validate` : mapping correct ; seul « drift » = index partiel non exprimable en ORM + comments (rejoués) — identique à product/category, **aucune** dérive jsonb/type/index. - `make test` : `EntitiesAreTimestampableBlamableTest` (conforme **sans** whitelist), `EntityConstraintsHaveFrenchMessageTest`, `AuditableEntitiesHaveI18nLabelTest`, `ColumnsHaveSqlCommentTest`, `CollectionsArePaginatedTest` verts. Échec résiduel = flaky JWT connu (Technique, passe en isolation). - `make php-cs-fixer-allow-risky` : 0 fichier à corriger.
tristan added the type/featbackM7-Stockage labels 2026-06-29 14:27:30 +00:00
Author
Owner

Revue de code — M7 Stockages

🔴 states accepte un objet JSON → 500 au lieu de 422Storage.php (propriété states)

states est typé array avec Assert\Count(min:1) + Assert\Choice(multiple:true). Un POST {"states":{"x":"RECEPTION"}} se dénormalise en tableau associatif, passe la validation (count 1, valeur valide), puis Doctrine json_encode produit un objet JSONB {"x":"RECEPTION"}. Le CHECK chk_storage_states_not_empty appelle alors jsonb_array_length() sur un non-tableau → erreur SQL remontée en 500 opaque au lieu d'un 422 propre sur states.

→ Garde de forme liste : array_values() au setter / dans le Processor, ou un Assert séquentiel garantissant un tableau séquentiel.

🟡 states accepte les doublons — même propriété

Ni le CHECK (jsonb_array_length >= 1) ni Assert\Choice n'imposent l'unicité. ["TRIAGE","TRIAGE"] est accepté et persisté (l'export masque via dédup par libellé). RG-7.04 décrit un sous-ensemble → ajouter Assert\Unique.

### Revue de code — M7 Stockages **🔴 `states` accepte un objet JSON → 500 au lieu de 422** — `Storage.php` (propriété `states`) `states` est typé `array` avec `Assert\Count(min:1)` + `Assert\Choice(multiple:true)`. Un POST `{"states":{"x":"RECEPTION"}}` se dénormalise en tableau **associatif**, passe la validation (count 1, valeur valide), puis Doctrine `json_encode` produit un objet JSONB `{"x":"RECEPTION"}`. Le CHECK `chk_storage_states_not_empty` appelle alors `jsonb_array_length()` sur un non-tableau → erreur SQL remontée en **500 opaque** au lieu d'un 422 propre sur `states`. → Garde de forme liste : `array_values()` au setter / dans le Processor, ou un `Assert` séquentiel garantissant un tableau séquentiel. **🟡 `states` accepte les doublons** — même propriété Ni le CHECK (`jsonb_array_length >= 1`) ni `Assert\Choice` n'imposent l'unicité. `["TRIAGE","TRIAGE"]` est accepté et persisté (l'export masque via dédup par libellé). RG-7.04 décrit un sous-ensemble → ajouter `Assert\Unique`.
tristan changed target branch from feat/erp-211-migration-storage to develop 2026-06-30 05:59:55 +00:00
tristan added 1 commit 2026-06-30 05:59:55 +00:00
tristan merged commit c78b8633b4 into develop 2026-06-30 05:59:57 +00:00
Sign in to join this conversation.