diff --git a/frontend/pages/bovine/[id].vue b/frontend/pages/bovine/[id].vue
index 941c3b6..79f61ff 100644
--- a/frontend/pages/bovine/[id].vue
+++ b/frontend/pages/bovine/[id].vue
@@ -194,7 +194,6 @@ interface BovineMovementData {
enteredAt: string
leftAt: string | null
buildingCase: BuildingCaseRef | null
- building: BuildingRef | null
}
interface BovinePassportData {
@@ -301,7 +300,7 @@ const movementRows = computed(() => {
const list = bovine.value?.movements ?? []
return list.map(m => ({
id: m.id,
- building: m.buildingCase?.building?.label ?? m.building?.label ?? '—',
+ building: m.buildingCase?.building?.label ?? '—',
case: m.buildingCase?.caseNumber != null ? `Case ${m.buildingCase.caseNumber}` : '—',
enteredAt: formatDate(m.enteredAt),
leftAt: m.leftAt ? formatDate(m.leftAt) : null,
diff --git a/frontend/pages/inventory.vue b/frontend/pages/inventory.vue
index e3faa7b..779801d 100644
--- a/frontend/pages/inventory.vue
+++ b/frontend/pages/inventory.vue
@@ -125,7 +125,7 @@
{{ formatDate(item.arrivalDate) }}
- {{ item.effectiveBuilding?.label ?? '—' }}
+ {{ item.buildingCase?.building?.label ?? '—' }}
{{ item.buildingCase?.caseNumber ?? '—' }}
diff --git a/frontend/services/bovine.ts b/frontend/services/bovine.ts
index 7a13a8b..1a1466c 100644
--- a/frontend/services/bovine.ts
+++ b/frontend/services/bovine.ts
@@ -9,34 +9,3 @@ export async function createBovine(payload: BovinePayload) {
toastSuccessKey: 'success.bovine.create'
})
}
-
-export async function createBovines(nationalNumbers: string[]): Promise<{ created: BovineData[]; errors: string[] }> {
- const created: BovineData[] = []
- const errors: string[] = []
-
- for (const nationalNumber of nationalNumbers) {
- try {
- const bovine = await createBovine({ nationalNumber })
- if (bovine) {
- created.push(bovine)
- }
- } catch {
- errors.push(nationalNumber)
- }
- }
-
- return { created, errors }
-}
-
-export async function getBovine(id: number) {
- const api = useApi()
- return api.get(`bovines/${id}`)
-}
-
-export async function updateBovine(id: number, payload: BovinePayload) {
- const api = useApi()
- return api.patch(`bovines/${id}`, payload, {
- toastErrorKey: 'errors.bovine.update',
- toastSuccessKey: 'success.bovine.update'
- })
-}
diff --git a/frontend/services/dto/bovine-data.ts b/frontend/services/dto/bovine-data.ts
index 96ef87c..ae6d21d 100644
--- a/frontend/services/dto/bovine-data.ts
+++ b/frontend/services/dto/bovine-data.ts
@@ -16,8 +16,6 @@ export interface BovineData {
arrivalDate: string | null
exitDate: string | null
buildingCase: BovineBuildingCaseRef | null
- building: BovineBuildingRef | null
- effectiveBuilding: BovineBuildingRef | null
supplier: string | null
workNumber: string | null
birthDate: string | null
@@ -29,9 +27,5 @@ export interface BovineData {
export type BovinePayload = {
nationalNumber?: string
- receivedWeight?: number | null
- pricePerKg?: number | null
- arrivalDate?: string | null
buildingCase?: string | null
- supplier?: string | null
}
diff --git a/src/Command/BackfillBovineMovementsCommand.php b/src/Command/BackfillBovineMovementsCommand.php
deleted file mode 100644
index 3f85759..0000000
--- a/src/Command/BackfillBovineMovementsCommand.php
+++ /dev/null
@@ -1,93 +0,0 @@
-entityManager->createQueryBuilder()
- ->select('b')
- ->from(Bovine::class, 'b')
- ->where('b.buildingCase IS NOT NULL OR b.building IS NOT NULL')
- ->andWhere('NOT EXISTS (SELECT 1 FROM '.BovineMovement::class.' m WHERE m.bovine = b)')
- ->getQuery()
- ->getResult()
- ;
-
- $total = count($bovines);
- if (0 === $total) {
- $io->success('Aucun bovin à backfiller.');
-
- return Command::SUCCESS;
- }
-
- $io->info(sprintf('%d bovin(s) à backfiller.', $total));
-
- $now = new DateTimeImmutable();
- $created = 0;
- $fallback = 0;
-
- foreach ($bovines as $i => $bovine) {
- $movement = new BovineMovement();
- $movement->setBovine($bovine);
-
- if (null !== $bovine->getBuildingCase()) {
- $movement->setBuildingCase($bovine->getBuildingCase());
- } else {
- $movement->setBuilding($bovine->getBuilding());
- }
-
- $enteredAt = $bovine->getArrivalDate();
- if (null === $enteredAt) {
- $enteredAt = $now;
- ++$fallback;
- }
- $movement->setEnteredAt($enteredAt);
-
- $this->entityManager->persist($movement);
- ++$created;
-
- if (0 === ($i + 1) % self::FLUSH_EVERY) {
- $this->entityManager->flush();
- }
- }
-
- $this->entityManager->flush();
-
- $io->success(sprintf('%d mouvement(s) créé(s).', $created));
- if ($fallback > 0) {
- $io->warning(sprintf("%d bovin(s) sans date d'arrivée → enteredAt = maintenant.", $fallback));
- }
-
- return Command::SUCCESS;
- }
-}
diff --git a/src/Command/FeedBovinePricesCommand.php b/src/Command/FeedBovinePricesCommand.php
deleted file mode 100644
index 729413a..0000000
--- a/src/Command/FeedBovinePricesCommand.php
+++ /dev/null
@@ -1,215 +0,0 @@
-addArgument('file', InputArgument::REQUIRED, 'Chemin absolu vers le fichier XLSX')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simule sans persister en BDD')
- ;
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
- $file = (string) $input->getArgument('file');
- $dryRun = (bool) $input->getOption('dry-run');
-
- if (!file_exists($file)) {
- $io->error(sprintf('Fichier introuvable : %s', $file));
-
- return Command::FAILURE;
- }
-
- $io->title('Feed bovins depuis '.basename($file));
- if ($dryRun) {
- $io->warning('Dry-run activé : aucune écriture en BDD.');
- }
-
- try {
- $spreadsheet = IOFactory::load($file);
- } catch (Throwable $e) {
- $io->error('Impossible de lire le fichier : '.$e->getMessage());
-
- return Command::FAILURE;
- }
-
- $sheet = $spreadsheet->getActiveSheet();
- $highestRow = $sheet->getHighestRow();
-
- // Pré-chargement des fournisseurs pour des lookups rapides (insensible casse).
- $supplierByName = [];
- foreach ($this->em->getRepository(Supplier::class)->findAll() as $supplier) {
- $supplierByName[mb_strtoupper($supplier->getName())] = $supplier;
- }
-
- // Pré-chargement des bâtiments par code (insensible casse).
- $buildingByCode = [];
- foreach ($this->em->getRepository(Building::class)->findAll() as $building) {
- $buildingByCode[mb_strtoupper($building->getCode())] = $building;
- }
-
- $bovineRepo = $this->em->getRepository(Bovine::class);
-
- $stats = [
- 'total' => 0,
- 'updated' => 0,
- 'notFound' => 0,
- 'invalid' => 0,
- 'supplierMissing' => 0,
- 'buildingMissing' => 0,
- ];
- $missingNationalNumbers = [];
- $missingSuppliers = [];
- $missingBuildings = [];
-
- $io->progressStart($highestRow);
- for ($row = 1; $row <= $highestRow; ++$row) {
- ++$stats['total'];
-
- $rawNationalNumber = (string) ($sheet->getCell([1, $row])->getValue() ?? '');
- $rawSupplier = (string) ($sheet->getCell([2, $row])->getValue() ?? '');
- $rawWeight = $sheet->getCell([3, $row])->getValue();
- $rawPrice = $sheet->getCell([4, $row])->getValue();
- $rawBuilding = (string) ($sheet->getCell([5, $row])->getValue() ?? '');
-
- $rawNationalNumber = trim($rawNationalNumber);
- if ('' === $rawNationalNumber) {
- ++$stats['invalid'];
- $io->progressAdvance();
-
- continue;
- }
-
- // Garde : strip "FR" + espace optionnel uniquement s'il est présent.
- $nationalNumber = preg_replace('/^FR\s*/i', '', $rawNationalNumber);
-
- $bovine = $bovineRepo->findOneBy(['nationalNumber' => $nationalNumber]);
- if (null === $bovine) {
- ++$stats['notFound'];
- $missingNationalNumbers[] = $nationalNumber;
- $io->progressAdvance();
-
- continue;
- }
-
- // Lookup supplier (peut être null si introuvable ou colonne vide).
- $supplier = null;
- $supplierName = mb_strtoupper(trim($rawSupplier));
- if ('' !== $supplierName) {
- $supplier = $supplierByName[$supplierName] ?? null;
- if (null === $supplier) {
- ++$stats['supplierMissing'];
- $missingSuppliers[$supplierName] = ($missingSuppliers[$supplierName] ?? 0) + 1;
- }
- }
-
- $weight = is_numeric($rawWeight) ? (int) $rawWeight : null;
- $price = is_numeric($rawPrice) ? (float) $rawPrice : null;
-
- if (null !== $weight) {
- $bovine->setReceivedWeight($weight);
- }
- if (null !== $price) {
- $bovine->setPricePerKg($price);
- }
- $bovine->setSupplier($supplier);
-
- // Bâtiment direct : on n'écrase pas une affectation à une case existante.
- $buildingCode = mb_strtoupper(trim($rawBuilding));
- if ('' !== $buildingCode && null === $bovine->getBuildingCase()) {
- $building = $buildingByCode[$buildingCode] ?? null;
- if (null !== $building) {
- $bovine->setBuilding($building);
- } else {
- ++$stats['buildingMissing'];
- $missingBuildings[$buildingCode] = ($missingBuildings[$buildingCode] ?? 0) + 1;
- }
- }
-
- ++$stats['updated'];
- $io->progressAdvance();
- }
- $io->progressFinish();
-
- if (!$dryRun) {
- $this->em->flush();
- }
-
- $io->section('Résultats');
- $io->table(
- ['Métrique', 'Valeur'],
- [
- ['Lignes totales', $stats['total']],
- ['Bovins mis à jour', $stats['updated']],
- ['Bovins introuvables', $stats['notFound']],
- ['Lignes invalides', $stats['invalid']],
- ['Fournisseurs introuvables (supplier=null)', $stats['supplierMissing']],
- ['Bâtiments introuvables (building non set)', $stats['buildingMissing']],
- ]
- );
-
- if ([] !== $missingNationalNumbers) {
- $preview = array_slice($missingNationalNumbers, 0, 10);
- $io->warning(sprintf(
- '%d bovin(s) introuvable(s). Aperçu : %s%s',
- count($missingNationalNumbers),
- implode(', ', $preview),
- count($missingNationalNumbers) > 10 ? '…' : '',
- ));
- }
-
- if ([] !== $missingSuppliers) {
- $list = [];
- foreach ($missingSuppliers as $name => $count) {
- $list[] = sprintf('%s (%d)', $name, $count);
- }
- $io->warning('Fournisseurs introuvables (bovins rattachés en null) : '.implode(', ', $list));
- }
-
- if ([] !== $missingBuildings) {
- $list = [];
- foreach ($missingBuildings as $code => $count) {
- $list[] = sprintf('%s (%d)', $code, $count);
- }
- $io->warning('Bâtiments introuvables (champ non renseigné) : '.implode(', ', $list));
- }
-
- if ($dryRun) {
- $io->success('Dry-run terminé. Relance sans --dry-run pour persister.');
- } else {
- $io->success('Feed terminé avec succès.');
- }
-
- return Command::SUCCESS;
- }
-}
diff --git a/src/Entity/Bovine.php b/src/Entity/Bovine.php
index bb464ea..c9a4c32 100644
--- a/src/Entity/Bovine.php
+++ b/src/Entity/Bovine.php
@@ -96,11 +96,6 @@ class Bovine
#[ApiProperty(readableLink: true)]
private ?BuildingCase $buildingCase = null;
- #[ORM\ManyToOne]
- #[Groups(['bovine:read'])]
- #[ApiProperty(readableLink: true)]
- private ?Building $building = null;
-
#[ORM\ManyToOne]
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
private ?Supplier $supplier = null;
@@ -244,28 +239,6 @@ class Bovine
return $this;
}
- public function getBuilding(): ?Building
- {
- return $this->building;
- }
-
- public function setBuilding(?Building $building): static
- {
- $this->building = $building;
-
- return $this;
- }
-
- /**
- * Bâtiment effectif d'un bovin : la case affectée si elle existe (logique
- * historique), sinon le bâtiment direct (fed depuis l'XLSX initial).
- */
- #[Groups(['bovine:read', 'building_case:read'])]
- public function getEffectiveBuilding(): ?Building
- {
- return $this->buildingCase?->getIdBuilding() ?? $this->building;
- }
-
public function getSupplier(): ?Supplier
{
return $this->supplier;
diff --git a/src/Entity/BovineMovement.php b/src/Entity/BovineMovement.php
index 918526b..986b941 100644
--- a/src/Entity/BovineMovement.php
+++ b/src/Entity/BovineMovement.php
@@ -44,11 +44,6 @@ class BovineMovement
#[ApiProperty(readableLink: true)]
private ?BuildingCase $buildingCase = null;
- #[ORM\ManyToOne]
- #[Groups(['bovine:read'])]
- #[ApiProperty(readableLink: true)]
- private ?Building $building = null;
-
#[ORM\Column(type: 'datetime_immutable')]
#[Groups(['bovine:read', 'bovine_movement:write'])]
private DateTimeImmutable $enteredAt;
@@ -86,18 +81,6 @@ class BovineMovement
return $this;
}
- public function getBuilding(): ?Building
- {
- return $this->building;
- }
-
- public function setBuilding(?Building $building): static
- {
- $this->building = $building;
-
- return $this;
- }
-
public function getEnteredAt(): DateTimeImmutable
{
return $this->enteredAt;
diff --git a/src/State/Bovin/BovineInventoryExportProvider.php b/src/State/Bovin/BovineInventoryExportProvider.php
index 4146e3d..e1d2f31 100644
--- a/src/State/Bovin/BovineInventoryExportProvider.php
+++ b/src/State/Bovin/BovineInventoryExportProvider.php
@@ -276,7 +276,7 @@ final class BovineInventoryExportProvider implements ProviderInterface
$type = $bovine->getBovineType();
$isLim = self::BREED_CODE_LIMOUSINE === $type?->getCode();
$isCharo = self::BREED_CODE_CHAROLAISE === $type?->getCode();
- $building = $bovine->getBuildingCase()?->getIdBuilding() ?? $bovine->getBuilding();
+ $building = $bovine->getBuildingCase()?->getIdBuilding();
$code = $building?->getCode();
$sheet->setCellValue('A'.$row, $isLim ? 'X' : '');
diff --git a/src/State/Bovin/BovineMovementProcessor.php b/src/State/Bovin/BovineMovementProcessor.php
index 4844ff8..d5da4cd 100644
--- a/src/State/Bovin/BovineMovementProcessor.php
+++ b/src/State/Bovin/BovineMovementProcessor.php
@@ -28,7 +28,6 @@ final class BovineMovementProcessor implements ProcessorInterface
$enteredAt = $data->hasEnteredAt() ? $data->getEnteredAt() : new DateTimeImmutable();
$data->setEnteredAt($enteredAt);
$data->setLeftAt(null);
- $data->setBuilding(null);
$bovine = $data->getBovine();