Acte la décision refonte-contact dans les specs : le contact principal inline (firstName/lastName/phonePrimary/phoneSecondary/email) est retiré des entités tiers (Client, Supplier). Les contacts vivent uniquement dans ClientContact / SupplierContact (onglet Contacts). Garantie « >=1 contact nommé » préservée par RG-1.05/1.14 (M1) et RG-2.04/2.13 (M2). - M1 (spec-back/spec-front/cahier) : modèle Client sans contact inline ; RG-1.01/1.02 supprimées ; D1 (recherche) / D2 (export) décrites ; version V1. - M2 (spec-back/spec-front) : FICHIERS NOUVEAUX (non versionnés sur develop), introduits déjà corrigés (Supplier sans contact inline, RG-2.01/2.02 supprimées) ; version V0.2. - docs/specs/M1-clients/refonte-contact/ : décision (README) + tickets (M1 back/front/specs, M2 specs) + prompts + amendement des tickets M2. Lesstime : tâches #103 (M1 back), #104 (M1 front), #105 (M1 specs), #106 (M2 specs) ; tickets M2 #85-#97 amendés. --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #54 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
7.7 KiB
M1 · Ticket 1/3 (Backend) — Supprimer le contact inline du Client
1. Objectif
Retirer de l'entité Client (et de la table client) les 5 champs du contact
principal inline : firstName, lastName, phonePrimary, phoneSecondary, email.
La gestion des contacts passe désormais exclusivement par la sous-entité
ClientContact (onglet « Contacts »), déjà en place et déjà porteuse des mêmes champs.
Le code M1 est déjà livré en prod : ce ticket inclut donc une migration de données (backfill) pour ne perdre aucune information de contact existante avant de supprimer les colonnes.
Contexte et justification : voir README.md du dossier refonte-contact.
2. Périmètre
IN
- Migration Doctrine : backfill puis suppression des 5 colonnes de
client. Client(entité) : supprimer les 5 propriétés, getters/setters, annotations ORM /Assert/Groups.ClientProcessor: retirer les 5 champs deMAIN_FIELDS,changedBusinessFields(),normalize(); supprimervalidateMainContact()(RG-1.01 — n'a plus d'objet).DoctrineClientRepository::applySearch(): trancher D1 (recherche) et l'appliquer.ClientExportController: trancher D2 (colonnes export) et l'appliquer.ClientFixtures: retirer les 5 paramètres inline deensureClient(); garantir que chaque client seedé possède au moins 1ClientContact(déjà géré paraddContact()).- Tests PHPUnit : mettre à jour / retirer les cas qui exercent ces 5 champs sur
Client.
OUT
- Toute modification de
ClientContact/ClientContactProcessor: inchangés (c'est la cible, les champs y restent).ClientFieldNormalizerreste tel quel (toujours appelé parClientContactProcessor). - Le front (formulaires, vues, types, i18n) → ticket 2/3.
- Les specs (
spec-back.md,spec-front.md, cahier de test) → ticket 3/3.
3. Fichiers à modifier
| Fichier | Action |
|---|---|
src/Module/Commercial/Domain/Entity/Client.php |
Supprimer props firstName (~l.158), lastName (~l.163), phonePrimary (~l.168), phoneSecondary (~l.172), email (~l.178) + leurs getters/setters (~l.329-382) + groupes client:read/client:write:main + Assert\*. |
src/Module/Commercial/Infrastructure/ApiPlatform/State/Processor/ClientProcessor.php |
Retirer les 5 clés de MAIN_FIELDS (~l.63) ; de changedBusinessFields() (~l.277-281) ; les 6 lignes de normalize() qui touchent email/phone/first/last/secondary (~l.433-441) ; supprimer validateMainContact() (~l.447-456) et son appel. |
src/Module/Commercial/Infrastructure/Doctrine/DoctrineClientRepository.php |
applySearch() (~l.110-124) : appliquer D1. |
src/Module/Commercial/Infrastructure/Controller/ClientExportController.php |
buildHeaders() (~l.94-114) + buildRows() (~l.121-143) : appliquer D2. |
src/Module/Commercial/Infrastructure/DataFixtures/ClientFixtures.php |
ensureClient() (~l.357-395) : retirer firstName/lastName/phonePrimary/phoneSecondary/email ; conserver addContact(). |
migrations/Version<timestamp>.php (NOUVELLE) |
Backfill + DROP COLUMN (cf. § 4). |
tests/Module/Commercial/** |
Voir § 5. |
4. Migration Doctrine — backfill puis suppression
Migration modulaire (
src/Module/Commercial/Infrastructure/Doctrine/Migrations/) : ce n'est PAS une migration d'initialisation, le schémaclient/client_contactexiste déjà (règle ABSOLUE n°11).
up()
-
Backfill — ne créer un contact que pour les clients qui n'en ont aucun, afin de ne pas dupliquer le contact déjà recopié à la création (
prefillFirstContact) :INSERT INTO client_contact (client_id, first_name, last_name, phone_primary, phone_secondary, email, position, created_at, updated_at) SELECT c.id, c.first_name, c.last_name, c.phone_primary, c.phone_secondary, c.email, 0, NOW(), NOW() FROM client c WHERE NOT EXISTS (SELECT 1 FROM client_contact cc WHERE cc.client_id = c.id) AND (c.first_name IS NOT NULL OR c.last_name IS NOT NULL);Le
WHERE ... first_name OU last_name IS NOT NULLrespecte le CHECKchk_client_contact_name. Les rares clients sans nom de contact ET sans contact existant ne reçoivent pas de ligne (cas théorique :phone_primary/emailétaientNOT NULLmais les noms nullables). -
Supprimer les 5 colonnes :
ALTER TABLE client DROP COLUMN first_name, DROP COLUMN last_name, DROP COLUMN phone_primary, DROP COLUMN phone_secondary, DROP COLUMN email;Pas de
COMMENT ON COLUMNà poser (on supprime). Vérifier qu'aucun index ne portait suremail(l'index uniqueuq_client_email_activea déjà été supprimé — décision Q4 / RG-1.17, cf.ClientMigrationTest).
down() (best-effort)
- Recréer les 5 colonnes (
phone_primary/emailenNOT NULLimpose un défaut transitoire ou un re-remplissage depuis le contactposition = 0). - Re-remplir depuis
client_contact(position = 0) si possible. - Reposer les
COMMENT ON COLUMNd'origine (textes RG-1.19/1.20/1.21/1.01/1.17 — cf.migrations/Version20260601000000.phpl.251-255).
down()ne peut pas restaurer parfaitement les données (ambiguïté si plusieurs contacts). Documenter cette limite dans le docblock de la migration.
5. Tests à mettre à jour
| Fichier | Action |
|---|---|
tests/Module/Commercial/Api/ClientApiTest.php |
Retirer firstName/lastName/phone/email des payloads POST/PATCH client et des assertions JSON. |
tests/.../ClientFormulaireMainTest.php |
Supprimer les tests RG-1.01 (firstName/lastName) et RG-1.02 (téléphones) côté Client — ils basculent côté ClientContact (couverts ailleurs). |
tests/.../ClientExportControllerTest.php |
Aligner les en-têtes/lignes attendus sur D2. |
tests/.../ClientMigrationTest.php |
Asserter que les 5 colonnes n'existent plus sur client ; vérifier le backfill (un client sans contact obtient bien 1 client_contact). |
tests/.../ClientFieldNormalizerTest.php |
Conserver les tests du normalizer (toujours utilisé par ClientContact) ; retirer les cas spécifiques aux champs Client s'il y en a. |
| RG-1.01/1.02 (matrice) | Ne plus tester sur Client ; vérifier qu'ils restent couverts sur ClientContact (RG-1.05). |
6. Décisions à trancher (cf. README § 3)
- D1 — recherche : recommandé =
LEFT JOIN client_contact(fuzzy surcompanyName+ contactfirst_name/last_name/email). Attention auDISTINCT/ risque de doublons de lignes si plusieurs contacts matchent (grouper parclient.id). - D2 — export : recommandé = alimenter les colonnes contact depuis le contact de plus
petit
position(fetch-joincontactspour éviter le N+1).
7. Critères d'acceptation (DoD)
- Les colonnes
first_name,last_name,phone_primary,phone_secondary,emailn'existent plus sur la tableclient. - La migration est jouable sur une base seedée sans perte de contact (backfill vérifié)
et
down()documenté comme best-effort. Client,ClientProcessor,DoctrineClientRepository,ClientExportController,ClientFixturesne référencent plus les 5 champs.- D1 et D2 implémentées conformément à la décision validée.
ClientContact/ClientContactProcessor/ClientFieldNormalizerinchangés.make testvert (notammenttests/Architecture/ColumnsHaveSqlCommentTestetEntitiesAreTimestampableBlamableTest).make php-cs-fixer-allow-riskyne signale rien sur les fichiers touchés.- Aucune régression du contrat de sérialisation : capturer le JSON réel de
GET /api/clients/{id}et vérifier l'absence des 5 champs (réflexe RETEX M1).