feat(commercial) : entités + repositories M2 fournisseurs (ERP-86) (#65)
Auto Tag Develop / tag (push) Successful in 7s

## 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`).

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #65
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
This commit was merged in pull request #65.
This commit is contained in:
2026-06-08 07:18:30 +00:00
committed by admin malio
parent cd98817b0a
commit 6a01067746
14 changed files with 1713 additions and 1 deletions
@@ -52,6 +52,10 @@ final class EntityConstraintsHaveFrenchMessageTest extends TestCase
private const array EXCLUDED_LENGTH_MIRROR = [
// Le Regex /^[0-9]{4,5}$/ borne deja la longueur a 5 caracteres (< 20).
'ClientAddress::postalCode' => 'Regex {4,5} borne deja la longueur.',
// Idem cote fournisseur (meme Regex CP).
'SupplierAddress::postalCode' => 'Regex {4,5} borne deja la longueur.',
// Le Choice {PROSPECT,DEPART,RENDU} borne les valeurs (<= 8 < 20).
'SupplierAddress::addressType' => 'Choice {PROSPECT,DEPART,RENDU} borne deja les valeurs.',
// Le Regex /^#[0-9A-Fa-f]{6}$/ borne la longueur a exactement 7 caracteres.
'Site::color' => 'Regex code hex #RRGGBB borne deja la longueur.',
];
@@ -70,6 +74,7 @@ final class EntityConstraintsHaveFrenchMessageTest extends TestCase
Assert\NotBlank::class,
Assert\NotNull::class,
Assert\Email::class,
Assert\Choice::class,
Assert\Regex::class,
Assert\Bic::class,
Assert\Iban::class,