docs : spec export PDF heures vue jour par sites

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 17:13:00 +02:00
parent 2745f4e476
commit edbb1f7b29
@@ -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<Employee> $employees
* @return list<array{employeeId:int, employeeName:string, statut:?string,
* morningFrom:string, morningTo:string, afternoonFrom:string, afternoonTo:string,
* eveningFrom:string, eveningTo:string, dayHours:string, nightHours:string,
* total:string, isWeekend:bool, hasContract:bool}>
*/
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 `<h2>` 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`.