Files
Starseed/docs/specs/M1-clients/refonte-contact/M1-ticket-01-back.md
T
matthieu 8fae987e15
Auto Tag Develop / tag (push) Successful in 6s
docs(commercial) : refonte contact — suppression du contact inline (specs M1 + M2) (#54)
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>
2026-06-03 13:16:11 +00:00

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 de MAIN_FIELDS, changedBusinessFields(), normalize() ; supprimer validateMainContact() (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 de ensureClient() ; garantir que chaque client seedé possède au moins 1 ClientContact (déjà géré par addContact()).
  • 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). ClientFieldNormalizer reste tel quel (toujours appelé par ClientContactProcessor).
  • 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éma client / client_contact existe déjà (règle ABSOLUE n°11).

up()

  1. 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 NULL respecte le CHECK chk_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 étaient NOT NULL mais les noms nullables).

  2. 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 sur email (l'index unique uq_client_email_active a déjà été supprimé — décision Q4 / RG-1.17, cf. ClientMigrationTest).

down() (best-effort)

  1. Recréer les 5 colonnes (phone_primary/email en NOT NULL impose un défaut transitoire ou un re-remplissage depuis le contact position = 0).
  2. Re-remplir depuis client_contact (position = 0) si possible.
  3. Reposer les COMMENT ON COLUMN d'origine (textes RG-1.19/1.20/1.21/1.01/1.17 — cf. migrations/Version20260601000000.php l.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 sur companyName + contact first_name/last_name/email). Attention au DISTINCT / risque de doublons de lignes si plusieurs contacts matchent (grouper par client.id).
  • D2 — export : recommandé = alimenter les colonnes contact depuis le contact de plus petit position (fetch-join contacts pour éviter le N+1).

7. Critères d'acceptation (DoD)

  • Les colonnes first_name, last_name, phone_primary, phone_secondary, email n'existent plus sur la table client.
  • 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, ClientFixtures ne référencent plus les 5 champs.
  • D1 et D2 implémentées conformément à la décision validée.
  • ClientContact / ClientContactProcessor / ClientFieldNormalizer inchangés.
  • make test vert (notamment tests/Architecture/ColumnsHaveSqlCommentTest et EntitiesAreTimestampableBlamableTest).
  • make php-cs-fixer-allow-risky ne 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).