Aller au contenu

Story AB-018 : Calendrier d'Équipe (Backend + Frontend)

Module : absence-management Tranche : VS-AB-013 de l'architecture Contexte de marque : [LES DEUX] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priorité : 18 Dépend de : AB-002 Peut se développer en parallèle avec : AB-019, AB-020 Ordre de fusion : Après AB-002 Complexité estimée : M PRD User Stories : US-ABS-003


Objectif

Fournir une vue calendrier d'équipe affichant les absences des employés par jour sous forme de timeline/Gantt, avec une visibilité respectueuse de la vie privée (les employés voient uniquement "Absent", les managers voient les noms, les RH/DG voient les types de congé) et un filtrage par département pour les RH.


Périmètre Backend

Entités et Enregistrements

Enregistrements de réponse :

public record TeamCalendarResponse(
    YearMonth month,
    List<TeamCalendarEmployee> employees,
    TeamCalendarSummary summary
) {}

public record TeamCalendarEmployee(
    UUID employeeId,
    String name,                        // null pour le rôle EMPLOYEE (confidentialité)
    List<TeamCalendarAbsence> absences
) {}

public record TeamCalendarAbsence(
    LocalDate startDate,
    LocalDate endDate,
    String leaveType                    // null sauf si le lecteur est HR_MANAGER ou DG
) {}

public record TeamCalendarSummary(
    BigDecimal absenceRate,             // pourcentage, 1 décimale
    int absentToday,
    int totalEmployees
) {}

Couche Repository

Utilise les repositories existants d'AB-002 : - LeaveRequestRepository — interroge les congés APPROVED, IN_PROGRESS, COMPLETED pour un mois donné - Requête personnalisée : findByTenantIdAndStatusInAndDateRange(UUID tenantId, List<LeaveStatus> statuses, LocalDate monthStart, LocalDate monthEnd) — retourne tous les congés chevauchant le mois

Couche Service

TeamCalendarService.java :

  • getTeamCalendar(UUID tenantId, YearMonth month, String scope, UUID orgUnitId, AuthenticatedUser currentUser) : TeamCalendarResponse
  • Déterminer l'ensemble d'employés selon le rôle :
    • EMPLOYEE : membres de l'équipe dans la même unité organisationnelle (depuis le service employé)
    • MANAGER : subordonnés directs uniquement
    • HR_MANAGER / DG : tous les employés, filtrés par orgUnitId si fourni
  • Interroger les congés APPROVED + IN_PROGRESS + COMPLETED pour le mois
  • Appliquer le niveau de détail basé sur le RBAC :
    • EMPLOYEE : name=null, leaveType=null (présence/absence uniquement)
    • MANAGER : name=abrégé (ex. "DIALLO F."), leaveType=null
    • HR_MANAGER / DG : name=abrégé, leaveType=visible
  • Calculer le résumé : absenceRate, absentToday, totalEmployees
  • Objectif de performance : < 1 sec pour une équipe de 50 personnes

  • getMyTeamCalendar(UUID tenantId, YearMonth month, AuthenticatedUser currentUser) : TeamCalendarResponse

  • Méthode de commodité pour les managers — limite automatiquement aux subordonnés directs

Points de terminaison API

Méthode Chemin Paramètres de requête Réponse Auth
GET /api/v1/absence/team-calendar month (obligatoire, YYYY-MM), scope (optionnel : ORG_UNIT), orgUnitId (optionnel) ApiResponse<TeamCalendarResponse> EMPLOYEE (présence uniquement), MANAGER (subordonnés directs), HR_MANAGER/DG (tous)
GET /api/v1/absence/team-calendar/my-team month (obligatoire, YYYY-MM) ApiResponse<TeamCalendarResponse> MANAGER, HR_MANAGER, DG

Règles de validation

  • Le paramètre de requête month doit être au format YYYY-MM valide — 400 "Format de mois invalide. Utilisez YYYY-MM."
  • orgUnitId est accepté uniquement pour HR_MANAGER/DG — 403 pour les autres rôles
  • EMPLOYEE ne peut voir que sa propre unité organisationnelle — 403 si tentative de requête inter-unités
  • MANAGER ne peut voir que ses subordonnés directs — 403 si tentative de consulter d'autres équipes

Considérations Multi-Tenant

  • Toutes les requêtes incluent tenantId depuis ScopedValue<TenantContext>
  • L'appartenance à l'unité organisationnelle est résolue via le service employé (événement inter-module ou données en cache)
  • Les données du calendrier sont en lecture seule — pas de mutation, pas de synchronisation hors ligne nécessaire
  • Fonctionne avec les 4 profils de base de données (conçu pour BYO-DB en priorité)

Périmètre Frontend

RAPPEL SYNCHRONISATION HORS LIGNE (Papillon — CRITIQUE) : Le calendrier d'équipe est une vue en lecture seule. Les données du calendrier en cache sont servies hors connexion avec un bandeau indiquant les données périmées et l'heure de la dernière synchronisation. Voir docs/standards/offline-sync-guidelines.md.

