From a60a6e46de1a0fb016fbcf61d8c3fc4f73eca7af Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 09:11:53 +0000 Subject: [PATCH] =?UTF-8?q?feat=20:=20parser=20SerieBoucles=20dans=20EarTa?= =?UTF-8?q?gSeriesDto=20typ=C3=A9=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: https://gitea.malio.fr/MALIO-DEV/ednotif-bundle/pulls/4 Co-authored-by: tristan Co-committed-by: tristan --- .../plans/2026-04-22-eartag-series-parsing.md | 297 ++++++++++++++++++ ...2026-04-22-eartag-series-parsing-design.md | 138 ++++++++ src/Bovin/Dto/EarTagSeriesDto.php | 29 +- src/Bovin/Mapper/InventoryMapper.php | 6 +- tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php | 49 +++ .../Unit/Bovin/Mapper/InventoryMapperTest.php | 30 +- 6 files changed, 531 insertions(+), 18 deletions(-) create mode 100644 docs/superpowers/plans/2026-04-22-eartag-series-parsing.md create mode 100644 docs/superpowers/specs/2026-04-22-eartag-series-parsing-design.md create mode 100644 tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php diff --git a/docs/superpowers/plans/2026-04-22-eartag-series-parsing.md b/docs/superpowers/plans/2026-04-22-eartag-series-parsing.md new file mode 100644 index 0000000..1f06cbb --- /dev/null +++ b/docs/superpowers/plans/2026-04-22-eartag-series-parsing.md @@ -0,0 +1,297 @@ +# Parsing de `EarTagSeriesDto` — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Transformer `EarTagSeriesDto` d'un wrapper `rawNode` vers un DTO plat à 3 champs (`countryCode`, `startNumber`, `quantity`) avec un helper calculé `endNumber()`, et adapter `InventoryMapper` pour peupler ces champs. + +**Architecture:** Single commit atomique — on casse et on rétablit la compat des tests dans la même opération. Un nouveau fichier `EarTagSeriesDtoTest` pin le comportement de `endNumber()` ; les deux tests existants de `InventoryMapperTest` sont adaptés pour consommer les nouveaux champs au lieu de `rawNode`. Aucun autre fichier touché. + +**Tech Stack:** PHP 8.4, PHPUnit 12, pas de nouvelle dépendance. + +Spec associée : `docs/superpowers/specs/2026-04-22-eartag-series-parsing-design.md`. + +--- + +## File Structure + +### À créer + +``` +tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php 3 tests sur endNumber() +``` + +### À modifier + +``` +src/Bovin/Dto/EarTagSeriesDto.php réécriture complète (3 fields + helper) +src/Bovin/Mapper/InventoryMapper.php 1 bloc modifié (instanciation du DTO) +tests/Unit/Bovin/Mapper/InventoryMapperTest.php 2 méthodes adaptées + helpers ajustés +``` + +--- + +## Task 1 — DTO typé + mapper adapté + tests + +**Exécution en une seule tâche** parce que DTO et mapper sont liés — les modifier séparément laisserait la suite rouge entre commits. On fait un seul commit atomique. + +**Files:** +- Create: `tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php` +- Modify: `src/Bovin/Dto/EarTagSeriesDto.php` (réécriture) +- Modify: `src/Bovin/Mapper/InventoryMapper.php` (bloc d'instanciation `SerieBoucles`) +- Modify: `tests/Unit/Bovin/Mapper/InventoryMapperTest.php` (2 tests + 1 helper) + +### Steps + +- [ ] **Step 1: Écrire les 3 nouveaux tests sur le DTO (RED phase)** + +Contenu complet de `tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php` : +```php +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()); + } +} +``` + +- [ ] **Step 2: Lancer ce test seul (il doit échouer)** + +Run : +``` +make test FILES=tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php +``` + +Expected : les 3 tests échouent parce que le constructeur actuel de `EarTagSeriesDto` attend `rawNode: object` et pas les 3 nouveaux champs. Message d'erreur typique : +``` +ArgumentCountError: Too few arguments to function ... 0 passed and exactly 1 expected +``` +ou +``` +TypeError: Malio\EdnotifBundle\Bovin\Dto\EarTagSeriesDto::__construct(): Argument #1 ($rawNode) must be of type object, string given +``` +L'important : on voit bien une erreur liée à la signature du constructeur, pas un problème de syntax/autoload. Si erreur différente, investiguer avant de continuer. + +- [ ] **Step 3: Réécrire `EarTagSeriesDto`** + +Remplacer intégralement le contenu de `src/Bovin/Dto/EarTagSeriesDto.php` par : +```php +startNumber + $this->quantity - 1), + 10, + '0', + STR_PAD_LEFT, + ); + } +} +``` + +- [ ] **Step 4: Vérifier les tests DTO (GREEN phase partielle)** + +Run : +``` +make test FILES=tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php +``` +Expected : 3 tests passent, 3 assertions. + +- [ ] **Step 5: Lancer la suite complète (la suite DOIT être rouge sur InventoryMapperTest)** + +Run : +``` +make test +``` +Expected : au moins 2 tests en échec dans `InventoryMapperTest` (`testMapFullInventory` et `testMapInventoryWithSerieBouclesAsListPreservesAllEntries`) à cause de l'incompatibilité entre le mapper actuel qui fait `new EarTagSeriesDto(rawNode: $serieNode)` et la nouvelle signature. C'est attendu et temporaire — on va le corriger dans les steps suivants. + +Si d'autres tests échouent, s'arrêter et investiguer avant de continuer. + +- [ ] **Step 6: Adapter `InventoryMapper`** + +Dans `src/Bovin/Mapper/InventoryMapper.php`, localiser la boucle qui construit `$earTagSeries`. Elle ressemble à : +```php + $seriesNode = $unzippedMessage->Boucles->SerieBoucles ?? null; + foreach ($this->normalizeToList($seriesNode) as $serieNode) { + if (!is_object($serieNode)) { + continue; + } + $earTagSeries[] = new EarTagSeriesDto(rawNode: $serieNode); + } +``` + +Remplacer la ligne d'instanciation par : +```php + $earTagSeries[] = new EarTagSeriesDto( + countryCode: $this->toNullableString($serieNode->CodePays ?? null) ?? '', + startNumber: $this->toNullableString($serieNode->DebutSerie ?? null) ?? '', + quantity: $this->toNullableInt($serieNode->Quantite ?? null) ?? 0, + ); +``` + +Reste du mapper inchangé. Les imports actuels couvrent déjà `EarTagSeriesDto` et le trait fournit déjà `toNullableString` / `toNullableInt`. + +- [ ] **Step 7: Lire `InventoryMapperTest` pour repérer les fixtures à adapter** + +Run : +``` +cat tests/Unit/Bovin/Mapper/InventoryMapperTest.php +``` + +Deux endroits utilisent aujourd'hui le champ `NumeroSerieDebut` (nom fictif, non-XSD — hérité d'une fixture approximative) : + +1. La méthode privée `makeUnzippedMessage()` qui construit `$message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001';` (série unique en objet, pas en liste). +2. Le test `testMapInventoryWithSerieBouclesAsListPreservesAllEntries` qui construit 2 objets avec `NumeroSerieDebut = 'A0001'` et `'B0001'`. + +Noter les lignes exactes avant de modifier. + +- [ ] **Step 8: Adapter `makeUnzippedMessage()` — série unique** + +Dans le helper privé `makeUnzippedMessage()` de `InventoryMapperTest`, remplacer : +```php + $message->Boucles = new stdClass(); + $message->Boucles->SerieBoucles = new stdClass(); + $message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001'; +``` + +par : +```php + $message->Boucles = new stdClass(); + $message->Boucles->SerieBoucles = new stdClass(); + $message->Boucles->SerieBoucles->CodePays = 'FR'; + $message->Boucles->SerieBoucles->DebutSerie = '0012345678'; + $message->Boucles->SerieBoucles->Quantite = 50; +``` + +- [ ] **Step 9: Adapter `testMapFullInventory` — assertion sur la série** + +Dans `testMapFullInventory`, trouver la ligne qui asserte une valeur sur le `rawNode` (s'il y en a une ; sinon juste `assertCount(1, $inventory->earTagSeries)` suffit). Si le test avait une assertion comme `$inventory->earTagSeries[0]->rawNode->NumeroSerieDebut`, la remplacer par : +```php + self::assertSame('FR', $inventory->earTagSeries[0]->countryCode); + self::assertSame('0012345678', $inventory->earTagSeries[0]->startNumber); + self::assertSame(50, $inventory->earTagSeries[0]->quantity); +``` + +Si le test se contentait de `assertCount(1, ...)`, **ajouter** les 3 assertions ci-dessus pour renforcer la couverture post-refactor. + +- [ ] **Step 10: Adapter `testMapInventoryWithSerieBouclesAsListPreservesAllEntries`** + +Localiser les 2 blocs qui construisent `$serie1` et `$serie2`. Les remplacer par : +```php + $serie1 = new stdClass(); + $serie1->CodePays = 'FR'; + $serie1->DebutSerie = '0012345678'; + $serie1->Quantite = 10; + + $serie2 = new stdClass(); + $serie2->CodePays = 'FR'; + $serie2->DebutSerie = '0055500000'; + $serie2->Quantite = 25; +``` + +Puis remplacer les assertions qui lisaient `rawNode->NumeroSerieDebut` par : +```php + self::assertCount(2, $inventory->earTagSeries); + self::assertSame('0012345678', $inventory->earTagSeries[0]->startNumber); + self::assertSame(10, $inventory->earTagSeries[0]->quantity); + self::assertSame('0055500000', $inventory->earTagSeries[1]->startNumber); + self::assertSame(25, $inventory->earTagSeries[1]->quantity); +``` + +(Le `assertCount(2, …)` doit être conservé si présent ; sinon l'ajouter au-dessus.) + +- [ ] **Step 11: Lancer la suite complète (GREEN phase finale)** + +Run : +``` +make test +``` +Expected : tous les tests verts. Compte attendu : **56 tests** (53 précédents + 3 nouveaux sur le DTO). Les 2 tests de `InventoryMapperTest` sont modifiés, pas ajoutés. + +Si un test est rouge, investiguer. Causes possibles : +- `endNumber()` calcule mal le padding → vérifier le `str_pad` et la conversion `(int) $this->startNumber`. +- Une assertion de la fixture a été mal recopiée → vérifier les noms de champs XSD. +- Un import manquant dans un fichier modifié → vérifier les `use` statements. + +- [ ] **Step 12: Commit** + +``` +git add src/Bovin/Dto/EarTagSeriesDto.php \ + src/Bovin/Mapper/InventoryMapper.php \ + tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php \ + tests/Unit/Bovin/Mapper/InventoryMapperTest.php +git commit -m "feat : parser SerieBoucles dans EarTagSeriesDto typé" +``` + +--- + +## Checklist finale + +- [ ] `make test` vert, 56 tests au total +- [ ] Un seul commit atomique +- [ ] `EarTagSeriesDto` n'a plus de propriété `$rawNode` +- [ ] `InventoryMapper` instancie le DTO avec les 3 champs XSD (`CodePays`, `DebutSerie`, `Quantite`) +- [ ] Aucun autre fichier n'a été touché (pas de changement sur `InventoryDto`, `BovinApi`, `BovinApiInterface`, `config/services.php`) diff --git a/docs/superpowers/specs/2026-04-22-eartag-series-parsing-design.md b/docs/superpowers/specs/2026-04-22-eartag-series-parsing-design.md new file mode 100644 index 0000000..c182964 --- /dev/null +++ b/docs/superpowers/specs/2026-04-22-eartag-series-parsing-design.md @@ -0,0 +1,138 @@ +# Parsing de `SerieBoucles` → `EarTagSeriesDto` typé + +## Contexte + +`EarTagSeriesDto` a été introduit en Phase 1 comme wrapper minimal (`$rawNode: object`) autour du noeud `SerieBoucles` retourné par `IpBGetInventaire` quand le consommateur passe `includeEarTagStock: true`. Le docblock actuel dit que le XSD `typeSerieBoucles` est « riche » et qu'on différera le parsing — c'est **faux**. Le XSD (`resources/ednotif-ws/IpBNotif_v1.xsd:1181-1197`) ne contient que 3 champs : + +```xml + + + + + + + +``` + +Sémantique : « j'ai `Quantite` boucles consécutives à partir du numéro `DebutSerie` dans le pays `CodePays` ». Une série = une plage de boucles physiques non utilisées, en stock chez l'éleveur. + +## But + +Remplacer le wrapper `rawNode` par un DTO typé à 3 champs, plus un helper calculé `endNumber()` qui rend l'usage UI immédiat (afficher « 50 boucles de 0012345678 à 0012345727 »). + +## Scope + +### Inclus + +- `EarTagSeriesDto` plat : `countryCode: string`, `startNumber: string`, `quantity: int`. +- Une méthode `endNumber(): string` qui renvoie `startNumber + quantity - 1` avec zéro-padding à 10 chiffres. +- Adaptation du `InventoryMapper` : instancier le DTO avec les 3 champs lus depuis `$serieNode->CodePays`/`DebutSerie`/`Quantite` au lieu de passer le noeud brut. +- Tests unitaires sur le DTO (helper) et sur le mapper (les 2 tests existants pour `SerieBoucles` — full inventory + list — passent à travers les nouveaux champs). +- Mise à jour du docblock sur `EarTagSeriesDto` (le texte Phase 1 est à refaire). + +### Exclus (YAGNI) + +- `contains(string $tagNumber): bool` et `iterableNumbers(): Generator` — à ajouter le jour où `IpBCreateRebouclage` ou une UI de sélection de boucle en aura vraiment besoin. +- Validation métier (vérifier que `startNumber` matche le pattern XSD `0[1-9][0-9]{8}|[1-9][0-9]{9}`) — EDNOTIF garantit déjà la forme, on n'ajoute pas une seconde ligne de défense. +- Changement du type de `$earTagSeries` dans `InventoryDto` — la liste reste `list`. + +## Changement non-rétro-compatible assumé + +Le remplacement de `$rawNode` par 3 champs casse l'API publique de `EarTagSeriesDto`. Comme cette DTO n'a **aucun consommateur connu à ce jour** (Ferme n'utilise pas `includeEarTagStock: true`), on fait le changement direct, sans transition. Si un consommateur tiers existait, il faudrait un cycle de dépréciation ; ce n'est pas le cas. + +## Architecture + +### DTO + +```php +final readonly class EarTagSeriesDto +{ + public function __construct( + 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, + ); + } +} +``` + +Décisions à documenter dans le code : + +- **`startNumber` en string**, pas int : le XSD impose 10 chiffres avec zéro-padding possible. Convertir en int ferait perdre le format (`'0012345678'` → `12345678`). Laisser en string préserve le format d'affichage. +- **`endNumber()` renvoie aussi une string** zéro-paddée à 10 chiffres, via `str_pad`. Cohérent avec `startNumber`. +- **`quantity` en int** : xsd:unsignedInt se mappe naturellement sur int PHP (les 10 milliards de boucles possibles tiennent dans int64 sans problème). + +### Mapper + +Dans `InventoryMapper::map()`, remplacer : + +```php +$earTagSeries[] = new EarTagSeriesDto(rawNode: $serieNode); +``` + +par : + +```php +$earTagSeries[] = new EarTagSeriesDto( + countryCode: $this->toNullableString($serieNode->CodePays ?? null) ?? '', + startNumber: $this->toNullableString($serieNode->DebutSerie ?? null) ?? '', + quantity: $this->toNullableInt($serieNode->Quantite ?? null) ?? 0, +); +``` + +Valeurs par défaut (`''`, `0`) pour les cas où EDNOTIF renverrait un noeud partiel — plus simple qu'une DTO avec des nullable partout, et `quantity = 0` / `startNumber = ''` sont des valeurs sentinelles immédiatement repérables en debug. + +### Docblock actualisé + +Remplacer le texte Phase 1 sur `EarTagSeriesDto` par une description factuelle de la structure XSD et de la sémantique (plage de boucles en stock). + +## Edge cases + +- **Noeud `SerieBoucles` partiel** (un des 3 champs manque) : le DTO est construit avec les valeurs sentinelles `''` ou `0`. Pas de crash, pas d'exception. Responsabilité du consommateur de filtrer si besoin. +- **`quantity = 0`** : `endNumber()` renvoie `startNumber - 1` (zéro-padded). Techniquement absurde côté métier mais techniquement déterministe. Ne vaut pas la peine de documenter un `throw`. +- **`startNumber` vide** : `(int) '' === 0`, `endNumber()` renvoie `str_pad((string)(-1), ...)` = `'00000000-1'`. Déjà en terrain « shouldn't happen ». Pas de garde défensive ajoutée. +- **`CodePays` vide** : `endNumber()` ne dépend pas du pays, aucun impact. + +## Tests + +### DTO + +Un fichier `tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php` : + +- `testEndNumberComputesStartPlusQuantityMinusOne` — cas standard, ex : start `0012345678`, quantity 50, end `0012345727`. +- `testEndNumberPreservesLeadingZeroPadding` — cas avec leading zero, ex : start `0000000001`, quantity 5, end `0000000005`. +- `testEndNumberWithQuantityOne` — une seule boucle dans la série, end === start. + +3 tests, ~15 assertions. + +### Mapper + +Les 2 tests existants (`testMapFullInventory` et `testMapInventoryWithSerieBouclesAsListPreservesAllEntries`) sont **modifiés** pour : + +- Peupler `CodePays`, `DebutSerie`, `Quantite` dans les fixtures à la place de `NumeroSerieDebut`. +- Asserter sur `$inventory->earTagSeries[0]->startNumber` et `$inventory->earTagSeries[0]->quantity` au lieu de `rawNode->NumeroSerieDebut`. + +Aucun nouveau test sur le mapper — la structure est déjà couverte. + +## Impact sur les autres fichiers + +- `src/Bovin/Dto/EarTagSeriesDto.php` — réécrit. +- `src/Bovin/Mapper/InventoryMapper.php` — un bloc modifié (instanciation de la DTO). +- `tests/Unit/Bovin/Mapper/InventoryMapperTest.php` — fixtures `SerieBoucles` adaptées (2 tests impactés). +- Nouveau : `tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php`. + +Aucun autre fichier touché. `InventoryDto`, `BovinApi`, `BovinApiInterface`, `config/services.php` : inchangés. + +## Comptage de tests attendu + +Avant : 53 tests. +Après : 53 + 3 nouveaux sur le DTO = 56 tests (les 2 du mapper sont modifiés, pas ajoutés). diff --git a/src/Bovin/Dto/EarTagSeriesDto.php b/src/Bovin/Dto/EarTagSeriesDto.php index 0e9e0d3..1098af2 100644 --- a/src/Bovin/Dto/EarTagSeriesDto.php +++ b/src/Bovin/Dto/EarTagSeriesDto.php @@ -5,18 +5,31 @@ declare(strict_types=1); namespace Malio\EdnotifBundle\Bovin\Dto; /** - * Wrapper minimal d'une entrée `SerieBoucles` du message de l'opération - * `IpBGetInventaire` (quand `includeEarTagStock: true`). + * Série de boucles (ear tags) en stock chez l'exploitation, retournée dans + * la réponse de `IpBGetInventaire` quand `includeEarTagStock: true`. * - * Le noeud XSD `typeSerieBoucles` est riche (plages de numéros, fournisseur, - * statut, dates...). En Phase 1 on garde volontairement la structure brute - * via `$rawNode` : les consommateurs qui ont besoin d'un champ précis y - * 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é. + * Correspond au type XSD `typeSerieBoucles` (resources/ednotif-ws/IpBNotif_v1.xsd) : + * une plage contigüe de `quantity` boucles à partir du numéro `startNumber` + * dans le pays `countryCode`. + * + * 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 { 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, + ); + } } diff --git a/src/Bovin/Mapper/InventoryMapper.php b/src/Bovin/Mapper/InventoryMapper.php index 3787ec1..50eaea3 100644 --- a/src/Bovin/Mapper/InventoryMapper.php +++ b/src/Bovin/Mapper/InventoryMapper.php @@ -50,7 +50,11 @@ final class InventoryMapper if (!is_object($serieNode)) { 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, + ); } } diff --git a/tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php b/tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php new file mode 100644 index 0000000..ed2ffe5 --- /dev/null +++ b/tests/Unit/Bovin/Dto/EarTagSeriesDtoTest.php @@ -0,0 +1,49 @@ +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()); + } +} diff --git a/tests/Unit/Bovin/Mapper/InventoryMapperTest.php b/tests/Unit/Bovin/Mapper/InventoryMapperTest.php index 2c4a524..4dfefa2 100644 --- a/tests/Unit/Bovin/Mapper/InventoryMapperTest.php +++ b/tests/Unit/Bovin/Mapper/InventoryMapperTest.php @@ -34,6 +34,9 @@ final class InventoryMapperTest extends TestCase self::assertCount(2, $inventory->animals); self::assertCount(1, $inventory->earTagSeries); 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 @@ -99,10 +102,15 @@ final class InventoryMapperTest extends TestCase { $mapper = new InventoryMapper(new AnimalSummaryMapper(), new StandardResponseMapper()); - $serie1 = new stdClass(); - $serie1->NumeroSerieDebut = 'A0001'; - $serie2 = new stdClass(); - $serie2->NumeroSerieDebut = 'B0001'; + $serie1 = new stdClass(); + $serie1->CodePays = 'FR'; + $serie1->DebutSerie = '0012345678'; + $serie1->Quantite = 10; + + $serie2 = new stdClass(); + $serie2->CodePays = 'FR'; + $serie2->DebutSerie = '0055500000'; + $serie2->Quantite = 25; $message = new stdClass(); $message->Boucles = new stdClass(); @@ -111,8 +119,10 @@ final class InventoryMapperTest extends TestCase $inventory = $mapper->map($this->makeSoapResponse(), $message); self::assertCount(2, $inventory->earTagSeries); - self::assertSame('A0001', $inventory->earTagSeries[0]->rawNode->NumeroSerieDebut); - self::assertSame('B0001', $inventory->earTagSeries[1]->rawNode->NumeroSerieDebut); + self::assertSame('0012345678', $inventory->earTagSeries[0]->startNumber); + 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 @@ -153,9 +163,11 @@ final class InventoryMapperTest extends TestCase $this->makeAnimalNode('FR123'), $this->makeAnimalNode('FR456'), ]; - $message->Boucles = new stdClass(); - $message->Boucles->SerieBoucles = new stdClass(); - $message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001'; + $message->Boucles = new stdClass(); + $message->Boucles->SerieBoucles = new stdClass(); + $message->Boucles->SerieBoucles->CodePays = 'FR'; + $message->Boucles->SerieBoucles->DebutSerie = '0012345678'; + $message->Boucles->SerieBoucles->Quantite = 50; return $message; }