# Saisie information bovin (post-EDNOTIF) — Plan d'implémentation
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
>
> **Mode utilisateur :** L'utilisateur souhaite valider chaque étape avant exécution (cf. memory `feedback_step_by_step_validation`). Avant chaque task, présenter ce qui va être fait et attendre OK explicite.
**Goal:** Ajouter un écran de saisie post-EDNOTIF (poids, prix/kg, bâtiment, case) accessible depuis le tableau "Entrées validées", structuré en accordéons-par-bovin.
**Architecture:** Un nouveau composant `UiAccordion` réutilisable. Une nouvelle page Nuxt `entry-exit/bovine-info/[id].vue` qui charge la réception et ses bovins, instancie un accordéon par bovin et délègue la saisie à un sous-composant `bovine-info-form.vue`. Pas de nouvel endpoint, pas de migration : on PATCH les `Bovine` existants (`receivedWeight`, `pricePerKg`, `buildingCase`). Mini ajustement backend : exposer les ids de `BuildingCase` et `Building` dans le groupe de sérialisation `bovine:read`, sinon on n'a pas de quoi pré-remplir les selectors.
**Tech Stack:** Symfony 8 + API Platform 4 (annotations Groups) ; Nuxt 4 + Vue 3 + Tailwind ; pas de tests automatisés (cohérent avec le reste de la feature entry-exit, cf. spec).
**Spec source:** `docs/superpowers/specs/2026-05-04-bovine-info-saisie-design.md`
**Branche de travail:** `feat/entree-sortie` (déjà créée).
---
## Synthèse du file-mapping
| Fichier | Type | Responsabilité |
| --- | --- | --- |
| `src/Entity/BuildingCase.php` | Modify | Ajouter `bovine:read` au groupe de `id` |
| `src/Entity/Building.php` | Modify | Ajouter `bovine:read` au groupe de `id` |
| `frontend/services/dto/bovine-data.ts` | Modify | Ajouter `id` à `BovineBuildingRef` et `BovineBuildingCaseRef` |
| `frontend/components/ui/UiAccordion.vue` | Create | Composant réutilisable, header en slot, body en slot, v-model boolean |
| `frontend/components/entry-exit/bovine-info-form.vue` | Create | Sous-composant : 4 champs + bouton Valider, émet `saved` |
| `frontend/pages/entry-exit/bovine-info/[id].vue` | Create | Page : header, fetch, tri, état d'ouverture, rendu liste d'accordéons |
| `frontend/pages/entry-exit/index.vue` | Modify | Ajouter `row-clickable` + `@row-click` au tableau "Entrées validées" |
---
## Task 1 : Exposer les ids `BuildingCase` et `Building` dans `bovine:read`
**Contexte :** Quand l'API normalise un `Bovine` avec le groupe `bovine:read`, l'embedded `buildingCase` ne contient que `caseNumber` et `building.label`. Pas d'ids → pas de pré-remplissage possible. On ajoute le groupe `bovine:read` aux deux propriétés `id` concernées (zéro changement de schéma, juste un attribut PHP).
**Files:**
- Modify: `src/Entity/BuildingCase.php:42`
- Modify: `src/Entity/Building.php:36`
- [ ] **Step 1 : Patch `BuildingCase.id`**
```php
// src/Entity/BuildingCase.php — remplacer
#[Groups(['building:read', 'building_case:read'])]
private ?int $id = null;
// par
#[Groups(['building:read', 'building_case:read', 'bovine:read'])]
private ?int $id = null;
```
- [ ] **Step 2 : Patch `Building.id`**
```php
// src/Entity/Building.php — remplacer
#[Groups(['building:read', 'building:summary', 'reception:read'])]
private ?int $id = null;
// par
#[Groups(['building:read', 'building:summary', 'reception:read', 'bovine:read'])]
private ?int $id = null;
```
- [ ] **Step 3 : Vider le cache (les groupes sont compilés)**
```bash
make cache-clear
```
- [ ] **Step 4 : Vérifier que les tests existants passent**
```bash
make test
```
Attendu : 9/9 tests OK (aucun changement de comportement, juste une exposition supplémentaire).
- [ ] **Step 5 : Vérification manuelle rapide**
```bash
curl -s -H "Authorization: Bearer $TOKEN" \
'http://localhost:8080/api/bovines/1' | jq '.buildingCase'
```
Attendu : la réponse contient `id` (numérique) en plus de `caseNumber`, et `buildingCase.building` contient `id` en plus de `label`. Si le bovin n'a pas de buildingCase, ce sera `null` — prendre un id de bovin qui en a un (sinon ignorer cette étape).
- [ ] **Step 6 : Commit**
```bash
git add src/Entity/BuildingCase.php src/Entity/Building.php
git commit -m "feat(api) : exposer BuildingCase.id et Building.id dans bovine:read"
```
---
## Task 2 : Compléter le DTO frontend `BovineData`
**Files:**
- Modify: `frontend/services/dto/bovine-data.ts`
- [ ] **Step 1 : Ajouter `id` aux deux interfaces de référence**
Remplacer le bloc en haut du fichier :
```ts
export interface BovineBuildingRef {
id: number
label: string
}
export interface BovineBuildingCaseRef {
id: number
caseNumber: number | null
building: BovineBuildingRef | null
}
```
- [ ] **Step 2 : Vérifier que TypeScript ne casse pas**
```bash
cd frontend && npx vue-tsc --noEmit 2>&1 | head -40
```
Attendu : pas d'erreur (les autres pages qui consomment `BovineData` ne lisaient pas l'`id` depuis ces sous-objets ; ajouter un champ ne casse rien).
Si erreurs inattendues, les corriger en touchant seulement les call-sites pointés par tsc.
- [ ] **Step 3 : Commit**
```bash
git add frontend/services/dto/bovine-data.ts
git commit -m "feat(front) : id dans BovineBuildingRef et BovineBuildingCaseRef"
```
---
## Task 3 : Créer `UiAccordion`
**Files:**
- Create: `frontend/components/ui/UiAccordion.vue`
- [ ] **Step 1 : Écrire le composant**
```vue
```
- [ ] **Step 2 : Vérifier l'auto-import**
Nuxt auto-importe les composants de `components/ui/` avec le préfixe `Ui` (cf. CLAUDE.md). Donc `` sera utilisable sans import explicite. Pas d'action ici, juste validation mentale.
- [ ] **Step 3 : Commit**
```bash
git add frontend/components/ui/UiAccordion.vue
git commit -m "feat(front) : composant UiAccordion réutilisable"
```
---
## Task 4 : Créer `bovine-info-form.vue` (sous-composant)
**Contexte :** Encapsule l'état local et le formulaire d'un bovin. Reçoit le bovin et la liste de bâtiments, émet `saved` avec le bovin mis à jour. Permet à la page parent de rester lisible.
**Files:**
- Create: `frontend/components/entry-exit/bovine-info-form.vue`
- [ ] **Step 1 : Écrire le composant**
```vue
```
> Note : on utilise `application/merge-patch+json` comme content-type côté API Platform pour les PATCH (la convention par défaut). `useApi.patch` a déjà ce content-type par défaut — la ligne `headers` est ici **à supprimer** si `useApi.patch` le pose déjà. Vérifier dans `composables/useApi.ts` à l'étape suivante.
- [ ] **Step 2 : Vérifier le content-type par défaut de `useApi.patch`**
```bash
grep -n "patch" frontend/composables/useApi.ts | head -10
```
- Si `useApi.patch` injecte déjà `application/merge-patch+json`, **retirer** le bloc `headers` du composant ci-dessus.
- Sinon, le garder.
- [ ] **Step 3 : Commit**
```bash
git add frontend/components/entry-exit/bovine-info-form.vue
git commit -m "feat(front) : sous-composant bovine-info-form (4 champs + valider)"
```
---
## Task 5 : Créer la page `bovine-info/[id].vue`
**Files:**
- Create: `frontend/pages/entry-exit/bovine-info/[id].vue`
- [ ] **Step 1 : Écrire la page**
```vue
Saisie information bovin {{ reception?.identificationNumber ?? '' }}
Chargement…
{{ bovine.nationalNumber }}
Saisie
Attente saisie
```
> Note de style : `BovineInfoForm` est référencé sans import — Nuxt auto-importe les composants `components/entry-exit/*.vue` avec un PascalCase basé sur le nom de fichier (à confirmer ; sinon, ajouter `import BovineInfoForm from '~/components/entry-exit/bovine-info-form.vue'`).
- [ ] **Step 2 : Vérifier l'auto-import**
```bash
cd frontend && npm run dev
```
Aller sur `http://localhost:3000/entry-exit/bovine-info/` (id d'une réception validée). Si erreur "BovineInfoForm is not defined", ajouter l'import explicite. Si rendu OK, continuer.
- [ ] **Step 3 : Commit**
```bash
git add frontend/pages/entry-exit/bovine-info/'[id].vue'
git commit -m "feat(front) : page saisie information bovin (accordéons)"
```
---
## Task 6 : Câbler la navigation depuis le tableau "Entrées validées"
**Files:**
- Modify: `frontend/pages/entry-exit/index.vue`
- [ ] **Step 1 : Ajouter la fonction de navigation**
Dans le `