diff --git a/frontend/pages/entry-exit/entry/[id].vue b/frontend/pages/entry-exit/entry/[id].vue index af6a7d8..6a0b2d7 100644 --- a/frontend/pages/entry-exit/entry/[id].vue +++ b/frontend/pages/entry-exit/entry/[id].vue @@ -1,6 +1,6 @@ + + + + + + + + + + - - - - + + + @@ -124,38 +152,41 @@ const { totalItems: totalEntries, page: entryPage, perPage: entryPerPage, + filters: entryFilters, loading: entriesLoading, reload } = useDataTableServerState( 'receptions', { 'isValid': 'true', - 'entryCompleted': 'false', - 'receptionType.code': 'BOVINS' + 'exists[validatedAt]': 'false', + 'receptionType.code': 'BOVINS', + 'identificationNumber': '', + 'receptionDate[after]': '', + 'receptionDate[strictly_before]': '' }, { initialPerPage: 5 } ) const { - items: history, - totalItems: totalHistory, - page: historyPage, - perPage: historyPerPage, - filters: historyFilters, - loading: historyLoading, - reload: reloadHistory + items: validated, + totalItems: totalValidated, + page: validatedPage, + perPage: validatedPerPage, + filters: validatedFilters, + loading: validatedLoading, + reload: reloadValidated } = useDataTableServerState( 'receptions', { 'isValid': 'true', - 'entryCompleted': 'true', + 'exists[validatedAt]': 'true', 'receptionType.code': 'BOVINS', 'identificationNumber': '', - 'supplier.name': '', 'receptionDate[after]': '', 'receptionDate[strictly_before]': '' }, - { initialPerPage: 10 } + { initialPerPage: 5 } ) const addOneDay = (dateString: string): string => { @@ -164,37 +195,49 @@ const addOneDay = (dateString: string): string => { return next.toISOString().slice(0, 10) } -const historyDateFilter = computed({ - get: () => (historyFilters.value['receptionDate[after]'] as string) ?? '', +const entryDateFilter = computed({ + get: () => (entryFilters.value['receptionDate[after]'] as string) ?? '', set: (value: string) => { if (!value) { - historyFilters.value['receptionDate[after]'] = '' - historyFilters.value['receptionDate[strictly_before]'] = '' + entryFilters.value['receptionDate[after]'] = '' + entryFilters.value['receptionDate[strictly_before]'] = '' return } - historyFilters.value['receptionDate[after]'] = value - historyFilters.value['receptionDate[strictly_before]'] = addOneDay(value) + entryFilters.value['receptionDate[after]'] = value + entryFilters.value['receptionDate[strictly_before]'] = addOneDay(value) + } +}) + +const validatedDateFilter = computed({ + get: () => (validatedFilters.value['receptionDate[after]'] as string) ?? '', + set: (value: string) => { + if (!value) { + validatedFilters.value['receptionDate[after]'] = '' + validatedFilters.value['receptionDate[strictly_before]'] = '' + return + } + validatedFilters.value['receptionDate[after]'] = value + validatedFilters.value['receptionDate[strictly_before]'] = addOneDay(value) } }) const entryColumns = [ - { key: 'identificationNumber', label: 'Numéro', width: '80px' }, + { key: 'identificationNumber', label: 'Numéro', width: '75px' }, { key: 'receptionDate', label: 'Date', width: '75px' }, - { key: 'supplier.name', label: 'Fournisseur', width: '1fr' }, - { key: 'declaredCount', label: 'Déclarés', width: '85px' }, - { key: 'registeredBovineCount', label: 'Saisis', width: '50px' } + { key: 'declaredCount', label: 'Déclarés', width: '75px' }, + { key: 'registeredBovineCount', label: 'Saisis', width: '70px' }, + { key: 'status', label: 'Statut', width: '1fr' } ] -const historyColumns = [ - { key: 'identificationNumber', label: 'Numéro', width: '110px' }, - { key: 'receptionDate', label: 'Date', width: '110px' }, - { key: 'supplier.name', label: 'Fournisseur', width: '1fr' }, - { key: 'registeredBovineCount', label: 'Saisis', width: '80px' }, - { key: 'confirmedBovineCount', label: 'Confirmés', width: '110px' }, - { key: 'status', label: 'Statut', width: '170px' } +const validatedColumns = [ + { key: 'identificationNumber', label: 'Numéro', width: '75px' }, + { key: 'receptionDate', label: 'Date', width: '75px' }, + { key: 'registeredBovineCount', label: 'Saisis', width: '50px' }, + { key: 'validatedAt', label: 'Validée le', width: '75px' }, + { key: 'status', label: 'Statut', width: '1fr' } ] -const formatDate = (date: string | null) => { +const formatDate = (date: string | null | undefined) => { if (!date) return '—' const d = new Date(date.replace(' ', 'T')) if (isNaN(d.getTime())) return date @@ -211,6 +254,6 @@ const goToEntry = (reception: ReceptionData) => { onMounted(() => { reload() - reloadHistory() + reloadValidated() }) diff --git a/frontend/services/dto/bovine-data.ts b/frontend/services/dto/bovine-data.ts index 146e109..de2425d 100644 --- a/frontend/services/dto/bovine-data.ts +++ b/frontend/services/dto/bovine-data.ts @@ -18,7 +18,7 @@ export interface BovineData { buildingCase: BovineBuildingCaseRef | null building: BovineBuildingRef | null effectiveBuilding: BovineBuildingRef | null - supplier: string | null + supplier: { id: number; name: string } | string | null workNumber: string | null birthDate: string | null bovineType: { id: number; label: string; code: string } | null diff --git a/frontend/services/dto/reception-data.ts b/frontend/services/dto/reception-data.ts index 71e5195..2cbe8dc 100644 --- a/frontend/services/dto/reception-data.ts +++ b/frontend/services/dto/reception-data.ts @@ -19,6 +19,7 @@ export interface ReceptionData { currentStep: number isValid: boolean entryCompleted?: boolean + validatedAt?: string | null registeredBovineCount?: number confirmedBovineCount?: number declaredBovineCount?: number diff --git a/migrations/Version20260430090000.php b/migrations/Version20260430090000.php new file mode 100644 index 0000000..26c33d4 --- /dev/null +++ b/migrations/Version20260430090000.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE reception ADD validated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE reception DROP validated_at'); + } +} diff --git a/src/Entity/Bovine.php b/src/Entity/Bovine.php index a3bc0d4..622bf00 100644 --- a/src/Entity/Bovine.php +++ b/src/Entity/Bovine.php @@ -118,6 +118,7 @@ class Bovine #[ORM\ManyToOne] #[Groups(['bovine:read', 'bovine:write', 'building_case:read'])] + #[ApiProperty(readableLink: true)] private ?Supplier $supplier = null; #[ORM\Column(length: 50, nullable: true)] diff --git a/src/Entity/Reception.php b/src/Entity/Reception.php index 3f7df37..e09dc03 100644 --- a/src/Entity/Reception.php +++ b/src/Entity/Reception.php @@ -6,6 +6,7 @@ namespace App\Entity; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; 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\ApiProperty; @@ -32,6 +33,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'reception')] #[ApiFilter(BooleanFilter::class, properties: ['isValid', 'entryCompleted'])] +#[ApiFilter(ExistsFilter::class, properties: ['validatedAt'])] #[ApiFilter(SearchFilter::class, properties: [ 'identificationNumber' => 'ipartial', 'supplier.name' => 'ipartial', @@ -115,6 +117,10 @@ class Reception #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] private bool $entryCompleted = false; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[Groups(['reception:read', 'reception-bovine:read'])] + private ?DateTimeImmutable $validatedAt = null; + #[ORM\Column(name: 'date_reception', type: 'datetime_immutable')] #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] #[Context( @@ -295,6 +301,45 @@ class Reception return $this; } + public function getValidatedAt(): ?DateTimeImmutable + { + return $this->validatedAt; + } + + public function setValidatedAt(?DateTimeImmutable $validatedAt): self + { + $this->validatedAt = $validatedAt; + + return $this; + } + + public function isFullyConfirmed(): bool + { + if ($this->bovines->isEmpty()) { + return false; + } + foreach ($this->bovines as $bovine) { + if (null === $bovine->getEdnotifConfirmedAt()) { + return false; + } + } + + return true; + } + + public function tryValidate(): void + { + if ($this->entryCompleted && null === $this->validatedAt && $this->isFullyConfirmed()) { + $this->validatedAt = new DateTimeImmutable(); + } + } + + #[ORM\PreUpdate] + public function onPreUpdate(): void + { + $this->tryValidate(); + } + #[Groups(['reception:read'])] public function getRegisteredBovineCount(): int { diff --git a/src/Entity/Supplier.php b/src/Entity/Supplier.php index 2e60707..7dcea41 100644 --- a/src/Entity/Supplier.php +++ b/src/Entity/Supplier.php @@ -54,11 +54,11 @@ class Supplier #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] - #[Groups(['supplier:read', 'reception:read'])] + #[Groups(['supplier:read', 'reception:read', 'bovine:read'])] private ?int $id = null; #[ORM\Column(length: 180)] - #[Groups(['supplier:read', 'reception:read', 'supplier:write'])] + #[Groups(['supplier:read', 'reception:read', 'supplier:write', 'bovine:read'])] private string $name = ''; #[ORM\Column(length: 180, nullable: true)] diff --git a/src/State/Bovin/BovineSyncInventoryProcessor.php b/src/State/Bovin/BovineSyncInventoryProcessor.php index f66159f..c1b1c93 100644 --- a/src/State/Bovin/BovineSyncInventoryProcessor.php +++ b/src/State/Bovin/BovineSyncInventoryProcessor.php @@ -52,7 +52,8 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface $existingByNationalNumber[$bovine->getNationalNumber()] = $bovine; } - $seen = []; + $seen = []; + $impactedReceptions = []; foreach ($inventory->animals as $animal) { $nationalNumber = $animal->identification?->bovin?->nationalNumber; if (null === $nationalNumber || '' === $nationalNumber) { @@ -77,9 +78,17 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface // voit ce bovin remonter dans l'inventaire. if (null === $bovine->getEdnotifConfirmedAt()) { $bovine->setEdnotifConfirmedAt(new DateTimeImmutable()); + $reception = $bovine->getReception(); + if (null !== $reception) { + $impactedReceptions[$reception->getId()] = $reception; + } } } + foreach ($impactedReceptions as $reception) { + $reception->tryValidate(); + } + $now = new DateTimeImmutable(); foreach ($existingByNationalNumber as $nationalNumber => $bovine) { if (isset($seen[$nationalNumber])) {