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.
111 lines
4.0 KiB
PHP
111 lines
4.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Tests\Module\Transport\Application\Idtf;
|
||
|
||
use App\Module\Transport\Application\Idtf\IdtfSheetParser;
|
||
use PHPUnit\Framework\TestCase;
|
||
use RuntimeException;
|
||
|
||
/**
|
||
* @internal
|
||
*/
|
||
final class IdtfSheetParserTest extends TestCase
|
||
{
|
||
public function testExtractsExportDate(): void
|
||
{
|
||
$parsed = IdtfSheetParser::parse($this->sampleMatrix());
|
||
self::assertSame('2026-06-12', $parsed['exportDate']);
|
||
}
|
||
|
||
public function testParsesAndNormalizesFirstRow(): void
|
||
{
|
||
$parsed = IdtfSheetParser::parse($this->sampleMatrix());
|
||
$row = $parsed['rows'][0];
|
||
|
||
self::assertSame(30748, $row['idtf_number']);
|
||
self::assertSame('Argiles avec régime de nettoyage C', $row['name']);
|
||
self::assertSame('C', $row['cleaning_regime']);
|
||
self::assertSame('2026-04-02', $row['mandatory_date']);
|
||
self::assertSame('Al2O3', $row['formula']);
|
||
self::assertSame('01 01 01', $row['eural_code']);
|
||
self::assertSame(['7631-86-9', '1344-28-1'], $row['cas_numbers']);
|
||
self::assertSame('Note 1', $row['footnotes']);
|
||
}
|
||
|
||
public function testSkipsEmptyAndNonNumericRows(): void
|
||
{
|
||
$parsed = IdtfSheetParser::parse($this->sampleMatrix());
|
||
|
||
// 2 lignes exploitables (30748 et 30744) ; vide + "abc" ignorees.
|
||
self::assertCount(2, $parsed['rows']);
|
||
self::assertSame(30744, $parsed['rows'][1]['idtf_number']);
|
||
}
|
||
|
||
public function testEmptyOptionalCellsBecomeNullAndCasEmpty(): void
|
||
{
|
||
$parsed = IdtfSheetParser::parse($this->sampleMatrix());
|
||
$row = $parsed['rows'][1]; // 30744
|
||
|
||
self::assertNull($row['mandatory_date']);
|
||
self::assertNull($row['formula']);
|
||
self::assertNull($row['product_group']);
|
||
self::assertSame([], $row['cas_numbers']);
|
||
}
|
||
|
||
public function testColumnOrderIsResolvedByLabel(): void
|
||
{
|
||
// En-tete dans un ordre different : le mapping doit suivre les libelles.
|
||
$matrix = [
|
||
['Export date: 1-1-2026'],
|
||
['Numéro CAS', 'Numéro IDTF', 'Nom de la marchandise', 'Régime de nettoyage'],
|
||
['7440-44-0', '99', 'Carbone', 'B'],
|
||
];
|
||
|
||
$parsed = IdtfSheetParser::parse($matrix);
|
||
$row = $parsed['rows'][0];
|
||
|
||
self::assertSame(99, $row['idtf_number']);
|
||
self::assertSame('Carbone', $row['name']);
|
||
self::assertSame('B', $row['cleaning_regime']);
|
||
self::assertSame(['7440-44-0'], $row['cas_numbers']);
|
||
}
|
||
|
||
public function testThrowsWhenHeaderMissing(): void
|
||
{
|
||
$this->expectException(RuntimeException::class);
|
||
IdtfSheetParser::parse([['foo', 'bar'], ['1', '2']]);
|
||
}
|
||
|
||
public function testExportDateNullWhenAbsent(): void
|
||
{
|
||
$matrix = [
|
||
['Numéro IDTF', 'Nom de la marchandise', 'Régime de nettoyage'],
|
||
['1', 'X', 'A'],
|
||
];
|
||
|
||
self::assertNull(IdtfSheetParser::parse($matrix)['exportDate']);
|
||
}
|
||
|
||
/**
|
||
* Matrice representative de l'export reel : preambule (lignes 0-1), ligne
|
||
* vide (2), en-tete (3) puis donnees.
|
||
*
|
||
* @return array<int, array<int, mixed>>
|
||
*/
|
||
private function sampleMatrix(): array
|
||
{
|
||
return [
|
||
['Export date: 12-6-2026'],
|
||
['Changes in the database after this date...'],
|
||
[],
|
||
['Numéro IDTF', 'Product Group', 'Nom de la marchandise', 'Régime de nettoyage', 'Exigences importantes', 'Date d’application obligatoire', 'Produits apparentés', 'Formule', 'Code EURAL', 'Numéro CAS', 'Annotations'],
|
||
['30748', 'Substances inorganiques', 'Argiles avec régime de nettoyage C', 'C', 'Exigence X', '02-04-2026', 'Poudre argile', 'Al2O3', '01 01 01', '7631-86-9 ; 1344-28-1', 'Note 1'],
|
||
['', '', '', '', '', '', '', '', '', '', ''],
|
||
['abc', 'ligne non numerique a ignorer', '', '', '', '', '', '', '', '', ''],
|
||
['30744', '', 'Additifs alimentaires', 'A', '', '', '', '', '', '', ''],
|
||
];
|
||
}
|
||
}
|