Files
Ferme/docs/superpowers/specs/2026-04-29-bovine-entry-exit-design.md
tristan 4f6b6ff3c3 docs : spec workflow entrée/sortie bovins
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:49:01 +02:00

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, default false, non nullable.
  • Pertinent uniquement quand receptionType.code === 'BOVINS'. Pour les autres types, reste false et ignoré côté UI.
  • Inclus dans les groupes reception:read et reception: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:read et bovine: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(): int dans le groupe reception:read.
  • L'implémentation côté provider/list peut utiliser un addSelect('COUNT(b.id) AS bovineCount') via un QueryExtension API 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 getRegisteredBovineCount exposé 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.buildings si 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/bovines avec application/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éswindow.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 UniqueConstraint BDD 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 /bovines suivants é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_completed sur Reception passe sans erreur.
  • Migration reception_id sur Bovine passe sans erreur, bovins existants intacts.
  • Card "CASES" sur home remplacée par "ENTRÉE / SORTIE".
  • /entry-exit affiche 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 entryCompleted et redirige.
  • Une réception fermée disparaît de la liste.
  • BovineProcessor corrigé pour utiliser setBovineType() avec auto-create.
  • make test passe 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 :

  1. Présenter ce qu'il s'apprête à faire (fichiers, changements).
  2. Attendre la validation explicite de l'utilisateur.
  3. Exécuter, puis présenter l'étape suivante.

Cette discipline permet des retours en direct et des ajustements fins en cours de route.