feat(heures) : export PDF des heures (vue jour) par sites #24
@@ -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`.
|
||||
Reference in New Issue
Block a user