docs(commercial) : Q4 unicité limitée au nom de société + fix onglet compta
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled

This commit is contained in:
Matthieu
2026-05-29 11:54:57 +02:00
parent 2c5fef2074
commit 9618974b70
2 changed files with 17 additions and 23 deletions
+14 -20
View File
@@ -108,13 +108,13 @@ Le M1 expose **uniquement le mécanisme Archive**. Le soft delete reste prépar
- PATCH `{ "isArchived": true }` archive ; PATCH `{ "isArchived": false }` restaure.
- Les unicités métier (SIREN, nom, email) ignorent les archivés ET les soft-deletés (cf. § 3.5).
### 2.4 Unicité partielle Postgres — 3 contraintes sur Client
### 2.4 Unicité partielle Postgres — 1 contrainte sur Client
Index uniques partiels (`WHERE is_archived = false AND deleted_at IS NULL`) sur :
> **Décision Q4 (29/05/2026, Matthieu)** : l'unicité métier porte **uniquement sur le nom de société**. Le SIREN et l'email principal ne sont **pas** uniques (un même email peut servir plusieurs clients ; un SIREN peut être partagé entre établissements).
1. `LOWER(company_name)` — unicité du nom d'entreprise
2. `siren` — unicité du SIREN
3. `LOWER(email)` — unicité de l'email principal
Index unique partiel (`WHERE is_archived = false AND deleted_at IS NULL`) sur :
1. `LOWER(company_name)` — unicité du nom d'entreprise (case-insensitive, parmi non-archivés et non soft-deletés)
Tentative de doublon → `409 Conflict` géré par le `ClientProcessor` qui attrape la `UniqueConstraintViolationException`.
@@ -346,19 +346,13 @@ CREATE INDEX idx_client_broker_id ON client(broker_id);
CREATE INDEX idx_client_created_by ON client(created_by);
CREATE INDEX idx_client_updated_by ON client(updated_by);
-- Unicités métier (partielles : on ignore archives + soft-delete)
-- Unicité métier (partielle : on ignore archives + soft-delete)
-- Décision Q4 (29/05/2026) : unicité sur le nom de société UNIQUEMENT.
-- SIREN et email NE SONT PAS uniques (pas d'index uq_client_siren_active ni uq_client_email_active).
CREATE UNIQUE INDEX uq_client_company_name_active
ON client (LOWER(company_name))
WHERE is_archived = FALSE AND deleted_at IS NULL;
CREATE UNIQUE INDEX uq_client_siren_active
ON client (siren)
WHERE siren IS NOT NULL AND is_archived = FALSE AND deleted_at IS NULL;
CREATE UNIQUE INDEX uq_client_email_active
ON client (LOWER(email))
WHERE is_archived = FALSE AND deleted_at IS NULL;
-- =====================================================================
-- Jointure M2M client ↔ category
-- =====================================================================
@@ -772,7 +766,7 @@ class Client implements TimestampableInterface, BlamableInterface
- **Réponse 201** : le client créé avec son `id`. Le front enchaîne ensuite les PATCH par onglet.
- **Codes** :
- `201` / `400` / `401` / `403`
- `409 Conflict` si doublon (companyName, siren, ou email — RG-1.15 / RG-1.16 / RG-1.17)
- `409 Conflict` si doublon de nom de société (`companyName` — RG-1.16). SIREN et email ne sont pas uniques (cf. Q4, § 2.4).
- `422 Unprocessable Entity` :
- RG-1.01 : ni firstName ni lastName
- RG-1.03 : distributor + broker remplis simultanément
@@ -912,9 +906,9 @@ Cf. § 2.6. Pattern Shared standard.
### Unicité
- **RG-1.15** : Le `siren` est unique parmi les clients non archivés ET non soft-deletés (index partiel `uq_client_siren_active`). Tentative de doublon → 409 avec message `"Un client avec le SIREN \"{siren}\" existe déjà."`.
- **RG-1.16** : Le `companyName` est unique (case-insensitive) parmi les clients non archivés ET non soft-deletés (index partiel `uq_client_company_name_active`). Doublon → 409.
- **RG-1.17** : L'`email` principal est unique (case-insensitive) parmi les clients non archivés ET non soft-deletés (index partiel `uq_client_email_active`). Doublon → 409.
- **RG-1.15** : ~~Unicité SIREN~~**supprimée (décision Q4, 29/05/2026)**. Le `siren` n'est plus contraint unique : un même SIREN peut être partagé (établissements multiples). Pas d'index `uq_client_siren_active`.
- **RG-1.16** : Le `companyName` est unique (case-insensitive) parmi les clients non archivés ET non soft-deletés (index partiel `uq_client_company_name_active`). Doublon → 409 avec message `"Un client nommé \"{companyName}\" existe déjà."`. **Seule unicité métier conservée (Q4).**
- **RG-1.17** : ~~Unicité email principal~~**supprimée (décision Q4, 29/05/2026)**. L'`email` principal n'est plus contraint unique (un même email peut servir plusieurs clients). Pas d'index `uq_client_email_active`.
### Normalisation serveur (formatage)
@@ -963,7 +957,7 @@ Cf. § 2.6. Pattern Shared standard.
- [ ] **RG-1.12** : POST onglet Comptabilité avec paymentType=VIREMENT sans bank → 422
- [ ] **RG-1.13** : POST onglet Comptabilité paymentType=LCR sans RIB → 422 ; DELETE du dernier RIB en LCR → 409
- [ ] **RG-1.14** : front-driven uniquement, pas de test back
- [ ] **RG-1.15/16/17** : POST avec SIREN/companyName/email déjà pris → 409 ; POST avec même SIREN/companyName/email après archivage → 201
- [ ] **RG-1.16** : POST avec `companyName` déjà pris → 409 ; POST avec même `companyName` après archivage de l'ancien → 201. SIREN et email dupliqués → 201 (plus d'unicité — RG-1.15/1.17 supprimées, Q4).
- [ ] **RG-1.18** : POST `companyName="acme sas"` → BDD persiste `"ACME SAS"`
- [ ] **RG-1.19** : POST `firstName="JEAN"`, `lastName="dupont"` → persiste `"Jean"`, `"Dupont"`
- [ ] **RG-1.20** : POST `phonePrimary="06.12.34.56.78"` → persiste `"0612345678"`
@@ -977,7 +971,7 @@ Cf. § 2.6. Pattern Shared standard.
- [ ] **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 partiel unique `uq_client_company_name_active` présent (un seul — cf. Q4)
### 8.2 Cas à couvrir (front — Vitest)