edbb1f7b29
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
171 lines
7.3 KiB
Markdown
171 lines
7.3 KiB
Markdown
# 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`.
|