addSql(<<<'SQL' CREATE TABLE idtf_product ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, idtf_number INTEGER NOT NULL, schema VARCHAR(8) NOT NULL, product_group VARCHAR(255) DEFAULT NULL, name TEXT NOT NULL, cleaning_regime VARCHAR(64) NOT NULL, important_requirements TEXT DEFAULT NULL, mandatory_date DATE DEFAULT NULL, related_products TEXT DEFAULT NULL, formula VARCHAR(255) DEFAULT NULL, eural_code VARCHAR(64) DEFAULT NULL, cas_numbers JSONB DEFAULT '[]' NOT NULL, footnotes TEXT DEFAULT NULL, source_export_date DATE NOT NULL, is_active BOOLEAN DEFAULT TRUE NOT NULL, last_synced_at TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_idtf_product_schema_number UNIQUE (schema, idtf_number), CONSTRAINT chk_idtf_product_schema CHECK (schema IN ('road', 'water')) ) SQL); $this->addSql('CREATE INDEX idx_idtf_product_active ON idtf_product (schema, is_active)'); $this->comment('idtf_product', '_table', "Referentiel des codes IDTF (marchandise + regime de nettoyage transport), synchronise depuis l'export Excel icrt-idtf.com."); $this->comment('idtf_product', 'id', 'Cle technique auto-incrementee.'); $this->comment('idtf_product', 'idtf_number', 'Numero IDTF de la marchandise (identifiant metier source). Unique par schema.'); $this->comment('idtf_product', 'schema', "Mode de transport / schema IDTF : 'road' (routier) ou 'water' (fluvial). Discriminant d'unicite avec idtf_number."); $this->comment('idtf_product', 'product_group', "Groupe de produit (colonne Product Group de l'export). Nullable."); $this->comment('idtf_product', 'name', "Nom de la marchandise (libelle FR de l'export)."); $this->comment('idtf_product', 'cleaning_regime', 'Regime de nettoyage minimal exige (A, B, C, Interdit, ...).'); $this->comment('idtf_product', 'important_requirements', 'Exigences importantes associees. Nullable.'); $this->comment('idtf_product', 'mandatory_date', "Date d'application obligatoire du regime (convertie depuis dd-mm-yyyy). Nullable."); $this->comment('idtf_product', 'related_products', 'Produits apparentes (texte libre). Nullable.'); $this->comment('idtf_product', 'formula', 'Formule chimique de la marchandise. Nullable.'); $this->comment('idtf_product', 'eural_code', 'Code EURAL (dechet) associe. Nullable.'); $this->comment('idtf_product', 'cas_numbers', 'Liste des numeros CAS (JSONB), eclatee depuis la cellule "Numero CAS" separee par ";". Tableau vide si absent.'); $this->comment('idtf_product', 'footnotes', "Annotations / notes de bas de page de l'export. Nullable."); $this->comment('idtf_product', 'source_export_date', 'Date d\'export du fichier source (preambule "Export date:").'); $this->comment('idtf_product', 'is_active', 'Faux = ligne absente du dernier export (soft-delete). Toute ligne non revue par le dernier run passe a FALSE.'); $this->comment('idtf_product', 'last_synced_at', 'Horodatage du run de synchro ayant vu cette ligne en dernier (soft-delete : last_synced_at < run courant).'); $this->addSql(<<<'SQL' CREATE TABLE idtf_sync_log ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, schema VARCHAR(8) NOT NULL, export_date DATE NOT NULL, rows_total INT NOT NULL, rows_upserted INT NOT NULL, rows_deactivated INT NOT NULL, created_at TIMESTAMP(6) WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, PRIMARY KEY (id) ) SQL); $this->comment('idtf_sync_log', '_table', 'Journal des synchronisations IDTF (une ligne par run de la commande app:idtf:sync).'); $this->comment('idtf_sync_log', 'id', 'Cle technique auto-incrementee.'); $this->comment('idtf_sync_log', 'schema', "Mode de transport synchronise : 'road' ou 'water'."); $this->comment('idtf_sync_log', 'export_date', "Date d'export du fichier source traite par ce run."); $this->comment('idtf_sync_log', 'rows_total', 'Nombre de lignes exploitables lues dans le fichier.'); $this->comment('idtf_sync_log', 'rows_upserted', 'Nombre de lignes inserees ou mises a jour.'); $this->comment('idtf_sync_log', 'rows_deactivated', 'Nombre de lignes passees a is_active=false (absentes de cet export).'); $this->comment('idtf_sync_log', 'created_at', 'Horodatage de fin du run (insertion du journal).'); } public function down(Schema $schema): void { $this->addSql('DROP TABLE IF EXISTS idtf_sync_log'); $this->addSql('DROP TABLE IF EXISTS idtf_product'); } /** * Pose un COMMENT ON TABLE/COLUMN en dollar-quoting Postgres ($_$...$_$) * pour eviter tout echappement d'apostrophes dans les descriptions. */ 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, )); } }