Files
Ferme/docs/superpowers/specs/2026-04-24-bovine-inventory-excel-export-design.md
tristan bde59bf9ee docs : spec export Excel inventaire bovin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:23:26 +02:00

5.0 KiB
Raw Permalink Blame History

Export Excel de l'inventaire bovin — Design Spec

Bouton sur la page /inventory qui télécharge un XLSX listant tous les bovins actuellement présents sur l'exploitation.

Contexte

Le métier veut un Excel exportable depuis l'écran inventaire. Ferme n'a aujourd'hui aucun outil d'export Excel (uniquement PDF via dompdf). On choisit phpoffice/phpspreadsheet côté serveur, en suivant le même pattern que la génération PDF actuelle (endpoint qui streame le fichier, front qui télécharge via blob).

Périmètre : tous les bovins actifs (exitedAt IS NULL), ordre birthDate ASC, ignore les filtres UI. Pas de modale de sélection (à voir si le métier en demande une plus tard).

Architecture

Backend

Dépendance : composer require phpoffice/phpspreadsheet

Nouveau resource : src/ApiResource/BovineInventoryExport.php

  • #[ApiResource] avec une seule opération Get :
    • uriTemplate: '/bovines/inventory-export'
    • output: false
    • provider: BovineInventoryExportProvider::class
    • security: "is_granted('ROLE_USER')" (cohérent avec la page /inventory)
    • OpenApi tag Bovines

Nouveau provider : src/State/Bovin/BovineInventoryExportProvider.php

  • Injecte EntityManagerInterface
  • Query Doctrine : WHERE exitedAt IS NULL ORDER BY birthDate ASC
  • Construit le Spreadsheet avec PhpSpreadsheet
  • Retourne une Symfony\Component\HttpFoundation\Response avec :
    • Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • Content-Disposition: attachment; filename="inventaire_bovins_YYYY-MM-DD.xlsx"
    • Body = IOFactory::createWriter($spreadsheet, 'Xlsx')->save('php://output') capturé via ob_*

Frontend

Page : frontend/pages/inventory.vue

  • Nouveau bouton "Exporter Excel" à droite du titre, à côté de "Rafraîchir"
  • Style : même que "Rafraîchir" (bg-primary-500, h-[50px], icône mdi:file-excel-outline)
  • Visible pour tout user authentifié (pas de gate admin)
  • Au clic : appelle useApi().getBlob('bovines/inventory-export'), crée un blob URL, déclenche un <a download> synthétique avec le filename retourné par le backend (lu depuis le header Content-Disposition)

Génération XLSX — détails

Fichier :

  • 1 seule feuille Inventaire
  • Filename : inventaire_bovins_YYYY-MM-DD.xlsx (date du jour serveur)

En-têtes (ligne 1) :

  • 9 colonnes dans l'ordre : N° National, N° Travail, Sexe, Né le, Age (mois), Race, Bâtiment, Case, Entrée le
  • Style : gras, fond #f1f5f9 (slate-100), bordure noire fine, alignement centré
  • Auto-filter activé sur la plage des en-têtes (Excel ajoute les boutons de filtre natifs)
  • Freeze pane : ligne 2 figée

Lignes de données (à partir de la ligne 2) :

  • Ordre birthDate ASC (plus vieux en haut, NULL à la fin via NULLS LAST natif Postgres)
  • Largeurs de colonnes :
    • N° National : 18
    • N° Travail : 12
    • Sexe : 10
    • Né le : 12
    • Age : 12
    • Race : 12
    • Bâtiment : 30
    • Case : 8
    • Entrée le : 12

Mapping des valeurs :

  • Sexe : MMâle, FFemelle, autre / null → vide
  • Né le, Entrée le : format JJ/MM/AAAA, vide si null
  • Age : entier (mois), vide si null
  • Bâtiment, Case : valeurs nestées via bovine.buildingCase.building.label et bovine.buildingCase.caseNumber, vide si null

Couleurs des lignes (basées sur ageMonths, mêmes seuils que l'UI) :

Tranche Hex Tailwind
24+ mois #ddd6fe violet-200
22-24 mois #fecaca red-200
20-22 mois #fed7aa orange-200
< 20 mois ou NULL #ffffff blanc

Le fond est appliqué sur toute la ligne (9 cellules).

Flux d'erreur

  • Exception PhpSpreadsheet (création buffer) → propage en 500 standard API Platform
  • Pas d'utilisateur (token expiré) → 401 standard via la sécurité

Performance

  • 936 lignes × 9 colonnes : génération en mémoire < 1s, fichier < 100 KB
  • Pas de pagination, pas de streaming row-by-row (overkill pour ce volume)

Tests

Optionnel ce lot : test PHPUnit du provider qui vérifie que :

  • Status 200
  • Content-Type XLSX
  • Header Content-Disposition: attachment; filename=...xlsx
  • Body non vide Mock simple de l'EntityManagerInterface pour retourner 2 bovins fictifs.

À faire en follow-up si on veut couvrir.

Verification manuelle

  1. make composer-install (après avoir ajouté la dep)
  2. Recharger /inventory
  3. Clic sur le bouton "Exporter Excel"
  4. Vérifier le téléchargement : nom de fichier = inventaire_bovins_2026-04-24.xlsx
  5. Ouvrir dans Excel/LibreOffice :
    • 9 colonnes attendues
    • En-tête figé en scrollant
    • Auto-filter natif Excel
    • Lignes colorées selon âge (violet/rouge/orange)
    • Tri par date de naissance croissante

Critères d'acceptation

  • L'export contient 100 % des bovins actifs (count = SELECT COUNT(*) FROM bovine WHERE exited_at IS NULL)
  • Le filename inclut la date du jour
  • Les couleurs correspondent aux seuils d'âge
  • L'ordre matche l'UI (birthDate ASC)
  • Pas de régression sur les autres endpoints /api/bovines