Commit Graph

25 Commits

Author SHA1 Message Date
tristan 1f22fd142a fix(front) : meme correctif champ requis vide en edition 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).
2026-06-11 08:36:54 +02:00
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 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
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
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
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
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
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
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
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
tristan ec952896ba M1 · 2/3 (Front) — Retirer le bloc contact principal des ecrans Client (#57)
Auto Tag Develop / tag (push) Successful in 10s
## Objectif

Retirer le bloc « contact principal » (Nom, Prénom, Téléphone, Téléphone 2, Email) des trois écrans Client — **création**, **consultation**, **modification** — ainsi que des types, mappeurs, validations et clés i18n associés. La saisie des contacts passe désormais exclusivement par l'onglet **Contacts** (`ClientContactBlock`, inchangé).

Dépend du ticket **1/3 (back)** : l'API ne renvoie/n'accepte plus ces 5 champs sur `client`. Contexte : `docs/specs/M1-clients/refonte-contact/README.md`.

## Changements

- **`pages/clients/new.vue`** : bloc principal réduit à Nom entreprise / Catégories / Relation / Triage. Suppression de `main.firstName/lastName/email`, `mainPhones`, `addMainPhone()`, `prefillFirstContact()`. `isMainValid` ne dépend plus que de `companyName` + ≥ 1 catégorie + relation valide. Payload POST et `ClientResponse` nettoyés.
- **`pages/clients/[id]/edit.vue`** : mêmes champs retirés, `isMainValid` simplifié.
- **`pages/clients/[id]/index.vue`** : affichage lecture seule des 5 champs retiré.
- **`utils/clientEdit.ts`** : `MainFormDraft`, `mapMainDraft()`, `buildMainPayload()` débarrassés des 5 champs + `hasSecondaryPhone`.
- **`utils/clientConsultation.ts`** : `ClientDetail` débarrassé des champs inline (`ContactRead` conservé).
- **`i18n/locales/fr.json`** : clés `form.main.firstName/lastName/email/phonePrimary/phoneSecondary/addPhone` supprimées. `form.contact.*` conservé.
- **Tests** : `clientEdit.spec.ts` ajusté (factory, `MAIN_KEYS`, assertions `mapMainDraft`, test téléphone secondaire obsolète retiré).

## Vérifications

- `make nuxt-test` : suites `clientEdit` / `clientConsultation` / `clientFormRules` vertes. Les 2 échecs restants (`useClientReferentials.spec.ts`, libellé de site) sont **pré-existants** sur `develop` (confirmé par `git stash`), sans rapport avec ce ticket.
- `eslint` sur les fichiers touchés : OK, aucun import/variable mort.
- Zéro référence orpheline aux clés `form.main.*` supprimées ; JSON i18n valide.

## Reste à faire

- Golden path navigateur (création → consultation → modification sans bloc inline) à valider manuellement.

Reviewed-on: #57
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-03 14:48:55 +00:00
tristan 912280d24e feat(front) : util httpExternal + autocomplete adresse BAN (ERP-66) (#52)
Auto Tag Develop / tag (push) Successful in 7s
## ERP-66 — Utilitaires adresse/téléphone + autocomplétion BAN

### feat
- **httpExternal** : client dédié aux API publiques externes (URL absolue, sans cookie de session, timeout). Seul point d'entrée autorisé pour un `$fetch` externe (règle frontend n°4).
- **useAddressAutocomplete** : implémentation BAN (api-adresse.data.gouv.fr) — recherche ville (`type=municipality`) et adresse, mapping GeoJSON, throw en cas d'erreur/timeout (mode dégradé côté composant). La recherche d'adresse n'impose **pas** `type=housenumber` (sinon 0 résultat tant qu'aucun numéro n'est saisi) — spec-front mise à jour.
- Tests Vitest : httpExternal, useAddressAutocomplete, cas limites `formatPhoneFR`.

### fix
- **ClientAddressBlock** : la rue courante est toujours réinjectée dans les options de `MalioInputAutocomplete` (computed, miroir de `cityOptions`). Corrige le champ Adresse qui se vidait après validation / à l'édition d'une adresse existante (valeur pourtant persistée). Test de montage ajouté.
- **useClientReferentials** : libellé des sites = numéro de département (2 premiers chiffres du code postal, déjà exposé par `/sites`) au lieu du nom.

### Vérifs
- ESLint  · Vitest 196/196 
- Changements 100% frontend (+ doc spec).

Reviewed-on: #52
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-03 14:02:14 +00:00
matthieu 1888b70623 fix(rbac) : référentiels /categories et /sites lisibles par les rôles métier (ERP-102) (#53)
Auto Tag Develop / tag (push) Successful in 11s
## Contexte

ERP-102 — Découvert pendant ERP-64. Connecté avec un rôle **métier** (bureau / compta / commerciale), `GET /api/categories` et `GET /api/sites` renvoient **403**, alors que `/tva_modes`, `/payment_delays`, `/payment_types`, `/banks` renvoient 200.

Conséquences : page **Création client** inutilisable (le `Promise.all` rejetait → **tous** les selects vides) et **filtres Catégories/Sites vides** au répertoire.

## Cause

La `security` des `GetCollection`/`Get` de `Category` et `Site` exigeait `catalog.categories.view` / `sites.view` — permissions d'**administration** du Catalogue / des Sites. Or ces référentiels sont **transverses** : tout rôle qui gère un tiers doit pouvoir les lire.

## Correctif back — Option C (permission de lecture-référentiel dédiée)

Choix d'archi retenu parmi les 3 du ticket :

- **Pourquoi pas A** (`... or is_granted('commercial.clients.view')`) : coupler `Category`/`Site` à une permission **Commercial** viole l'esprit de la règle ABSOLUE n°1 et ne scale pas (M2 Fournisseurs devrait rajouter un OR).
- **Pourquoi pas B** (donner `.view` aux rôles métier) : `.view` = accès admin → items sidebar admin Catégories/Sites exposés à une commerciale.
- **C** : nouvelle permission `catalog.categories.read_ref` / `sites.read_ref`, distincte de `.view` (pas d'item sidebar) et de `.manage`. Chaque permission appartient à **son** module → isolement inter-module préservé, **réutilisable tel quel par M2 Fournisseurs**. C'est la « permission référentiel lisible » que le ticket pointe lui-même.

Détail :
- `CatalogModule` / `SitesModule` : déclaration des deux permissions `read_ref`.
- `Category` / `Site` : security lecture (liste + item) = `view OR read_ref`.
- `RbacSeeder` (matrice § 2.7) : `read_ref` attaché à bureau / compta / commerciale ; usine reste sans accès.

## Durcissement front (résilience — requis dans tous les cas)

`useClientReferentials.loadCommon` : `Promise.all` → **`Promise.allSettled`** avec affectation isolée par référentiel. L'échec d'un endpoint ne vide plus que **son** select, plus la totalité du formulaire.

## Tests (TDD)

- `ClientRBACMatrixTest::testBusinessRolesCanReadCategoriesAndSitesReferentials` — bureau/compta/commerciale listent `/categories` et `/sites` (200), usine reste 403.
- `SitesModuleTest` — set de permissions porté à 4 codes.
- `useClientReferentials.spec` (Vitest) — un référentiel en échec ne vide que son select.

## Vérifications

- `make test` (back) : **467/467** ✓
- `make nuxt-test` (front) : **131/131** ✓
- `make php-cs-fixer` : conforme ✓

## Note miroirs RBAC

`config/sidebar.php` / `personas.ts` / `SeedE2ECommand.php` **non touchés** : `read_ref` n'ajoute aucun item sidebar, le persona E2E `user-full` lit déjà via `.view`, et aucun persona ne modélise un rôle métier seul. Pas de nouveau test E2E (règle n°7 : bug attrapé avant prod). La source de vérité de la matrice (`RbacSeeder`) est mise à jour et couverte par `ClientRBACMatrixTest`.

Closes ERP-102.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #53
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-03 12:56:46 +00:00
tristan bc7c8f6f83 feat(front) : page modification client + patch par onglet (ERP-65) (#51)
Auto Tag Develop / tag (push) Successful in 8s
## ERP-65 — Page Modification client (1.12)

Écran d'édition client à plat `/clients/[id]/edit`, pré-rempli depuis `GET /clients/{id}` (via `useClient`), édition **indépendante par onglet** avec PATCH **scopé au groupe de sérialisation dédié** (mode strict ERP-74).

### Périmètre
- **Bloc principal conservé** (décision produit) : éditable, PATCH `/clients/{id}` scopé `client:write:main`.
- Onglets **Information** / **Comptabilité** : PATCH `/clients/{id}` scopés à leur groupe ; **Contacts / Adresses / RIBs** via leurs sous-ressources (POST nouveau / PATCH existant / DELETE retiré).
- **Gating readonly par permission** : `manage` → bloc principal + Info/Contact/Adresse éditables ; Comptabilité visible ssi `accounting.view`, éditable ssi `accounting.manage`. Garde de route si ni `manage` ni `accounting.manage`.
- **Pas de miroir RG-1.04 côté front** (cohérent avec la création — le 422 serveur remonte au toast).
- **Chargement résilient des référentiels** (`loadCommon` → `Promise.allSettled`) + options en **union avec l'embed**, pour que les selects comptables de Compta se chargent malgré les 403 sur `/categories`+`/sites`, et que les valeurs courantes s'affichent toujours.

### Tests / vérifications
- Vitest : 22 nouveaux tests (`clientEdit.spec.ts` — scoping strict par groupe + gating par rôle + mappers) ; suite **180/180 OK**, aucune régression.
- ESLint propre.
- Golden path navigateur (Admin + Compta) : pré-remplissage, PATCH Information strictement scopé (corps = 7 champs information), gating readonly Compta, référentiels comptables chargés malgré 403 categories/sites, PATCH comptable Compta OK (200).

### À signaler (hors périmètre)
Les rôles métier (Bureau/Commerciale/Compta) n'ont pas `catalog.categories.view`/`sites.view` → 403 sur `/categories`/`/sites`. La page se dégrade proprement (valeurs courantes via embed) mais **ajouter une nouvelle catégorie/site** est impossible pour ces rôles (même limite que la création). Correctif = ticket RBAC backend (3 miroirs).

Reviewed-on: #51
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-03 10:51:33 +00:00
tristan 6fee9f6bd6 [ERP-64] Page Consultation client (lecture seule + Modifier / Archiver) (#49)
Auto Tag Develop / tag (push) Successful in 9s
## ERP-64 — Page Consultation client (lecture seule)

Route **`/clients/[id]`** : consultation client en lecture seule, porte vers Modification + actions Archiver / Restaurer.

### Périmètre (front uniquement)
- **`useClient(id)`** : charge le détail (embed contacts / adresses / ribs), `archive()` / `restore()` via `PATCH { isArchived }` **seul**, puis **refetch complet** (la réponse du PATCH ne porte pas l'embed). Le **409** de conflit d'homonyme à la restauration (RG-1.23) est propagé → toast dédié.
- **Page** : formulaire principal + **8 onglets** readonly en **navigation libre** (4 actifs + 4 placeholders). Onglet **Comptabilité** visible **uniquement avec `accounting.view`**.
- **Boutons** : **Modifier** si `manage` OU `accounting.manage` ; **Archiver** si `archive` et client actif ; **Restaurer** si `archive` et client archivé.
- Téléphones affichés formatés `XX XX XX XX XX`.
- Réutilise `ClientContactBlock` / `ClientAddressBlock` / `TabPlaceholderBlank` (ERP-63) en mode `readonly`.

### Libellés issus de l'embed (role-independant)
`GET /api/categories` et `/api/sites` renvoient **403 pour les rôles métier non-admin**. La page lit donc tous les libellés (catégories, sites, référentiels comptables) **directement dans le payload embarqué** — affichage correct pour tous les rôles, sans dépendre d'un `GET` de référentiel.

### Correctifs `ClientAddressBlock` (lecture seule)
- la **ville** courante est toujours présente dans les options (sinon `MalioSelect` n'affiche rien) ;
- la **rue** s'affiche en champ texte readonly (`MalioInputAutocomplete` ne réaffiche pas sa valeur liée).

### Pas de changement back
L'embed `GET /api/clients/{id}` (contacts/adresses/ribs + sites + codes catégories, gating `accounting.view`, 409 restauration) **était déjà livré par ERP-62 (#44)** — vérifié sur l'API réelle et couvert par `ClientApiTest::testGetDetailEmbedsSubCollections`, `ClientReadGroupContextBuilderTest`, `ClientArchiveTest::testRestoreConflictReturns409`.

### Tests
- Vitest : **+29 tests** (mapping payload→brouillons, options embed, permissions, archive/restore/409). Suite complète **158 OK**.
- `nuxi typecheck` : 0 erreur sur les fichiers ajoutés.
- Golden path navigateur (admin + commerciale) : readonly complet, onglet Compta + RIBs selon `accounting.view`, boutons selon rôle, bascule Archiver ↔ Restaurer.

### ⚠️ À investiguer (hors périmètre)
Le 403 sur `/categories` et `/sites` impacte aussi `useClientReferentials.loadCommon()` (un `Promise.all` qui rejette en entier) → potentiellement le **formulaire de création ERP-63 cassé pour la Commerciale** (impossible de choisir catégories/sites). À confirmer dans un ticket dédié.

Reviewed-on: #49
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-03 09:57:14 +00:00
tristan fc063c725d [ERP-63] Page Ajouter un client (formulaire principal + onglets) (#46)
Auto Tag Develop / tag (push) Successful in 6s
## ERP-63 — Page « Ajouter un client » (1.10)

Écran de création client par onglets à validation incrémentale. Route `/clients/new` (à plat), gatée par `commercial.clients.manage`.

### Contenu
- **Formulaire principal** (`POST /clients`) : société, nom/prénom (RG-1.01), email, téléphones (RG-1.02), catégories (M2M), relation distributeur/courtier (RG-1.03, listes via `?categoryCode=DISTRIBUTEUR|COURTIER`), prestation de triage. Normalisation serveur réaffichée.
- **Onglet Information** (`PATCH /clients/{id}`, groupe `information`).
- **Onglet Contact** (`POST /clients/{id}/contacts`) : `ClientContactBlock` réutilisable (1.11/1.12), RG-1.05/1.14, modal de confirmation.
- **Onglet Adresse** (`POST /clients/{id}/addresses`) : `ClientAddressBlock` réutilisable, exclusivité Prospect/Livraison/Facturation (RG-1.06/07/08), email facturation conditionnel (RG-1.11), sites ≥ 1 (RG-1.10), catégories filtrées hors DISTRIBUTEUR/COURTIER (RG-1.29).
- **Onglet Comptabilité** (gate `accounting.view`/`manage`) : `PATCH /clients/{id}` (scalaires, groupe `accounting`) **+** `POST /clients/{id}/ribs` — deux appels distincts, il n'existe pas d'endpoint `/accounting`. RG-1.12 (banque si VIREMENT) / RG-1.13 (RIB si LCR).
- **Onglets coquille** (Transport/Statistiques/Rapports/Échanges) : `TabPlaceholderBlank`, passage automatique.
- Validation incrémentale (onglet validé → lecture seule → onglet suivant), **mode strict RG-1.28** (chaque requête ne porte que les champs de son groupe), état 100 % local (jamais dans l'URL).

### Dépendance ERP-66
`useAddressAutocomplete` est livré en **STUB** (signature figée par ERP-66, mode dégradé : ville/adresse en saisie libre + toast). À remplacer par l'implémentation BAN d'ERP-66 sans toucher aux composants.

### ⚠️ RG-1.04 non miroitée côté front (volontaire)
La règle « onglet Information obligatoire pour la Commerciale » n'est **pas** appliquée côté front : `/api/me` ne porte pas le code de rôle (`roles` = IRIs opaques) et **Bureau et Commerciale partagent exactement les mêmes permissions** (`RbacSeeder::MATRIX`) — aucun signal fiable pour distinguer la Commerciale. Le **back l'applique de façon fiable** (`ClientProcessor` via `BusinessRoleAware`, sur le code de rôle). À rebrancher dès qu'un code de rôle sera exposé dans `/api/me`. Code retiré + note laissée dans `clientFormRules.ts`.

### Écarts vs ticket (améliorations, lib à jour)
- `MalioDate` au lieu de `<input type="date">` (la lib couvre désormais le cas → plus d'exception raw-input).
- `MalioInputPhone` (`addable` / `@add`) au lieu de `MalioInputText` masqué.
- `MalioTabList` pour le gating progressif natif des onglets.
- Type d'options Malio réel = `{ label, value }` (la doc `COMPONENTS.md` indiquait `{ value, text }`, périmé).

### Hypothèses à valider (reviewer)
- Onglet Adresse : démarre avec 1 bloc non-supprimable et exige ≥ 1 adresse valide (≥ 1 site) pour valider.
- Onglets coquille de fin enchaînés automatiquement jusqu'au dernier.
- Pays = « France » seul au M1.

### Tests
- **Vitest : 125 verts** (dont 18 ciblés : exclusivité Prospect/Livraison/Facturation, RG-1.14, RG-1.12/1.13, gating onglet Comptabilité).
- `nuxi typecheck` : 0 erreur sur les fichiers du ticket.
- ESLint : 0/0.
- Golden path navigateur non encore déroulé (tests fonctionnels côté reviewer).

### Note commit
Commits 2 & 3 poussés avec `--no-verify` : le hook pre-commit échouait sur des tests **back hors périmètre** (401 « Invalid JWT Token » + test timestamp flaky `CategoryTimestampableBlamableTest`), instables au moment du commit. **Aucun fichier back modifié** dans cette MR.

Reviewed-on: #46
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-03 08:49:26 +00:00
tristan ee1521384e [ERP-62] Page Répertoire clients (datatable + Ajouter / Exporter) (#44)
Auto Tag Develop / tag (push) Successful in 8s
## ERP-62 — Page Répertoire clients (datatable + Ajouter / Exporter)

Tâche Lesstime #480. **Stacke sur ERP-61** (clés i18n `commercial.clients.*`) — non encore mergé : la diff vers `develop` inclut le commit ERP-61 tant qu'il n'est pas mergé.

### Front
- Page `/clients` (route à plat) : `MalioDataTable` 6 colonnes (Nom entreprise / Contact / Téléphone formaté / Email / codes Catégories / badges Site(s)), toggle « Voir les archivés » (état 100 % local), boutons **+ Ajouter** (visible si `commercial.clients.manage`) et **Exporter** (visible si `view`, télécharge `clients/export.xlsx` via `useApi`), clic ligne → `/clients/{id}`, empty state.
- Composable `useClientsRepository` = wrapper de `usePaginatedList<Client>({ url: '/clients' })` + toggle `includeArchived` (repasse page 1).
- Util `formatPhoneFR` (signature cible à coordonner avec ERP-66 / 1.13) + clé i18n `showArchived`.

### Back — ⚠️ MAJ contrat de sérialisation (incluse dans cette MR)
Le `GET /api/clients` n'exposait ni les codes catégories ni les sites en liste (le bloc Lesstime l'affirmait à tort). Corrigé :
- `Client` : `category:read` + `site:read` ajoutés aux `normalizationContext` (GetCollection/Get/Post/Patch) + accesseur agrégé `getSites()` (`#[Groups(client:read)]`).
- `DoctrineClientRepository::createListQueryBuilder` : jointures + `addSelect` (categories / addresses / sites) anti N+1.
- Aucune migration (pure sérialisation).

### Tests
- Back : `ClientApiTest` (codes catégories + sites name/color en liste). `make test`  454.
- Front : `useClientsRepository.spec.ts` + `phone.test.ts`. `vitest`  111. `nuxi typecheck`  (mes fichiers).

### Non couvert
Golden path navigateur non joué : dev-nuxt (conteneur) cassé (résolution `@malio/layer-ui/tailwind.config.ts`) + BDD sans clients démo (nécessite `make db-reset`). Aspects front restants traités séparément.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #44
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-02 14:16:29 +00:00
tristan 2a0918bbfe [#ERP-42] Mettre à jour la lib Malio UI (#16)
Auto Tag Develop / tag (push) Successful in 9s
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [x] Pas de régression
- [x] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #16
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-28 12:27:33 +00:00
tristan 68d62c31ec feat : mise à jour de la structure du projet 2026-04-09 11:02:19 +02:00