## Résumé Ajoute un bouton **Exporter** (admin uniquement) à droite du titre « Heures » qui génère un **PDF d'une journée**, regroupé par site, reprenant les colonnes de la vue Jour **sans la colonne « Valider »**. - Drawer : champ date (préremplit la date affichée) + cases à cocher des sites (préselectionnées sur le filtre courant). - Portée identique à l'écran : non-conducteurs, sous contrat à la date, sites cochés (lignes vides incluses). - Jour/Nuit/Total incluent le crédit d'absence et le crédit virtuel férié. ## Implémentation - Back : `WorkHourDayExport` (ApiResource) + `WorkHourDayExportProvider`, endpoint `GET /work-hours/day-export?workDate=&siteIds=` (ROLE_ADMIN). - Calcul des cellules mutualisé via `YearlyHoursExportBuilder::buildDayRowsForEmployees` (source unique de vérité). - Gabarit `templates/work-hour-day-export/print.html.twig` (A4 portrait compact). - Front : `HoursDayExportDrawer.vue` + câblage dans `pages/hours.vue`. - Docs : `doc/hours-day-export.md`, `documentation-content.ts`, `CLAUDE.md`. ## Tests - Test unitaire `YearlyHoursDayRowsTest` ajouté. - Suite complète verte : 173 tests, 359 assertions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #24 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
7.3 KiB
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é paruseHoursPage). - 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 :
- Date — champ date (input date), prérempli avec
selectedDatede l'écran. - Sites —
MalioSelectCheckboxavecdisplay-select-all, mêmes options que la toolbar (sitesdu composable), présélectionné surselectedSiteIdscourants.
Bouton « Exporter » : désactivé si aucune date ou aucun site sélectionné.
Déclenchement
- À la validation :
usePdfPrinter().printPdf(url)avecGET /work-hours/day-export?workDate=YYYY-MM-DD&siteIds=1,2,3. - Le téléchargement réutilise le pattern blob existant (
usePdfPrinter). - État
isLoadingsur le bouton pendant la génération.
Câblage dans hours.vue / useHoursPage.ts
hours.vuegère l'état d'ouverture du drawer et passesites,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 gardantuseHoursPagecomme 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/Finmatin/après-midi/soir) : valeursWorkHourbrutes (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 :
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 :
- Lire/valider
workDate(Y-m-d) etsiteIds(CSV d'entiers). - Charger les employés (
EmployeeRepository::findAll()— feature admin-only), filtrer : non-drivers, site ∈ siteIds. - Pour chaque site (ordre
displayOrder), trier les employés par nom. - 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).
- Construire les lignes via
YearlyHoursExportBuilder(méthode dédiée, voir ci-dessous). - Rendre le Twig → Dompdf (
A4,portrait), renvoyerResponsebinaire avecContent-Disposition: attachment; filename="heures_jour_YYYY-MM-DD.pdf".
Réutilisation YearlyHoursExportBuilder
Ajouter une méthode publique :
/**
* @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 deWorkMetrics.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
statutagrè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
Totalexprime 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)
doc/: nouvelle section (ou ajout à un doc heures existant) décrivant l'export jour.frontend/data/documentation-content.ts: entrée niveau admin dans la section Heures.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 absencecountAsWorkedHours. - (Optionnel) test provider : validation des paramètres
workDate/siteIds.