feat(commercial) : entités + repositories M2 fournisseurs (ERP-86) #65

Merged
malio merged 5 commits from feature/ERP-86-entites-repos-m2 into develop 2026-06-08 07:18:31 +00:00
Owner

ERP-86 — Entités + Repositories M2 Fournisseurs (étape 2/7)

PR empilée sur ERP-85 (#64) : ne contient que le commit ERP-86. À merger après #64 (la base rebascule automatiquement au fil des merges de la chaîne #63#64 → develop).

Dépend de #64 (migration BDD). Bloque #87 (Provider + Processor) et suivants.

Contenu

4 entités jumelles du M1 Client*, mapping ORM aligné exactement sur la migration ERP-85 (noms, types, longueurs, FK, M2M, index), sans contact inline (ERP-106) :

  • Supplier#[Auditable] + Timestampable/Blamable. Formulaire principal, onglet Information (+ volumeForecast, spécifique fournisseur), onglet Comptabilité (FK référentiels M1 partagés), archivage (isArchived/archivedAt), soft-delete préparé. Catégories M2M via CategoryInterface (règle n°1, pas d'import inter-module). Pas de distributor/broker.
  • SupplierContact — onglet Contacts (RG-2.04 : firstName OU lastName).
  • SupplierAddress — enum addressType (PROSPECT/DEPART/RENDU via Assert\Choice), bennes, triageProvider ; M2M sites/contacts/categories.
  • SupplierRib — RIB, embed gaté comptable.
  • Repositories : interfaces Domain/Repository/ + impls Infrastructure/Doctrine/.

Points clés

  • Contrat de sérialisation (RETEX M1, 3 maillons posés sur l'entité) : read-groups sur les propriétés ; getters isArchived() / isTriageProvider() avec #[Groups] + #[SerializedName('isX')] (parade piège booléen n°3) ; embed contacts/addresses (supplier:item:read) et ribs (supplier:read:accounting). getSites() agrège/dédoublonne les Site des adresses (name/postalCode, pas de code).
  • Fetch-joins anti-N+1 dans le repository de liste : hydrateListCollections() en 2 passes (categories, puis addresses.sites) — évite le produit cartésien (pattern ERP-100). Filtres : recherche companyName + contacts liés (D1), categoryCode, siteId, archivage.
  • Pas d'#[ApiResource] : Provider/Processor (gating accounting, archivage, mode strict) sont au ticket ERP-87. L'ajouter ici référencerait des classes inexistantes → boot/tests cassés. Les groupes de lecture/écriture sont déjà en place ; le normalizationContext viendra avec #87.
  • Validation FR (ERP-107) : messages FR sur toutes les contraintes ; Assert\Length(max) calé sur les colonnes. Garde-fou EntityConstraintsHaveFrenchMessageTest étendu : Assert\Choice ajouté au mapping ; addressType et postalCode whitelistés du miroir Length (déjà bornés par Choice / Regex).
  • Clés i18n audit.entity.commercial_supplier* ajoutées (garde-fou AuditableEntitiesHaveI18nLabelTest).

Vérifications

  • make test : 483/483 OK (1965 assertions).
  • make php-cs-fixer-allow-risky : 0 correction.
  • doctrine:schema:validate : mapping correct (bruit d'index FK cosmétique identique au M1 client).
## ERP-86 — Entités + Repositories M2 Fournisseurs (étape 2/7) PR **empilée sur ERP-85** (#64) : ne contient que le commit ERP-86. À merger après #64 (la base rebascule automatiquement au fil des merges de la chaîne #63 → #64 → develop). Dépend de #64 (migration BDD). Bloque #87 (Provider + Processor) et suivants. ### Contenu 4 entités jumelles du M1 `Client*`, mapping ORM aligné **exactement** sur la migration ERP-85 (noms, types, longueurs, FK, M2M, index), **sans contact inline** (ERP-106) : - **`Supplier`** — `#[Auditable]` + Timestampable/Blamable. Formulaire principal, onglet Information (+ `volumeForecast`, spécifique fournisseur), onglet Comptabilité (FK référentiels M1 partagés), archivage (`isArchived`/`archivedAt`), soft-delete préparé. Catégories M2M via `CategoryInterface` (règle n°1, pas d'import inter-module). Pas de `distributor`/`broker`. - **`SupplierContact`** — onglet Contacts (RG-2.04 : `firstName` OU `lastName`). - **`SupplierAddress`** — enum `addressType` (`PROSPECT`/`DEPART`/`RENDU` via `Assert\Choice`), `bennes`, `triageProvider` ; M2M sites/contacts/categories. - **`SupplierRib`** — RIB, embed gaté comptable. - **Repositories** : interfaces `Domain/Repository/` + impls `Infrastructure/Doctrine/`. ### Points clés - **Contrat de sérialisation (RETEX M1, 3 maillons posés sur l'entité)** : read-groups sur les propriétés ; getters `isArchived()` / `isTriageProvider()` avec `#[Groups]` + `#[SerializedName('isX')]` (parade piège booléen n°3) ; embed `contacts`/`addresses` (`supplier:item:read`) et `ribs` (`supplier:read:accounting`). `getSites()` agrège/dédoublonne les `Site` des adresses (`name`/`postalCode`, pas de `code`). - **Fetch-joins anti-N+1** dans le **repository de liste** : `hydrateListCollections()` en 2 passes (`categories`, puis `addresses.sites`) — évite le produit cartésien (pattern ERP-100). Filtres : recherche `companyName` + contacts liés (D1), `categoryCode`, `siteId`, archivage. - **Pas d'`#[ApiResource]`** : Provider/Processor (gating accounting, archivage, mode strict) sont au ticket **ERP-87**. L'ajouter ici référencerait des classes inexistantes → boot/tests cassés. Les groupes de lecture/écriture sont déjà en place ; le `normalizationContext` viendra avec #87. - **Validation FR (ERP-107)** : messages FR sur toutes les contraintes ; `Assert\Length(max)` calé sur les colonnes. Garde-fou `EntityConstraintsHaveFrenchMessageTest` étendu : `Assert\Choice` ajouté au mapping ; `addressType` et `postalCode` whitelistés du miroir Length (déjà bornés par Choice / Regex). - Clés i18n `audit.entity.commercial_supplier*` ajoutées (garde-fou `AuditableEntitiesHaveI18nLabelTest`). ### Vérifications - `make test` : **483/483 OK** (1965 assertions). - `make php-cs-fixer-allow-risky` : 0 correction. - `doctrine:schema:validate` : mapping correct (bruit d'index FK cosmétique identique au M1 `client`).
matthieu added the backdbM2-Fournisseurtype/feat labels 2026-06-05 08:56:05 +00:00
malio changed target branch from feature/ERP-85-migration-m2 to develop 2026-06-08 07:06:03 +00:00
malio added 3 commits 2026-06-08 07:06:03 +00:00
Recree le CategoryType FOURNISSEUR (unifie sur CLIENT par ERP-78) et implemente
un vrai filtre ?typeCode= sur GET /api/categories (inexistant en prod).

- CategoryProvider lit ?typeCode= depuis les filtres (meme pattern que
  includeDeleted) et le passe au repository ; naltere pas ?pagination=false.
- DoctrineCategoryRepository::createListQueryBuilder joint le CategoryType et
  filtre sur son code (compatible Paginator ORM fetchJoinCollection).
- Migration racine Version20260605120000 : seed du type FOURNISSEUR en
  ON CONFLICT + 5 categories de demo (Negociant, Cooperative, Producteur,
  Grossiste, Importateur) en NOT EXISTS. Aucune colonne creee.
- CategoryTypeFixtures / CategoryFixtures etendus a FOURNISSEUR (idempotent,
  survit a make db-reset).
- Test CategoryTypeCodeFilterTest : filtre exclusif, compat pagination Hydra,
  code inexistant -> liste vide.
Cree le schema M2 sous le module Commercial, jumeau du M1 client :
- supplier (formulaire + Information + Comptabilite + archive + soft-delete)
  sans contact inline (ERP-106) ni auto-reference distributor/broker ;
  ajout volume_forecast.
- Sous-collections : supplier_category (M2M), supplier_contact, supplier_address,
  supplier_rib + jointures supplier_address_site/_contact/_category.
- supplier_address : enum address_type (PROSPECT|DEPART|RENDU, CHECK exclusif),
  bennes + triage_provider, sans billing_email.
- Index partiel unique uq_supplier_company_name_active (nom seul, hors
  archives/soft-delete).
- COMMENT ON COLUMN sur chaque colonne (regle n12) + helper Timestampable/Blamable.

Referentiels comptables (tva_mode/payment_delay/payment_type/bank) et CategoryType
FOURNISSEUR reutilises (zero duplication). Namespace racine DoctrineMigrations
(FK cross-module, exception regle n11).
Entités jumelles du M1 client, mapping ORM aligné sur la migration ERP-85,
sans contact inline (ERP-106) :

- Supplier (#[Auditable] + Timestampable/Blamable) : formulaire principal,
  Information (+ volumeForecast), Comptabilité (FK référentiels M1), archivage,
  soft-delete préparé. Catégories M2M via CategoryInterface (règle n°1).
- SupplierContact / SupplierAddress (enum addressType, bennes, triageProvider)
  / SupplierRib.
- Repositories : interfaces Domain + impls Doctrine. DoctrineSupplierRepository
  porte les fetch-joins anti-N+1 de la liste (categories + addresses.sites en
  2 passes, pattern ERP-100) et les filtres (search companyName + contacts,
  categoryCode, siteId, archivage).

Contrat de sérialisation (RETEX M1, 3 maillons posés sur l'entité) :
read-groups sur les propriétés, getters isArchived/isTriageProvider avec
SerializedName, embed contacts/addresses (supplier:item:read) et ribs
(supplier:read:accounting). L'#[ApiResource] + Provider/Processor sont au
ticket suivant (ERP-87).

Validation FR (ERP-107) : messages FR sur toutes les contraintes, Length(max)
calé sur les colonnes. Garde-fou EntityConstraintsHaveFrenchMessageTest étendu
(Assert\Choice + whitelist addressType/postalCode). Clés i18n audit des 4
entités ajoutées.

make test : 483/483 OK.
malio added 1 commit 2026-06-08 07:06:10 +00:00
Merge branch 'develop' into feature/ERP-86-entites-repos-m2
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
c483bd9703
malio added 1 commit 2026-06-08 07:07:13 +00:00
Merge branch 'develop' into feature/ERP-86-entites-repos-m2
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m9s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 1m52s
15c27aa396
malio merged commit 6a01067746 into develop 2026-06-08 07:18:31 +00:00
malio deleted branch feature/ERP-86-entites-repos-m2 2026-06-08 07:18:31 +00:00
Sign in to join this conversation.