feat : parser SerieBoucles dans EarTagSeriesDto typé
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,18 +5,31 @@ declare(strict_types=1);
|
|||||||
namespace Malio\EdnotifBundle\Bovin\Dto;
|
namespace Malio\EdnotifBundle\Bovin\Dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper minimal d'une entrée `SerieBoucles` du message de l'opération
|
* Série de boucles (ear tags) en stock chez l'exploitation, retournée dans
|
||||||
* `IpBGetInventaire` (quand `includeEarTagStock: true`).
|
* la réponse de `IpBGetInventaire` quand `includeEarTagStock: true`.
|
||||||
*
|
*
|
||||||
* Le noeud XSD `typeSerieBoucles` est riche (plages de numéros, fournisseur,
|
* Correspond au type XSD `typeSerieBoucles` (resources/ednotif-ws/IpBNotif_v1.xsd) :
|
||||||
* statut, dates...). En Phase 1 on garde volontairement la structure brute
|
* une plage contigüe de `quantity` boucles à partir du numéro `startNumber`
|
||||||
* via `$rawNode` : les consommateurs qui ont besoin d'un champ précis y
|
* dans le pays `countryCode`.
|
||||||
* accèdent par `$serie->rawNode->NomDuChamp`. Si un cas d'usage métier concret
|
*
|
||||||
* remonte (rebouclage, commande de boucles), on parsera dans un DTO dédié.
|
* Les numéros sont stockés en string pour préserver le zero-padding du XSD
|
||||||
|
* (10 chiffres, pattern `0[1-9][0-9]{8}|[1-9][0-9]{9}`).
|
||||||
*/
|
*/
|
||||||
final readonly class EarTagSeriesDto
|
final readonly class EarTagSeriesDto
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public object $rawNode,
|
public string $countryCode,
|
||||||
|
public string $startNumber,
|
||||||
|
public int $quantity,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function endNumber(): string
|
||||||
|
{
|
||||||
|
return str_pad(
|
||||||
|
(string) ((int) $this->startNumber + $this->quantity - 1),
|
||||||
|
10,
|
||||||
|
'0',
|
||||||
|
STR_PAD_LEFT,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ final class InventoryMapper
|
|||||||
if (!is_object($serieNode)) {
|
if (!is_object($serieNode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$earTagSeries[] = new EarTagSeriesDto(rawNode: $serieNode);
|
$earTagSeries[] = new EarTagSeriesDto(
|
||||||
|
countryCode: $this->toNullableString($serieNode->CodePays ?? null) ?? '',
|
||||||
|
startNumber: $this->toNullableString($serieNode->DebutSerie ?? null) ?? '',
|
||||||
|
quantity: $this->toNullableInt($serieNode->Quantite ?? null) ?? 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php
Normal file
49
tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Malio\EdnotifBundle\Tests\Unit\Bovin\Dto;
|
||||||
|
|
||||||
|
use Malio\EdnotifBundle\Bovin\Dto\EarTagSeriesDto;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
#[CoversClass(EarTagSeriesDto::class)]
|
||||||
|
final class EarTagSeriesDtoTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEndNumberComputesStartPlusQuantityMinusOne(): void
|
||||||
|
{
|
||||||
|
$series = new EarTagSeriesDto(
|
||||||
|
countryCode: 'FR',
|
||||||
|
startNumber: '0012345678',
|
||||||
|
quantity: 50,
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame('0012345727', $series->endNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndNumberPreservesLeadingZeroPadding(): void
|
||||||
|
{
|
||||||
|
$series = new EarTagSeriesDto(
|
||||||
|
countryCode: 'FR',
|
||||||
|
startNumber: '0000000001',
|
||||||
|
quantity: 5,
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame('0000000005', $series->endNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndNumberWithQuantityOneEqualsStartNumber(): void
|
||||||
|
{
|
||||||
|
$series = new EarTagSeriesDto(
|
||||||
|
countryCode: 'FR',
|
||||||
|
startNumber: '0012345678',
|
||||||
|
quantity: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame('0012345678', $series->endNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,9 @@ final class InventoryMapperTest extends TestCase
|
|||||||
self::assertCount(2, $inventory->animals);
|
self::assertCount(2, $inventory->animals);
|
||||||
self::assertCount(1, $inventory->earTagSeries);
|
self::assertCount(1, $inventory->earTagSeries);
|
||||||
self::assertSame('FR123', $inventory->animals[0]->identification?->bovin?->nationalNumber);
|
self::assertSame('FR123', $inventory->animals[0]->identification?->bovin?->nationalNumber);
|
||||||
|
self::assertSame('FR', $inventory->earTagSeries[0]->countryCode);
|
||||||
|
self::assertSame('0012345678', $inventory->earTagSeries[0]->startNumber);
|
||||||
|
self::assertSame(50, $inventory->earTagSeries[0]->quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMapInventoryWithoutMessageZipReturnsEmptyLists(): void
|
public function testMapInventoryWithoutMessageZipReturnsEmptyLists(): void
|
||||||
@@ -99,10 +102,15 @@ final class InventoryMapperTest extends TestCase
|
|||||||
{
|
{
|
||||||
$mapper = new InventoryMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
|
$mapper = new InventoryMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
|
||||||
|
|
||||||
$serie1 = new stdClass();
|
$serie1 = new stdClass();
|
||||||
$serie1->NumeroSerieDebut = 'A0001';
|
$serie1->CodePays = 'FR';
|
||||||
$serie2 = new stdClass();
|
$serie1->DebutSerie = '0012345678';
|
||||||
$serie2->NumeroSerieDebut = 'B0001';
|
$serie1->Quantite = 10;
|
||||||
|
|
||||||
|
$serie2 = new stdClass();
|
||||||
|
$serie2->CodePays = 'FR';
|
||||||
|
$serie2->DebutSerie = '0055500000';
|
||||||
|
$serie2->Quantite = 25;
|
||||||
|
|
||||||
$message = new stdClass();
|
$message = new stdClass();
|
||||||
$message->Boucles = new stdClass();
|
$message->Boucles = new stdClass();
|
||||||
@@ -111,8 +119,10 @@ final class InventoryMapperTest extends TestCase
|
|||||||
$inventory = $mapper->map($this->makeSoapResponse(), $message);
|
$inventory = $mapper->map($this->makeSoapResponse(), $message);
|
||||||
|
|
||||||
self::assertCount(2, $inventory->earTagSeries);
|
self::assertCount(2, $inventory->earTagSeries);
|
||||||
self::assertSame('A0001', $inventory->earTagSeries[0]->rawNode->NumeroSerieDebut);
|
self::assertSame('0012345678', $inventory->earTagSeries[0]->startNumber);
|
||||||
self::assertSame('B0001', $inventory->earTagSeries[1]->rawNode->NumeroSerieDebut);
|
self::assertSame(10, $inventory->earTagSeries[0]->quantity);
|
||||||
|
self::assertSame('0055500000', $inventory->earTagSeries[1]->startNumber);
|
||||||
|
self::assertSame(25, $inventory->earTagSeries[1]->quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMapInventoryWithMissingNbBovinsDefaultsToZero(): void
|
public function testMapInventoryWithMissingNbBovinsDefaultsToZero(): void
|
||||||
@@ -153,9 +163,11 @@ final class InventoryMapperTest extends TestCase
|
|||||||
$this->makeAnimalNode('FR123'),
|
$this->makeAnimalNode('FR123'),
|
||||||
$this->makeAnimalNode('FR456'),
|
$this->makeAnimalNode('FR456'),
|
||||||
];
|
];
|
||||||
$message->Boucles = new stdClass();
|
$message->Boucles = new stdClass();
|
||||||
$message->Boucles->SerieBoucles = new stdClass();
|
$message->Boucles->SerieBoucles = new stdClass();
|
||||||
$message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001';
|
$message->Boucles->SerieBoucles->CodePays = 'FR';
|
||||||
|
$message->Boucles->SerieBoucles->DebutSerie = '0012345678';
|
||||||
|
$message->Boucles->SerieBoucles->Quantite = 50;
|
||||||
|
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user