From a4342af0957ea7f8e48ab1af5c8476cf48bacd81 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 17:53:37 +0200 Subject: [PATCH 1/7] =?UTF-8?q?feat=20:=20=C3=A9tape=201/5=20-=20ajout=20d?= =?UTF-8?q?es=20champs=20sex=20et=20exitedAt=20sur=20Bovine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migration pour les nouvelles colonnes - ExistsFilter sur exitedAt, SearchFilter sur sex/workNumber/breedCode, DateFilter sur birthDate - DTO front étendu avec workNumber, birthDate, breedCode, sex, exitedAt Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/services/dto/bovine-data.ts | 5 ++++ migrations/Version20260422155300.php | 33 +++++++++++++++++++++++ src/Entity/Bovine.php | 40 +++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 migrations/Version20260422155300.php diff --git a/frontend/services/dto/bovine-data.ts b/frontend/services/dto/bovine-data.ts index 58bb736..4cf0b50 100644 --- a/frontend/services/dto/bovine-data.ts +++ b/frontend/services/dto/bovine-data.ts @@ -5,6 +5,11 @@ export interface BovineData { arrivalDate: string | null buildingCase: string | null supplier: string | null + workNumber: string | null + birthDate: string | null + breedCode: string | null + sex: string | null + exitedAt: string | null } export type BovinePayload = { diff --git a/migrations/Version20260422155300.php b/migrations/Version20260422155300.php new file mode 100644 index 0000000..7ff15b8 --- /dev/null +++ b/migrations/Version20260422155300.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE bovine ADD sex VARCHAR(1) DEFAULT NULL'); + $this->addSql('ALTER TABLE bovine ADD exited_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE bovine DROP sex'); + $this->addSql('ALTER TABLE bovine DROP exited_at'); + } +} diff --git a/src/Entity/Bovine.php b/src/Entity/Bovine.php index 275c1f5..ff18b04 100644 --- a/src/Entity/Bovine.php +++ b/src/Entity/Bovine.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Entity; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; @@ -24,10 +25,14 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; #[ORM\UniqueConstraint(name: 'uniq_bovine_national_number', columns: ['national_number'])] #[ApiFilter(SearchFilter::class, properties: [ 'nationalNumber' => 'ipartial', + 'workNumber' => 'ipartial', + 'breedCode' => 'ipartial', + 'sex' => 'exact', 'buildingCase' => 'exact', 'receivedWeight' => 'exact', ])] -#[ApiFilter(DateFilter::class, properties: ['arrivalDate'])] +#[ApiFilter(DateFilter::class, properties: ['arrivalDate', 'birthDate'])] +#[ApiFilter(ExistsFilter::class, properties: ['exitedAt'])] #[ApiResource( operations: [ new Get( @@ -95,6 +100,15 @@ class Bovine #[Groups(['bovine:read', 'building_case:read'])] private ?string $breedCode = null; + #[ORM\Column(length: 1, nullable: true)] + #[Groups(['bovine:read', 'building_case:read'])] + private ?string $sex = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[Groups(['bovine:read', 'building_case:read'])] + #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] + private ?DateTimeImmutable $exitedAt = null; + public function getId(): ?int { return $this->id; @@ -195,4 +209,28 @@ class Bovine return $this; } + + public function getSex(): ?string + { + return $this->sex; + } + + public function setSex(?string $sex): static + { + $this->sex = $sex; + + return $this; + } + + public function getExitedAt(): ?DateTimeImmutable + { + return $this->exitedAt; + } + + public function setExitedAt(?DateTimeImmutable $exitedAt): static + { + $this->exitedAt = $exitedAt; + + return $this; + } } -- 2.39.5 From 2cde091813334beed82daa39e6a755ad986d048b Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 17:56:08 +0200 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20=C3=A9tape=202/5=20-=20endpoint?= =?UTF-8?q?=20POST=20/bovines/sync-inventory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Appelle BovinApiInterface::getInventory() puis upsert local par nationalNumber - Marque exitedAt sur les bovins absents de la réponse EDNOTIF - Retourne les stats { created, updated, exited, total } - Admin only Co-Authored-By: Claude Opus 4.7 (1M context) --- src/ApiResource/BovineSyncInventoryResult.php | 42 ++++++++ .../Bovin/BovineSyncInventoryProcessor.php | 99 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/ApiResource/BovineSyncInventoryResult.php create mode 100644 src/State/Bovin/BovineSyncInventoryProcessor.php diff --git a/src/ApiResource/BovineSyncInventoryResult.php b/src/ApiResource/BovineSyncInventoryResult.php new file mode 100644 index 0000000..adf32ad --- /dev/null +++ b/src/ApiResource/BovineSyncInventoryResult.php @@ -0,0 +1,42 @@ + + */ +final class BovineSyncInventoryProcessor implements ProcessorInterface +{ + public function __construct( + private BovinApiInterface $bovinApi, + private EntityManagerInterface $em, + ) {} + + public function process( + mixed $data, + Operation $operation, + array $uriVariables = [], + array $context = [], + ): BovineSyncInventoryResult { + $inventory = $this->bovinApi->getInventory(new DateTimeImmutable('2000-01-01')); + + $result = new BovineSyncInventoryResult(); + $result->total = count($inventory->animals); + + $existingByNationalNumber = []; + foreach ($this->em->getRepository(Bovine::class)->findAll() as $bovine) { + $existingByNationalNumber[$bovine->getNationalNumber()] = $bovine; + } + + $seen = []; + foreach ($inventory->animals as $animal) { + $nationalNumber = $animal->identification?->bovin?->nationalNumber; + if (null === $nationalNumber || '' === $nationalNumber) { + continue; + } + $seen[$nationalNumber] = true; + + if (isset($existingByNationalNumber[$nationalNumber])) { + $bovine = $existingByNationalNumber[$nationalNumber]; + ++$result->updated; + } else { + $bovine = new Bovine(); + $bovine->setNationalNumber($nationalNumber); + $this->em->persist($bovine); + ++$result->created; + } + + $this->applyEdnotifData($bovine, $animal); + $bovine->setExitedAt(null); + } + + $now = new DateTimeImmutable(); + foreach ($existingByNationalNumber as $nationalNumber => $bovine) { + if (isset($seen[$nationalNumber])) { + continue; + } + if (null !== $bovine->getExitedAt()) { + continue; + } + $bovine->setExitedAt($now); + ++$result->exited; + } + + $this->em->flush(); + + return $result; + } + + private function applyEdnotifData(Bovine $bovine, AnimalSummaryDto $animal): void + { + $identification = $animal->identification; + if (null !== $identification) { + $bovine->setSex($identification->sex); + $bovine->setBreedCode($identification->breedType); + $bovine->setWorkNumber($identification->workNumber); + $bovine->setBirthDate($identification->birthDate?->date); + } + + foreach ($animal->presencePeriods as $period) { + if (null === $period->exit && null !== $period->entry?->date) { + $bovine->setArrivalDate($period->entry->date); + + break; + } + } + } +} -- 2.39.5 From 72a3a98bfaea747a2f96cdb3f57fde5343448c28 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 17:57:42 +0200 Subject: [PATCH 3/7] =?UTF-8?q?feat=20:=20=C3=A9tape=203/5=20-=20page=20/i?= =?UTF-8?q?nventory=20(lecture=20seule)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UiDataTable server-side sur /api/bovines - 6 colonnes : N° National, N° Travail, Sex, Né le, Race, Entrée le - Filtres texte + select sex (Mâle/Femelle) + deux date pickers single input - Filtre implicite exists[exitedAt]=false pour n'afficher que les bovins actifs Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/pages/inventory.vue | 134 +++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 frontend/pages/inventory.vue diff --git a/frontend/pages/inventory.vue b/frontend/pages/inventory.vue new file mode 100644 index 0000000..0cc696b --- /dev/null +++ b/frontend/pages/inventory.vue @@ -0,0 +1,134 @@ + + + -- 2.39.5 From 6e20dbf00e35df8e8855295b6bd10f048cd74986 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 17:59:33 +0200 Subject: [PATCH 4/7] =?UTF-8?q?feat=20:=20=C3=A9tape=204/5=20-=20bouton=20?= =?UTF-8?q?Rafra=C3=AEchir=20l'inventaire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Admin only avec confirmation window.confirm au clic - Loader sur le bouton pendant l'appel POST - Toast de succès avec les stats (créés, maj, sortis, total) - reload() du tableau après succès Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/pages/inventory.vue | 53 ++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/frontend/pages/inventory.vue b/frontend/pages/inventory.vue index 0cc696b..a708217 100644 --- a/frontend/pages/inventory.vue +++ b/frontend/pages/inventory.vue @@ -1,7 +1,19 @@