Onglet Comptabilite : on ne saute une amorce RIB neuve vide QUE s'il reste un autre
RIB soumettable (garde hasSubmittableRib, miroir de l'onglet Contacts). Sinon — ex.
unique RIB existant supprime puis remplace par un bloc vide en LCR — le bloc vide est
soumis et le back renvoie une 422 NotBlank mappee en rouge sous label/bic/iban, au lieu
de laisser le DELETE echouer en « dernier RIB d'une LCR » (message plat sans propertyPath).
L'ancien RIB est preserve (arret avant le DELETE). Applique creation + edition, fournisseur + client.
La poubelle de suppression d'un bloc RIB n'apparait que s'il y a plusieurs RIB
(visibleRibs.length > 1) ; elle s'affiche alors sur chaque bloc. Avec un RIB unique
(minimum requis pour une LCR), pas de poubelle. Aligne sur les blocs contact/adresse.
Applique aux ecrans creation + modification, fournisseur et client (M1).
Port du fix fournisseur (blankEmptyRequired + flag forUpdate) a l'edition client :
en PATCH d'une ligne existante, un champ requis vide (companyName / postalCode /
city / street / label / bic / iban) est envoye en '' au lieu d'etre omis, sinon le
merge-patch garde l'ancienne valeur (faux 200). Creation (POST) inchangee (omit).
En edition (PATCH merge), omettre la cle d'un champ requis vide laissait la valeur
serveur inchangee -> faux 200 (l'ancien code postal etait conserve). Nouveau helper
blankEmptyRequired + flag forUpdate sur les builders : a la creation (POST) on omet
toujours la cle (NotBlank), en edition d'une ligne existante on envoie '' (chaine
valide, pas de 400 de type) pour declencher NotBlank 422 inline sous le champ.
Applique au bloc principal, aux adresses et aux RIB (selon id !== null).
- page edit.vue : champs pre-remplis depuis GET /suppliers/{id}, PATCH partiel
INDEPENDANT par onglet (mode strict RG-2.16 : un seul groupe de serialisation
par appel), pas de formulaire principal masque mais editable via son propre PATCH
- pas de contact inline (ERP-106) ; onglets metier readonly sans manage, Comptabilite
visible/editable selon accounting.view / accounting.manage (resolveTabEditability)
- collections contacts/adresses/RIB : POST/PATCH par ligne + DELETE differe des
retraits ; erreurs 422 mappees inline par champ (propertyPath) via useSupplierFormErrors
- supplierEdit : mappers d'hydratation (mapMainDraft/mapInformationDraft/
mapAccountingFormDraft, + volumeForecast) et resolveTabEditability
- tests Vitest : mappers d'hydratation + gating par role (matrice 2.7)
- miroir de l'ecran Modification client (M1), adapte M2 (addressType/bennes/
triageProvider/volumeForecast, pas de relation Distributeur/Courtier)
## Contexte
Branche ERP-119 — revue de la validation des formulaires clients (déclencheur : écran « Ajouter un client »), accompagnée de plusieurs évolutions de l'écran client (M1).
## Contenu
### Validation front (clients)
- Boutons « Valider » toujours actifs (retrait du gating de validité) : c'est le back qui renvoie les 422, mappées en rouge par champ.
- Champs requis adossés à une colonne non-nullable : la clé est omise du payload si vide (companyName, RIB, adresse) → 422 NotBlank au lieu d'un 400 de type.
- Onglet Contact : au moins un contact requis (l'amorce vide est soumise → 422 RG-1.05).
- Onglet Adresse : affichage inline des erreurs type / sites / catégories + RG back « au moins un type d'adresse obligatoire ».
### Nouveaux types d'adresse
- Courtier / Distributeur, types autonomes exclusifs : colonnes `is_broker` / `is_distributor` (migration + CHECK miroir d'exclusivité), entité + Callback, et front (select, drapeaux, payloads).
### Saisies manuelles
- Adresse : `allow-create` sur le champ Adresse → saisie libre si la BAN ne propose rien.
- Date de création : `MalioDate :editable` → saisie clavier JJ/MM/AAAA en plus du calendrier.
### 2e email de facturation
- Colonne `billing_email_secondary` (optionnel, max 2), miroir du téléphone secondaire. Bump `@malio/layer-ui` 1.7.8 (prop `addable`).
### Fin d'ajout d'un client
- Redirection vers la liste à la validation du dernier onglet remplissable par le rôle (Adresse pour Bureau/Commerciale, Comptabilité pour Admin) + toast « Client ajouté ». Dérivé de `tabKeys`, sans règle RBAC custom.
## Vérifications
- Back : suites Module/Commercial + Architecture vertes (Client : 124/124). Migrations appliquées (dev + test).
- Front : Vitest vert (272), ESLint OK.
> Note : le hook pré-commit flake aléatoirement (JWT 401 / timeout DB) sur des tests sans rapport (Supplier) ; les commits ont été faits après vérification des suites concernées en isolation.
Reviewed-on: #80
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## ERP-118 — Validation onglet Comptabilité (LCR / RIB)
### 1. Fix — 422 « Au moins un RIB est obligatoire pour le type de règlement LCR »
L'onglet Comptabilité envoyait le `PATCH /clients/{id}` des scalaires (`paymentType=LCR`) **avant** le `POST /clients/{id}/ribs`. Or le back valide RG-1.13 (LCR ⟹ ≥1 RIB persisté) sur ce PATCH, en lisant les RIB en base — vides à ce stade. Résultat : 422, et le `return` empêchait la création des RIB. Premier passage en LCR impossible (deadlock).
**Correctif :** inverser l'ordre — RIB d'abord, puis PATCH des scalaires.
- `new.vue` : `POST/PATCH RIB` → `PATCH scalaires`.
- `[id]/edit.vue` : ordre universel `CREATE/UPDATE RIB` → `PATCH scalaires` → `DELETE RIB retirés` (suppressions après le PATCH : le guard back n'autorise la suppression du dernier RIB qu'une fois quitté LCR). Corrige au passage un 409 latent sur le swap du dernier RIB en LCR.
### 2. Feat — contrôle croisé pays BIC/IBAN
`Assert\Bic(ibanPropertyPath: 'iban')` sur `ClientRib` et `SupplierRib` : le pays du BIC (positions 5-6) doit correspondre au pays de l'IBAN (positions 1-2). Un BIC et un IBAN valides isolément mais de pays différents → 422, violation portée par le champ `bic` avec message FR (`ibanMessage`), mappée inline côté front. Aucune modif front nécessaire.
### Tests
- Tests fonctionnels du mismatch (BIC DE + IBAN FR → 422 sur `propertyPath=bic`, message FR) côté client et fournisseur.
- Suite back complète au vert (garde-fou `EntityConstraintsHaveFrenchMessageTest` inclus), suite front Vitest au vert.
### Points d'attention
- **Durcissement de RG** (cross-check BIC/IBAN) hors spec initiale : des RIB existants avec BIC/IBAN de pays différents deviendraient non modifiables sans correction.
- L'orchestration de submit n'est pas couverte par un test unitaire (pas d'infra de test composant sur ces écrans) — vérification golden path recommandée.
Reviewed-on: #78
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Contexte
ERP-117 — correctifs frontend sur l'ecran de gestion des categories et alignement des boutons d'action des ecrans admin.
## Changements
### Drawer categories
- Titre stable « Modifier la categorie » (plus de bascule view → edit selon l'etat « dirty »), aligne sur les drawers simples du projet.
- Bouton Enregistrer toujours actif : il sauvegarde a tout moment, meme sans modification (PATCH du payload complet `name` + `categoryTypes`, comme `SiteDrawer`).
- Champ « Types de categorie » : suppression du label « Selectionner un ou plusieurs types ».
### Alignement des boutons admin
- Ecran Categories : ordre des boutons Filtres avant Ajouter + gap reduit (`gap-8`), comme le repertoire client.
- Boutons d'ajout admin (categories, roles, sites) passes en `variant=secondary`.
- Boutons Filtres (categories, audit-log, clients) en `tertiary` simple : suppression des surcharges de classe, icone a gauche 24px.
## Tests
- `useCategoryForm` mis a jour (PATCH payload complet).
- `make nuxt-test` : 256/256 OK.
- `make nuxt-lint` : OK.
Reviewed-on: #77
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
Lot de correctifs sur l'écran Client (M1), + un retrait de règle métier et une petite fonctionnalité.
## Formulaire client (création / édition)
- Boutons « ajouter un bloc » (Adresse, RIB) désactivés tant que le dernier bloc n'est pas valide.
- Onglet Information : bouton Valider désactivé si aucun champ rempli (création) ; onglet Contact accessible dès la création (Information facultatif).
- Champs « Relation » (Distributeur/Courtier) et « Prestation de triage » masqués par défaut, révélés seulement si une catégorie ordinaire (≠ Distributeur/Courtier) est sélectionnée.
- Bloc RIB affiché uniquement si le type de règlement est LCR (création, édition, consultation) ; plus de RIB fantôme soumis.
- Alignement du bas du textarea « Description » sur les autres champs.
## Recherche d'adresse (BAN)
- Une erreur de l'API ne bloque plus définitivement la recherche : chaque frappe réessaie (le mode dégradé restait verrouillé).
- Garde minimum 3 caractères avant l'appel à l'API.
## Répertoire client
- Titres de colonne en noir 16px, corps + tags de site en 14px.
## Navigation
- L'onglet actif est conservé au passage consultation ↔ édition (via history.state, hors URL).
## Règle métier
- Retrait de RG-1.04 : l'onglet Information n'est plus obligatoire pour le rôle Commerciale — facultatif pour tous (back + tests + docs).
Tests : suites front (Vitest) et back (PHPUnit) vertes hormis flakes d'infra connus.
Reviewed-on: #76
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Contexte
Une `Category` ne pouvait appartenir qu'à **un seul** `CategoryType` (ManyToOne). Le besoin métier : plusieurs types par catégorie. Cette MR fait passer la relation en **ManyToMany** et ajoute le bouton **« Filtres »** à droite de la liste des catégories (modèle Répertoire Clients).
Slice vertical complet (le passage M:N casse mécaniquement le contrat inter-module `CategoryInterface`, consommé par la RG-2.10 fournisseurs).
## Volet A — Relation M:N
- `Category.categoryType` (ManyToOne) → `categoryTypes` (ManyToMany, jonction `category_category_type`). Au moins un type obligatoire (`Assert\\Count(min:1)`).
- **Unicité du nom GLOBALE** parmi les actifs (`uq_category_name_active`, remplace `uq_category_name_type_active`). Message 409 reformulé.
- Migration : table de jonction + backfill + drop colonne `category_type_id` + nouvel index. Validée **rejouable sur base fraîche**.
- Contrat Shared : `getCategoryTypeCode()` → `getCategoryTypeCodes(): array`. `Supplier`/`SupplierAddress`/`SupplierFixtures` revalident « contient FOURNISSEUR » (RG-2.10).
- Provider/Repository : filtre type via sous-requête `EXISTS` (ne tronque pas la collection embarquée), eager-load anti-N+1.
## Volet B — Bouton « Filtres »
- Drawer recherche par nom + types multi (OR). Compteur de filtres actifs. État local, jamais persisté en URL.
- Back : filtres `?name=` et `?typeId[]=` sur la collection.
## Front
- Multi-select `MalioSelectCheckbox`, `useCategoryForm` en `categoryTypeIds[]`, colonne « Types », clés i18n.
## Tests / vérifs
- `make test` : **582 tests, 2474 assertions, 0 échec** ✅
- `make nuxt-test` : **236 tests** ✅
- `make php-cs-fixer-allow-risky` ✅
- Migration rejouée sur base fraîche (`make db-reset`) ✅
- Nouveau `CategoryFilterTest` (name partiel + typeId[] OR + multi-type non dupliqué)
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #75
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Correctifs issus de la review lead du stack M2 fournisseurs (ERP-84→113), répartis en priorités. Base : `develop`. Suite verte : `make test` 577 tests / 2475 assertions, `php-cs-fixer` 0 correction.
## P1 — défauts bloquants
- **ERP-89** — Le message de complétude Information ne fuit plus le nom de champ technique (`(champ "%s")` retiré). Correction miroir appliquée aux deux validators (Supplier + Client), accent uniformisé. Le `propertyPath` est conservé pour le mapping inline front.
- **ERP-112** — La fixture fournisseurs résout désormais la catégorie en filtrant sur le type `FOURNISSEUR` (via `CategoryInterface::getCategoryTypeCode()`), évitant de rattacher une catégorie homonyme d'un autre type (RG-2.10).
- **ERP-113** — Tests d'export complétés : dédup F3 (fournisseur multi-catégories rendu sur une seule ligne) ; gating SIREN prouvé via un utilisateur minimal non-admin portant `suppliers.view` + `suppliers.accounting.view` (nouveau helper `createUserWithPermissions`).
## P2 / P3
- **ERP-86** — `maxMessage` explicite sur `competitors` (Supplier).
- **ERP-92** — Garde `skipIfSitesModuleDisabled()` sur le test POST adresse sans site (évite un faux positif si le module Sites est désactivé).
- **ERP-89 bis** — Nouveau test : Admin authentifié non-Commerciale + Information incomplète → 200 (distinct du cas `user=null`).
- **ERP-85** — `down()` de la migration fournisseurs en `DROP TABLE IF EXISTS`.
- **ERP-87** — Reset de la mémoïsation payload en début de `process()` du SupplierProcessor + documentation du filtre `?archivedOnly` de l'export (parité avec le provider liste).
- **spec-back.md (M2)** — Alignée sur le code (le code fait foi) : security PATCH `manage or accounting.manage`, gating accounting par ajout de groupe (`SupplierReadGroupContextBuilder`), anti-N+1 via `hydrateListCollections` (pas de fetch-join), types de colonnes réels (`IDENTITY` / `TIMESTAMP(0)`).
## Alignement M1 ↔ M2
- **ERP-86/87 (Client)** — Mêmes corrections appliquées aux jumeaux M1 : message `competitors` explicite + reset mémoïsation `ClientProcessor`.
## Décision actée
- **RG-2.10 (catégorie)** : court-circuit conservé (une seule violation sur `categories`). Les violations partageant path + message sont fusionnées côté front ; ERP-101 (toutes les erreurs en un aller-retour) est déjà respecté car le Callback n'interrompt pas la validation des autres champs.
---------
Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #74
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Suivi du finding F3 de la review ERP-92. **Test uniquement** — aucune modif de code applicatif (le controller d'export ERP-91 est correct).
### Problème (F3)
`SupplierExportControllerTest` étendait `AbstractCommercialApiTestCase` et redéfinissait un `seedSupplier()` privé appelant `createCategory()` du parent → catégorie de **type CLIENT**, ce qui viole RG-2.10 dans les données de test (latent : l'export ne filtre pas par type de catégorie, mais le contrat de test était faux).
### Changements
- Bascule de base : `extends AbstractSupplierApiTestCase` (helpers `seedSupplier`/`addContact`/`supplierCategory` sur type **FOURNISSEUR**).
- Suppression du `seedSupplier()` privé (type CLIENT) et du `tearDown()` redondant — dédup F3.
- `testExportUsesPrincipalContactColumns` : utilise `addContact()` de la base ; le téléphone secondaire (non porté par ce helper) est posé via le setter sur le contact retourné.
- `testExportPopulatesCategoryAndSiteColumns` : l'assertion de la colonne « Catégories » dérive le libellé de `supplierCategory('NEGOCIANT')->getName()` au lieu de hardcoder le préfixe de nom de test (la base nomme `test_cli_cat_fr_negociant`).
- Imports `Supplier` / `SupplierContact` / `DateTimeImmutable` retirés (inutilisés).
### Vérifications
- `SupplierExportControllerTest` : 9 tests, 48 assertions — vert sous APP_DEBUG=0.
- Suite complète `make test` : 574 tests, 2448 assertions — OK sous APP_DEBUG=0.
- `make php-cs-fixer-allow-risky` : 0 correction.
> MR stackée sur `feature/ERP-112-fixtures-fournisseurs`.
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #73
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
## ERP-112 — Fixtures Doctrine fournisseurs (M2)
`SupplierFixtures` (calquée sur `ClientFixtures` / ERP-68) : ~13 fournisseurs de démonstration couvrant les cas pivots du répertoire fournisseurs (M2), chargés par `make db-reset`.
### Contenu
- **13 fournisseurs** (dont **2 archivés** — RG-2.17), `companyName` variés (UPPERCASE serveur), mono et multi-catégories de type FOURNISSEUR (RG-2.10).
- **19 contacts** (1 à 3 par fournisseur, dont un avec téléphone secondaire et un nommé par le seul nom — RG-2.04).
- **15 adresses** multi-types PROSPECT / DEPART / RENDU (RG-2.09) et multi-sites 86/17/82 (RG-2.06), avec `bennes` et `triageProvider`.
- **3 RIB**, compta complète sur une partie (siren, tvaMode, paymentDelay, paymentType).
### Cas pivots
- VIREMENT → banque renseignée (RG-2.07) ; LCR → 1 puis 2 RIB (RG-2.08) ; CHEQUE et NON_SOUMISE sans RIB.
- Onglet Information complet (dont `volumeForecast`, spécifique fournisseur).
- Cohérence gating comptable (un rôle sans `accounting.view` ne voit pas la compta) — support des tests ERP-92 et du golden path front.
### Notes
- **Idempotent** (lookup par companyName normalisé, aligné sur `uq_supplier_company_name_active`) ; rejouable sans doublon même purger désactivé.
- Référentiels comptables **réutilisés de M1** (tva_modes / payment_delays / payment_types / banks) — aucune nouvelle table.
- Données de démonstration **dev uniquement** : early return en env `test` (les tests seedent leurs propres données).
### Vérifications
- `make db-reset` : 13 fournisseurs (2 archivés), 19 contacts, 15 adresses, 3 RIB chargés sans erreur.
- Idempotence `--append` : compteurs inchangés.
- `make php-cs-fixer-allow-risky` : 0 fichier à corriger.
- `make test` : 574 tests OK.
Base : `feature/ERP-92-tests-phpunit-m2` (sommet de la pile M2).
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #72
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Export XLSX du répertoire fournisseurs (spec-back M2 § 4.6), jumeau de l'export client M1. **Stack : cible `feature/ERP-90-rbac-fournisseurs`** (ERP-84→91 non encore mergés dans develop).
## Périmètre
- **`SupplierExportController`** avec `#[Route(priority: 1)]` (anti-conflit API Platform `{id}`) + `is_granted('commercial.suppliers.view')`.
- Mêmes filtres que la liste (`includeArchived`/`archivedOnly`/`search`/`categoryCode`/`siteId`) via `createListQueryBuilder()` partagé avec le `SupplierProvider` ; non archivés par défaut.
- Colonnes : Nom fournisseur, **Contact principal** (Nom + Prénom du `SupplierContact` de plus petit `position`, ERP-106), Tél principal, Tél secondaire, Email, Catégories (CSV), Sites (CSV), **SIREN omis sans `accounting.view`**, Date de création.
- Fichier `repertoire-fournisseurs-{YYYYMMDD}.xlsx`.
- **`hydrateContacts()`** ajouté au repository : chargement batché des contacts en une requête `IN` (anti-N+1). Méthode dédiée à l'export — la liste paginée n'embarque pas les contacts, on ne lui impose pas ce coût.
## Correctif hors-périmètre (signalé)
Tables `supplier*` ajoutées à `ColumnCommentsCatalog` : leurs `COMMENT ON COLUMN` (posés par la migration ERP-85) étaient dropés par le `schema:update --force` du `test-db-setup` et non restaurés (catalogue = source rejouée par `app:apply-column-comments`), cassant `ColumnsHaveSqlCommentTest` dès un re-setup de la base de test. Trou laissé par ERP-85/86, vert tant que personne ne re-setup la base.
## Tests
- `SupplierExportControllerTest` (9 cas) : réponse/filename, exclusion archives, filtre search, contact principal, colonnes catégories/sites, gating SIREN avec/sans `accounting.view`, 403, 401.
- `make test` : 508 tests / 2035 assertions, 0 échec. `php-cs-fixer` clean.
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #70
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Étape 4/7 du M2 fournisseurs — stackée sur #67 (ERP-88).
## Périmètre (RG-2.03 / 2.07 / 2.08 / 2.10)
Décision figée ERP-89 : les RG inter-champs passent par `Assert\Callback` + `->atPath()` sur l'entité Supplier (et non dans le Processor), pour que chaque 422 porte un `propertyPath` consommable par `extractApiViolations` (mapping inline, pas un toast — ERP-101).
- **RG-2.10** — `Supplier::validateCategoryType()` → `atPath('categories')` : catégories de type FOURNISSEUR uniquement sur `supplier.categories` (miroir d'ERP-88 côté adresse).
- **RG-2.07** — `Supplier::validatePaymentTypeConsistency()` → `atPath('bank')` : VIREMENT impose une banque.
- **RG-2.08** — même Callback → `atPath('ribs')` : LCR impose ≥ 1 RIB (le 409 sur DELETE du dernier RIB en LCR reste porté par ERP-88).
- **RG-2.03** — `SupplierInformationCompletenessValidator` (8 champs Information dont `volumeForecast`), invoqué par le `SupplierProcessor` après détection back du rôle Commerciale via `BusinessRoleAwareInterface`. Le Processor ne porte que rôle / mode strict / gating.
## Tests (16, verts)
- `SupplierValidationTest` — Callbacks RG-2.07/2.08/2.10, assertion par propertyPath.
- `SupplierInformationCompletenessValidatorTest` — complétude / champs manquants / zéros valides.
- `SupplierProcessorTest` — détection rôle RG-2.03 (POST + PATCH main-only + non-Commerciale).
`make test` : 499 tests OK. `php-cs-fixer` : clean.
---------
Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #68
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
## ERP-87 — Provider + Processor du répertoire fournisseurs (M2)
Étape 3/7 du pipeline M2. Dépend de #86, bloque #88/#91/#92. Jumelle du M1 (Client*).
### Livré
- **SupplierProvider** : liste paginée (Paginator ORM), exclusion archivés + soft-deletes par défaut, filtres `includeArchived`/`categoryCode`/`siteId`/`search`, échappatoire `?pagination=false`, item 404 si soft-delete (RG-2.17).
- **SupplierProcessor** : normalisation `companyName`, archivage `isArchived`/`archivedAt` (RG-2.14/2.15), gating fin accounting/manage en **mode strict** (403 sur tout payload hors-permission, RG-2.16), 409 doublon `companyName` + conflit de restauration (RG-2.11).
- **SupplierReadGroupContextBuilder** : ajoute `supplier:read:accounting` au contexte de lecture si `accounting.view` → gating compta + RIB **par omission de clé** (parade bug #4 M1). Un Provider ne pouvant pas influencer les groupes de sérialisation, c'est le point d'extension idiomatique (miroir de `ClientReadGroupContextBuilder`).
- **SupplierFieldNormalizer** : normalisation serveur (RG-2.12).
- **Supplier** : ajout `#[ApiResource]` (GetCollection/Get/Post/Patch) wirant Provider/Processor.
### Décision d'archi
La spec décrit « le Provider retire le groupe accounting » — techniquement impossible (le Provider ne touche pas les groupes de sérialisation). Implémenté via décorateur `SerializerContextBuilder` (mirror M1), résultat fonctionnel identique (clé absente sans permission).
### Hors périmètre (ticket suivant #5)
Validators métier : RG-2.03 (complétude Information Commerciale), RG-2.07 (Virement→banque), RG-2.08 (LCR→RIB), RG-2.10 (catégorie type FOURNISSEUR). Le Processor est structuré pour les accueillir.
### À noter
Les permissions `commercial.suppliers.*` (référencées par les `security`) ne sont pas encore déclarées — ticket RBAC #7. Sans elles, `is_granted` renvoie `false` (pas d'erreur de compilation).
### Vérifs
- `make test` : 483/483 vert
- `make php-cs-fixer-allow-risky` : appliqué
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #66
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>