Story AB-008 : Configuration des jours fériés et congés exceptionnels¶
@assignee: @joel23p
Module : absence-management Slice : VS-AB-019 de l'architecture Contexte produit : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priorité : 8 Dépend de : AB-002 Peut être développé en parallèle avec : AB-006, AB-007 Ordre de merge : Dans n'importe quel ordre avec AB-006, AB-007 Complexité estimée : S PRD User Stories : US-ABS-007
Objectif¶
Permettre aux responsables RH de consulter et gérer les jours fériés (les jours fériés fixes sont en lecture seule ; les jours fériés islamiques variables peuvent avoir des dates personnalisées par tenant) et de configurer les durées des permissions exceptionnelles au-delà des minimums CCI. Les deux sont initialisés avec les valeurs par défaut CI à la création du tenant.
Périmètre Backend¶
Entités & Enregistrements¶
PublicHoliday (défini dans AB-002, base tenant) :
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK, server-generated |
| tenant_id | UUID | NOT NULL |
| country_code | String | NOT NULL, default "CI", max 2 |
| year | int | NOT NULL |
| holiday_code | String | NOT NULL, max 50 (e.g., "NEW_YEAR", "EID_FITR") |
| name_fr | String | NOT NULL, max 100 |
| date | LocalDate | NOT NULL |
| is_fixed | boolean | NOT NULL |
| source | HolidaySource enum | NOT NULL |
| created_at | Instant | NOT NULL |
| updated_at | Instant | NOT NULL |
Enum HolidaySource :
14 jours fériés CI (valeurs de données) :
| Code | name_fr | is_fixed | Default date (2026) |
|---|---|---|---|
| NEW_YEAR | Jour de l'An | true | 01/01 |
| LABOUR_DAY | Fête du Travail | true | 01/05 |
| INDEPENDENCE_DAY | Fête de l'Indépendance | true | 07/08 |
| ASSUMPTION | Assomption | true | 15/08 |
| ALL_SAINTS | Toussaint | true | 01/11 |
| PEACE_DAY | Journée de la Paix | true | 15/11 |
| CHRISTMAS | Noël | true | 25/12 |
| EASTER_MONDAY | Lundi de Pâques | false | variable |
| ASCENSION | Ascension | false | variable |
| WHIT_MONDAY | Lundi de Pentecôte | false | variable |
| EID_FITR | Aïd el-Fitr | false | variable |
| EID_ADHA | Aïd el-Adha | false | variable |
| MAWLID | Mawlid (Naissance du Prophète) | false | variable |
| LAYLAT_AL_QADR | Laylat al-Qadr (Nuit du Destin) | false | variable |
ExceptionalLeaveConfig (défini dans AB-002, base tenant) :
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK, server-generated |
| tenant_id | UUID | NOT NULL |
| event_type_code | String | NOT NULL, unique per tenant, max 50 |
| name_fr | String | NOT NULL, max 100 |
| duration_days | int | NOT NULL, >= CCI minimum |
| cci_minimum_days | int | NOT NULL (immutable reference) |
| created_at | Instant | NOT NULL |
| updated_at | Instant | NOT NULL |
9 types d'événements de permissions exceptionnelles CCI (initialisés par tenant) :
| Code | name_fr | CCI minimum (days) |
|---|---|---|
| MARRIAGE_SELF | Mariage du salarié | 4 |
| MARRIAGE_CHILD | Mariage d'un enfant | 2 |
| BIRTH_CHILD | Naissance d'un enfant | 2 |
| DEATH_SPOUSE | Décès du conjoint | 5 |
| DEATH_CHILD | Décès d'un enfant | 5 |
| DEATH_PARENT | Décès père/mère | 5 |
| DEATH_SIBLING | Décès frère/sœur | 2 |
| DEATH_IN_LAW | Décès beau-père/belle-mère | 2 |
| MOVE | Déménagement | 2 |
DTOs :
public record PublicHolidayResponse(
UUID id,
String holidayCode,
String nameFr,
LocalDate date,
boolean isFixed,
String source
) {}
public record UpdatePublicHolidayRequest(
@NotBlank String countryCode,
@NotNull int year,
@NotBlank String holidayCode,
@NotNull LocalDate date
) {}
public record ExceptionalLeaveConfigResponse(
UUID id,
String eventTypeCode,
String nameFr,
int durationDays,
int cciMinimumDays
) {}
public record UpdateExceptionalLeaveRequest(
@NotNull int durationDays
) {}
Couche Repository¶
PublicHolidayRepository.java:findByTenantIdAndCountryCodeAndYear(UUID tenantId, String countryCode, int year) → List<PublicHoliday>- Requête personnalisée pour fusionner les valeurs par défaut de la plateforme avec les surcharges tenant (la surcharge tenant a priorité pour le même
holiday_code) ExceptionalLeaveConfigRepository.java:findByTenantId(UUID tenantId) → List<ExceptionalLeaveConfig>findByTenantIdAndEventTypeCode(UUID tenantId, String eventTypeCode) → Optional<ExceptionalLeaveConfig>
Couche Service¶
HolidayConfigService.java :
- getEffectiveHolidays(UUID tenantId, int year) → List<PublicHoliday> — Fusionne les valeurs par défaut de la plateforme avec les surcharges tenant. Applique la règle de substitution du dimanche (FR-ABS-103) : si un jour férié tombe un dimanche, le lundi est la date observée.
- updateVariableHoliday(UUID tenantId, int year, String holidayCode, LocalDate newDate) → PublicHoliday — Crée un enregistrement TENANT_OVERRIDE pour les jours fériés variables uniquement. Rejette les jours fériés fixes.
- seedHolidays(UUID tenantId, int year) → void — Initialise les jours fériés fixes CI et les dates variables de la plateforme pour l'année donnée.
ExceptionalLeaveConfigService.java :
- getConfigs(UUID tenantId) → List<ExceptionalLeaveConfig> — Retourne les 9 types d'événements avec les durées configurées par le tenant.
- updateDuration(UUID tenantId, String eventTypeCode, int durationDays) → ExceptionalLeaveConfig — Valide durationDays >= cci_minimum_days, met à jour, publie l'événement ExceptionalLeaveConfigUpdated.
- seedDefaults(UUID tenantId) → void — Initialise les 9 types d'événements avec les minimums CCI.
Endpoints API¶
| Method | Path | Request Body | Response | Auth |
|---|---|---|---|---|
| GET | /api/v1/absence/config/public-holidays?year=2026 | — | PublicHolidayResponse[] | All (read-only) |
| PATCH | /api/v1/absence/config/public-holidays | UpdatePublicHolidayRequest | PublicHolidayResponse | HR_MANAGER, DG |
| GET | /api/v1/absence/config/exceptional-leave | — | ExceptionalLeaveConfigResponse[] | All (read-only) |
| PATCH | /api/v1/absence/config/exceptional-leave/:eventTypeCode | UpdateExceptionalLeaveRequest | ExceptionalLeaveConfigResponse | HR_MANAGER, DG |
Règles de validation¶
- Les jours fériés fixes (
is_fixed=true) ne peuvent pas être modifiés — PATCH bloqué avec 422 - Les dates des jours fériés variables doivent être des dates valides pour l'année donnée
- La
durationDaysdes permissions exceptionnelles doit être >= au minimum CCI : - MARRIAGE_SELF >= 4, MARRIAGE_CHILD >= 2, BIRTH_CHILD >= 2
- DEATH_SPOUSE >= 5, DEATH_CHILD >= 5, DEATH_PARENT >= 5
- DEATH_SIBLING >= 2, DEATH_IN_LAW >= 2, MOVE >= 2
- Si
durationDays< minimum CCI → 422 avec "La durée minimale légale (CCI) pour cet événement est de {min} jours." - Le paramètre de requête
yearest obligatoire pour l'endpoint des jours fériés holidayCodedoit correspondre à un jour férié variable existant
Considérations multi-tenant¶
- Les deux entités sont stockées dans la base tenant, filtrées par
tenant_id - Les valeurs par défaut de la plateforme sont initialisées par tenant (non partagées entre tenants)
- Les surcharges tenant n'affectent que le tenant spécifique
- La liste des jours fériés est spécifique à l'année — les tenants peuvent surcharger les dates variables différemment chaque année
Périmètre Frontend¶
Pages & Routes¶
/absences/parametres/jours-feries— Page des jours fériés (S12 dans le spec design)- Section permissions exceptionnelles dans
/absences/parametres/types(page de paramètres S11)
Composants¶
Page des jours fériés :
- PublicHolidaysPage.tsx — Navigation par année (flèches précédent/suivant), deux sections : fixes + variables
- FixedHolidayRow.tsx — Ligne en lecture seule : nom, date, icône cadenas
- VariableHolidayRow.tsx — Ligne modifiable : nom, DatePicker pour la date, valeur par défaut de la plateforme affichée en texte d'aide
- Sélecteur d'année avec navigation précédent/suivant
Section permissions exceptionnelles (dans la page de paramètres) :
- ExceptionalLeaveConfigSection.tsx — Liste des 9 types d'événements
- Chaque ligne : nom de l'événement, Input (type=number) en ligne pour la durée, minimum CCI en texte d'aide
- Bouton de sauvegarde par ligne ou sauvegarde globale
États UI (TOUS OBLIGATOIRES)¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton list of holidays / event types | — |
| Empty | N/A (always seeded) | — |
| Error | Toast on save | "Impossible d'enregistrer. Réessayez." |
| Error (CCI min) | Inline error below duration input | "La durée minimale légale (CCI) pour cet événement est de {min} jours." |
| Offline | Read from cache, edits blocked | "Cette action nécessite une connexion internet" |
| Success | Toast | "Paramètres enregistrés" |
Comportement responsive¶
- 360px (mobile) : Colonne unique, jours fériés empilés verticalement, sélecteurs de date pleine largeur
- 768px (tablette) : Disposition en deux colonnes pour les jours fériés fixes
- 1024px+ (desktop) : Conteneur Card dans la disposition avec barre latérale, vue tableau pour les jours fériés
Interactions¶
Jours fériés : - Titre de la page : "Jours fériés — {year}" - En-tête de section fixe : "Jours fériés fixes" - En-tête de section variable : "Jours fériés variables" - Infobulle de l'icône cadenas sur les jours fériés fixes : "Non modifiable — fixé par la loi" - Texte d'aide sous le DatePicker : "Défaut plateforme : {date}" - Texte d'information en haut : "Les jours fériés fixes sont définis par la loi. Vous pouvez modifier les dates des fêtes religieuses variables." - Navigation par année : "< 2025 | 2026 | 2027 >"
Permissions exceptionnelles : - Titre de la section : "Permissions exceptionnelles" - Libellé de durée par ligne : "Durée : [{n}] jours (min : {min})" - Texte d'information : "Les durées minimales sont fixées par la CCI. Vous pouvez les augmenter mais pas les réduire." - Noms des types d'événements (français) : - Mariage du salarié - Mariage d'un enfant - Naissance d'un enfant - Décès du conjoint - Décès d'un enfant - Décès père/mère - Décès frère/sœur - Décès beau-père/belle-mère - Déménagement
Critères d'acceptation¶
AC-025: List fixed and variable holidays per year
Given the ABSMGT module is active for a CI tenant
When anyone calls GET /api/v1/absence/config/public-holidays?year=2026
Then both fixed (7) and variable (7) holidays are returned with their dates for 2026
AC-026: HR can override variable holiday dates
Given Aïd el-Fitr has a platform default date of 30/03/2026
When HR PATCHes the public-holidays endpoint with { holidayCode: "EID_FITR", date: "2026-03-31" }
Then a TENANT_OVERRIDE record is created and the effective holiday list for this tenant shows 31/03
AC-027: Exceptional leave durations enforce CCI minimum
Given the CCI minimum for "Mariage du salarié" is 4 jours
When the tenant tries to configure a duration of 3 jours
Then the API returns 422 with "La durée minimale légale (CCI) pour cet événement est de 4 jours."
AC-028: Seeded at tenant creation with CI defaults
Given a new CI tenant is created
When the ABSMGT module initializes
Then 14 public holidays (7 fixed + 7 variable) and 9 exceptional leave event types are seeded with CCI defaults
Règles OHADA & Réglementaires¶
- FR-ABS-100 : 10 jours fériés CI fixes (7 à date fixe + 3 chrétiens variables : Lundi de Pâques, Ascension, Lundi de Pentecôte).
- FR-ABS-101 : 4 jours fériés islamiques variables (Aïd el-Fitr, Aïd el-Adha, Mawlid, Laylat al-Qadr).
- FR-ABS-102 : Gestion à deux niveaux — l'administrateur de la plateforme définit les valeurs par défaut, le tenant peut surcharger les dates variables.
- FR-ABS-103 : Règle de substitution du dimanche — si un jour férié tombe un dimanche, le lundi est observé.
- FR-ABS-PERM-01 : Application des minimums CCI pour les durées de permissions exceptionnelles.
- CCI Art. 46 : Durées des permissions exceptionnelles — mariage=4, naissance=2, décès conjoint/enfant/parent=5, décès frère-sœur/beau-parent=2, déménagement=2.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Entities, §Service layer, §Validationdocs/standards/database-guidelines.md— §Multi-tenant repository pattern, §Tenant isolation, §Date handlingdocs/standards/api-guidelines.md— §Query parameters, §PATCH semantics, §Standard envelopedocs/standards/react-typescript-guidelines.md— §List components, §DatePicker, §Inline editingdocs/standards/design-system.md— §Input, §DatePicker, §Card, §Toast components
Exigences de test¶
Tests unitaires¶
- HolidayConfigService :
getEffectiveHolidays()fusionne les valeurs par défaut de la plateforme avec les surcharges tenant - HolidayConfigService :
updateVariableHoliday()rejette les jours fériés fixes - HolidayConfigService : la règle de substitution du dimanche déplace le jour férié au lundi
- HolidayConfigService :
seedHolidays()crée exactement 14 jours fériés - ExceptionalLeaveConfigService :
updateDuration()rejette une durée inférieure au minimum CCI - ExceptionalLeaveConfigService :
updateDuration()accepte une durée égale au minimum CCI - ExceptionalLeaveConfigService :
seedDefaults()crée exactement 9 types d'événements
Tests d'intégration¶
- GET /api/v1/absence/config/public-holidays?year=2026 — retourne 14 jours fériés
- PATCH /api/v1/absence/config/public-holidays avec un jour férié fixe → 422
- PATCH /api/v1/absence/config/public-holidays avec un jour férié variable → 200, crée TENANT_OVERRIDE
- GET /api/v1/absence/config/exceptional-leave — retourne 9 types d'événements
- PATCH /api/v1/absence/config/exceptional-leave/MARRIAGE_SELF avec durationDays=3 → 422
- PATCH /api/v1/absence/config/exceptional-leave/MARRIAGE_SELF avec durationDays=5 → 200
- Isolation tenant : les surcharges du tenant A ne sont pas visibles par le tenant B
Ce que la QA validera¶
- La page des jours fériés s'affiche correctement à tous les points de rupture (360px, 768px, 1024px)
- Les jours fériés fixes affichent l'icône cadenas, sans sélecteur de date
- Les jours fériés variables affichent un sélecteur de date avec la valeur par défaut de la plateforme en texte d'aide
- La navigation par année fonctionne (précédent/suivant)
- La section des permissions exceptionnelles affiche les 9 types d'événements avec les minimums CCI corrects
- Erreur de validation en ligne quand la durée < minimum CCI
- La sauvegarde déclenche un toast en cas de succès
- L'état hors connexion bloque les modifications avec le message approprié
Hors périmètre¶
- Ajout de nouveaux jours fériés fixes
- Suppression de jours fériés du calendrier
- Support multi-pays des jours fériés (V2 pour BJ/CM)
- Calcul automatique des dates des jours fériés islamiques (l'administrateur de la plateforme saisit les dates manuellement)
- Impact des jours fériés sur le calcul du solde de congés (traité dans les stories de traitement des demandes de congés)
- Flux de demande de permissions exceptionnelles (AB-009+)
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 (loading, empty, error, offline, success)
- [ ] Le micro-copy français correspond exactement au spec 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 (aucune fuite de données inter-tenant)
- [ ] Offline write+sync fonctionne (Papillon) — modifications bloquées hors connexion, lecture depuis le cache
- [ ] Pas de
anyTypeScript — tous les types explicites - [ ] Pas de types raw Java — tous les generics explicites
- [ ] Les réponses API suivent le format envelope standard
- [ ] Entrées de piste d'audit créées pour chaque mutation de données