# 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 ``` > 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 `