96ddd15c86
Auto Tag Develop / tag (push) Successful in 9s
## Contexte M1 · Ticket 1/3 (Backend) de la refonte contact. Le contact principal inline du `Client` (firstName, lastName, phonePrimary, phoneSecondary, email) faisait doublon avec la sous-entité `ClientContact` (onglet Contact). Il est supprimé : les contacts vivent désormais **uniquement** dans `client_contact`. RG-1.01 (firstName OU lastName sur Client) et RG-1.02 (max 2 téléphones sur Client) sont **supprimées** du Client — leur équivalent vit déjà sur `ClientContact` (RG-1.05 / RG-1.14). ## Changements - **Migration** `Version20260603120000` (namespace racine `DoctrineMigrations` — tri par timestamp, cf. AlphabeticalComparator) : **backfill** des clients sans contact vers `client_contact` (position 0) **avant** le `DROP` des 5 colonnes. `down()` best-effort documenté. - **Entité `Client`** : retrait des 5 propriétés + getters/setters + groupes de sérialisation. - **`ClientProcessor`** : `MAIN_FIELDS` / `changedBusinessFields()` / `normalize()` allégés ; `validateMainContact()` (RG-1.01) supprimée. - **Recherche répertoire (D1)** : sur `companyName` seul (les anciens critères lastName/email vivaient sur les colonnes supprimées). - **Export XLSX (D2)** : colonnes de contact retirées (Nom entreprise / Catégories / Sites / [SIREN] / Date). - **Fixtures** + **catalogue de commentaires SQL** (`ColumnCommentsCatalog`) alignés. - **Tests** fonctionnels et unitaires mis à jour. ## Décisions actées - **Migration** au namespace racine (et non modulaire Commercial) : une migration `App\Module\Commercial\…` trierait avant le `CREATE TABLE client` sur base fraîche → casse. Conforme à la règle ABSOLUE n°11. - **D1** = recherche `companyName` seul. **D2** = retrait des colonnes contact de l'export. ## Vérifications - ✅ `make db-reset && make migration-migrate` : migration rejouable sur base fraîche (backfill no-op si contacts déjà présents). - ✅ `make test` : 466 tests verts. - ✅ `make php-cs-fixer-allow-risky` : clean. - ✅ Contrat réel `GET /api/clients/{id}` : les 5 champs ont disparu de la racine, `contacts[]` porte l'info. --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #55 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
132 lines
6.3 KiB
PHP
132 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* M1 — Suppression du contact principal inline du `Client` (refonte contact).
|
|
*
|
|
* Modele AVANT : le `Client` portait 5 colonnes de contact principal
|
|
* (first_name, last_name, phone_primary, phone_secondary, email) en doublon
|
|
* conceptuel de la sous-entite `ClientContact` (onglet Contact).
|
|
*
|
|
* Modele APRES (decision produit, README refonte-contact) : les contacts vivent
|
|
* UNIQUEMENT dans `client_contact`. Les 5 colonnes inline disparaissent du
|
|
* `client`. RG-1.01 (firstName OU lastName sur Client) et RG-1.02 (max 2
|
|
* telephones sur Client) sont supprimees : leur equivalent vit deja sur
|
|
* `client_contact` (RG-1.05 / RG-1.14).
|
|
*
|
|
* Le code etant deja en prod, la suppression est precedee d'un BACKFILL : pour
|
|
* tout client n'ayant encore AUCUN contact, on materialise son contact principal
|
|
* inline en une ligne `client_contact` (position 0) avant le DROP, afin de ne
|
|
* perdre aucune donnee.
|
|
*
|
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire
|
|
* Commercial : avec plusieurs migrations_paths, Doctrine Migrations 3.x trie par
|
|
* FQCN alphabetique (AlphabeticalComparator). Une migration
|
|
* `App\Module\Commercial\...` trierait AVANT toutes les `DoctrineMigrations\...`
|
|
* sur base vide -> ce DROP s'executerait avant le CREATE TABLE client
|
|
* (Version20260601000000). Le namespace racine garantit l'ordre par timestamp.
|
|
*/
|
|
final class Version20260603120000 extends AbstractMigration
|
|
{
|
|
/** Colonnes de contact inline supprimees du `client`. */
|
|
private const array INLINE_CONTACT_COLUMNS = [
|
|
'first_name', 'last_name', 'phone_primary', 'phone_secondary', 'email',
|
|
];
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'M1 : suppression du contact inline du Client (backfill vers client_contact puis DROP des 5 colonnes).';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
// 1. Backfill : tout client SANS contact recoit une ligne client_contact
|
|
// (position 0) reprenant ses champs inline. phone_primary / email du
|
|
// client sont NOT NULL -> toujours une donnee a reporter. Le CHECK
|
|
// chk_client_contact_name (first_name OU last_name) est garanti par le
|
|
// fallback company_name si jamais les deux noms etaient null (cas qui ne
|
|
// devrait pas exister, RG-1.01 ayant ete appliquee a l'ecriture).
|
|
// created_at/updated_at NOT NULL -> NOW() ; created_by/updated_by null
|
|
// (backfill hors contexte HTTP, libelle « Systeme » cote front).
|
|
$this->addSql(<<<'SQL'
|
|
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,
|
|
CASE
|
|
WHEN c.first_name IS NULL AND c.last_name IS NULL THEN c.company_name
|
|
ELSE c.last_name
|
|
END,
|
|
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
|
|
)
|
|
SQL);
|
|
|
|
// 2. DROP des 5 colonnes inline (rien a documenter : suppression).
|
|
$this->addSql(<<<'SQL'
|
|
ALTER TABLE client
|
|
DROP COLUMN first_name,
|
|
DROP COLUMN last_name,
|
|
DROP COLUMN phone_primary,
|
|
DROP COLUMN phone_secondary,
|
|
DROP COLUMN email
|
|
SQL);
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
// Best-effort : on RECREE les 5 colonnes (en NULLABLE — l'etat NOT NULL
|
|
// d'origine de phone_primary/email ne peut etre restaure sur une table
|
|
// peuplee sans risque) et on retro-alimente depuis le contact principal
|
|
// (position minimale) de chaque client. La donnee n'est pas garantie
|
|
// identique a l'origine : ce down() sert au rollback technique, pas a une
|
|
// restauration fidele.
|
|
$this->addSql('ALTER TABLE client ADD COLUMN first_name VARCHAR(120) DEFAULT NULL');
|
|
$this->addSql('ALTER TABLE client ADD COLUMN last_name VARCHAR(120) DEFAULT NULL');
|
|
$this->addSql('ALTER TABLE client ADD COLUMN phone_primary VARCHAR(20) DEFAULT NULL');
|
|
$this->addSql('ALTER TABLE client ADD COLUMN phone_secondary VARCHAR(20) DEFAULT NULL');
|
|
$this->addSql('ALTER TABLE client ADD COLUMN email VARCHAR(180) DEFAULT NULL');
|
|
|
|
// Retro-alimentation depuis le contact de position la plus basse.
|
|
$this->addSql(<<<'SQL'
|
|
UPDATE client c SET
|
|
first_name = cc.first_name,
|
|
last_name = cc.last_name,
|
|
phone_primary = cc.phone_primary,
|
|
phone_secondary = cc.phone_secondary,
|
|
email = cc.email
|
|
FROM (
|
|
SELECT DISTINCT ON (client_id)
|
|
client_id, first_name, last_name, phone_primary, phone_secondary, email
|
|
FROM client_contact
|
|
ORDER BY client_id, position ASC, id ASC
|
|
) cc
|
|
WHERE cc.client_id = c.id
|
|
SQL);
|
|
|
|
// Re-pose des commentaires d'origine (regle ABSOLUE n°12) — dollar-quoting
|
|
// Postgres pour eviter tout echappement d apostrophe.
|
|
$this->addSql('COMMENT ON COLUMN client.first_name IS $_$Prenom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).$_$');
|
|
$this->addSql('COMMENT ON COLUMN client.last_name IS $_$Nom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).$_$');
|
|
$this->addSql('COMMENT ON COLUMN client.phone_primary IS $_$Telephone principal — stocke en chiffres uniquement (RG-1.20). Obligatoire.$_$');
|
|
$this->addSql('COMMENT ON COLUMN client.phone_secondary IS $_$Telephone secondaire optionnel — chiffres uniquement (RG-1.20).$_$');
|
|
$this->addSql('COMMENT ON COLUMN client.email IS $_$Email principal (lowercase serveur, RG-1.21). NON unique (RG-1.17 supprimee, Q4).$_$');
|
|
}
|
|
}
|