9.3 KiB
Entrée / Sortie des bovins — Design
Contexte
Aujourd'hui, l'application gère les réceptions (arrivée d'un camion) qui déclarent un nombre de bovins par race (ex : 5 charolais + 3 limousine + 2 autres). Une fois la réception terminée, ces déclarations sont des indicateurs imprécis et il manque l'étape de saisie individuelle des bovins (numéro national, poids, prix…).
L'objectif est d'introduire un workflow d'entrée qui transforme une réception bovins finie en saisies individuelles enrichies via EDNOTIF, et de poser les fondations pour un futur workflow de sortie symétrique.
Pour ce lot, les sorties sont hors scope mais l'écran liste prévoit déjà leur emplacement.
Décisions structurantes
| Décision | Choix |
|---|---|
| Distinction "en attente" vs "terminée" | Flag explicite entryCompleted: bool sur Reception |
| Lien Bovine → Reception | FK 1-N, Bovine.reception ManyToOne nullable |
| Rendu de l'écran de saisie | UN formulaire (2 lignes) + tableau récap dessous |
| Bâtiment + Case | Choisis par bovin dans le formulaire |
| Persistance | Save individuel à chaque "Ajouter" (POST /bovines) |
| Enrichissement EDNOTIF | Au backend via le BovineProcessor existant (pas de lookup live) |
Modèle de données
Reception — modification
Nouveau champ :
entryCompleted: bool, defaultfalse, non nullable.- Pertinent uniquement quand
receptionType.code === 'BOVINS'. Pour les autres types, restefalseet ignoré côté UI. - Inclus dans les groupes
reception:readetreception:write.
Migration : ALTER TABLE reception ADD COLUMN entry_completed BOOLEAN NOT NULL DEFAULT false.
Ajout d'un BooleanFilter sur entryCompleted dans #[ApiFilter].
Bovine — modification
Nouveau champ :
reception: Reception(ManyToOne, nullable).- Inclus dans
bovine:readetbovine:write.
Migration : ALTER TABLE bovine ADD COLUMN reception_id INTEGER NULL + index + FK contrainte. Bovins existants restent à NULL — aucune migration de données.
Ajout d'un SearchFilter exact sur reception dans #[ApiFilter] pour permettre GET /bovines?reception={id}.
Reception — relation inverse pour le compteur
Pour permettre l'affichage du compteur "bovins saisis" dans la liste sans N+1 :
- Ajouter
bovines: Collection<Bovine>côtéReception(OneToMany inverse,mappedBy: 'reception', fetch lazy). - Exposer un getter calculé
getRegisteredBovineCount(): intdans le groupereception:read. - L'implémentation côté provider/list peut utiliser un
addSelect('COUNT(b.id) AS bovineCount')via unQueryExtensionAPI Platform si le N+1 devient un problème (à mesurer).
Aucune autre entité
Pas de table de jointure (un bovin entre une seule fois via une réception unique). Pas de nouvelle entité Entry (la Reception joue ce rôle). Pas d'entité Exit pour ce lot — la symétrie sera traitée plus tard.
Endpoints API
Tous les endpoints réutilisent les ressources existantes ; aucun endpoint custom n'est créé.
Liste des entrées en attente
GET /api/receptions?receptionType.code=BOVINS&isValid=true&entryCompleted=false
Validation finale d'une entrée
PATCH /api/receptions/{id} avec { entryCompleted: true }.
Création d'un bovin lié
POST /api/bovines (Content-Type application/ld+json) avec :
{
"nationalNumber": "FR1234567890",
"receivedWeight": 368,
"pricePerKg": 5.7,
"arrivalDate": "2026-04-29",
"supplier": "/api/suppliers/12",
"reception": "/api/receptions/45",
"buildingCase": "/api/building_cases/8"
}
Le BovineProcessor enrichit automatiquement (workNumber, birthDate, race auto-créée via BovineType).
Nettoyage en passant : le BovineProcessor actuel appelle setBreedCode() qui n'existe plus (héritage avant la migration vers BovineType FK). À corriger pour qu'il fasse setBovineType() avec auto-create d'un BovineType si la race retournée par EDNOTIF n'existe pas en base.
Suppression d'un bovin
DELETE /api/bovines/{id} — sécurité actuelle ROLE_ADMIN à abaisser à ROLE_USER pour permettre la correction immédiate depuis le tableau.
Front-end
Home (pages/index.vue)
- Card "CASES" → renommée "ENTRÉE / SORTIE" (multi-ligne
Entrée<br>Sortie). - Lien :
/entry-exit. - Icône :
mdi:swap-horizontal-bold(à finaliser à l'implémentation).
Page liste — pages/entry-exit/index.vue
Deux sections empilées :
Entrées en attente
- Composant :
UiDataTable. - Filtres serveur :
receptionType.code=BOVINS,isValid=true,entryCompleted=false. - Colonnes :
- Date réception
- Fournisseur (
supplier.name) - Total déclaré (calculé côté front :
sum(bovines_types.quantity) + parseInt(bovineDetail ?? '0')) - Bovins saisis (depuis
getRegisteredBovineCountexposé sur Reception) - Action (rangée cliquable)
- Click row →
/entry-exit/entry/{receptionId}.
Sorties en attente
- Tableau placeholder vide avec message "À venir".
Écran de saisie — pages/entry-exit/entry/[id].vue
Header
- Titre : "Entrée bovins #N-BR-XXXX — Fournisseur YYY"
- Sous-titre : "Bovins déclarés : 8 · Bovins saisis : 3"
- Icône retour à gauche.
Formulaire (2 lignes)
Ligne 1 : Numéro national · Poids à l'arrivée · Date d'arrivée · Vendeur (Supplier select) Ligne 2 : Prix au kilo · Bâtiment (Building select) · Case (BuildingCase select dépendant du bâtiment) · Bouton Ajouter
Pré-remplissage (au chargement et après chaque add) :
- Date d'arrivée =
reception.receptionDate(date seule, modifiable) - Vendeur =
reception.supplier(modifiable) - Bâtiment = premier de
reception.buildingssi dispo, sinon vide - Case = vide (à choisir explicitement)
- Numéro national, poids, prix : vides
Comportement bouton "Ajouter"
- Disabled si form invalide (n° national vide, poids ≤ 0, prix ≤ 0, building/case manquants).
- Click →
POST /api/bovinesavecapplication/ld+json. - Succès → reload du tableau, reset form (en gardant les pré-remplissages), focus sur Numéro national.
- Erreur 409 (doublon n° national) → toast "Ce bovin existe déjà".
- Erreur EDNOTIF → bovin créé sans enrichissement (race/naissance vides), toast warning.
Tableau récap (dessous)
Colonnes : N° national · N° travail · Race · Sexe · Date naissance · Poids arrivée · Date arrivée · Prix/kg · Prix total · Bâtiment · Case · Action (icône poubelle).
Source : GET /api/bovines?reception={id} au mount + après chaque add/delete.
Suppression : DELETE /api/bovines/{id} avec window.confirm.
Footer
- Bouton Valider l'entrée (à droite).
- Si
bovins saisis < bovins déclarés→window.confirm("Vous n'avez saisi que X/Y bovins. Confirmer la fermeture ?"). - Disabled si 0 bovin saisi.
- Click →
PATCH /api/receptions/{id}avec{ entryCompleted: true }→ toast succès → redirection/entry-exit.
Sécurité (rôles)
| Action | Rôle requis |
|---|---|
| Voir la page entrée/sortie | ROLE_USER |
| Ajouter un bovin (POST /bovines) | ROLE_USER (actuellement ROLE_ADMIN — à abaisser, ce flux est métier opérationnel) |
| Supprimer un bovin (DELETE /bovines) | ROLE_USER (idem, à abaisser) |
| Valider l'entrée (PATCH receptions) | ROLE_USER |
L'abaissement à ROLE_USER sur Bovine::Post, Bovine::Patch et Bovine::Delete est délibéré : ce flux fait partie des opérations métier quotidiennes, pas de l'administration. À confirmer pendant l'implémentation.
Cas limites
- Total saisi > déclaré : autorisé (les déclarations en réception sont des indicateurs imprécis).
- Doublon n° national : la
UniqueConstraintBDD le rejette → toast. - EDNOTIF indisponible : bovin créé sans enrich, comportement actuel du processor.
- Réception supprimée pendant la saisie : impossible côté UI tant qu'on est dans l'écran. Si ça arrive (autre user), les
POST /bovinessuivants échoueront en 404 sur l'IRI reception → toast. - Sortie d'un bovin : non géré dans ce lot. Le futur workflow de sortie viendra basculer
Bovine.exitedAt.
Critères d'acceptation
- Migration
entry_completedsur Reception passe sans erreur. - Migration
reception_idsur Bovine passe sans erreur, bovins existants intacts. - Card "CASES" sur home remplacée par "ENTRÉE / SORTIE".
/entry-exitaffiche les entrées en attente et un placeholder sorties.- Click sur une entrée → écran saisie avec form pré-rempli.
- "Ajouter" → bovin créé, ligne au tableau, form reset (pré-remplissages restaurés).
- Suppression d'une ligne fonctionne avec confirmation.
- "Valider l'entrée" bascule
entryCompletedet redirige. - Une réception fermée disparaît de la liste.
BovineProcessorcorrigé pour utilisersetBovineType()avec auto-create.make testpasse sans régression.
Mode d'implémentation
Sur ce projet, l'utilisateur souhaite valider chaque étape du plan avant exécution. À chaque étape du plan d'implémentation, l'agent doit :
- Présenter ce qu'il s'apprête à faire (fichiers, changements).
- Attendre la validation explicite de l'utilisateur.
- Exécuter, puis présenter l'étape suivante.
Cette discipline permet des retours en direct et des ajustements fins en cours de route.