Story AB-014 : Congé maladie + Téléversement de document (Backend + Frontend)¶
@assignee: @joel23p
Module : absence-management Slice : VS-AB-005 (extension sick) de l'architecture Contexte produit : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priorité : 14 Dépend de : AB-010, AB-012 Peut être développé en parallèle avec : AB-015, AB-016 Ordre de merge : Après AB-012 Complexité estimée : M PRD User Stories : US-ABS-005
Objectif¶
Étendre le flux de création de demande de congés pour prendre en charge le congé maladie (conge maladie) avec téléversement obligatoire du certificat médical. Les demandes sans certificat passent au statut PENDING_DOCUMENT ; le téléversement du certificat les fait passer à PENDING_APPROVAL.
Périmètre Backend¶
Entités & Enregistrements¶
Pas de nouvelles entités. Étend le LeaveRequest existant avec leaveType=SICK et le champ supportingDocumentId (déjà défini dans le schéma AB-002).
Enregistrement de demande (pour la pièce jointe) :
public record AttachDocumentRequest(
@NotNull UUID supportingDocumentId // MinIO file reference
) {}
Couche Repository¶
Utilise les repositories existants d'AB-002 :
- LeaveRequestRepository.findByIdAndTenantId(UUID id, UUID tenantId)
- LeaveRequestRepository.save(LeaveRequest request)
Couche Service¶
LeaveRequestService.java — étend createRequest() pour le type SICK :
1. Quand leaveType=SICK :
- Si supportingDocumentId EST NULL → statut = PENDING_DOCUMENT (pas PENDING_APPROVAL)
- Si supportingDocumentId N'EST PAS NULL → statut = PENDING_APPROVAL (flux normal)
2. Pas de vérification de solde pour le congé SICK (le congé maladie ne déduit pas du solde annuel)
3. Calcul des jours identique à ANNUAL (jours ouvrables, jours fériés, demi-journées)
LeaveRequestService.java — attachDocument(UUID requestId, UUID supportingDocumentId) :
1. Charger la demande, vérifier que le statut == PENDING_DOCUMENT
2. Vérifier que l'appelant est le propriétaire de la demande (EMPLOYEE) ou HR_MANAGER
3. Définir supportingDocumentId sur la demande
4. Faire passer le statut de PENDING_DOCUMENT → PENDING_APPROVAL
5. Publier DocumentAttached
Visibilité des documents (FR-ABS-052) : - L'approbateur (MANAGER) voit uniquement le nom du fichier du document — ne peut pas télécharger/consulter le contenu - HR_MANAGER et DG peuvent consulter le contenu du document (lien de téléchargement) - Document stocké via l'API de pièce jointe basique MinIO de la plateforme (non géré par ce module)
Endpoints API¶
| Method | Path | Request | Response | Auth |
|---|---|---|---|---|
| PATCH | /api/v1/absence/leave-requests/{id}/attach-document |
AttachDocumentRequest |
LeaveRequestResponse |
EMPLOYEE (own), HR_MANAGER |
Note : Le POST pour créer des demandes de congé maladie utilise le même endpoint qu'AB-010 (POST /api/v1/absence/leave-requests), étendu pour gérer leaveType=SICK.
Règles de validation¶
- FR-ABS-050 : type SICK → document requis pour l'approbation ; si manquant →
PENDING_DOCUMENT - FR-ABS-051 : téléversement du document via l'API MinIO de la plateforme — max 10 Mo, formats acceptés : image/*, application/pdf
- FR-ABS-052 : visibilité du document — l'approbateur voit uniquement le nom du fichier ; HR/DG peuvent consulter le contenu
- Impossible de joindre un document à une demande qui n'est pas au statut
PENDING_DOCUMENT→ 422 - Impossible de joindre un document à la demande d'un autre employé (sauf HR_MANAGER) → 403
Considérations multi-tenant¶
- Toutes les requêtes incluent
tenantIddepuisScopedValue<TenantContext> - Stockage des documents dans MinIO filtré par tenant (préfixe tenant dans le chemin du bucket)
- Fonctionne sur les 4 profils DB
Périmètre Frontend¶
RAPPEL OFFLINE SYNC (Papillon — CRITIQUE) : Cette story implique des mutations de données. Le formulaire de demande de congé maladie DOIT fonctionner hors connexion — enregistrer comme DRAFT dans IndexedDB. Pièces jointes : stocker le blob dans IndexedDB, téléverser via le Service Worker à la reconnexion. Si le formulaire est soumis sans document hors connexion, le DRAFT inclut un indicateur que le document est en attente. À la synchronisation : le serveur crée la demande avec le statut PENDING_DOCUMENT. Voir
docs/standards/offline-sync-guidelines.md.
Pages & Routes¶
Pas de nouvelles routes. Étend le formulaire de demande de congés existant d'AB-010.
| Route | Page | Description |
|---|---|---|
/absences/demande |
LeaveRequestPage (étendu) | Le formulaire s'adapte quand type=SICK |
/absences |
AbsenceHome (étendu) | La carte PENDING_DOCUMENT affiche un bouton de pièce jointe |
Composants¶
S2 — Variante formulaire SICK (sur LeaveRequestPage) :
- FileUpload — Zone de dépôt / zone de tap pour le téléversement de document (accept="image/*,.pdf", maxSize=10MB)
- FilePreview — Aperçu miniature du fichier joint
S1 — Carte PENDING_DOCUMENT (sur AbsenceHome) :
- AttachDocumentButton — Bouton secondaire bien visible sur les cartes de demandes PENDING_DOCUMENT
États UI (TOUS OBLIGATOIRES)¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | N/A (form extension) | — |
| Error (file too large) | Inline error on FileUpload | "Fichier trop volumineux -- max 10 Mo" |
| Error (upload failure) | Toast | "Impossible d'envoyer le document. Reessayez." |
| Offline (file) | Stored locally as blob in IndexedDB, sync icon | "Document enregistre -- sera envoye automatiquement" |
| Success (submit without doc) | Toast + navigate to /absences |
"Demande envoyee ! N'oubliez pas de joindre votre certificat medical." |
| Success (doc attached) | Badge updates on card | "Justificatif joint -- demande transmise a votre responsable" |
Comportement responsive¶
Mobile (360px — priorité Papillon) :
- Le formulaire s'adapte quand type=SICK : ajoute une zone FileUpload bien visible sous les sélecteurs de date
- Texte d'aide sous la zone de téléversement : "Joindre un document — Photo, PDF — max 10 Mo"
- Peut soumettre sans document → légende explicite sous le bouton de soumission : "Vous pouvez envoyer sans document. La demande restera en attente du justificatif."
- Sur AbsenceHome : la carte PENDING_DOCUMENT affiche le badge amber "En attente de justificatif" + bouton "Joindre certificat" bien visible (secondary, sm)
- Cibles de toucher >= 48px sur la zone de téléversement et les boutons
Desktop (1024px+ — priorité Enterprise) : - Zone FileUpload dans la modal/panneau du formulaire, glisser-déposer activé - Ligne PENDING_DOCUMENT dans le DataTable affiche un bouton icône de pièce jointe dans la colonne actions
Interactions¶
- Sélectionner type=SICK → la zone FileUpload apparaît avec une animation de glissement vers le bas
- Appuyer/cliquer sur la zone de téléversement → sélecteur de fichier natif (image/*, .pdf)
- Glisser-déposer un fichier sur la zone (desktop)
- Fichier sélectionné → aperçu miniature + nom du fichier affichés
- Supprimer le fichier → appuyer sur X sur l'aperçu
- Soumettre sans fichier → toast d'avertissement, demande créée comme PENDING_DOCUMENT
- Sur AbsenceHome : appuyer sur "Joindre certificat" → ouvre le sélecteur de fichier → téléverse → le badge se met à jour
Micro-copy français¶
sick.upload_label: "Certificat médical"sick.upload_helper: "Joindre un document — Photo, PDF — max 10 Mo"sick.upload_optional: "Vous pouvez envoyer sans document. La demande restera en attente du justificatif."sick.pending_badge: "En attente de justificatif"sick.attach_button: "Joindre certificat"sick.attached_success: "Justificatif joint — demande transmise à votre responsable"sick.submit_no_doc: "Demande envoyée ! N'oubliez pas de joindre votre certificat médical."sick.file_too_large: "Fichier trop volumineux — max 10 Mo"
Critères d'acceptation¶
AC-045: Sick leave without certificate stays pending
Given I submit a sick leave request
When I don't upload a medical certificate
Then the request stays in PENDING_DOCUMENT status — it is not sent for approval
AC-046: Uploading certificate transitions to pending approval
Given I upload the certificate
When I confirm
Then the request moves to PENDING_APPROVAL and is sent to my manager
AC-047: Approved sick leave integrates with payroll
Given my sick leave is approved
When Payroll processes the month
Then my pay is calculated per the CCI indemnization table (full pay → half pay → zero pay, based on category and seniority)
Règles OHADA & Réglementaires¶
- FR-ABS-050 (CT Art. 26.1) : Le congé maladie nécessite un certificat médical (certificat médical). Sans lui, la demande reste au statut
PENDING_DOCUMENTet n'est pas transmise pour approbation. - FR-ABS-051 : Téléversement du document via l'API de pièce jointe basique MinIO. Max 10 Mo. Formats acceptés : images (JPEG, PNG) et PDF.
- FR-ABS-052 (CT Art. 26.2) : Règles de visibilité des documents — le manager approbateur voit uniquement le nom du fichier (protection de la vie privée) ; HR_MANAGER et DG peuvent consulter le contenu du document à des fins de vérification.
- FR-ABS-080 (CCI Art. 32) : Référence à la table d'indemnisation CCI pour le calcul de la paie du congé maladie — plein salaire, puis demi-salaire, puis zéro, selon la catégorie et l'ancienneté de l'employé. Le calcul effectif est dans AB-017 (intégration paie), pas dans cette story.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §REST controller patterns, §Application Eventsdocs/standards/database-guidelines.md— §Multi-tenant repository patterndocs/standards/api-guidelines.md— §PATCH endpoint patterns, §ApiResponse envelope, §File upload patternsdocs/standards/react-typescript-guidelines.md— §Component patterns, §CSS Modules, §i18n, §File upload handlingdocs/standards/design-system.md— §FileUpload, §Badge, §Toast, §Buttondocs/standards/offline-sync-guidelines.md— §Blob storage in IndexedDB, §File upload sync, §Offline form submission
Exigences de test¶
Tests unitaires¶
LeaveRequestService.createRequest(): SICK + pas de document → status=PENDING_DOCUMENTLeaveRequestService.createRequest(): SICK + document fourni → status=PENDING_APPROVALLeaveRequestService.attachDocument(): PENDING_DOCUMENT + document valide → PENDING_APPROVALLeaveRequestService.attachDocument(): status != PENDING_DOCUMENT → 422LeaveRequestService.attachDocument(): demande d'un autre employé → 403- Visibilité du document : rôle MANAGER → nom de fichier uniquement dans la réponse
- Visibilité du document : rôle HR_MANAGER → référence complète du document dans la réponse
Tests d'intégration¶
- POST
/api/v1/absence/leave-requestsavec type SICK, pas de document → 201, status=PENDING_DOCUMENT - PATCH
/api/v1/absence/leave-requests/{id}/attach-document→ 200, statut passe à PENDING_APPROVAL - PATCH sur une demande non-PENDING_DOCUMENT → 422
- Multi-tenant : la pièce jointe dans le tenant A n'est pas accessible depuis le tenant B
Ce que la QA validera¶
- Sélectionner le type SICK → la zone FileUpload apparaît
- Soumettre sans fichier → toast d'avertissement, demande enregistrée comme PENDING_DOCUMENT
- Sur AbsenceHome : la carte PENDING_DOCUMENT affiche le badge amber + bouton de pièce jointe
- Appuyer sur le bouton de pièce jointe → sélecteur de fichier → téléversement → le badge passe à "En attente d'approbation"
- Fichier trop volumineux (>10Mo) → erreur en ligne
- Hors connexion : le formulaire fonctionne, fichier stocké dans IndexedDB, synchronisé à la reconnexion
- Desktop : le glisser-déposer fonctionne sur la zone FileUpload
Hors périmètre¶
- Moteur de consultation de la table d'indemnisation CCI (AB-017)
- Gestion des versions de documents (module ADVDOC)
- Restrictions d'accès au contenu du certificat médical au niveau API (géré par le service de documents)
- Suivi du solde de congé maladie (le congé maladie utilise un compteur glissant sur 12 mois, pas le solde annuel)
- Règles de congé maladie prolongé (>6 mois)
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 — pièce jointe filtrée par tenant, RBAC appliqué
- [ ] Offline write+sync fonctionne (Papillon) / N/A (Enterprise) — Blob du fichier stocké dans IndexedDB, téléversé à la synchronisation, sauvegarde automatique du formulaire
- [ ] Pas de
anyTypeScript - [ ] Pas de types raw Java — tous les generics explicites
- [ ] Les réponses API suivent le format envelope standard
- [ ] Entrées de piste d'audit pour chaque mutation de données — DocumentAttached publié, transitions de statut enregistrées