createAccountingReferentials(); $this->createClientTable(); $this->createClientCategory(); $this->createClientContact(); $this->createClientAddress(); $this->createClientAddressJoinTables(); $this->createClientRib(); $this->seedCategoryTypes(); } public function down(Schema $schema): void { // Ordre inverse des dependances FK : on supprime d'abord les jointures // et sous-collections, puis client, puis les referentiels. $this->addSql('DROP TABLE client_address_category'); $this->addSql('DROP TABLE client_address_contact'); $this->addSql('DROP TABLE client_address_site'); $this->addSql('DROP TABLE client_rib'); $this->addSql('DROP TABLE client_address'); $this->addSql('DROP TABLE client_contact'); $this->addSql('DROP TABLE client_category'); $this->addSql('DROP TABLE client'); $this->addSql('DROP TABLE bank'); $this->addSql('DROP TABLE payment_type'); $this->addSql('DROP TABLE payment_delay'); $this->addSql('DROP TABLE tva_mode'); // Retire uniquement les 4 types seedes par cette migration. Les autres // types eventuels (CRUD futur) sont preserves. $this->addSql(<<<'SQL' DELETE FROM category_type WHERE code IN ('DISTRIBUTEUR', 'COURTIER', 'SECTEUR', 'AUTRE') SQL); } // ================================================================= // Referentiels comptables (4 tables statiques, memes colonnes) // ================================================================= private function createAccountingReferentials(): void { $referentials = [ 'tva_mode' => 'Referentiel des modes de TVA appliques a un client (France, Export, Intracom).', 'payment_delay' => 'Referentiel des delais de reglement (15 jours, 30 jours, a reception).', 'payment_type' => 'Referentiel des types de reglement (virement, LCR, cheque, non soumise).', 'bank' => 'Referentiel des banques selectionnables pour le reglement par virement.', ]; foreach ($referentials as $table => $tableComment) { $this->addSql(sprintf(<<<'SQL' CREATE TABLE %s ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(30) NOT NULL, label VARCHAR(120) NOT NULL, position INT DEFAULT 0 NOT NULL, PRIMARY KEY (id) ) SQL, $table)); $this->addSql(sprintf('CREATE UNIQUE INDEX uq_%s_code ON %s (code)', $table, $table)); $this->comment($table, '_table', $tableComment); $this->comment($table, 'id', 'Identifiant interne auto-incremente.'); $this->comment($table, 'code', 'Code technique stable (UPPER_SNAKE, ≤ 30 caracteres) — unique, utilise par le code metier.'); $this->comment($table, 'label', 'Libelle affichable (FR, ≤ 120 caracteres).'); $this->comment($table, 'position', 'Ordre d affichage croissant dans les selecteurs (tri position ASC puis label ASC).'); } // Seed initial (cf. spec § 3.2). Tables fraichement creees donc vides : // INSERT direct sans ON CONFLICT. $this->addSql(<<<'SQL' INSERT INTO tva_mode (code, label, position) VALUES ('FRANCE_VENTES', 'France (ventes)', 10), ('EXPORT_VENTES', 'Export (ventes)', 20), ('INTRACOM_VENTES', 'Intracom (ventes)', 30) SQL); $this->addSql(<<<'SQL' INSERT INTO payment_delay (code, label, position) VALUES ('J15', '15 jours', 10), ('J30', '30 jours', 20), ('A_RECEPTION', 'À réception', 30) SQL); $this->addSql(<<<'SQL' INSERT INTO payment_type (code, label, position) VALUES ('VIREMENT', 'Virement', 10), ('LCR', 'LCR', 20), ('NON_SOUMISE', 'Non soumise', 30), ('CHEQUE', 'Chèque', 40) SQL); $this->addSql(<<<'SQL' INSERT INTO bank (code, label, position) VALUES ('SG', 'Société Générale', 10), ('CIC', 'CIC', 20), ('CA', 'Crédit Agricole', 30) SQL); } // ================================================================= // Table principale `client` // ================================================================= private function createClientTable(): void { $this->addSql(<<<'SQL' CREATE TABLE client ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, company_name VARCHAR(180) NOT NULL, first_name VARCHAR(120) DEFAULT NULL, last_name VARCHAR(120) DEFAULT NULL, phone_primary VARCHAR(20) NOT NULL, phone_secondary VARCHAR(20) DEFAULT NULL, email VARCHAR(180) NOT NULL, distributor_id INT DEFAULT NULL, broker_id INT DEFAULT NULL, triage_service BOOLEAN DEFAULT FALSE NOT NULL, description TEXT DEFAULT NULL, competitors VARCHAR(255) DEFAULT NULL, founded_at DATE DEFAULT NULL, employees_count INT DEFAULT NULL, revenue_amount NUMERIC(15, 2) DEFAULT NULL, director_name VARCHAR(120) DEFAULT NULL, profit_amount NUMERIC(15, 2) DEFAULT NULL, siren VARCHAR(20) DEFAULT NULL, account_number VARCHAR(40) DEFAULT NULL, tva_mode_id INT DEFAULT NULL, n_tva VARCHAR(40) DEFAULT NULL, payment_delay_id INT DEFAULT NULL, payment_type_id INT DEFAULT NULL, bank_id INT DEFAULT NULL, is_archived BOOLEAN DEFAULT FALSE NOT NULL, archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT chk_client_distrib_or_broker CHECK (NOT (distributor_id IS NOT NULL AND broker_id IS NOT NULL)), CONSTRAINT fk_client_distributor FOREIGN KEY (distributor_id) REFERENCES client (id) ON DELETE SET NULL, CONSTRAINT fk_client_broker FOREIGN KEY (broker_id) REFERENCES client (id) ON DELETE SET NULL, CONSTRAINT fk_client_tva_mode FOREIGN KEY (tva_mode_id) REFERENCES tva_mode (id) ON DELETE RESTRICT, CONSTRAINT fk_client_payment_delay FOREIGN KEY (payment_delay_id) REFERENCES payment_delay (id) ON DELETE RESTRICT, CONSTRAINT fk_client_payment_type FOREIGN KEY (payment_type_id) REFERENCES payment_type (id) ON DELETE RESTRICT, CONSTRAINT fk_client_bank FOREIGN KEY (bank_id) REFERENCES bank (id) ON DELETE RESTRICT, CONSTRAINT fk_client_created_by FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL, CONSTRAINT fk_client_updated_by FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL ) SQL); $this->addSql('CREATE INDEX idx_client_is_archived ON client (is_archived)'); $this->addSql('CREATE INDEX idx_client_deleted_at ON client (deleted_at)'); $this->addSql('CREATE INDEX idx_client_distributor_id ON client (distributor_id)'); $this->addSql('CREATE INDEX idx_client_broker_id ON client (broker_id)'); $this->addSql('CREATE INDEX idx_client_created_by ON client (created_by)'); $this->addSql('CREATE INDEX idx_client_updated_by ON client (updated_by)'); // Unicite metier partielle (Q4) : nom de societe insensible a la casse, // parmi les non-archives ET non soft-deletes uniquement. Pas d'index // unique sur siren ni email. $this->addSql(<<<'SQL' CREATE UNIQUE INDEX uq_client_company_name_active ON client (LOWER(company_name)) WHERE is_archived = FALSE AND deleted_at IS NULL SQL); $this->comment('client', '_table', 'Repertoire clients (M1 Commercial) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M2).'); $this->comment('client', 'id', 'Identifiant interne auto-incremente.'); $this->comment('client', 'company_name', 'Raison sociale (stockee en MAJUSCULES, RG-1.18). Unique case-insensitive parmi les actifs non archives/non supprimes (RG-1.16, uq_client_company_name_active).'); $this->comment('client', 'first_name', 'Prenom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).'); $this->comment('client', 'last_name', 'Nom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).'); $this->comment('client', 'phone_primary', 'Telephone principal — stocke en chiffres uniquement (RG-1.20). Obligatoire.'); $this->comment('client', 'phone_secondary', 'Telephone secondaire optionnel — chiffres uniquement (RG-1.20).'); $this->comment('client', 'email', 'Email principal (lowercase serveur, RG-1.21). NON unique (RG-1.17 supprimee, Q4).'); $this->comment('client', 'distributor_id', 'FK auto-referente vers un client porteur de la categorie DISTRIBUTEUR — exclusive avec broker_id (RG-1.03, chk_client_distrib_or_broker). FK -> client.id, ON DELETE SET NULL.'); $this->comment('client', 'broker_id', 'FK auto-referente vers un client porteur de la categorie COURTIER — exclusive avec distributor_id (RG-1.03). FK -> client.id, ON DELETE SET NULL.'); $this->comment('client', 'triage_service', 'Drapeau service triage active pour le client. Faux par defaut.'); $this->comment('client', 'description', 'Onglet Information : description libre. Obligatoire pour le role Commerciale (RG-1.04), optionnel sinon.'); $this->comment('client', 'competitors', 'Onglet Information : concurrents identifies (texte libre ≤ 255). Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'founded_at', 'Onglet Information : date de creation de l entreprise. Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'employees_count', 'Onglet Information : effectif (entier >= 0). Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'revenue_amount', 'Onglet Information : chiffre d affaires (NUMERIC 15,2). Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'director_name', 'Onglet Information : nom du dirigeant. Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'profit_amount', 'Onglet Information : resultat / benefice (NUMERIC 15,2). Obligatoire role Commerciale (RG-1.04).'); $this->comment('client', 'siren', 'Onglet Comptabilite : SIREN (9 chiffres attendus). NON unique — peut etre partage entre etablissements (RG-1.15 supprimee, Q4).'); $this->comment('client', 'account_number', 'Onglet Comptabilite : numero de compte comptable du client.'); $this->comment('client', 'tva_mode_id', 'Onglet Comptabilite : mode de TVA applique — FK -> tva_mode.id, ON DELETE RESTRICT.'); $this->comment('client', 'n_tva', 'Onglet Comptabilite : numero de TVA intracommunautaire.'); $this->comment('client', 'payment_delay_id', 'Onglet Comptabilite : delai de reglement — FK -> payment_delay.id, ON DELETE RESTRICT.'); $this->comment('client', 'payment_type_id', 'Onglet Comptabilite : type de reglement — FK -> payment_type.id, ON DELETE RESTRICT. Code LCR impose >= 1 RIB (RG-1.13), VIREMENT impose une banque (RG-1.12).'); $this->comment('client', 'bank_id', 'Onglet Comptabilite : banque — FK -> bank.id, ON DELETE RESTRICT. Obligatoire si payment_type = VIREMENT (RG-1.12).'); $this->comment('client', 'is_archived', 'Drapeau fonctionnel d archivage — masque par defaut dans la liste. Bascule via permission commercial.clients.archive (RG-1.22/23).'); $this->comment('client', 'archived_at', 'Horodatage de l archivage — pose quand is_archived passe a vrai, remis a null a la restauration (RG-1.22/23).'); $this->comment('client', 'deleted_at', 'Horodatage du soft-delete technique (HP M2) — non expose par l API au M1. Null = ligne active.'); $this->addTimestampableBlamableComments('client'); } // ================================================================= // M2M client <-> category // ================================================================= private function createClientCategory(): void { $this->addSql(<<<'SQL' CREATE TABLE client_category ( client_id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY (client_id, category_id), CONSTRAINT fk_client_category_client FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE, CONSTRAINT fk_client_category_category FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT ) SQL); $this->addSql('CREATE INDEX idx_client_category_category ON client_category (category_id)'); $this->comment('client_category', '_table', 'Jointure M2M client <-> category (Catalog) — categories metier du client (au moins une obligatoire).'); $this->comment('client_category', 'client_id', 'FK -> client.id, ON DELETE CASCADE — client porteur de la categorie.'); $this->comment('client_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie rattachee au client.'); } // ================================================================= // Sous-collection : contacts (1:n) // ================================================================= private function createClientContact(): void { $this->addSql(<<<'SQL' CREATE TABLE client_contact ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, client_id INT NOT NULL, first_name VARCHAR(120) DEFAULT NULL, last_name VARCHAR(120) DEFAULT NULL, job_title VARCHAR(120) DEFAULT NULL, phone_primary VARCHAR(20) DEFAULT NULL, phone_secondary VARCHAR(20) DEFAULT NULL, email VARCHAR(180) DEFAULT NULL, position INT DEFAULT 0 NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT chk_client_contact_name CHECK (first_name IS NOT NULL OR last_name IS NOT NULL), CONSTRAINT fk_client_contact_client FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE, CONSTRAINT fk_client_contact_created_by FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL, CONSTRAINT fk_client_contact_updated_by FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL ) SQL); $this->addSql('CREATE INDEX idx_client_contact_client ON client_contact (client_id)'); $this->comment('client_contact', '_table', 'Contacts d un client (1:n) — au moins firstName OU lastName par contact (RG-1.05).'); $this->comment('client_contact', 'id', 'Identifiant interne auto-incremente.'); $this->comment('client_contact', 'client_id', 'FK -> client.id, ON DELETE CASCADE — client proprietaire du contact.'); $this->comment('client_contact', 'first_name', 'Prenom du contact (capitalise serveur). first_name OU last_name obligatoire (RG-1.05, chk_client_contact_name).'); $this->comment('client_contact', 'last_name', 'Nom du contact (capitalise serveur). first_name OU last_name obligatoire (RG-1.05, chk_client_contact_name).'); $this->comment('client_contact', 'job_title', 'Fonction / intitule de poste du contact (≤ 120 caracteres).'); $this->comment('client_contact', 'phone_primary', 'Telephone principal du contact — chiffres uniquement (RG-1.20).'); $this->comment('client_contact', 'phone_secondary', 'Telephone secondaire du contact — chiffres uniquement (RG-1.20).'); $this->comment('client_contact', 'email', 'Email du contact (lowercase serveur, RG-1.21).'); $this->comment('client_contact', 'position', 'Ordre d affichage du contact dans la liste du client (croissant).'); $this->addTimestampableBlamableComments('client_contact'); } // ================================================================= // Sous-collection : adresses (1:n) // ================================================================= private function createClientAddress(): void { $this->addSql(<<<'SQL' CREATE TABLE client_address ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, client_id INT NOT NULL, is_prospect BOOLEAN DEFAULT FALSE NOT NULL, is_delivery BOOLEAN DEFAULT FALSE NOT NULL, is_billing BOOLEAN DEFAULT FALSE NOT NULL, country VARCHAR(80) DEFAULT 'France' NOT NULL, postal_code VARCHAR(20) NOT NULL, city VARCHAR(120) NOT NULL, street VARCHAR(255) NOT NULL, street_complement VARCHAR(255) DEFAULT NULL, billing_email VARCHAR(180) DEFAULT NULL, position INT DEFAULT 0 NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT chk_client_address_prospect_exclusive CHECK (NOT (is_prospect = TRUE AND (is_delivery = TRUE OR is_billing = TRUE))), CONSTRAINT chk_client_address_billing_email CHECK ((is_billing = FALSE AND billing_email IS NULL) OR (is_billing = TRUE AND billing_email IS NOT NULL)), CONSTRAINT fk_client_address_client FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE, CONSTRAINT fk_client_address_created_by FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL, CONSTRAINT fk_client_address_updated_by FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL ) SQL); $this->addSql('CREATE INDEX idx_client_address_client ON client_address (client_id)'); $this->comment('client_address', '_table', 'Adresses d un client (1:n) — prospect exclusif de livraison/facturation (RG-1.06/07/08), >= 1 site rattache (RG-1.10).'); $this->comment('client_address', 'id', 'Identifiant interne auto-incremente.'); $this->comment('client_address', 'client_id', 'FK -> client.id, ON DELETE CASCADE — client proprietaire de l adresse.'); $this->comment('client_address', 'is_prospect', 'Adresse de prospection — exclusive de is_delivery/is_billing (RG-1.06/07/08, chk_client_address_prospect_exclusive). Faux par defaut.'); $this->comment('client_address', 'is_delivery', 'Adresse de livraison. Exclusive de is_prospect. Faux par defaut.'); $this->comment('client_address', 'is_billing', 'Adresse de facturation. Exclusive de is_prospect. Impose billing_email (RG-1.11). Faux par defaut.'); $this->comment('client_address', 'country', 'Pays de l adresse — defaut France.'); $this->comment('client_address', 'postal_code', 'Code postal (4-5 chiffres attendus, RG-1.09).'); $this->comment('client_address', 'city', 'Ville — preremplie depuis le code postal via API BAN cote front (RG-1.09).'); $this->comment('client_address', 'street', 'Numero et voie de l adresse.'); $this->comment('client_address', 'street_complement', 'Complement d adresse (etage, batiment...) — optionnel.'); $this->comment('client_address', 'billing_email', 'Email de facturation — obligatoire si is_billing, null sinon (RG-1.11, chk_client_address_billing_email).'); $this->comment('client_address', 'position', 'Ordre d affichage de l adresse dans la liste du client (croissant).'); $this->addTimestampableBlamableComments('client_address'); } // ================================================================= // Jointures de client_address (M2M) // ================================================================= private function createClientAddressJoinTables(): void { $this->addSql(<<<'SQL' CREATE TABLE client_address_site ( client_address_id INT NOT NULL, site_id INT NOT NULL, PRIMARY KEY (client_address_id, site_id), CONSTRAINT fk_client_address_site_address FOREIGN KEY (client_address_id) REFERENCES client_address (id) ON DELETE CASCADE, CONSTRAINT fk_client_address_site_site FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT ) SQL); $this->comment('client_address_site', '_table', 'Jointure M2M client_address <-> site (Sites) — sites desservis par l adresse (>= 1 obligatoire, RG-1.10).'); $this->comment('client_address_site', 'client_address_id', 'FK -> client_address.id, ON DELETE CASCADE — adresse concernee.'); $this->comment('client_address_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site rattache a l adresse.'); $this->addSql(<<<'SQL' CREATE TABLE client_address_contact ( client_address_id INT NOT NULL, client_contact_id INT NOT NULL, PRIMARY KEY (client_address_id, client_contact_id), CONSTRAINT fk_client_address_contact_address FOREIGN KEY (client_address_id) REFERENCES client_address (id) ON DELETE CASCADE, CONSTRAINT fk_client_address_contact_contact FOREIGN KEY (client_contact_id) REFERENCES client_contact (id) ON DELETE CASCADE ) SQL); $this->comment('client_address_contact', '_table', 'Jointure M2M client_address <-> client_contact — contacts associes a une adresse.'); $this->comment('client_address_contact', 'client_address_id', 'FK -> client_address.id, ON DELETE CASCADE — adresse concernee.'); $this->comment('client_address_contact', 'client_contact_id', 'FK -> client_contact.id, ON DELETE CASCADE — contact associe a l adresse.'); $this->addSql(<<<'SQL' CREATE TABLE client_address_category ( client_address_id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY (client_address_id, category_id), CONSTRAINT fk_client_address_category_address FOREIGN KEY (client_address_id) REFERENCES client_address (id) ON DELETE CASCADE, CONSTRAINT fk_client_address_category_category FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT ) SQL); $this->comment('client_address_category', '_table', 'Jointure M2M client_address <-> category — categories d adresse (types SECTEUR/AUTRE uniquement, RG-1.29).'); $this->comment('client_address_category', 'client_address_id', 'FK -> client_address.id, ON DELETE CASCADE — adresse concernee.'); $this->comment('client_address_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie d adresse (type SECTEUR ou AUTRE, RG-1.29).'); } // ================================================================= // Sous-collection : RIB (1:n) // ================================================================= private function createClientRib(): void { $this->addSql(<<<'SQL' CREATE TABLE client_rib ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, client_id INT NOT NULL, label VARCHAR(120) NOT NULL, bic VARCHAR(20) NOT NULL, iban VARCHAR(34) NOT NULL, position INT DEFAULT 0 NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT fk_client_rib_client FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE, CONSTRAINT fk_client_rib_created_by FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL, CONSTRAINT fk_client_rib_updated_by FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL ) SQL); $this->addSql('CREATE INDEX idx_client_rib_client ON client_rib (client_id)'); $this->comment('client_rib', '_table', 'Coordonnees bancaires d un client (1:n) — >= 1 RIB obligatoire si payment_type = LCR (RG-1.13). Tous les champs audites (pas d AuditIgnore).'); $this->comment('client_rib', 'id', 'Identifiant interne auto-incremente.'); $this->comment('client_rib', 'client_id', 'FK -> client.id, ON DELETE CASCADE — client proprietaire du RIB.'); $this->comment('client_rib', 'label', 'Libelle du RIB (ex: compte principal).'); $this->comment('client_rib', 'bic', 'Code BIC/SWIFT de la banque (8 ou 11 caracteres).'); $this->comment('client_rib', 'iban', 'IBAN du compte (≤ 34 caracteres).'); $this->comment('client_rib', 'position', 'Ordre d affichage du RIB dans la liste du client (croissant).'); $this->addTimestampableBlamableComments('client_rib'); } // ================================================================= // Seed extension category_type (M0) // ================================================================= private function seedCategoryTypes(): void { // Idempotent : la table category_type peut deja porter des donnees en // prod. ON CONFLICT (code) s appuie sur l index unique uq_category_type_code. // NB : la table M0 n a pas de colonne `position` (id/code/label seulement), // contrairement au pseudo-SQL de la spec § 3.3. $this->addSql(<<<'SQL' INSERT INTO category_type (code, label) VALUES ('DISTRIBUTEUR', 'Distributeur'), ('COURTIER', 'Courtier'), ('SECTEUR', 'Secteur'), ('AUTRE', 'Autre') ON CONFLICT (code) DO NOTHING SQL); } // ================================================================= // Helpers // ================================================================= /** * Pose les 4 commentaires standardises Timestampable/Blamable sur une table, * en reutilisant le catalogue partage (source unique, cf. ERP-67). */ private function addTimestampableBlamableComments(string $table): void { foreach (ColumnCommentsCatalog::timestampableBlamableComments() as $column => $description) { $this->comment($table, $column, $description); } } /** * Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou * `COMMENT ON COLUMN` en dollar-quoting Postgres ($_$...$_$) pour eviter * tout echappement d apostrophe. */ private function comment(string $table, string $column, string $description): void { $quotedTable = '"'.str_replace('"', '""', $table).'"'; if ('_table' === $column) { $this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description)); return; } $this->addSql(sprintf( 'COMMENT ON COLUMN %s.%s IS $_$%s$_$', $quotedTable, '"'.str_replace('"', '""', $column).'"', $description, )); } }