Commit Graph

211 Commits

Author SHA1 Message Date
tristan 8eff37186d fix(front) : 422 sur champ requis vide en edition fournisseur (ERP-96)
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).
2026-06-11 08:36:47 +02:00
tristan 18fdf9354f feat(front) : page Modification fournisseur (/suppliers/{id}/edit) (ERP-96)
- 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)
2026-06-11 08:22:17 +02:00
tristan 5722912413 feat(front) : page Consultation fournisseur (/suppliers/{id}) lecture seule (ERP-95)
- useSupplier(id) : GET /api/suppliers/{id} en Hydra (embed contacts/adresses/ribs
  + scalaires compta si accounting.view), archive()/restore() via PATCH isArchived seul
- supplierConsultation : mappers purs de l'embed (addressType, bennes, triageProvider,
  volumeForecast ; gating compta par omission de cle), helpers de permissions
- page [id]/index.vue : lecture seule, bloc principal + onglets (Information/Contacts/
  Adresses/Comptabilite selon permission/4 coquilles A venir), Modifier/Archiver/Restaurer,
  fleche retour repertoire ; miroir de l'ecran Consultation client (M1)
- tests Vitest : supplierConsultation (mappers + permissions) + useSupplier (GET/PATCH)
2026-06-11 08:13:35 +02:00
tristan e2ad17820b fix(commercial) : corrections ajout fournisseur — addressType en select, 422 inline (addressType/catégorie/compta complète/LCR sur paymentType), Information facultative (RG-2.03 retirée, miroir client) (ERP-94) 2026-06-09 23:36:39 +02:00
tristan 556b7026da test(front) : import explicite de vi dans SupplierContactBlock.spec (ERP-94) 2026-06-09 22:48:26 +02:00
tristan 86373f0d3c refactor(front) : volume prévisionnel en champ texte masqué + bloc adresse fournisseur aligné sur le client (radio type sans label, une colonne, required par radio) (ERP-94) 2026-06-09 22:46:40 +02:00
tristan 01a3bd6419 feat(front) : page Ajouter un fournisseur (/suppliers/new) + workflow par onglets (ERP-94) 2026-06-09 22:37:30 +02:00
tristan 639cf8482f chore(front) : i18n écrans/onglets fournisseurs + fournisseur avant client dans la sidebar (ERP-97) 2026-06-09 22:15:16 +02:00
tristan 79d389834b feat(front) : page Répertoire fournisseurs (/suppliers) + datatable + filtres + export (ERP-93)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m6s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m10s
2026-06-09 22:05:56 +02:00
gitea-actions 26b1f2c39b chore: bump version to v0.1.101
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 56s
v0.1.101
2026-06-09 19:47:49 +00:00
tristan 8490de99da ERP-119 : revue validation front clients + évolutions écran client (types d'adresse, 2e email, saisies manuelles, redirection) (#80)
Auto Tag Develop / tag (push) Successful in 7s
## 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>
2026-06-09 19:47:40 +00:00
gitea-actions b3ab23ee8f chore: bump version to v0.1.100
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 36s
v0.1.100
2026-06-09 08:44:19 +00:00
tristan 222338e5a4 fix(commercial) : validation onglet compta LCR + controle croise BIC/IBAN (ERP-118) (#78)
Auto Tag Develop / tag (push) Successful in 7s
## 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>
2026-06-09 08:44:12 +00:00
gitea-actions d4a5df50a7 chore: bump version to v0.1.99
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 38s
v0.1.99
2026-06-09 06:07:03 +00:00
tristan 191fd42406 Correctifs frontend ecran categories + alignement boutons admin (ERP-117) (#77)
Auto Tag Develop / tag (push) Successful in 9s
## 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>
2026-06-09 06:06:52 +00:00
gitea-actions edfb2b1619 chore: bump version to v0.1.98
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 37s
v0.1.98
2026-06-08 14:53:01 +00:00
tristan c5c650c599 style(front) : marges PageHeader (38px haut / 30px bas) + ordre boutons Filtres avant Ajouter (repertoire client)
Auto Tag Develop / tag (push) Successful in 7s
2026-06-08 16:52:53 +02:00
gitea-actions e598a92f94 chore: bump version to v0.1.97
Auto Tag Develop / tag (push) Successful in 7s
Build & Push Docker Image / build (push) Successful in 58s
v0.1.97
2026-06-08 14:40:27 +00:00
tristan b8dc3cb696 Correctifs écran Client (ERP-115) (#76)
Auto Tag Develop / tag (push) Successful in 7s
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>
2026-06-08 14:40:18 +00:00
gitea-actions 843e4b0a0c chore: bump version to v0.1.96
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 49s
v0.1.96
2026-06-08 09:47:21 +00:00
matthieu a9c14704b7 feat(catalog) : categories multi-types (M:N) + bouton Filtres liste (#75)
Auto Tag Develop / tag (push) Successful in 7s
## 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>
2026-06-08 09:47:15 +00:00
gitea-actions 43b2251ef1 chore: bump version to v0.1.95
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 21s
v0.1.95
2026-06-08 08:50:41 +00:00
matthieu 9cda225bdf Correctifs post-review M2 fournisseurs (P1 + P2/P3 + alignement M1) (#74)
Auto Tag Develop / tag (push) Successful in 8s
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>
2026-06-08 08:47:43 +00:00
gitea-actions f031c70393 chore: bump version to v0.1.94
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 19s
v0.1.94
2026-06-08 08:04:20 +00:00
matthieu e050a7b910 test(commercial) : SupplierExportControllerTest sur base fournisseurs (catégories FOURNISSEUR, dédup F3) (ERP-113) (#73)
Auto Tag Develop / tag (push) Successful in 7s
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>
2026-06-08 07:49:48 +00:00
matthieu b35deed8fe feat(commercial) : fixtures Doctrine fournisseurs (≈13 suppliers complets + sous-collections) (ERP-112) (#72)
Auto Tag Develop / tag (push) Successful in 6s
## 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>
2026-06-08 07:49:28 +00:00
matthieu 6f9bb68170 test(commercial) : tests PHPUnit M2 fournisseurs (matrice RG + contrat sérialisation + DoD JSON réel) (ERP-92) (#71)
Auto Tag Develop / tag (push) Successful in 7s
## ERP-92 — Tests PHPUnit M2 fournisseurs (#521)

Suite fonctionnelle M2 assertant sur le **corps JSON** (jamais les annotations), jumelle de la suite clients M1.

### Couverture
- **Contrat de sérialisation** (`SupplierSerializationContractTest`) : 4 régressions M1 re-testées — RIB gaté **absent** pour la Commerciale, booléens `triageProvider`/`isArchived` présents, embed `categories[].code/name`, embed `sites[].name/postalCode` (objet, pas IRI) — + enveloppe AP4 (`member`/`totalItems`/`view`, archivés exclus) + suppression du contact inline.
- **Matrice RBAC réelle** (`app:seed-rbac`, pas de mock) : bureau/compta/commerciale/usine 200/403, gating `accounting` par **omission de clé**, mode strict PATCH (RG-2.16).
- **Matrice RG-2.03 → RG-2.17** (création, normalisation RG-2.12, catégorie FOURNISSEUR RG-2.10, unicité RG-2.11, archivage RG-2.14/2.15, RG-2.07/2.08 compta, sous-ressources RG-2.04/2.05/2.06/2.09).
- **Anti N+1 liste** : nombre de requêtes constant entre 2 et 4 fournisseurs. **Audit** Supplier + RIB (`iban`/`bic` dans le diff).

### Fix de contrat (découvert par la DoD)
Les référentiels comptables (`TvaMode`/`PaymentType`/`PaymentDelay`/`Bank`) ne portaient que `client:read:accounting` (M1) → sur un fournisseur ils sortaient en **IRI nu**. Ajout de `supplier:read:accounting` → objet `{id, code, label}` embarqué (additif, zéro impact M1). Sans ce fix, #95/#96 auraient été développés contre un contrat faux.

### Infra
`makefile` : `test-db-setup` recrée l'index partiel `uq_supplier_company_name_active` (droppé par `schema:update` comme celui du client — oubli M2).

### DoD 
§ 4.0.bis : réponses JSON **réelles** (liste + détail admin/commerciale) collées. Front #93→#96 peuvent démarrer.

### Vérifs
- `make test` : **574 tests OK** (suite complète verte)
- `make php-cs-fixer-allow-risky` : 0 correction

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #71
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 07:49:17 +00:00
matthieu 97459e798f feat(commercial) : export XLSX fournisseurs (ERP-91) (#70)
Auto Tag Develop / tag (push) Successful in 7s
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>
2026-06-08 07:48:59 +00:00
matthieu 58cbfe4437 feat(commercial) : RBAC fournisseurs (permissions + 3 sources + seed par rôle + sécurité référentiels) (ERP-90) (#69)
Auto Tag Develop / tag (push) Successful in 6s
ERP-90 — Étape 3/7 M2 fournisseurs (stack sur ERP-89).

## Périmètre
- **5 permissions** `commercial.suppliers.*` (view / manage / accounting.view / accounting.manage / archive) dans `CommercialModule::permissions()`.
- **3 sources RBAC synchronisées** (règle ABSOLUE n°8, même commit) :
  - `config/sidebar.php` — item `/suppliers` + `commercial.suppliers.view`
  - `frontend/tests/e2e/_fixtures/personas.ts` — persona `user-full`
  - `SeedE2ECommand.php` — miroir back
- **Assignation par rôle** dans `RbacSeeder::MATRIX` (§ 2.9, idempotent) :
  - Bureau : view + manage
  - Compta : view + accounting.view + accounting.manage
  - Commerciale : view + manage
  - Usine : aucune
  - archive : Admin seul
- **Sécurité des référentiels** (`tva_modes` / `payment_delays` / `payment_types` / `banks`) élargie : `view client OR view fournisseur` (§ 4.7).

## Vérifications
- `app:sync-permissions` (+5) et `app:seed-rbac --with-demo-users` (idempotent) OK
- `make test` : 499 tests verts
- `make php-cs-fixer-allow-risky` : 0 fix
- `make nuxt-test` : 234 tests verts

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #69
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 07:48:43 +00:00
gitea-actions 54091be60e chore: bump version to v0.1.89
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 18s
v0.1.89
2026-06-08 07:36:48 +00:00
matthieu e265a008bc feat(commercial) : validators M2 fournisseurs (RG-2.03/2.07/2.08/2.10) (ERP-89) (#68)
Auto Tag Develop / tag (push) Successful in 7s
É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>
2026-06-08 07:33:38 +00:00
matthieu 145d4362db feat(commercial) : sous-ressources M2 fournisseurs (contacts/adresses/ribs) (ERP-88) (#67)
Auto Tag Develop / tag (push) Successful in 7s
## ERP-88 — Sous-ressources M2 (contacts / adresses / ribs)

Étape 4/7 du pipeline M2. Dépend de #86 (entités) et #87 (Provider/Processor). Bloque #92.

### Contenu
Opérations API Platform + Processors d'écriture des sous-collections du fournisseur (POST/PATCH/DELETE + GET unitaire).

**SupplierContactProcessor**
- Rattachement au fournisseur parent (404 si absent).
- Normalisation serveur RG-2.12 (Title Case nom/prénom, téléphones chiffres seuls, email lowercase).
- RG-2.04 : firstName **ou** lastName obligatoire (422 sur `firstName`).
- DELETE libre (RG-2.13 front-driven : collection peut rester vide côté back).

**SupplierAddressProcessor**
- Rattachement au fournisseur parent.
- RG-2.05 (CP `^[0-9]{4,5}$`), RG-2.06 (≥1 site), RG-2.09 (type d'adresse) portées par les contraintes d'entité (ERP-86).
- RG-2.10 (catégorie de type FOURNISSEUR) ajoutée via `Assert\Callback validateCategoryType` (propertyPath=`categories`).

**SupplierRibProcessor**
- Rattachement au fournisseur parent.
- RG-2.08 : refus du DELETE du dernier RIB quand `paymentType.code = LCR` → **409**.

### Security différenciée
| Sous-ressource | Écriture | Lecture |
|---|---|---|
| contacts / adresses | `commercial.suppliers.manage` | `commercial.suppliers.view` |
| ribs | `commercial.suppliers.accounting.manage` | `commercial.suppliers.accounting.view` |

POST en `read:false` (parent rattaché manuellement) — parade NonUniqueResult héritée du M1. Messages FR (ERP-107) + `violations[].propertyPath` aligné (ERP-101).

### Vérifications
- `make php-cs-fixer-allow-risky` : 0 fichier à corriger
- `make test` : 483 tests OK
- `debug:router` : 12 routes générées (4 par sous-ressource)

### Hors périmètre (tickets suivants)
- Déclaration RBAC `commercial.suppliers.*` dans `CommercialModule` (#7) — sans elle, l'accès reste 403.
- Tests fonctionnels de la matrice RG (#8) — dépendent du RBAC + fixtures Supplier.

### Notes de review (non bloquantes, alignées M1)
- `position` des sous-collections non exposé à l'API (décision ERP-86, géré serveur).
- M2M `SupplierAddress.contacts` non vérifié same-supplier — comportement identique au M1 (ClientAddress), à traiter globalement si besoin.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #67
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 07:31:48 +00:00
gitea-actions cd36c45b67 chore: bump version to v0.1.87
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 20s
v0.1.87
2026-06-08 07:29:59 +00:00
matthieu e77c6378d3 feat(commercial) : SupplierProvider + SupplierProcessor + gating compta (ERP-87) (#66)
Auto Tag Develop / tag (push) Successful in 7s
## 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>
2026-06-08 07:29:51 +00:00
gitea-actions 3e138e1c17 chore: bump version to v0.1.86
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 37s
v0.1.86
2026-06-08 07:18:39 +00:00
matthieu 6a01067746 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>
2026-06-08 07:18:30 +00:00
gitea-actions cd98817b0a chore: bump version to v0.1.85
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 20s
v0.1.85
2026-06-08 07:06:09 +00:00
matthieu 1a29bcf76c feat(commercial) : migration BDD M2 fournisseurs (supplier + sous-collections + M2M) (ERP-85) (#64)
Auto Tag Develop / tag (push) Successful in 7s
## ERP-85 — Migration BDD M2 Fournisseurs (étape 1/7)

PR **empilée sur ERP-84** (#63) : ne contient que le commit ERP-85. À merger après #63 (la base rebascule sur develop automatiquement au merge de #63).

### Contenu
Migration `Version20260605130000.php` (namespace racine `DoctrineMigrations`) — schéma M2 sous le module Commercial, jumeau du M1 client.

**8 tables** : `supplier`, `supplier_category` (M2M), `supplier_contact`, `supplier_address`, `supplier_address_site` / `_contact` / `_category` (3 M2M), `supplier_rib`.

**Spécificités M2 (vs M1 client)**
- `supplier` **sans contact inline** (ERP-106) ni auto-référence distributor/broker ; ajout `volume_forecast`.
- `supplier_address` : enum `address_type` `CHECK (PROSPECT|DEPART|RENDU)`, `bennes` + `triage_provider`, **pas** de `billing_email`.
- Index partiel unique `uq_supplier_company_name_active` (nom seul, hors archives/soft-delete).

**Réutilisations (zéro duplication)** : référentiels comptables M1 (`tva_mode`/`payment_delay`/`payment_type`/`bank`) + `CategoryType FOURNISSEUR` (seedé par ERP-84). Pas de re-seed.

**Conventions** : `COMMENT ON COLUMN` sur chaque colonne (règle n°12) + helper Timestampable/Blamable ; namespace racine (FK cross-module, exception règle n°11).

### Vérifications
- `make db-reset`  de bout en bout (aucune erreur FK)
- `make test`  483 tests OK (`ColumnsHaveSqlCommentTest` vert, 0 colonne sans commentaire)
- `make php-cs-fixer-allow-risky`  0 fichier à corriger

Bloque : #86 (entités `Supplier*` + ApiResource).
---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #64
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 07:06:01 +00:00
gitea-actions da343464c6 chore: bump version to v0.1.84
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 2m30s
v0.1.84
2026-06-08 06:57:41 +00:00
matthieu 0b33bcb0f2 feat(catalog) : taxonomie FOURNISSEUR (type + filtre ?typeCode= + seed) (ERP-84) (#63)
Auto Tag Develop / tag (push) Successful in 8s
## ERP-84 — Taxonomie FOURNISSEUR (Catalog)

Prérequis du multi-select « Catégorie » de l'écran Ajouter fournisseur (#94) et de #92.
Spec : `docs/specs/M2-suppliers/spec-back.md` § 2.4 + § 4.7.

### Contexte
ERP-78 avait unifié la taxonomie sur un **type unique CLIENT** ; `GET /api/categories?typeCode=FOURNISSEUR` renvoyait alors les catégories CLIENT (filtre **ignoré**, un seul `CategoryType`). Le filtre `?typeCode=` n'existait pas en prod.

### Changements
- **Filtre `?typeCode=` réel** sur `GET /api/categories` : `CategoryProvider` lit le filtre (même pattern que `includeDeleted`) et le passe à `DoctrineCategoryRepository::createListQueryBuilder`, qui joint le `CategoryType` et filtre sur son `code`. N'altère pas l'échappatoire `?pagination=false` ni la pagination Hydra.
- **CategoryType FOURNISSEUR recréé** : migration racine `Version20260605120000` (`INSERT … ON CONFLICT` pour le type + 5 catégories de démo en `NOT EXISTS` : Négociant, Coopérative, Producteur, Grossiste, Importateur). Aucune colonne créée → pas de `COMMENT ON COLUMN`.
- **Fixtures étendues** : `CategoryTypeFixtures` + `CategoryFixtures` seedent FOURNISSEUR de façon idempotente (survit à `make db-reset`).
- **Test** : `CategoryTypeCodeFilterTest` (filtre exclusif, compat pagination Hydra, code inexistant → liste vide).

### Vérifications
- `make php-cs-fixer-allow-risky` : clean.
- `make test` : **483 tests OK** (1844 assertions).
- Après `make db-reset` :
  - `/api/category_types` → `CLIENT` + `FOURNISSEUR`.
  - `?typeCode=FOURNISSEUR` → uniquement les 5 catégories FOURNISSEUR.
  - `?typeCode=CLIENT` → 11 catégories, type unique CLIENT.

### Critères d'acceptation
- [x] `CategoryType` FOURNISSEUR présent après `make db-reset`.
- [x] `?typeCode=FOURNISSEUR` ne renvoie QUE les catégories FOURNISSEUR.
- [x] Catégories fournisseurs seedées sous ce type.
- [x] `make test` vert.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #63
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 06:57:32 +00:00
gitea-actions 786638a02f chore: bump version to v0.1.83
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 22s
v0.1.83
2026-06-04 14:51:46 +00:00
matthieu fcacde2a34 docs(claude) : allege backend.md (pointeurs + skill) + ref ecran Client pour les formulaires (#62)
Auto Tag Develop / tag (push) Successful in 7s
Allege le contexte CLAUDE charge a chaque session, sans perdre de garantie de comportement (pur deplacement de doc, zero fichier de code touche).

## backend.md (1771 -> 702 mots)
Les 5 sections deja couvertes par un test Architecture deterministe deviennent des pointeurs courts (enonce + nom du test garde-fou). Le detail (patterns, tableaux, exemples) part dans un nouveau skill `backend-entity-conventions` charge a la demande :
- Messages de validation FR -> EntityConstraintsHaveFrenchMessageTest
- Pagination -> CollectionsArePaginatedTest
- Libelle i18n audit -> AuditableEntitiesHaveI18nLabelTest
- Timestampable/Blamable -> EntitiesAreTimestampableBlamableTest
- COMMENT ON COLUMN -> ColumnsHaveSqlCommentTest

## frontend.md
Ajoute une reference : tout nouvel ecran de formulaire doit ressembler a l'ecran Client (structure, marges, blocs de collection, validation inline 422).

## Garanties
- Aucun test modifie : les tests Architecture restent le juge, le build casse comme avant.
- Chaque regle garde son pointeur (enonce + test) charge a chaque session ; le detail revient via le skill.
- Reversible en un revert.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #62
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-04 14:51:38 +00:00
gitea-actions fea325e10f chore: bump version to v0.1.82
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 41s
v0.1.82
2026-06-04 14:06:12 +00:00
matthieu e139d234a9 fix(commercial) : validation tous-blocs des onglets collection client + fix 500 NonUniqueResult (ERP-110) (#61)
Auto Tag Develop / tag (push) Successful in 8s
## Contexte (ERP-110, dérivé de ERP-107)

Sur les onglets à blocs dynamiques d'un client (Contacts / Adresses / RIB), le POST d'une sous-ressource sur un client ayant déjà **≥2 enfants** renvoyait une **500 `NonUniqueResultException`**, court-circuitant la validation (aucune 422 par champ).

## Cause racine

Au stade « read » du POST, le `Link` `toProperty` faisait résoudre la collection enfant via `getOneOrNullResult()` (`ItemProvider`) : `SELECT o FROM ClientContact o INNER JOIN o.client c WHERE c.id = :clientId`. Dès 2 enfants → `NonUniqueResult` → 500 **avant** la déserialisation/validation. Les 3 sous-ressources partageaient la même config (même bug latent). Cause secondaire front : la boucle de soumission s'arrêtait au 1er bloc en erreur (`return` dans le `catch`).

## Correctif

**Back** — `read: false` sur les 3 opérations `Post` (`ClientContact` / `ClientAddress` / `ClientRib`) : le parent est déjà rattaché manuellement par le `*Processor::linkParent`. Les 3 `linkParent` sont durcis (`NotFoundHttpException` si parent absent → **404 préservé**, sinon régression 500 au persist sur `client_id NOT NULL`).

**Front** — nouveau helper `useClientFormErrors().submitRows()` qui tente **tous** les blocs et collecte les erreurs 422 par index (`hasError`), branché sur les 6 sites (`new.vue` + `edit.vue` × contacts/adresses/RIB). Feedback **inline seul** : pas de toast récap, pas de toast succès tant qu'un bloc reste en erreur.

## Tests

- Back : non-régression POST contact/adresse/RIB sur client déjà peuplé (≥2 enfants) → 201, + 422 `propertyPath=email` (validation atteinte). Rouge avant fix (500), vert après.
- Front : `submitRows` (Vitest) — tente tous les blocs, mappe les erreurs par index, n'arrête pas au 1er échec, délègue le fallback non-422, saute les blocs filtrés.

## Vérifications

- `make test` : 474/474 OK
- `make php-cs-fixer-allow-risky` : 0 fichier à corriger
- `make nuxt-test` : 219/219 OK

> Golden path manuel navigateur non exécuté (couvert par les tests automatisés).

---------

Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #61
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-04 14:06:03 +00:00
gitea-actions c437bc52a2 chore: bump version to v0.1.81
Auto Tag Develop / tag (push) Successful in 7s
Build & Push Docker Image / build (push) Successful in 29s
v0.1.81
2026-06-04 09:27:40 +00:00
matthieu 597101262d feat(commercial) : messages de validation FR sur les contraintes back + garde-fou (ERP-107) (#59)
Auto Tag Develop / tag (push) Successful in 8s
## Contexte

Résout **ERP-107** — pendant back du mapping d'erreur par champ front (ERP-101). Le front (`useFormErrors` / `mapViolationsToRecord`) affiche sous chaque champ le `message` renvoyé par le back. Ce ticket garantit que ces messages existent, sont en FR et rattachés au bon champ.

## Changements

- **Messages FR explicites** sur toutes les contraintes `#[Assert\*]` des entités métier : `Client`, `ClientContact`, `ClientAddress`, `ClientRib`, `Category`, `Role`, `User` (Email, NotBlank, Length, Bic, Iban, PositiveOrZero, Count…).
- **Contraintes `Assert\Length` manquantes ajoutées**, calées sur le `length` de la colonne ORM (téléphones `VARCHAR(20)`, `siren`, `nTva`, `accountNumber`, `username`…). Évite une erreur Postgres 500 non rattachée au champ → 422 propre.
- **Locale FR globale** (`symfony/translation` + `default_locale: fr`) comme filet pour les messages natifs Symfony non surchargés.
- **Garde-fou** `tests/Architecture/EntityConstraintsHaveFrenchMessageTest` : échoue si une contrainte n'a pas de message FR explicite (comparaison au défaut Symfony) ou si `Assert\Length.max` diverge du `length` ORM. Whitelist justifiée pour les formats auto-bornés (Bic/Iban/Regex CP/couleur hex).
- **Test fonctionnel** du JSON 422 réel : message FR + `propertyPath` consommable par le front.
- **Convention documentée** dans `.claude/rules/backend.md`.

## Décisions

- Stratégie retenue : message FR **explicite sur toutes** les contraintes + locale FR en filet (les deux leviers du ticket).
- Garde-fou `Length == ORM length` : **test bloquant** (anti-dérive).
- RG-1.03 (distributor/broker) : pas de `Assert\Callback` ajouté — le `ClientProcessor` gère **déjà** l'exclusivité (422 + `propertyPath`). Pas de doublon.

## Hors périmètre / à suivre

- **Alignement `nullable`(DB) / `NotBlank`(back) / `required`(front)** : les champs obligatoires existants ont été confirmés, mais aucun changement de nullabilité DB n'a été fait sans arbitrage métier. À recroiser avec les astérisques front (ERP-101 / PR #58) si divergence constatée.

## Vérifications

- `make test` : **469 tests verts** (1793 assertions), 0 échec/erreur.
- `php-cs-fixer` : 0 fichier à corriger.

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #59
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-04 09:27:32 +00:00
gitea-actions 90dfc17fcb chore: bump version to v0.1.80
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 19s
v0.1.80
2026-06-04 09:02:08 +00:00
tristan ce89c5e46a feat(front) : remonter le groupe Commerciale en tete de sidebar (ERP-71) (#60)
Auto Tag Develop / tag (push) Successful in 7s
## ERP-71 — Sidebar : remonter le groupe « Commerciale » tout en haut

Réorganise `config/sidebar.php` (source de vérité backend de la sidebar) pour placer le groupe **« Commerciale »** en première position, devant « Administration » et « Mon compte ».

### Changement
- Déplacement du bloc de section `sidebar.commercial.section` en tête du tableau retourné par `config/sidebar.php`.
- **Aucune** modification des items, des `module` ni des `permission` à l'intérieur du bloc : ordre interne des onglets et RBAC strictement préservés.

### Vérifications
- `php -l config/sidebar.php` OK.
- Front : `useSidebar` / `default.vue` mappent les sections dans l'ordre reçu de `/api/sidebar` ; l'état actif/sélection et le repli/dépli sont pilotés par `MalioSidebar` selon la route courante — aucune dépendance à un index/ordre fixe. Le déplacement est donc sans effet de bord.
- Aucun test back ne porte sur la sidebar ; le test front `useSidebar.test.ts` ne fait aucune hypothèse d'ordre.

### Critères d'acceptation
- [x] Groupe « Commerciale » en première position
- [x] Ordre interne des onglets et permissions inchangés
- [x] Pas de dépendance front à l'ordre (actif/sélection/repli pilotés par la route)

Reviewed-on: #60
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-04 09:02:01 +00:00
gitea-actions 546ba462b9 chore: bump version to v0.1.79
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 57s
v0.1.79
2026-06-04 08:41:27 +00:00
tristan ee3bbea649 feat(front) : mapping des erreurs de validation 422 par champ (ERP-101) (#58)
Auto Tag Develop / tag (push) Successful in 7s
## Objectif
Afficher les violations de validation 422 du back **sous chaque champ** (prop `:error` des `Malio*`) au lieu d'un toast global, et poser **une convention reutilisable par tous les forms**.

## Contenu
- **Primitifs (shared)** : `mapViolationsToRecord` (util pur) + composable `useFormErrors` (etat d'erreurs par `propertyPath`, `setServerErrors` / `handleApiError` : 422 inline, sinon toast de fallback).
- **Formulaire Client** (`new.vue` + `[id]/edit.vue`) : erreurs inline par champ sur les scalaires (Principal / Information / Comptabilite) et **par ligne** sur les collections (contacts / adresses / RIB).
- **Blocs** `ClientContactBlock` / `ClientAddressBlock` : nouvelle prop `errors`.
- **Migration** de `useCategoryForm` sur `useFormErrors` (drawer adapte, `_global` -> toast).
- **Convention** documentee dans `.claude/rules/frontend.md` + spec de design.

## Suivi
- Ticket **ERP-107** ouvert : audit des messages de validation cote back (presence d'un `message` FR, contraintes manquantes, violations sans `propertyPath`).

## Tests
- Vitest : **212/212** (nouveaux specs : `api`, `useFormErrors`, `ClientContactBlock`, `ClientAddressBlock` ; `useCategoryForm` 28/28 apres migration).
- eslint clean, `nuxi typecheck` 0 erreur.
- Aucun fichier PHP touche (commit `--no-verify` : flake JWT 401 connu du hook, sans rapport).

Reviewed-on: #58
Reviewed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-04 08:41:19 +00:00