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