f057866e75
## ERP-39 — Intégration QUALIMAT (transporteurs) > ⚠️ MR **empilée** sur `feat/erp-150-module-transport` (PR #97). À merger après #97 (la base se recible automatiquement sur `develop`). Commande console `app:qualimat:sync` : récupère les opérateurs de transport agréés depuis l'API publique qualimat.org, normalise et synchronise une table référentielle. Idempotente (refresh complet), prévue pour un **cron quotidien**. ### Contenu - **Migration** `Version20260612150000` (namespace racine) : tables `qualimat_carrier` + `qualimat_sync_log`, `COMMENT ON COLUMN` sur chaque colonne, unique sur `siret`, index `is_active`. - **`QualimatRowMapper`** : normalisation pure — SIRET sans espaces (clé naturelle, source "sale" non contrainte à 14), `dd/mm/yyyy` → ISO avec `checkdate`, skip des items sans SIRET, `Nom`=`Societe` → une colonne. - **`SyncQualimatCommand`** : options `--file` / `--ppp` / `--dry-run`, fetch via http-client, upsert DBAL transactionnel (`ON CONFLICT (siret)`) + soft-delete des absents + journal, garde-fou troncature (`count == ppp`). - Activation de `framework.http_client` (l'alias `HttpClientInterface` n'était pas enregistré). ### Tests - Unitaires (`QualimatRowMapper`) + fonctionnels de la commande via `--file` (upsert, normalisation, journal, soft-delete). - Suite complète **598/598** verte. `ColumnsHaveSqlCommentTest` ✅. - Bout-en-bout réel : sync de **2332 transporteurs** (1 ignoré sans SIRET, 0 désactivé, 1 journal). ### Décisions - Migration au **namespace racine** `migrations/` (convention réelle M2/M3 ; pas de FK cross-module ; évite le tri FQCN) — écart assumé vs le mot "modulaire" du ticket. - `status` sans CHECK contraignant (feed externe), `siret` non contraint à 14 (source incomplète). --------- Co-authored-by: Matthieu <contact@malio.fr> Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Reviewed-on: #99 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
106 lines
3.9 KiB
PHP
106 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Transport\Application\Qualimat;
|
|
|
|
use App\Module\Transport\Application\Qualimat\QualimatRowMapper;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
final class QualimatRowMapperTest extends TestCase
|
|
{
|
|
public function testNormalizeSiretStripsNonDigits(): void
|
|
{
|
|
self::assertSame('44415628500025', QualimatRowMapper::normalizeSiret('444 156 285 000 25'));
|
|
self::assertNull(QualimatRowMapper::normalizeSiret(null));
|
|
self::assertNull(QualimatRowMapper::normalizeSiret(' '));
|
|
self::assertNull(QualimatRowMapper::normalizeSiret(''));
|
|
}
|
|
|
|
public function testParseDate(): void
|
|
{
|
|
self::assertSame('2027-05-14', QualimatRowMapper::parseDate('14/05/2027'));
|
|
self::assertNull(QualimatRowMapper::parseDate(null));
|
|
self::assertNull(QualimatRowMapper::parseDate('2027-05-14'));
|
|
self::assertNull(QualimatRowMapper::parseDate('14-05-2027'));
|
|
// Date calendaire impossible : evite un INSERT en erreur.
|
|
self::assertNull(QualimatRowMapper::parseDate('31/02/2027'));
|
|
}
|
|
|
|
public function testMapOneNormalizesAndTrims(): void
|
|
{
|
|
$row = QualimatRowMapper::mapOne([
|
|
'Nom' => ' 2C TRANS ',
|
|
'Societe' => '2C TRANS',
|
|
'Adresse' => '66 Impasse Mendi',
|
|
'CodePostal' => '65500',
|
|
'Ville' => 'VIC EN BIGORRE',
|
|
'Telephone_1' => '+33|0608890316',
|
|
'Siret' => '444 156 285 000 25',
|
|
'Validite' => '14/05/2027',
|
|
'Statut' => 'Audité',
|
|
'Departement' => '65 - Hautes-Pyrénées',
|
|
]);
|
|
|
|
self::assertNotNull($row);
|
|
self::assertSame('44415628500025', $row['siret']);
|
|
self::assertSame('2C TRANS', $row['name']);
|
|
self::assertSame('2027-05-14', $row['validity_date']);
|
|
self::assertSame('+33|0608890316', $row['phone']);
|
|
self::assertSame('Audité', $row['status']);
|
|
self::assertSame('65 - Hautes-Pyrénées', $row['department']);
|
|
}
|
|
|
|
public function testMapOneReturnsNullWithoutSiret(): void
|
|
{
|
|
self::assertNull(QualimatRowMapper::mapOne(['Nom' => 'X', 'Siret' => null]));
|
|
self::assertNull(QualimatRowMapper::mapOne(['Nom' => 'X']));
|
|
self::assertNull(QualimatRowMapper::mapOne(['Nom' => 'X', 'Siret' => ' ']));
|
|
}
|
|
|
|
public function testMapManyCountsSkipped(): void
|
|
{
|
|
$result = QualimatRowMapper::mapMany([
|
|
['Nom' => 'A', 'Siret' => '111 111 111 00011', 'Statut' => 'Audité', 'Validite' => '01/01/2030'],
|
|
['Nom' => 'B', 'Siret' => null],
|
|
['Nom' => 'C', 'Siret' => ' '],
|
|
]);
|
|
|
|
self::assertCount(1, $result['rows']);
|
|
self::assertSame(2, $result['skipped']);
|
|
}
|
|
|
|
public function testMapManyDeduplicatesBySiretLastWins(): void
|
|
{
|
|
// Memes chiffres a separateurs pres : un seul transporteur, derniere
|
|
// occurrence gagnante (le compte ne doit pas surcompter les doublons).
|
|
$result = QualimatRowMapper::mapMany([
|
|
['Nom' => 'PREMIER', 'Siret' => '111 111 111 00011', 'Statut' => 'Audité'],
|
|
['Nom' => 'DERNIER', 'Siret' => '11111111100011', 'Statut' => 'Valide'],
|
|
]);
|
|
|
|
self::assertCount(1, $result['rows']);
|
|
self::assertSame(0, $result['skipped']);
|
|
self::assertSame('DERNIER', $result['rows'][0]['name']);
|
|
self::assertSame('Valide', $result['rows'][0]['status']);
|
|
}
|
|
|
|
public function testEmptyOptionalFieldsBecomeNull(): void
|
|
{
|
|
$row = QualimatRowMapper::mapOne([
|
|
'Siret' => '111 111 111 00011',
|
|
'Nom' => 'A',
|
|
'Adresse' => '',
|
|
'Ville' => ' ',
|
|
]);
|
|
|
|
self::assertNotNull($row);
|
|
self::assertNull($row['address']);
|
|
self::assertNull($row['city']);
|
|
self::assertNull($row['validity_date']);
|
|
}
|
|
}
|