Files
SIRH/doc/hours-validated-days.md
T
tristan 74abecbe03
Auto Tag Develop / tag (push) Successful in 10s
feat(heures) : calendrier des jours validés (vue Jour) + harmonisation Malio UI (#30)
## Fonctionnel
- Calendrier MalioDate en vue Jour (écrans Heures ET Heures Conducteurs) : les jours entièrement validés par un admin sont peints en vert.
  - Endpoint `GET /work-hours/validation-status?from=&to=[&driver=1]` (scope conducteur inversé via `driver=1`), périmètre complet (ignore le filtre sites).
  - Chargement à la volée par mois (event `@month-change`), refresh après validation / saisie / absence.

## Harmonisation @malio/layer-ui 1.7.11
- `reserveMessageSpace=false` sur tous les champs (alignement).
- Tous les drawers migrés sur `MalioDrawer` (titre via slot `#header`, `AppDrawer` custom supprimé).
- Boutons d'action en `MalioButton` ; deux boutons côte à côte partagent l'espace.
- Inputs date en `MalioDate`, sélecteur semaine en `MalioDateWeek`.
- Boutons d'ajout uniformisés sur « Ajouter » + icône.

## Divers
- `.env` : `EXCLUDED_PUBLIC_HOLIDAYS="null"`.
- Doc : `doc/hours-validated-days.md`, `documentation-content.ts`, `CLAUDE.md`.
- Tests : provider `WorkHourValidationStatus` (suite complète 236/236 OK via pre-commit hook).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #30
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-16 13:53:03 +00:00

4.5 KiB

Calendrier des jours validés (écran Heures — vue Jour)

Objectif

Sur l'écran Heures, en vue Jour, le sélecteur de date est un calendrier (composant MalioDate du layer @malio/layer-ui) qui peint en vert les jours entièrement validés par un admin. La RH repère ainsi d'un coup d'œil les jours où il reste de la validation à faire.

Définition « jour validé » (vert)

Un jour est vert ssi, dans le périmètre complet de l'utilisateur :

  • il porte au moins une ligne WorkHour dans le scope ciblé ce jour-là, et
  • aucune de ces lignes n'est en attente de validation (isValid = false).

La même mécanique sert les deux écrans, avec un scope opposé : écran Heures → non-conducteurs (défaut) ; écran Heures Conducteurs → conducteurs (?driver=1).

Conséquences :

  • Un jour sans aucune ligne (rien saisi, ex. week-end, jour futur) reste neutre (jamais vert) — « rien fait » n'est pas « tout validé ».
  • On se base sur la seule colonne work_hours.is_valid (validation admin/RH). isSiteValid (chef de site) n'entre pas en compte → modifier une validation site ne change pas la couleur.
  • Scope conducteur : écran Heures → conducteurs exclus ; écran Heures Conducteurs → seuls les conducteurs (le filtre est inversé via ?driver=1).

Périmètre

  • ROLE_ADMIN → tous les employés / tous les sites.
  • Chef de site → ses sites uniquement.
  • Le filtre sites de l'écran est volontairement ignoré : le vert reflète tout le périmètre (objectif : repérer le moindre jour incomplet, où qu'il soit). Changer le filtre sites de la vue Jour ne recalcule pas le calendrier.

Chargement des données

  • Endpoint : GET /work-hours/validation-status?from=YYYY-MM-DD&to=YYYY-MM-DD[&driver=1] (ROLE_USER). Réponse : { from, to, validatedDays: string[] } (dates Y-m-d).
  • Provider : App\State\WorkHourValidationStatusProvider (ressource App\ApiResource\WorkHourValidationStatus).
    • EmployeeRepository::findScoped($user) pour le périmètre (ignore tout siteIds).
    • Une requête WorkHourReadRepositoryInterface::findByDateRangeAndEmployees.
    • Filtrage conducteur par date via EmployeeContractResolver::resolveIsDriverForEmployeeAndDate (mémoïsé par couple employé/jour) : if ($isDriver !== $driverOnly) continue; (driverOnly = ?driver=1).
    • Agrégation par jour : vert ⇔ total > 0 (lignes du scope) et pending = 0 (aucune isValid=false).
    • Garde-fou : plage bornée à 366 jours.
  • Le chargement est à la volée par mois affiché (jamais préchargé sur plusieurs années) : MalioDate émet @month-change { month, year } à l'ouverture du popover et à chaque navigation ; le front fetch la grille visible (lundi avant le 1er → dimanche après le dernier jour, pour colorer aussi les jours débordants) et met le résultat en cache par mois (useHoursPage / useDriverHoursPagevalidatedDaysByMonth ; ce dernier appelle le service avec { driver: true }). La prop réactive markedDates (ISO → 'success') recolore la grille.

Rafraîchissement

Toute action qui touche la validation d'un jour recharge le mois concerné s'il est en cache (reloadValidationMonth), donc le calendrier se recolore aussitôt :

  • validation admin d'une ligne (toggleValidation) ou en masse (toggleValidationBulk) ;
  • sauvegarde d'heures (handleSave) — toute modification réelle remet isValid=false ;
  • création / suppression d'absence (refreshAfterAbsenceChange). La validation site ne déclenche pas de rechargement (sans effet sur le vert).

Périmètre d'affichage

  • Vue Jour uniquement : le vert (calendrier MalioDate + markedDates) est à la maille jour, sur les deux écrans (Heures et Heures Conducteurs, via showValidationCalendar). La vue Semaine utilise un MalioDateWeek (sélecteur de semaine, sans coloration). Le PeriodStepperPicker ne subsiste que comme fallback de la vue Jour quand showValidationCalendar est absent (aucun appelant actuel, conservé par flexibilité).
  • Précédence d'affichage dans la grille (côté layer) : sélection (fond plein primary) > variante marquée ; le jour courant (today) garde sa bordure et reçoit le fond vert s'il est validé.

Dépendance layer

Nécessite @malio/layer-ui >= 1.7.x : prop markedDates (Record<"YYYY-MM-DD", 'success' | 'danger'>) + event month-change sur MalioDate (ticket Malio UI MUI-45).