feat : cause d'entrée bovin + confirmation EDNOTIF asynchrone + historique entrées
- Champ entryCause sur Bovine (enum App\Enum\CauseEntree : Achat/Naissance/PretOuPension) - Sélecteur "Cause d'entrée" sur le formulaire de saisie (default Achat, required) - ednotif_confirmed_at sur Bovine : timestamp set par le sync EDNOTIF la première fois qu'un bovin remonte dans getInventory. Backfill des bovins existants au jour de la migration. - Inventaire (page + export + stats) filtre les bovins encore "en attente EDNOTIF" : ils n'apparaissent qu'une fois confirmés par le sync. - getter getConfirmedBovineCount sur Reception, exposé en reception:read. - Tableau Historique full-width sur /entry-exit listant les entrées validées, avec filtres de colonnes (numéro, date, fournisseur), compteur Confirmés/Saisis, et badge de statut "Confirmée" / "EDNOTIF en attente". - Tableau récap de l'écran de saisie passé en useDataTableServerState pour bénéficier du loading et de la pagination serveur. - Validation entrée : confirm window obligatoire, message renforcé en cas d'écart entre saisis et déclarés. - Pattern projet "submitted" sur le formulaire d'ajout pour le visuel required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Enum\CauseEntree;
|
||||
use App\Repository\BovineRepository;
|
||||
use App\State\Bovin\BovineProcessor;
|
||||
use DateTimeImmutable;
|
||||
@@ -38,7 +39,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
'reception' => 'exact',
|
||||
])]
|
||||
#[ApiFilter(DateFilter::class, properties: ['arrivalDate', 'birthDate', 'exitDate'])]
|
||||
#[ApiFilter(ExistsFilter::class, properties: ['exitedAt'])]
|
||||
#[ApiFilter(ExistsFilter::class, properties: ['exitedAt', 'ednotifConfirmedAt'])]
|
||||
#[ApiResource(
|
||||
order: ['birthDate' => 'ASC'],
|
||||
operations: [
|
||||
@@ -106,6 +107,10 @@ class Bovine
|
||||
#[ApiProperty(readableLink: false)]
|
||||
private ?Reception $reception = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 1, nullable: true, enumType: CauseEntree::class)]
|
||||
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
||||
private ?CauseEntree $entryCause = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[Groups(['bovine:read'])]
|
||||
#[ApiProperty(readableLink: true)]
|
||||
@@ -147,6 +152,11 @@ class Bovine
|
||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||
private ?DateTimeImmutable $exitedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
#[Groups(['bovine:read', 'building_case:read'])]
|
||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s'])]
|
||||
private ?DateTimeImmutable $ednotifConfirmedAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -235,6 +245,18 @@ class Bovine
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEntryCause(): ?CauseEntree
|
||||
{
|
||||
return $this->entryCause;
|
||||
}
|
||||
|
||||
public function setEntryCause(?CauseEntree $entryCause): static
|
||||
{
|
||||
$this->entryCause = $entryCause;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBuilding(): ?Building
|
||||
{
|
||||
return $this->building;
|
||||
@@ -341,6 +363,18 @@ class Bovine
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEdnotifConfirmedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->ednotifConfirmedAt;
|
||||
}
|
||||
|
||||
public function setEdnotifConfirmedAt(?DateTimeImmutable $ednotifConfirmedAt): static
|
||||
{
|
||||
$this->ednotifConfirmedAt = $ednotifConfirmedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAgeMonths(): ?int
|
||||
{
|
||||
return $this->ageMonths;
|
||||
|
||||
@@ -301,6 +301,32 @@ class Reception
|
||||
return $this->bovines->count();
|
||||
}
|
||||
|
||||
#[Groups(['reception:read'])]
|
||||
public function getConfirmedBovineCount(): int
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($this->bovines as $bovine) {
|
||||
if (null !== $bovine->getEdnotifConfirmedAt()) {
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
#[Groups(['reception:read'])]
|
||||
public function getDeclaredBovineCount(): int
|
||||
{
|
||||
$fromTypes = 0;
|
||||
foreach ($this->bovines_types as $rb) {
|
||||
$fromTypes += (int) ($rb->getQuantity() ?? 0);
|
||||
}
|
||||
|
||||
$fromOther = is_numeric($this->bovineDetail) ? (int) $this->bovineDetail : 0;
|
||||
|
||||
return $fromTypes + $fromOther;
|
||||
}
|
||||
|
||||
#[Groups(['reception:read'])]
|
||||
public function getReceptionDate(): ?DateTimeImmutable
|
||||
{
|
||||
|
||||
27
src/Enum/CauseEntree.php
Normal file
27
src/Enum/CauseEntree.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
/**
|
||||
* Cause d'une entrée de bovin sur l'exploitation (opération `IpBCreateEntree`).
|
||||
*
|
||||
* Source : `resources/ednotif-ws/CauseEntree.XSD` + doc IPG Table 9.
|
||||
* Le `.value` est le code IPG transmis dans le payload SOAP.
|
||||
*
|
||||
* Note : duplique l'enum `Malio\EdnotifBundle\Bovin\Enum\CauseEntree` (pas
|
||||
* encore présente dans la release installée v0.0.6). À remplacer par l'import
|
||||
* bundle quand une version embarquant l'enum sera publiée.
|
||||
*/
|
||||
enum CauseEntree: string
|
||||
{
|
||||
/** Entrée par achat. */
|
||||
case Achat = 'A';
|
||||
|
||||
/** Entrée par naissance. */
|
||||
case Naissance = 'N';
|
||||
|
||||
/** Entrée par prêt ou pension. */
|
||||
case PretOuPension = 'P';
|
||||
}
|
||||
@@ -35,6 +35,7 @@ final class BovineRepository extends ServiceEntityRepository
|
||||
{
|
||||
$qb = $this->createQueryBuilder('b')
|
||||
->where('b.exitedAt IS NULL')
|
||||
->andWhere('b.ednotifConfirmedAt IS NOT NULL')
|
||||
->orderBy('b.birthDate', 'ASC')
|
||||
;
|
||||
|
||||
@@ -81,6 +82,7 @@ final class BovineRepository extends ServiceEntityRepository
|
||||
'SUM(CASE WHEN b.ageMonths >= 20 AND b.ageMonths < 22 THEN 1 ELSE 0 END) AS between20And22',
|
||||
)
|
||||
->where('b.exitedAt IS NULL')
|
||||
->andWhere('b.ednotifConfirmedAt IS NOT NULL')
|
||||
;
|
||||
|
||||
if (null !== $buildingCaseId) {
|
||||
|
||||
@@ -72,6 +72,12 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface
|
||||
|
||||
$this->applyEdnotifData($bovine, $animal);
|
||||
$bovine->setExitedAt(null);
|
||||
|
||||
// Marque la confirmation EDNOTIF si c'est la première fois qu'on
|
||||
// voit ce bovin remonter dans l'inventaire.
|
||||
if (null === $bovine->getEdnotifConfirmedAt()) {
|
||||
$bovine->setEdnotifConfirmedAt(new DateTimeImmutable());
|
||||
}
|
||||
}
|
||||
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
Reference in New Issue
Block a user