abe663d355
Commande console app:idtf:sync : récupère l'export Excel des codes IDTF (régimes de nettoyage transport) depuis icrt-idtf.com, le parse et synchronise une table référentielle (upsert sur (schema, idtf_number) + soft-delete + journal). Scope road ; discriminant schema road/water conservé. - migration : tables idtf_product + idtf_sync_log (COMMENT ON COLUMN sur chaque colonne, unique (schema, idtf_number), cas_numbers JSONB) - IdtfSheetParser : parsing pur d'une matrice (détection dynamique de l'en-tête, mapping par libellé, CAS split, date dd-mm-yyyy -> ISO) + tests unitaires - SyncIdtfCommand : options --schema / --file / --dry-run, POST avec fields[] explicites (export 11 colonnes), upsert DBAL transactionnel - cible make idtf-sync - tests fonctionnels via .xlsx généré (parsing/upsert/journal/soft-delete) Réutilise framework.http_client (activé pour QUALIMAT, ERP-39). phpoffice/phpspreadsheet déjà présent.
121 lines
6.4 KiB
PHP
121 lines
6.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* ERP-149 (Module Transport) : referentiel des codes IDTF (regimes de nettoyage
|
|
* transport).
|
|
*
|
|
* Tables alimentees par la commande `app:idtf:sync` (parsing de l'export Excel
|
|
* icrt-idtf.com, upsert sur (schema, idtf_number) + soft-delete + journal).
|
|
* Aucune FK cross-module : migration au namespace racine `DoctrineMigrations`.
|
|
*/
|
|
final class Version20260612160000 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'ERP-149 : tables idtf_product + idtf_sync_log (referentiel codes IDTF, synchro console depuis l\'export Excel).';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->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,
|
|
));
|
|
}
|
|
}
|