Pages et Routes

Route Page Description
/absences/calendrier TeamCalendarPage Vue timeline du calendrier d'équipe

Composants

  • MonthNavigator — Navigation mensuelle : flèches gauche/droite + label mois/année
  • DepartmentFilter — Select pour filtrer par département (RH/DG uniquement, masqué pour les autres)
  • CalendarTimeline — Grille timeline avec défilement horizontal (lignes=personnes, colonnes=jours)
  • AbsenceBar — Barre orange représentant une période d'absence
  • CalendarLegend — Légende : Absent, Présent, Aujourd'hui
  • CalendarSummary — Section résumé : taux d'absence, nombre d'absents aujourd'hui
  • PersonAbsenceSheet — BottomSheet (mobile) affichant les absences d'une personne pour le mois
  • AbsenceTooltip — Infobulle (desktop) affichant les détails de l'absence au survol

États UI (TOUS REQUIS)

État Comportement Copie Française
Chargement Cellules de grille squelette (lignes x colonnes)
Vide (aucune absence) EmptyState avec illustration de personnes "Toute l'equipe est presente ce mois-ci" / CTA : "Mois precedent"
Erreur Toast + bouton réessayer "Impossible de charger le calendrier."
Hors ligne Données du calendrier en cache + bandeau données périmées "Hors connexion — derniere synchro : {time}"
Succès N/A (vue en lecture seule)

Comportement Responsive

Mobile (360px — Papillon en priorité) : - Navigation mensuelle en haut : "< Mars 2026 >" - Filtre département (Select, RH/DG uniquement) sous la navigation - Timeline avec défilement horizontal : - Lignes = personnes (abréviation 3 lettres, ex. "DIA"), colonne gauche fixe - Colonnes = jours du mois (1-31), défilable - Aujourd'hui mis en évidence avec fond primary-light - Absent = barre orange, Présent = remplissage clair, Week-end = atténué - Légende sous la timeline : "Absent / Présent / Aujourd'hui" - Appui sur le nom → BottomSheet avec les absences de la personne pour le mois visible - Section résumé en bas : "Taux d'absence : X% / Absents aujourd'hui : N/total" - Toutes les cibles tactiles >= 48px

Desktop (1024px+ — Enterprise en priorité) : - Mois complet visible (31 jours), toutes les colonnes sans défilement horizontal - Navigation latérale visible - Survol sur la barre d'absence → infobulle : "DIALLO F. — Absent 11-13/03" - Clic sur le nom (RH uniquement) → naviguer vers le détail du solde employé (AB-021) - Filtre département + bouton export CSV (zone en haut à droite) - Hauteur de ligne plus compacte, affichage d'informations plus dense

Interactions

  • Navigation mensuelle : appuyer sur les flèches pour aller au mois précédent/suivant, animation glissement gauche/droite
  • Filtre département (RH/DG) : composant Select, onChange déclenche un nouvel appel API
  • Appui mobile sur la ligne d'une personne → BottomSheet s'ouvre avec la liste des absences de cette personne
  • Survol desktop sur la barre d'absence → AbsenceTooltip avec les détails
  • Clic desktop sur le nom (RH uniquement) → naviguer vers /absences/employes/{id}/solde
  • Hors ligne : données en cache affichées, bandeau affiché, navigation fonctionne toujours dans les mois en cache

Règles de Confidentialité

Lecteur Voit
EMPLOYEE "Absent" ou "Présent" uniquement, pas de noms, pas de types
MANAGER Noms des subordonnés directs + barres "Absent", pas de type de congé
HR_MANAGER/DG Tous les employés, type de congé au survol/appui, filtre département

Micro-Copie Française

  • calendar.title: "Calendrier d'equipe"
  • calendar.month_nav: "{month} {year}" (ex. "Mars 2026")
  • calendar.department_filter: "Departement"
  • calendar.all_departments: "Tous les departements"
  • calendar.legend_absent: "Absent"
  • calendar.legend_present: "Present"
  • calendar.legend_today: "Aujourd'hui"
  • calendar.absence_rate: "Taux d'absence : {rate}%"
  • calendar.absent_today: "Absents aujourd'hui : {count}/{total}"
  • calendar.empty: "Toute l'equipe est presente ce mois-ci"
  • calendar.empty_cta: "Mois precedent"
  • calendar.error: "Impossible de charger le calendrier."
  • calendar.offline: "Hors connexion — derniere synchro : {time}"
  • calendar.tooltip: "{name} — Absent {startDay}-{endDay}/{month}"
  • calendar.sheet_title: "Absences de {name}"

Critères d'Acceptation

AC-057 : Le manager voit les absences de ses subordonnés directs
Étant donné que je suis manager
Quand je consulte le calendrier d'équipe
Alors je vois toutes les absences de mes subordonnés directs pour le mois en cours

