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>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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).
|
||||
```php
|
||||
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`.
|
||||
Reference in New Issue
Block a user