Commit Graph

4 Commits

Author SHA1 Message Date
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
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
matthieu 120058049c test(commercial) : cover RG-1.01..1.29 except role-gated (M1) + polish stack (#38)
Auto Tag Develop / tag (push) Successful in 7s
Dernier wagon de la stack back M1. ERP-60 = polish stack + couverture de tests PHPUnit NON dépendante des rôles métier (cf. spec § 7 / § 8.1).

## Phase 0 — polish stack (déjà mergé dans les branches basses via rebase)
- ERP-59 : route sidebar `/clients` (au lieu de `/commercial/clients`), cohérente avec `/suppliers`.
- One-liner pagination Client abandonné : `pagination_client_enabled: true` est déjà le défaut global → `?pagination=false` marche déjà sur `/api/clients` (décision P7).

## Phase 1 — tests (combler les trous, zéro duplication)
8 nouvelles suites couvrant les RG non encore testées par ERP-55/56/57/58 :
- `ClientFormulaireMainTest` — RG-1.02 (téléphone secondaire, max 2).
- `ClientAddressTest` — RG-1.06/07/08 + RG-1.11 (CHECK BDD prospect/billing).
- `ClientUniquenessTest` — RG-1.15/1.17 (Q4 : SIREN/email NON uniques).
- `ClientArchiveTest` — **RG-1.23 : 409 restauration en conflit (gap P1)**.
- `ClientAuditTest` — RG-1.27 (created* figés / updatedBy modificateur) + iban/bic présents dans le diff audité.
- `ClientMigrationTest` — index partiel unique `uq_client_company_name_active` (1 seul) ; pas d'index siren/email.
- `ClientSecurityTest` — 401 anonyme + 403 sans `commercial.clients.view`.
- `ClientPatchStrictTest` — RG-1.28 (403 strict mix de groupes, fonctionnel).

Cahier de test complet (mapping de TOUTES les RG → test) : `docs/specs/M1-clients/cahier-test-back-M1.md`.

## Délégué à ERP-74 (#493)
Matrice RBAC différenciée (bureau/compta/commerciale/usine) + RG-1.04 fonctionnel — exigent les rôles métier seedés après le merge de la stack.

## Gaps documentés (cahier)
- RG-1.29 validation écriture (catégorie type sur adresse → 422) non implémentée back (hors § 8.1, ticket test-only).
- Violations CHECK adresse → rejet (≥400) sans mapping fin 422 (amélioration possible).

## Vérifs
`make db-reset && make php-cs-fixer-allow-risky && make test` → **421 tests OK, 1386 assertions, 0 risky**. Nouveaux tests : 17, 71 assertions.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #38
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-01 19:46:39 +00:00
matthieu b495e4030a [ERP-54] Créer les entités Client + sous-entités + référentiels (#29)
Auto Tag Develop / tag (push) Failing after 28s
## Contexte

Ticket Lesstime **#54** (1.1 / Backend / M) — spec `docs/specs/M1-clients/spec-back.md` § 3.4 / § 3.5.

> 🔗 **MR stackée sur ERP-53** — cible `feature/ERP-53-migrer-tables-client-m1`, **pas** `develop`. À repointer vers `develop` quand ERP-53 sera mergé (cf. `STACK-BRANCHES-PROCEDURE.md`). Le diff ne montre que les fichiers d'ERP-54.

## Contenu

**9 entités** (`src/Module/Commercial/Domain/Entity/`) :
- Métier : `Client`, `ClientContact`, `ClientAddress`, `ClientRib` — `#[Auditable]` + Timestampable/Blamable.
- Référentiels statiques lecture seule : `TvaMode`, `PaymentDelay`, `PaymentType`, `Bank` — whitelistés dans `EntitiesAreTimestampableBlamableTest::EXCLUDED`.

**8 repositories** interfaces (`Domain/Repository/`) + impl Doctrine (`Infrastructure/Doctrine/`).

> La spec § 3.5 ne définit que 8 entités (4 métier + 4 référentiels) ; pas de 9ᵉ entité malgré la formulation « 9 paires » du ticket.

## Décisions

- **Aucun `#[ApiResource]` dans ce ticket** : le bloc ApiResource du `Client` (§ 3.4) référence `ClientProvider`/`ClientProcessor` = périmètre **ERP-55**. L'inclure casserait `cache:clear`/`make test`/`schema:validate`. Les entités sont des entités Doctrine pures (ORM + Assert + Groups). Endpoints lecture seule des référentiels → ticket dédié.
- **Q4** : `Client` sans `#[ORM\UniqueConstraint]` — unicité du nom de société portée par l'index partiel Postgres `uq_client_company_name_active` (inexprimable en attribut ORM).
- **Audit RIB (29/05)** : aucun `#[AuditIgnore]` sur `ClientRib.iban`/`bic` (tous champs audités, audit admin-only).
- **Cross-module (règle n°1)** : M2M `Category` via le contrat `Shared\Domain\Contract\CategoryInterface` + `resolve_target_entities` (pas d'import direct Catalog→Commercial) ; `ClientAddress.sites` via `SiteInterface` existant.

## Infra nécessaire (découvert pendant le dev)

- `doctrine.yaml` : mapping ORM du module `Commercial` (mappings explicites par module) + résolution `CategoryInterface → Category`.
- `CommercialReferentialFixtures` **créée** (n'existait pas — ERP-53 avait seedé les CategoryType côté Catalog) : re-seed idempotent des 4 référentiels, sinon vidés au `db-reset` (désormais tables mappées).
- `ColumnCommentsCatalog` étendu (colonnes M1) pour le chemin `schema:update`/test — sinon `ColumnsHaveSqlCommentTest` (garde-fou n°12) échoue.
- Migration retrofit `Version20260528120000` (ERP-67) rendue résiliente (`$schema->hasTable()`) : elle rejouait tout le catalogue mais s'exécute avant la création des tables M1 → `relation tva_mode does not exist`. Conforme à son docblock (« les futures migrations posent leurs propres COMMENT »).
- `makefile test-db-setup` : recréation de l'index partiel `uq_client_company_name_active` (analogue de la ligne existante pour `category`).

## Vérifications

- `make php-cs-fixer-allow-risky` ✓
- `make db-reset` ✓ (bout en bout ; 4 référentiels + 4 CategoryType présents, 2 index partiels créés)
- `make test` ✓ **312/312** (Architecture vert, 0 régression M0)
- `doctrine:schema:validate` : Mapping **OK** ; « not in sync » = bruit cosmétique pré-existant du projet (clear COMMENT hors-ORM, drop index partiels, renommages d'index). Seul diff introduit : renommage cosmétique de l'index M2M `idx_client_category_category` (même colonne) — aucun écart de type/colonne/FK vs migration ERP-53.

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: Matthieu <contact@malio.fr>
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #29
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-01 15:20:22 +00:00