Aller au contenu

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 :

public enum HolidaySource { PLATFORM_DEFAULT, TENANT_OVERRIDE }

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 durationDays des 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 year est obligatoire pour l'endpoint des jours fériés
  • holidayCode doit 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, §Validation
  • docs/standards/database-guidelines.md — §Multi-tenant repository pattern, §Tenant isolation, §Date handling
  • docs/standards/api-guidelines.md — §Query parameters, §PATCH semantics, §Standard envelope
  • docs/standards/react-typescript-guidelines.md — §List components, §DatePicker, §Inline editing
  • docs/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 any TypeScript — 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