# Écran Récap. congés ## Objet Vue tableau des soldes de congés par employé, figée à un cutoff temporel (fin de semaine S-2). 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 − 14 jours)`. Exemple : mardi 14/04/2026 (S16) → **dimanche 05/04/2026 23:59:59** (fin S14). 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.