[docs] M1 — Répertoire clients : specs front + back #23

Merged
malio merged 6 commits from feature/M1-spec-clients into develop 2026-05-29 09:58:32 +00:00
2 changed files with 27 additions and 17 deletions
Showing only changes of commit 2c5fef2074 - Show all commits
+22 -12
View File
@@ -122,7 +122,7 @@ Tentative de doublon → `409 Conflict` géré par le `ClientProcessor` qui attr
Pattern Starseed standard : Pattern Starseed standard :
- `#[Auditable]` sur `Client`, `ClientContact`, `ClientAddress`, `ClientRib` - `#[Auditable]` sur `Client`, `ClientContact`, `ClientAddress`, `ClientRib`
- `#[AuditIgnore]` sur les champs sensibles : `ClientRib.iban`, `ClientRib.bic` - **Tous les champs sont auditables** (pas d'`#[AuditIgnore]`) — y compris `ClientRib.iban` et `ClientRib.bic`. Décision validée par Matthieu en revue de MR (29/05/2026) : l'audit étant **admin-only** côté Starseed, l'équipe a besoin de tracer les modifications RIB pour le suivi comptable et la conformité.
Outdated
Review

Pas d'auditIgnore sur cette partie. L'audit est accessible que par les admin et on va avoir besoin de ces infos.

Pas d'auditIgnore sur cette partie. L'audit est accessible que par les admin et on va avoir besoin de ces infos.
- Audit Many-to-Many automatique pour la collection `client.categories` (cf. doc audit-log) - Audit Many-to-Many automatique pour la collection `client.categories` (cf. doc audit-log)
### 2.6 Timestampable + Blamable ### 2.6 Timestampable + Blamable
@@ -138,10 +138,12 @@ Toutes les entités métier nouvelles implémentent `TimestampableInterface` + `
| `commercial.clients.view` | ✅ | ✅ | ✅ | ✅ | ❌ | | `commercial.clients.view` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `commercial.clients.manage` | ✅ | ✅ | ❌ | ✅ | ❌ | | `commercial.clients.manage` | ✅ | ✅ | ❌ | ✅ | ❌ |
| `commercial.clients.accounting.view` | ✅ | ❌ | ✅ | ❌ | ❌ | | `commercial.clients.accounting.view` | ✅ | ❌ | ✅ | ❌ | ❌ |
| `commercial.clients.accounting.manage` | ✅ | ❌ | | ❌ | ❌ | | `commercial.clients.accounting.manage` | ✅ | ❌ | | ❌ | ❌ |
| `commercial.clients.archive` | ✅ | ❌ | ❌ | ❌ | ❌ | | `commercial.clients.archive` | ✅ | ❌ | ❌ | ❌ | ❌ |
**Note** : Commerciale a `view` global mais **n'a pas** `accounting.view` → l'onglet Comptabilité est masqué pour ce rôle. Le filtre se fait à 2 niveaux : (a) API Platform `security` sur les opérations qui exposent les groupes `client:accounting:*` ; (b) front masque l'onglet via `usePermissions().has('commercial.clients.accounting.view')`. **Notes** :
- **Compta peut éditer l'onglet Comptabilité** (`accounting.manage`) d'un client existant — décision revue MR Matthieu 29/05, aligné avec le docx d'origine. Compta **ne peut pas créer** un client (pas de `manage` global) ni modifier les autres onglets.
- Commerciale a `view` global mais **n'a pas** `accounting.view` → l'onglet Comptabilité est masqué pour ce rôle. Le filtre se fait à 2 niveaux : (a) API Platform `security` sur les opérations qui exposent les groupes `client:accounting:*` ; (b) front masque l'onglet via `usePermissions().has('commercial.clients.accounting.view')`.
### 2.8 Validation incrémentale par onglet (workflow front-driven) ### 2.8 Validation incrémentale par onglet (workflow front-driven)
@@ -842,7 +844,7 @@ Cf. § 2.7 (matrice détaillée).
2. **`frontend/tests/e2e/_fixtures/personas.ts`** — attribuer les permissions : 2. **`frontend/tests/e2e/_fixtures/personas.ts`** — attribuer les permissions :
- Admin : `view` + `manage` + `accounting.view` + `accounting.manage` + `archive` - Admin : `view` + `manage` + `accounting.view` + `accounting.manage` + `archive`
- Bureau : `view` + `manage` - Bureau : `view` + `manage`
- Compta : `view` + `accounting.view` - Compta : `view` + `accounting.view` + `accounting.manage`
- Commerciale : `view` + `manage` - Commerciale : `view` + `manage`
- Usine : aucune - Usine : aucune
@@ -859,12 +861,10 @@ Synchronisation finale : `php bin/console app:sync-permissions`.
### 6.1 Audit complet via `#[Auditable]` ### 6.1 Audit complet via `#[Auditable]`
- `Client` : `#[Auditable]` (tous les champs sauf ceux marqués `#[AuditIgnore]`) - `Client` : `#[Auditable]` (tous les champs)
- `ClientContact` : `#[Auditable]` - `ClientContact` : `#[Auditable]`
- `ClientAddress` : `#[Auditable]` - `ClientAddress` : `#[Auditable]`
- `ClientRib` : - `ClientRib` : `#[Auditable]` sur l'entité, **tous les champs auditables y compris `iban` et `bic`** (décision Matthieu en revue MR 29/05 : l'audit étant admin-only, l'équipe a besoin de ces infos pour le suivi comptable et la conformité).
Outdated
Review

Comme vu plus haut, on laisse auditable ces champs

Comme vu plus haut, on laisse auditable ces champs
- `#[Auditable]` sur l'entité
- `#[AuditIgnore]` sur `iban` et `bic` (RGPD / sécurité)
L'audit M2M sur `client.categories` est automatique (cf. doc audit-log) — produit `{categories: {added: [3], removed: [7]}}`. L'audit M2M sur `client.categories` est automatique (cf. doc audit-log) — produit `{categories: {added: [3], removed: [7]}}`.
@@ -938,6 +938,14 @@ Cf. § 2.6. Pattern Shared standard.
- **RG-1.27** : Pattern Shared standard (cf. RG-1.15 à RG-1.17 du M0). Tous les actes (POST/PATCH/archive/RIB/contact/adresse) tracent `updatedAt` + `updatedBy`. `createdAt` + `createdBy` posés au POST initial. - **RG-1.27** : Pattern Shared standard (cf. RG-1.15 à RG-1.17 du M0). Tous les actes (POST/PATCH/archive/RIB/contact/adresse) tracent `updatedAt` + `updatedBy`. `createdAt` + `createdBy` posés au POST initial.
### PATCH mix de groupes (mode strict)
- **RG-1.28** : Si un PATCH contient des champs de **plusieurs groupes** de sérialisation et que l'utilisateur **n'a pas toutes les permissions** correspondantes, le `ClientProcessor` renvoie **403 Forbidden sur l'ensemble du payload** (mode strict — pas de filtrage silencieux). Le front est responsable de ne JAMAIS envoyer de champs hors-permission (les onglets masqués via `usePermissions()` ne génèrent pas de payload). Cette règle protège contre les appels API directs malveillants. Exemple : un Bureau qui envoie `{ "companyName": "...", "siren": "..." }` → 403, le message d'erreur précise « Champ `siren` requiert la permission `commercial.clients.accounting.manage` ».
### Catégorie sur ClientAddress (filtrage par type)
- **RG-1.29** : Le `<MalioSelectCheckbox>` Catégorie de l'onglet Adresse n'expose **que** les `Category` dont `categoryType.code IN ('SECTEUR', 'AUTRE')`. Les types `DISTRIBUTEUR` et `COURTIER` qualifient une **relation entre clients** (cf. RG-1.03) et n'ont pas de sens sur une adresse physique. Implémentation : `ClientAddressProvider` filtre côté serveur via paramètre de requête à l'endpoint `GET /api/categories?categoryType.code[]=SECTEUR&categoryType.code[]=AUTRE` (SearchFilter API Platform). Côté validation du POST/PATCH : si l'utilisateur tente de poster une catégorie de type DISTRIBUTEUR ou COURTIER sur une adresse → **422** avec violation `categories: "Type de catégorie non autorisé sur une adresse."`.
## 8. Tests à automatiser ## 8. Tests à automatiser
### 8.1 Cas à couvrir (back — PHPUnit) ### 8.1 Cas à couvrir (back — PHPUnit)
@@ -965,8 +973,10 @@ Cf. § 2.6. Pattern Shared standard.
- [ ] **RG-1.26** : GET liste → tri companyName ASC - [ ] **RG-1.26** : GET liste → tri companyName ASC
- [ ] **RG-1.27** : POST + PATCH → createdAt/createdBy figés, updatedAt/updatedBy mis à jour - [ ] **RG-1.27** : POST + PATCH → createdAt/createdBy figés, updatedAt/updatedBy mis à jour
- [ ] **RBAC** : Bureau, Commerciale, Compta sur chaque permission (matrice § 2.7) — 200/403 selon le verbe - [ ] **RBAC** : Bureau, Commerciale, Compta sur chaque permission (matrice § 2.7) — 200/403 selon le verbe
- [ ] **Compta accounting.view** : GET client retourne les champs accounting ; PATCH accounting par Compta → 403 - [ ] **Compta accounting.view + accounting.manage** : GET client retourne les champs accounting ; PATCH onglet accounting par Compta → 200 ; PATCH onglet info / contacts / adresses par Compta → 403
- [ ] **Audit** : POST + PATCH + archive → audit_log avec entity_type='Client', `changes` correct ; iban/bic absents du diff (AuditIgnore) - [ ] **Compta POST création** : Compta → 403 (pas de `manage` global)
- [ ] **PATCH mix groupes** : Bureau envoie payload avec `companyName` (write:main) + `siren` (write:accounting) → **403 sur tout le payload** (strict, RG-1.28)
- [ ] **Audit** : POST + PATCH + archive → audit_log avec entity_type='Client', `changes` correct ; **iban/bic présents dans le diff** (pas d'AuditIgnore, cf. § 6.1)
- [ ] **Migration** : `make db-reset` → schéma OK, seed des 4 référentiels + CategoryType (DISTRIBUTEUR/COURTIER/SECTEUR/AUTRE) présent ; index partiels présents - [ ] **Migration** : `make db-reset` → schéma OK, seed des 4 référentiels + CategoryType (DISTRIBUTEUR/COURTIER/SECTEUR/AUTRE) présent ; index partiels présents
### 8.2 Cas à couvrir (front — Vitest) ### 8.2 Cas à couvrir (front — Vitest)
@@ -994,9 +1004,9 @@ Cf. § 2.6. Pattern Shared standard.
- **HP-M2-7** : **Onglet Échanges** (timeline d'emails / appels). Placeholder blanc. - **HP-M2-7** : **Onglet Échanges** (timeline d'emails / appels). Placeholder blanc.
- **HP-M2-8** : **Restauration d'un client soft-deleted** (post-DELETE M2). Pas pertinent au M1. - **HP-M2-8** : **Restauration d'un client soft-deleted** (post-DELETE M2). Pas pertinent au M1.
- **HP-M2-9** : **Export CSV** (en plus du XLSX). À étudier si besoin métier. - **HP-M2-9** : **Export CSV** (en plus du XLSX). À étudier si besoin métier.
- **HP-M2-10** : **Compta en édition de l'onglet Comptabilité** (réintroduction de la ligne du tableau du `.docx` invalidée par décision Tristan 28/05). Demande explicite + décision archi à formaliser. - **HP-M2-10** : ~~Compta en édition de l'onglet Comptabilité~~**devenu nominal au M1** suite à revue MR Matthieu 29/05 (cf. § 2.7 et § 5.2). HP supprimé.
- **HP-M2-11** : **Périmètre Commerciale** (« consultation selon périmètre » — formulation floue du doc). Au M1, Commerciale voit **tous** les clients en consultation (sauf Comptabilité). Si besoin de cloisonner par portefeuille (un commercial ne voit que SES clients), à spec dédiée. - **HP-M2-11** : **Périmètre Commerciale** (« consultation selon périmètre » — formulation floue du doc). Au M1, Commerciale voit **tous** les clients en consultation (sauf Comptabilité). Si besoin de cloisonner par portefeuille (un commercial ne voit que SES clients), à spec dédiée.
- **HP-M2-12** : **Création par Compta limitée à l'onglet Comptabilité** (idem HP-M2-10 — invalidée au M1). - **HP-M2-12** : ~~Création par Compta limitée à l'onglet Comptabilité~~ — Compta n'a toujours pas le droit de **créer** un client au M1 (pas de `manage` global). Si demande métier future, à spec dédiée.
- **HP-M2-13** : **Référencement entrant** (autres modules ajoutent une FK `client_id`). Les modules Commandes, Factures, etc. ajouteront leurs FK quand ils arriveront. Aucun changement côté Client. - **HP-M2-13** : **Référencement entrant** (autres modules ajoutent une FK `client_id`). Les modules Commandes, Factures, etc. ajouteront leurs FK quand ils arriveront. Aucun changement côté Client.
- **HP-M2-14** : **Validation IBAN/BIC stricte côté serveur**. Au M1, validation Symfony standard `Assert\Iban` et `Assert\Bic` côté entité `ClientRib`. Pas de check externe (banque réelle, etc.). - **HP-M2-14** : **Validation IBAN/BIC stricte côté serveur**. Au M1, validation Symfony standard `Assert\Iban` et `Assert\Bic` côté entité `ClientRib`. Pas de check externe (banque réelle, etc.).
- **HP-M2-15** : **Validation SIREN stricte** (algorithme Luhn). Au M1, validation `Assert\Length(min: 9, max: 9)` + `Assert\Regex('/^\d{9}$/')` + algorithme Luhn dans un validator custom. - **HP-M2-15** : **Validation SIREN stricte** (algorithme Luhn). Au M1, validation `Assert\Length(min: 9, max: 9)` + `Assert\Regex('/^\d{9}$/')` + algorithme Luhn dans un validator custom.
+5 -5
View File
@@ -10,7 +10,7 @@ date_redaction: 2026-05-28
# === LIENS === # === LIENS ===
maquette_figma: "https://www.figma.com/design/jRYgT0T9c03VsEbjGhCwwS/Composants---Design-System?node-id=1132-31898" maquette_figma: "https://www.figma.com/design/jRYgT0T9c03VsEbjGhCwwS/Composants---Design-System?node-id=1132-31898"
regles_metier: [RG-1.01, RG-1.02, RG-1.03, RG-1.04, RG-1.05, RG-1.06, RG-1.07, RG-1.08, RG-1.09, RG-1.10, RG-1.11, RG-1.12, RG-1.13, RG-1.14, RG-1.15, RG-1.16, RG-1.17, RG-1.18, RG-1.19, RG-1.20, RG-1.21] regles_metier: [RG-1.01, RG-1.02, RG-1.03, RG-1.04, RG-1.05, RG-1.06, RG-1.07, RG-1.08, RG-1.09, RG-1.10, RG-1.11, RG-1.12, RG-1.13, RG-1.14, RG-1.15, RG-1.16, RG-1.17, RG-1.18, RG-1.19, RG-1.20, RG-1.21, RG-1.22, RG-1.23, RG-1.24, RG-1.25, RG-1.26, RG-1.27, RG-1.28, RG-1.29]
roles: [Admin, Bureau, Compta, Commerciale, Usine] roles: [Admin, Bureau, Compta, Commerciale, Usine]
lien_spec_back: ./spec-back.md lien_spec_back: ./spec-back.md
@@ -46,11 +46,11 @@ Permettre aux utilisateurs Starseed (selon rôle) de gérer le **répertoire des
|---|---|---|---| |---|---|---|---|
| **Admin** | ✅ Tout | ✅ Tout | ✅ | | **Admin** | ✅ Tout | ✅ Tout | ✅ |
| **Bureau** | ✅ Tout | ✅ Tout sauf onglet Comptabilité | ❌ | | **Bureau** | ✅ Tout | ✅ Tout sauf onglet Comptabilité | ❌ |
| **Compta** | ✅ Tout | ❌ (lecture seule) | ❌ | | **Compta** | ✅ Tout | ✅ Onglet Comptabilité uniquement | ❌ |
Outdated
Review

Le rôle compta peux modifier l'onglet compta.

Le rôle compta peux modifier l'onglet compta.
| **Commerciale** | ✅ Tout sauf Comptabilité | ✅ Tout sauf Comptabilité | ❌ | | **Commerciale** | ✅ Tout sauf Comptabilité | ✅ Tout sauf Comptabilité | ❌ |
| **Usine** | ❌ | ❌ | ❌ | | **Usine** | ❌ | ❌ | ❌ |
> **⚠ Décision validée par Tristan (28/05/2026)** : le rôle **Compta est en lecture seule** sur l'ensemble du module clients, y compris l'onglet Comptabilité. Le tableau d'origine du `.docx` indiquait « Compta = Ajout / Modification : Onglet Comptabilité uniquement » — cette ligne est **invalidée** par cette spec. Si un besoin métier d'édition apparaît plus tard, une décision archi dédiée sera prise (cf. HP-X de [`spec-back.md`](./spec-back.md)). > **Note** : aligné sur le docx d'origine — Compta édite uniquement l'onglet Comptabilité (champs SIREN / TVA / Délai de règlement / Type de règlement / Banque / RIBs). Compta ne peut pas **créer** un client (pas de droit `manage` général), mais peut éditer la partie comptable d'un client existant créé par Admin ou Bureau.
## Navigation ## Navigation
@@ -150,7 +150,7 @@ Saisir une ou plusieurs adresses du client, rattachées à un ou plusieurs sites
| **Prospect** | `<MalioCheckbox>` | Non | RG-1.06 — masque Adresse de livraison + Facturation si coché | | **Prospect** | `<MalioCheckbox>` | Non | RG-1.06 — masque Adresse de livraison + Facturation si coché |
| **Adresse de livraison** | `<MalioCheckbox>` | Non | RG-1.07 — masque Prospect si coché | | **Adresse de livraison** | `<MalioCheckbox>` | Non | RG-1.07 — masque Prospect si coché |
| **Facturation** | `<MalioCheckbox>` | Non | RG-1.08 — masque Prospect si coché ; affiche le champ Email (RG-1.11) | | **Facturation** | `<MalioCheckbox>` | Non | RG-1.08 — masque Prospect si coché ; affiche le champ Email (RG-1.11) |
| **Catégorie** | `<MalioSelectCheckbox>` (multi) | Oui | Liste des `Category` | | **Catégorie** | `<MalioSelectCheckbox>` (multi) | Oui | Liste des `Category` de **type SECTEUR + AUTRE** uniquement (cf. décision Q5 — DISTRIBUTEUR et COURTIER qualifient une relation entre clients, pas un lieu) |
| **Pays** | `<MalioSelect>` | Oui | Préremplie « France » | | **Pays** | `<MalioSelect>` | Oui | Préremplie « France » |
| **Code postal** | `<MalioInputText>` (masque numérique) | Oui | RG-1.09 — déclenche autocomplete ville via BAN | | **Code postal** | `<MalioInputText>` (masque numérique) | Oui | RG-1.09 — déclenche autocomplete ville via BAN |
| **Ville** | `<MalioSelect>` | Oui | RG-1.09 — alimentée par api-adresse.data.gouv.fr suivant le CP | | **Ville** | `<MalioSelect>` | Oui | RG-1.09 — alimentée par api-adresse.data.gouv.fr suivant le CP |
@@ -270,7 +270,7 @@ Le composant `Code postal` + `Ville` + `Adresse` est branché sur **api-adresse.
|---|---|---| |---|---|---|
| 1 | Catégorie en multi-select non clarifiée (1 ou n par client) | **M2M `client_category`** validée. CategoryType seedé avec `DISTRIBUTEUR`, `COURTIER`, `SECTEUR`, `AUTRE` (HP-3 du M0 levé). | | 1 | Catégorie en multi-select non clarifiée (1 ou n par client) | **M2M `client_category`** validée. CategoryType seedé avec `DISTRIBUTEUR`, `COURTIER`, `SECTEUR`, `AUTRE` (HP-3 du M0 levé). |
| 2 | Distributeur / Courtier : liste de quoi ? | **Auto-référence Client** via 2 FK nullables `distributor_id` et `broker_id` (cf. RG-1.03). Une seule des deux est remplie à la fois. | | 2 | Distributeur / Courtier : liste de quoi ? | **Auto-référence Client** via 2 FK nullables `distributor_id` et `broker_id` (cf. RG-1.03). Une seule des deux est remplie à la fois. |
| 3 | Onglet « Comptabilité » : qui édite ? | **Admin uniquement au M1.** Compta lecture seule (décision validée par Tristan 28/05). Bureau / Commerciale ne voient pas l'onglet. | | 3 | Onglet « Comptabilité » : qui édite ? | **Admin et Compta** peuvent éditer l'onglet Comptabilité (`commercial.clients.accounting.manage`). Bureau / Commerciale ne voient pas l'onglet. Compta ne peut pas créer un client (pas de `manage` global), mais peut éditer la partie comptable d'un client existant. |
Outdated
Review

Le rôle compta peut éditer l'onglet.

Le rôle compta peut éditer l'onglet.
| 4 | Workflow par onglet | **Sauvegarde incrémentale**. POST formulaire principal crée le `Client` (status implicite « actif »). Chaque onglet validé = PATCH partiel par groupe de sérialisation dédié. Pas d'état « draft ». | | 4 | Workflow par onglet | **Sauvegarde incrémentale**. POST formulaire principal crée le `Client` (status implicite « actif »). Chaque onglet validé = PATCH partiel par groupe de sérialisation dédié. Pas d'état « draft ». |
| 5 | Onglets « À venir » | **Placeholders blancs** (frames vides, pas de message). Ré-activables sans rebuild quand les modules associés arriveront. | | 5 | Onglets « À venir » | **Placeholders blancs** (frames vides, pas de message). Ré-activables sans rebuild quand les modules associés arriveront. |
| 6 | Archive vs soft delete | **Flag `is_archived` séparé de `deleted_at`**. Archive ≠ delete : un client archivé est masqué par défaut mais reste en BDD éditable (Admin seul). Filtres UI distincts. Soft delete = HP M2. | | 6 | Archive vs soft delete | **Flag `is_archived` séparé de `deleted_at`**. Archive ≠ delete : un client archivé est masqué par défaut mais reste en BDD éditable (Admin seul). Filtres UI distincts. Soft delete = HP M2. |