Files
Lesstime/docs/superpowers/specs/2026-05-22-absence-legal-compliance-fixes-design.md
T
Matthieu e9aaccc62c docs(absences) : spec de mise en conformité légale (périmètre 1-6)
Design des corrections légales retenues (modèle événements familiaux sans
solde, décès=motif obligatoire, ajout naissance, parental=suspension,
garde-fou demi-journée, CCN documentée). Points lourds (ancienneté, CP
pendant maladie, rétention) explicitement reportés en backlog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:20:20 +02:00

6.2 KiB

Mise en conformité légale du module Absences — Design

Date : 2026-05-22 Branche : feat/absence-management Origine : audit 2026-05-22-absence-legal-compliance-review.md. Ce design ne traite QUE les corrections retenues (périmètre arrêté avec l'utilisateur). Les points « lourds » sont explicitement reportés.

Objectif

Corriger les non-conformités légales structurantes mais à faible risque du module Absences, sans multiplier les types/paramètres (préférence utilisateur : gérer le détail via le motif plutôt que par des paramètres structurés).

Décisions cadrées

  • Décès : un seul type, le lien de parenté est saisi dans le motif (rendu obligatoire) ; pas d'éclatement par lien.
  • Points lourds reportés en backlog (NON traités ici) : congés d'ancienneté Syntec + arrondi légal, acquisition de CP pendant arrêt maladie (loi 2024-364), politique de rétention/purge des justificatifs.

Périmètre (6 corrections)

1. Les événements familiaux ne décrémentent plus de solde

Problème : AbsenceType::decrementsBalance() renvoie true pour tout sauf la maladie. Mariage/PACS, décès et congé parental décrémentent donc un « solde » par (type, année) qui n'a aucune acquisition (artefact sémantique). Légalement, ces congés sont des droits par événement, non déduits d'un solde annuel.

Solution : decrementsBalance() ne renvoie true que pour PaidLeave (CP).

return self::PaidLeave === $this;

Conséquence : pour tous les autres types, AbsenceBalanceService::reservePending/applyApproval/release deviennent des no-op (déjà gardés par shouldTrack()/decrementsBalance()). Les demandes restent enregistrées, validées et décomptées (countedDays), mais ne touchent aucun AbsenceBalance.

Impact données : les fixtures ne doivent plus semer de AbsenceBalance pour des types ≠ CP (à vérifier dans AppFixtures). Aucune migration (pas de changement de schéma).

2. Décès — motif obligatoire, plus de forfait trompeur

Problème : forfait unique daysPerEvent = 3 < minimum légal (enfant 5 j + deuil 8 j ; conjoint/parent/fratrie 3 j).

Solution :

  • Policy décès : daysPerEvent = null (« selon lien de parenté », pas de forfait codé en dur faux).
  • Motif obligatoire pour le type décès, contrôlé à la création dans les deux chemins : AbsenceRequestProcessor (REST) et CreateAbsenceRequestTool (MCP). Erreur explicite si reason vide.
  • Les minimums légaux sont rappelés dans l'aide /help (06-absences) ; l'admin accorde le nombre de jours correct en validant les dates.

3. Ajout du congé naissance

Problème : type absent.

Solution : nouveau cas AbsenceType::Birth = 'naissance'.

  • label() = « Naissance ».
  • decrementsBalance() = false (couvert par le point 1).
  • Policy par défaut (fixtures) : daysPerEvent = 3, justificationRequired = true, countWorkingDaysOnly = true, active = true.
  • Frontend : ajouter « naissance » à la liste des types proposés (form Nouvelle demande) + libellés i18n + couleur de badge dans useAbsenceHelpers.

4. Congé parental = suspension

Problème : modélisé comme décompte de solde.

Solution : couvert par le point 1 (decrementsBalance() false). Le congé parental reste enregistré comme absence longue justifiée, sans impact solde. daysPerYear/daysPerEvent restent null. Justificatif requis (policy inchangée si déjà à true).

5. Garde-fou demi-journée

Problème : AbsenceDayCalculator::countWorkingDays retire -0,5 pour startHalfDay/endHalfDay sans vérifier que le jour-borne est réellement décompté (week-end/férié). Sous-décompte possible.

Solution : n'appliquer la déduction de demi-journée que si le jour-borne (début pour startHalfDay, fin pour endHalfDay) est un jour effectivement compté. Couvert par un test unitaire dédié dans AbsenceDayCalculatorTest (TDD : test rouge d'abord).

6. Documenter la convention collective

Problème : hypothèses Syntec codées en dur, CCN nulle part formalisée.

Solution : paramètre de config app.absence.convention (valeur "Syntec (IDCC 1486)"), affiché :

  • en sous-titre de l'onglet admin « Absences » (AdminAbsencePolicyTab) ;
  • dans l'aide /help (06-absences). Aucune logique conditionnée à cette valeur (purement documentaire/affichage).

Hors périmètre (backlog, documenté dans le rapport d'audit)

  • Congés d'ancienneté Syntec (1/2/3/4 j à 5/10/15/20 ans) + arrondi légal à l'entier supérieur.
  • Acquisition de CP pendant arrêt maladie (loi 2024-364, 2 j ouvrables/mois plafonnés).
  • Politique de rétention/purge des justificatifs (données de santé) et des demandes.
  • Contrôle de solde négatif à l'approbation (comportement « poser le N en cours » volontairement conservé).
  • Alsace-Moselle (jours fériés spécifiques).

Tests & validation

  • TDD : test rouge → vert pour le garde-fou demi-journée (point 5) dans tests/Unit/Service/AbsenceDayCalculatorTest.php.
  • Test du motif décès obligatoire (création MCP) dans le cycle de vie existant ou un test ciblé.
  • make test au vert (49 + nouveaux), lint:container OK, doctrine:schema:validate OK (aucune migration), php-cs-fixer propre.
  • Build Nuxt OK (changements front : type naissance + i18n + libellés CCN).
  • Vérif fonctionnelle : créer une demande décès sans motif → refus ; avec motif → OK et aucun AbsenceBalance créé pour le décès.

Composants touchés

  • src/Enum/AbsenceType.php (decrementsBalance, cas Birth, label).
  • src/State/AbsenceRequestProcessor.php + src/Mcp/Tool/Absence/CreateAbsenceRequestTool.php (motif décès obligatoire).
  • src/Service/AbsenceDayCalculator.php (garde-fou demi-journée).
  • src/DataFixtures/AppFixtures.php (policy naissance, décès daysPerEvent=null, plus de balance non-CP).
  • config/services.yaml (param convention) + AdminAbsencePolicyTab.vue + 06-absences.md (affichage CCN).
  • frontend/composables/useAbsenceHelpers.ts + frontend/components/absence/AbsenceRequestDrawer.vue + i18n/locales/fr.json (type naissance).
  • Tests : AbsenceDayCalculatorTest, AbsenceRequestLifecycleTest.