From edbb1f7b29b9a418976972c296b50223d69cbade Mon Sep 17 00:00:00 2001 From: tristan Date: Mon, 8 Jun 2026 17:13:00 +0200 Subject: [PATCH] docs : spec export PDF heures vue jour par sites Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-08-hours-day-export-design.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-08-hours-day-export-design.md diff --git a/docs/superpowers/specs/2026-06-08-hours-day-export-design.md b/docs/superpowers/specs/2026-06-08-hours-day-export-design.md new file mode 100644 index 0000000..05f270e --- /dev/null +++ b/docs/superpowers/specs/2026-06-08-hours-day-export-design.md @@ -0,0 +1,170 @@ +# Export PDF des heures — vue Jour (par sites) + +**Date** : 2026-06-08 +**Branche** : feature/SIRH-35-export-des-heures-employe + +## Objectif + +Ajouter un bouton **Exporter** sur l'écran « Heures », réservé aux administrateurs, +qui produit un **PDF d'une journée** reprenant les colonnes de la vue Jour (sans la +colonne de validation), pour les employés des sites sélectionnés, **regroupés par site**. + +## Décisions validées + +| Sujet | Choix | +|-------|-------| +| Format | PDF (Twig → Dompdf) | +| Période | Un seul jour | +| Orientation | A4 **portrait**, mise en page compacte (objectif : tenir sur une page ; débordement multipage seulement si le nombre d'employés l'impose) | +| Regroupement | Une section par site | +| Accès | `ROLE_ADMIN` uniquement | + +## Comportement frontend + +### Bouton + +- Dans `frontend/pages/hours.vue`, à droite du titre « Heures » (le conteneur titre est + déjà `flex flex-wrap items-center justify-between`). +- Visible uniquement si `isAdmin` (déjà exposé par `useHoursPage`). +- Style cohérent avec les autres boutons d'action de l'app ; libellé « Exporter » + (préfixe non requis ici, ce n'est pas un « + Ajouter »). + +### Drawer `HoursDayExportDrawer.vue` + +Nouveau composant utilisant `AppDrawer` (mode create — bouton centré). + +Champs : +1. **Date** — champ date (input date), prérempli avec `selectedDate` de l'écran. +2. **Sites** — `MalioSelectCheckbox` avec `display-select-all`, mêmes options que la + toolbar (`sites` du composable), présélectionné sur `selectedSiteIds` courants. + +Bouton **« Exporter »** : désactivé si aucune date ou aucun site sélectionné. + +### Déclenchement + +- À la validation : `usePdfPrinter().printPdf(url)` avec + `GET /work-hours/day-export?workDate=YYYY-MM-DD&siteIds=1,2,3`. +- Le téléchargement réutilise le pattern blob existant (`usePdfPrinter`). +- État `isLoading` sur le bouton pendant la génération. + +### Câblage dans `hours.vue` / `useHoursPage.ts` + +- `hours.vue` gère l'état d'ouverture du drawer et passe `sites`, `selectedSiteIds`, + `selectedDate`, `isAdmin`. +- L'appel d'export peut vivre dans un petit handler local (`hours.vue`) ou dans le + composable ; au choix de l'implémentation, en gardant `useHoursPage` comme source des + données affichées. + +## Portée des données (identique à l'écran Jour) + +- Employés **non-conducteurs** (`isDriver !== true`). +- **Sous contrat** à la date choisie. +- Appartenant aux **sites cochés**. +- **Tous les employés sous contrat sont affichés**, même sans saisie (lignes vides) — + cohérent avec la règle des exports heures annuelles. + +## Colonnes du PDF + +Mêmes colonnes que la vue Jour, **sans la colonne Valider** : + +`Nom` · `Statut` · `Début matin` · `Fin matin` · `Début après-midi` · +`Fin après-midi` · `Début soir` · `Fin soir` · `Jour` · `Nuit` · `Total` + +- **Statut** : libellé d'absence (ou formation, ou nom du férié) si présent, sinon vide. +- **Heures** (`Début/Fin` matin/après-midi/soir) : valeurs `WorkHour` brutes (`HH:MM`), + vides si non saisies. +- **Jour / Nuit / Total** : calculés comme à l'écran — minutes jour vs nuit, total + incluant le crédit d'absence (`countAsWorkedHours`) et le **crédit virtuel férié** + (`HolidayVirtualHoursResolver`). +- Week-ends / fériés : lignes grisées/colorées comme dans les templates existants. + +## Architecture backend + +### ApiResource `WorkHourDayExport` + +`src/ApiResource/WorkHourDayExport.php` — calqué sur `EmployeeYearlyHoursBulkPrint` : + +```php +new Get( + uriTemplate: '/work-hours/day-export', + provider: WorkHourDayExportProvider::class, + parameters: [ + new QueryParameter(key: 'workDate', required: true), + new QueryParameter(key: 'siteIds', required: true), + ], + security: "is_granted('ROLE_ADMIN')" +) +``` + +### Provider `WorkHourDayExportProvider` + +`src/State/WorkHourDayExportProvider.php` : + +1. Lire/valider `workDate` (`Y-m-d`) et `siteIds` (CSV d'entiers). +2. Charger les employés (`EmployeeRepository::findAll()` — feature admin-only), + filtrer : non-drivers, site ∈ siteIds. +3. Pour chaque site (ordre `displayOrder`), trier les employés par nom. +4. Filtrer les employés sous contrat à la date (le builder ignore déjà les jours hors + contrat — un employé sans contrat ce jour produit une ligne vide à exclure). +5. Construire les lignes via `YearlyHoursExportBuilder` (méthode dédiée, voir ci-dessous). +6. Rendre le Twig → Dompdf (`A4`, `portrait`), renvoyer `Response` binaire avec + `Content-Disposition: attachment; filename="heures_jour_YYYY-MM-DD.pdf"`. + +### Réutilisation `YearlyHoursExportBuilder` + +Ajouter une méthode publique : + +```php +/** + * @param list $employees + * @return list + */ +public function buildDayRowsForEmployees(array $employees, DateTimeImmutable $date): array +``` + +- Réutilise les helpers privés existants (`computeMetrics`, résolution d'absences, + `HolidayVirtualHoursResolver`, `EmployeeContractResolver`, fériés) — **source unique + de vérité** pour le calcul des cellules d'une journée. +- Émet en plus `dayHours` / `nightHours` (issus de `WorkMetrics.dayMinutes` / + `nightMinutes`) que l'export annuel n'affichait pas par ligne en mode TIME. +- Les employés sans contrat ce jour sont exclus (pas de ligne). +- Le `statut` agrège absence / formation / libellé férié (réutilise la logique de + résolution d'absence/formation déjà présente dans le contexte jour si nécessaire). + +> Note : la vue Jour mélange potentiellement modes TIME et PRESENCE selon le contrat à +> la date. Pour l'export, on suit le mode résolu à la date (comme l'écran). En mode +> PRESENCE, les cellules horaires restent vides et `Total` exprime les demi-journées, +> identique à l'affichage écran. + +### Template `templates/work-hour-day-export/print.html.twig` + +- A4 portrait, marges fines, police ~9px (réf. `employee-yearly-hours/print.html.twig`). +- Barre de titre : « Heures — {date} » + date d'export en haut à droite. +- Une `

` par site, suivie d'un tableau avec les 11 colonnes ci-dessus. +- Week-ends / fériés grisés (`#c0c0c0` / `#b3e5fc`) comme les templates existants. +- `table-layout: auto`, largeurs compactes pour viser une page. + +## Limites connues + +- Un grand nombre d'employés (beaucoup de sites cochés) peut déborder sur plusieurs + pages — on vise une page sans la garantir. +- Pas de risque mémoire particulier (un seul jour, volume très inférieur à l'export + annuel tous employés). + +## Documentation à mettre à jour (règles CLAUDE.md) + +1. `doc/` : nouvelle section (ou ajout à un doc heures existant) décrivant l'export jour. +2. `frontend/data/documentation-content.ts` : entrée niveau **admin** dans la section + Heures. +3. `CLAUDE.md` : note sous la section heures/exports (provider, builder réutilisé, + colonnes, scope identique écran, portrait). + +## Tests + +- Test unitaire `YearlyHoursExportBuilder::buildDayRowsForEmployees` : un employé TIME + avec saisie (vérifier day/night/total), un employé sans contrat (exclu), un jour férié + (crédit virtuel), une absence `countAsWorkedHours`. +- (Optionnel) test provider : validation des paramètres `workDate` / `siteIds`.