# 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`.