AC-058 : Masquage du type de congé pour respecter la confidentialité
Étant donné qu'un membre de l'équipe est en congé
Quand je consulte le calendrier en tant qu'employé non-RH
Alors je vois "Absent" (pas le type de congé) pour protéger la confidentialité

AC-059 : Le RH peut filtrer par département
Étant donné que je suis HR_MANAGER ou DG
Quand je consulte le calendrier
Alors je peux filtrer par département et voir tous les employés

Règles OHADA et Réglementaires

N/A — le calendrier est une fonctionnalité d'affichage sans exigences réglementaires. Les règles de confidentialité sont des décisions de conception au niveau applicatif, non imposées par l'OHADA.


Standards et Conventions

  • docs/standards/java-spring-guidelines.md — §Patterns REST controller, §Autorisation RBAC, §Optimisation des requêtes
  • docs/standards/database-guidelines.md — §Pattern repository multi-tenant, §Optimisation des requêtes en lecture seule
  • docs/standards/api-guidelines.md — §Patterns endpoints GET, §Enveloppe ApiResponse, §Validation des paramètres de requête
  • docs/standards/react-typescript-guidelines.md — §Patterns de composants, §CSS Modules, §i18n, §Patterns responsive
  • docs/standards/design-system.md — §Select, §BottomSheet, §EmptyState, §Toast
  • docs/standards/offline-sync-guidelines.md — §Données en lecture seule en cache, §Pattern bandeau données périmées

Exigences de Test

Tests Unitaires

  • TeamCalendarService.getTeamCalendar() : rôle EMPLOYEE → names=null, leaveTypes=null
  • TeamCalendarService.getTeamCalendar() : rôle MANAGER → names=abrégé, leaveTypes=null
  • TeamCalendarService.getTeamCalendar() : rôle HR_MANAGER → names=abrégé, leaveTypes=visible
  • TeamCalendarService.getTeamCalendar() : rôle DG → identique à HR_MANAGER
  • TeamCalendarService.getTeamCalendar() : EMPLOYEE tente une requête inter-unités → 403
  • TeamCalendarService.getTeamCalendar() : MANAGER tente de consulter une autre équipe → 403
  • Calcul résumé : 2 absents sur 10 employés → absenceRate=20.0
  • Calcul résumé : aucune absence → absenceRate=0.0, absentToday=0

Tests d'Intégration

  • GET /team-calendar?month=2026-03 en tant que MANAGER → 200, subordonnés directs uniquement
  • GET /team-calendar?month=2026-03 en tant que EMPLOYEE → 200, présence/absence uniquement
  • GET /team-calendar?month=2026-03&orgUnitId=... en tant que HR_MANAGER → 200, filtré par département
  • GET /team-calendar?month=2026-03&orgUnitId=... en tant que EMPLOYEE → 403
  • GET /team-calendar?month=invalide → 400
  • Multi-tenant : les employés du tenant A ne sont pas visibles par le tenant B

Ce que QA Validera

  • Le calendrier s'affiche à 360px avec défilement horizontal, aujourd'hui mis en évidence
  • La navigation mensuelle fonctionne (précédent/suivant)
  • Appui sur le nom d'une personne → BottomSheet avec les absences (mobile)
  • Survol sur la barre d'absence → infobulle avec les détails (desktop)
  • EMPLOYEE ne voit pas les noms, pas les types de congé
  • MANAGER voit les subordonnés directs avec les noms, pas les types de congé
  • RH/DG voit tous les employés, types de congé au survol, filtre département fonctionne
  • L'état vide s'affiche quand il n'y a pas d'absence dans le mois
  • Hors ligne : données en cache affichées avec le bandeau de données périmées

Hors Périmètre

  • Planification des congés à l'échelle entreprise (V2)
  • Règles de couverture / niveaux de dotation minimaux (V2)
  • Export CSV/PDF (Enterprise V2)
  • Vue multi-mois / vue annuelle (V2)
  • Création de congé par glisser-déposer depuis le calendrier (V2)

Définition de Done

  • [ ] Tous les endpoints backend implémentés et retournant les réponses correctes
  • [ ] Toutes les pages frontend s'affichent correctement à 360px, 768px, 1024px
  • [ ] Les 5 états UI implémentés (chargement, vide, erreur, hors ligne, succès)
  • [ ] La micro-copie française correspond exactement à la spécification design
  • [ ] Tous les critères d'acceptation passent manuellement
  • [ ] Tests unitaires écrits et passants
  • [ ] Tests d'intégration écrits et passants
  • [ ] Isolation multi-tenant vérifiée — employés et congés limités au tenant
  • [ ] La lecture hors ligne fonctionne (Papillon) — données en cache affichées avec bandeau de données périmées
  • [ ] Pas de any TypeScript
  • [ ] Pas de types bruts Java — tous les génériques explicites
  • [ ] Les réponses API suivent le format d'enveloppe standard
  • [ ] Règles de confidentialité appliquées — niveau de détail basé sur le RBAC vérifié pour les 4 rôles