7.6 KiB
Saisie information bovin (post-EDNOTIF)
Contexte
Sur la page /entry-exit, le tableau "Entrées validées" liste les receptions
dont les bovins sont tous confirmés EDNOTIF (reception.validatedAt non null).
Une fois cette validation acquise, l'utilisateur doit encore renseigner pour
chaque bovin quatre informations métier qui ne viennent pas d'EDNOTIF :
- poids d'arrivée
- prix d'achat au kg
- bâtiment
- case
Cette spec décrit l'écran de saisie et le composant accordéon qu'il introduit.
Périmètre
- Nouvelle page accessible uniquement via clic sur une ligne d'"Entrées validées" — pas d'entrée dans la nav globale.
- Aucun changement d'entité Doctrine, aucune migration : les quatre champs
existent déjà sur
Bovine(receivedWeight,pricePerKg,buildingCase). - Le champ
building(legacy XLSX) n'est pas écrit. Côté affichage,getEffectiveBuilding()continue de dériver le bâtiment effectif depuisbuildingCase. pricePerKgreste protégé parROLE_BUREAUcôté API. La page exigeROLE_ADMIN; la hiérarchie Symfony fait queROLE_ADMINhériteROLE_BUREAU, donc pas de cas particulier.- Pas de gestion de "session interrompue" : chaque accordéon validé individuellement est persisté immédiatement.
Routing & navigation
- Page :
frontend/pages/entry-exit/bovine-info/[id].vueoù[id]est lereceptionId. - Sur
frontend/pages/entry-exit/index.vue, le tableau "Entrées validées" reçoitrow-clickableet@row-click="goToBovineInfo". Le handler pousse vers/entry-exit/bovine-info/{reception.id}. - Le bouton flèche-retour de la page renvoie vers
/entry-exit.
Composant UiAccordion
Fichier : frontend/components/ui/UiAccordion.vue. Réutilisable, sans logique
métier.
Props
modelValue: boolean— état ouvert/fermé, supportev-model.
Slots
#header— contenu libre du header (badge, titre, etc., aligné à gauche).- default — corps de l'accordéon, rendu uniquement quand ouvert.
Comportement
- Click sur le header → emit
update:modelValueavec la valeur inversée. - Header :
bg-slate-100, padding identique aux headersUiDataTable(px-4 py-3), texte semi-bold uppercase. - Chevron à droite (
mdi:chevron-down), rotation 180° quand ouvert, transition CSS courte. - Pas d'animation de hauteur au déploiement (pour rester simple) — on rend ou
pas via
v-if.
Page bovine-info/[id].vue
Layout — copie du pattern entry-exit/entry/[id].vue :
<div class="px-[86px]"> + bandeau titre avec flèche retour absolue, titre
<h1>Saisie information bovin {{ reception.identificationNumber }}</h1>.
Pas de sous-titre.
Chargement (onMounted)
GET receptions/{id}→ alimente le titre.GET bovines?reception={id}&itemsPerPage=200(pas de pagination — on suppose qu'une réception a au plus quelques dizaines de bovins).GET buildings— la réponse contientbuildingCasesimbriqués (BuildingData.buildingCases). On dérive de là à la fois la liste de bâtiments (selector "Bâtiment") et l'index des cases par bâtiment.
État local par bovin (Map<bovineId, FormState>) :
type FormState = {
receivedWeight: number | null
pricePerKg: number | null
buildingId: number | null // UI-only, drive le filtre Case
buildingCaseId: number | null
submitted: boolean // pour les borders rouges au submit
isSaving: boolean
}
Initialisé depuis l'API : receivedWeight, pricePerKg directement,
buildingCaseId = bovine.buildingCase?.id,
buildingId = bovine.effectiveBuilding?.id.
Source de vérité du badge : bovine.receivedWeight != null && bovine.pricePerKg != null && bovine.buildingCase != null — donc calculé
sur les valeurs persistées, pas sur le FormState en cours d'édition. Vert
"Saisie" si les trois sont non-null (le bâtiment est dérivé de la case),
sinon jaune "Attente saisie".
Note : on garde 4 champs côté UI mais 3 conditions backend, parce que
buildingn'est pas persisté indépendamment.
Tri — non-saisis (badge jaune) en haut puis saisis (badge vert) en bas, ordre d'API préservé à l'intérieur de chaque groupe. Le tri est calculé
- au chargement initial,
- après chaque PATCH OK (le bovin qui vient d'être saisi descend dans le groupe vert).
Il ne se recompute pas pendant qu'un accordéon est ouvert et en cours d'édition — sinon les bovins sauteraient de position au moindre changement de l'état "saisi/non-saisi", ce qui ne se produit ici que sur un PATCH réussi.
Open state — ref<number | null> qui contient l'id du bovin
actuellement ouvert. Un seul accordéon ouvert à la fois.
- Initialisation : id du premier bovin non-saisi de la liste triée, ou
nullsi tout est déjà saisi. - Click sur un header autre que celui ouvert → ferme l'ouvert, ouvre le cliqué.
- Click sur le header ouvert → ferme (open =
null). - Validation OK d'un accordéon → ferme l'actuel, ouvre l'id du prochain
non-saisi de la liste (recalculée). Si plus de non-saisi →
null.
Formulaire par accordéon
Tous les champs required, validation au submit (pattern submitted flag +
.submitted :invalid du CSS global).
| Champ | Composant | Type / format |
|---|---|---|
| Poids d'arrivée | UiNumberInput |
entier kg |
| Prix d'achat (kg) | UiNumberInput |
float, step 0.01 |
| Bâtiment | UiSelect |
options = liste building |
| Case | UiSelect |
options = cases du building sélectionné |
- Watch sur
buildingId: si l'utilisateur change le bâtiment et que la case actuellement sélectionnée n'appartient pas au nouveau, on remetbuildingCaseId = null. - Bouton
Validercentré,bg-primary-500, désactivé pendantisSaving.
Soumission
PATCH /bovines/{id}
{
receivedWeight,
pricePerKg,
buildingCase: `/api/building_cases/${buildingCaseId}`
}
Content-Type: application/ld+json
À la réponse OK, on remplace le bovin dans la liste locale par la version
retournée par l'API (qui contient buildingCase hydraté pour recomputer le
badge), puis on déclenche la transition d'état (resort + open suivant).
En cas d'erreur HTTP, le toast par défaut de useApi suffit ; on garde
l'accordéon ouvert et le FormState intact.
Hors périmètre
- Pas de bulk-save (pas de "Tout valider").
- Pas de tracking "modifié non sauvé" / warning au unload — chaque accordéon est validé explicitement, pas d'autosave.
- Pas de tests automatisés ajoutés dans ce lot (cohérent avec le reste de la feature entry-exit).
- Pas d'exposition de cet écran ailleurs que via le tableau "Entrées validées".
Critères d'acceptation
- Cliquer sur une ligne du tableau "Entrées validées" ouvre la page
/entry-exit/bovine-info/{id}. - La page liste tous les bovins de la réception, non-saisis en haut.
- Au chargement, un seul accordéon est ouvert : le premier non-saisi (ou aucun si tout est déjà saisi).
- Cliquer sur un autre header ferme l'ouvert et ouvre le cliqué.
- Soumettre un accordéon avec un champ vide affiche les borders rouges
(
submittedflag) et bloque la requête. - Soumettre un accordéon valide PATCH le bovin et, après réponse OK, ferme l'accordéon, met le badge en vert et ouvre le suivant non-saisi.
- Recharger la page après une saisie partielle réaffiche les valeurs pré-remplies et le bon badge pour chaque bovin.
php-cs-fixeretmake testrestent verts (pas de code backend modifié, donc rien à régresser).