# Écran Récap. congés

## Objet

Vue tableau des soldes de congés par employé, figée à un cutoff temporel (fin de semaine S-1).
Complémentaire à l'export PDF admin : mêmes colonnes, accès étendu aux employés et chefs de site.

## Cutoff

La formule est : `cutoffDate = dimanche de (lundi de la semaine courante − 7 jours)`.

Exemple : mardi 14/04/2026 (S16) → **dimanche 12/04/2026 23:59:59** (fin S15).

Le cutoff est purement temporel : l'état `isValid` des heures n'entre pas en compte. Les heures
et absences postérieures au cutoff sont ignorées dans le calcul des soldes.

Implémentation : `App\Util\LeaveRecapCutoff::resolveCutoff()` côté backend, helper `parseYmd` +
`getIsoWeekNumber` côté frontend pour l'affichage du badge.

## Colonnes

Identiques au PDF :

- Nom
- Prénom
- Contrat
- CP N-1 restant
- CP N
- Samedis acquis
- RTT

Pour les admins et chefs de site, une colonne **Site** est ajoutée à gauche.

## Scoping

| Profil        | Données visibles                        |
|---------------|-----------------------------------------|
| `ROLE_ADMIN`  | Tous les employés actifs, tous sites    |
| `ROLE_USER` (chef de site) | Employés actifs des sites autorisés via `UserSiteRole` |
| `ROLE_SELF`   | Uniquement l'employé lié à son compte   |

## Flag d'accès

Le champ `User.hasLeaveRecapAccess` (boolean, défaut `false`) conditionne :

- L'affichage de l'entrée "Récap. congés" dans la sidebar
- L'accès à la route `/leave-recap` (middleware `leave-recap-access.ts`)
- L'endpoint API `GET /api/leave-recap` (le provider renvoie `403` si le flag est faux)

Le flag s'applique même aux admins : un admin sans le flag ne voit pas l'écran. Il se configure
dans le drawer de création/édition d'un utilisateur.

## Service partagé

`App\Service\Leave\LeaveRecapRowBuilder::build(Employee $employee, DateTimeImmutable $asOfDate)`
construit une ligne de récap. Il est utilisé par :

- `LeaveRecapPrintProvider` (PDF admin) avec `$asOfDate = today`
- `EmployeeLeaveRecapProvider` (écran) avec `$asOfDate = cutoff`

## Propagation du cutoff dans les calculs

`EmployeeLeaveSummaryProvider::computeYearSummary()` accepte un `?DateTimeImmutable $asOfDate`.
Lorsqu'il est fourni et appliqué à l'année cible, il remplace "today" dans :

- `resolveAccrualCalculationEndDate()` — la borne d'accrual devient le dernier jour du mois
  précédant `asOfDate` (au lieu du mois précédent today).
- `resolveTakenCalculationEndDate()` — les absences postérieures à `asOfDate` sont ignorées.

Pour les années antérieures (carry forward), le comportement reste inchangé (pas de cap).

Le RTT est capé via `RttRecoveryComputationService::computeTotalRecoveryForExercise(..., $limitDate)`
qui existait déjà, en passant `cutoff` comme date de référence.
