feat : Reception.validatedAt + statut entrées + mode consultation
- Backend : champ Reception.validatedAt (timestamp) avec PreUpdate + helpers
isFullyConfirmed/tryValidate ; sync EDNOTIF déclenche tryValidate sur
les receptions impactées ; expose Supplier.name dans le groupe bovine:read.
- Migration : ajout colonne validated_at sans backfill (les receptions
remontent en attente jusqu'au prochain sync).
- Front /entry-exit : remplace Historique par 'Entrées validées' (filtre
exists[validatedAt]=true), ajoute filtres et colonne Statut sur les
deux tableaux, retire Fournisseur, layout 2x2 (entrées + sorties).
- Front /entry-exit/entry/{id} : mode consultation quand entryCompleted=true
(formulaire + actions masqués, colonne EDNOTIF par bovin) ; ajoute
colonnes Vendeur et Cause dans le récap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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])) {
|
||||
|
||||
Reference in New Issue
Block a user