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 uniquementHR_MANAGER/DG: tous les employés, filtrés parorgUnitIdsi 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=nullHR_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
monthdoit être au formatYYYY-MMvalide — 400"Format de mois invalide. Utilisez YYYY-MM." orgUnitIdest 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
tenantIddepuisScopedValue<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éeDepartmentFilter— 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'absenceCalendarLegend— Légende : Absent, Présent, Aujourd'huiCalendarSummary— Section résumé : taux d'absence, nombre d'absents aujourd'huiPersonAbsenceSheet— BottomSheet (mobile) affichant les absences d'une personne pour le moisAbsenceTooltip— 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êtesdocs/standards/database-guidelines.md— §Pattern repository multi-tenant, §Optimisation des requêtes en lecture seuledocs/standards/api-guidelines.md— §Patterns endpoints GET, §Enveloppe ApiResponse, §Validation des paramètres de requêtedocs/standards/react-typescript-guidelines.md— §Patterns de composants, §CSS Modules, §i18n, §Patterns responsivedocs/standards/design-system.md— §Select, §BottomSheet, §EmptyState, §Toastdocs/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=nullTeamCalendarService.getTeamCalendar(): rôle MANAGER → names=abrégé, leaveTypes=nullTeamCalendarService.getTeamCalendar(): rôle HR_MANAGER → names=abrégé, leaveTypes=visibleTeamCalendarService.getTeamCalendar(): rôle DG → identique à HR_MANAGERTeamCalendarService.getTeamCalendar(): EMPLOYEE tente une requête inter-unités → 403TeamCalendarService.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
anyTypeScript - [ ] 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