diff --git a/src/Bovin/Dto/EarTagSeriesDto.php b/src/Bovin/Dto/EarTagSeriesDto.php new file mode 100644 index 0000000..bbadc7e --- /dev/null +++ b/src/Bovin/Dto/EarTagSeriesDto.php @@ -0,0 +1,12 @@ + $animals + * @param list $earTagSeries + */ + public function __construct( + public StandardResponseDto $standardResponse, + public int $nbBovins, + public ?DateTimeImmutable $startDate, + public ?DateTimeImmutable $endDate, + public bool $includesEarTagStock, + public array $animals, + public array $earTagSeries, + public ?object $rawSoapResponse, + ) {} +} diff --git a/src/Bovin/Mapper/InventoryMapper.php b/src/Bovin/Mapper/InventoryMapper.php new file mode 100644 index 0000000..9ec5884 --- /dev/null +++ b/src/Bovin/Mapper/InventoryMapper.php @@ -0,0 +1,85 @@ +mapStandardResponse($soapResponse->ReponseStandard ?? null); + + $nbBovins = $this->toNullableInt($soapResponse->ReponseSpecifique->NbBovins ?? null) ?? 0; + + $startDate = null; + $endDate = null; + $includesEarTagStock = false; + $animals = []; + $earTagSeries = []; + + if (is_object($unzippedMessage)) { + $infoNode = $unzippedMessage->InformationsMessage ?? null; + if (is_object($infoNode)) { + $startDate = $this->toNullableDate($infoNode->DateDebut ?? null); + $endDate = $this->toNullableDate($infoNode->DateFin ?? null); + $includesEarTagStock = (bool) $this->toNullableBool($infoNode->StockBoucles ?? null); + } + + $bovinsNode = $unzippedMessage->Bovins->Bovin ?? null; + foreach ($this->normalizeToList($bovinsNode) as $bovinNode) { + if (!is_object($bovinNode)) { + continue; + } + $animals[] = $this->animalSummaryMapper->map($bovinNode); + } + + $seriesNode = $unzippedMessage->Boucles->SerieBoucles ?? null; + foreach ($this->normalizeToList($seriesNode) as $serieNode) { + if (!is_object($serieNode)) { + continue; + } + $earTagSeries[] = new EarTagSeriesDto(rawNode: $serieNode); + } + } + + return new InventoryDto( + standardResponse: $standardResponse, + nbBovins: $nbBovins, + startDate: $startDate, + endDate: $endDate, + includesEarTagStock: $includesEarTagStock, + animals: $animals, + earTagSeries: $earTagSeries, + rawSoapResponse: $soapResponse, + ); + } + + private function mapStandardResponse(mixed $standardResponseNode): StandardResponseDto + { + $result = (bool) ($standardResponseNode->Resultat ?? false); + $anomalyNode = $standardResponseNode->Anomalie ?? null; + $anomaly = null; + + if (is_object($anomalyNode)) { + $anomaly = new AnomalyDto( + code: $this->toNullableString($anomalyNode->Code ?? null), + severity: $this->toNullableInt($anomalyNode->Severite ?? null), + message: $this->toNullableString($anomalyNode->Message ?? null), + ); + } + + return new StandardResponseDto($result, $anomaly); + } +} diff --git a/tests/Unit/Bovin/Mapper/InventoryMapperTest.php b/tests/Unit/Bovin/Mapper/InventoryMapperTest.php new file mode 100644 index 0000000..899accf --- /dev/null +++ b/tests/Unit/Bovin/Mapper/InventoryMapperTest.php @@ -0,0 +1,99 @@ +map($this->makeSoapResponse(), $this->makeUnzippedMessage()); + + self::assertInstanceOf(InventoryDto::class, $inventory); + self::assertTrue($inventory->standardResponse->result); + self::assertSame(2, $inventory->nbBovins); + self::assertEquals(new DateTimeImmutable('2026-01-01'), $inventory->startDate); + self::assertEquals(new DateTimeImmutable('2026-01-31'), $inventory->endDate); + self::assertTrue($inventory->includesEarTagStock); + self::assertCount(2, $inventory->animals); + self::assertCount(1, $inventory->earTagSeries); + self::assertSame('FR123', $inventory->animals[0]->identification?->bovin?->nationalNumber); + } + + public function testMapInventoryWithoutMessageZipReturnsEmptyLists(): void + { + $mapper = new InventoryMapper(new AnimalSummaryMapper()); + + $soapResponse = new stdClass(); + $soapResponse->ReponseStandard = new stdClass(); + $soapResponse->ReponseStandard->Resultat = true; + $soapResponse->ReponseSpecifique = new stdClass(); + $soapResponse->ReponseSpecifique->NbBovins = 0; + + $inventory = $mapper->map($soapResponse, null); + + self::assertSame(0, $inventory->nbBovins); + self::assertSame([], $inventory->animals); + self::assertSame([], $inventory->earTagSeries); + self::assertNull($inventory->startDate); + } + + private function makeSoapResponse(): object + { + $response = new stdClass(); + $response->ReponseStandard = new stdClass(); + $response->ReponseStandard->Resultat = true; + $response->ReponseSpecifique = new stdClass(); + $response->ReponseSpecifique->NbBovins = 2; + + return $response; + } + + private function makeUnzippedMessage(): object + { + $message = new stdClass(); + $message->InformationsMessage = new stdClass(); + $message->InformationsMessage->DateDebut = '2026-01-01'; + $message->InformationsMessage->DateFin = '2026-01-31'; + $message->InformationsMessage->StockBoucles = '1'; + $message->Bovins = new stdClass(); + $message->Bovins->Bovin = [ + $this->makeAnimalNode('FR123'), + $this->makeAnimalNode('FR456'), + ]; + $message->Boucles = new stdClass(); + $message->Boucles->SerieBoucles = new stdClass(); + $message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001'; + + return $message; + } + + private function makeAnimalNode(string $nationalNumber): object + { + $node = new stdClass(); + $node->IdentiteBovin = new stdClass(); + $node->IdentiteBovin->Bovin = new stdClass(); + $node->IdentiteBovin->Bovin->NumeroNational = $nationalNumber; + $node->PeriodesPresences = new stdClass(); + $node->PeriodesPresences->PeriodePresence = new stdClass(); + $node->PeriodesPresences->PeriodePresence->Entree = new stdClass(); + $node->PeriodesPresences->PeriodePresence->Entree->DateEntree = '2025-05-01'; + + return $node; + } +}