Story AB-014: Sick Leave + Document Upload (Backend + Frontend)¶
@assignee: @joel23p
Module: absence-management Slice: VS-AB-005 (sick extension) from architecture Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 14 Depends on: AB-010, AB-012 Can develop concurrently with: AB-015, AB-016 Merge order: After AB-012 Estimated complexity: M PRD User Stories: US-ABS-005
Objective¶
Extend the leave request creation flow to support sick leave (conge maladie) with mandatory medical certificate upload. Requests without a certificate enter PENDING_DOCUMENT status; uploading the certificate transitions to PENDING_APPROVAL.
Backend Scope¶
Entities & Records¶
No new entities. Extends existing LeaveRequest with leaveType=SICK and supportingDocumentId field (already defined in AB-002 schema).
Request record (for document attachment):
public record AttachDocumentRequest(
@NotNull UUID supportingDocumentId // MinIO file reference
) {}
Repository Layer¶
Uses existing repositories from AB-002:
- LeaveRequestRepository.findByIdAndTenantId(UUID id, UUID tenantId)
- LeaveRequestRepository.save(LeaveRequest request)
Service Layer¶
LeaveRequestService.java — extends createRequest() for SICK type:
1. When leaveType=SICK:
- If supportingDocumentId IS NULL → status = PENDING_DOCUMENT (not PENDING_APPROVAL)
- If supportingDocumentId IS NOT NULL → status = PENDING_APPROVAL (normal flow)
2. No balance check for SICK leave (sick leave does not deduct from annual balance)
3. Day computation same as ANNUAL (business days, holidays, half-days)
LeaveRequestService.java — attachDocument(UUID requestId, UUID supportingDocumentId):
1. Load request, verify status == PENDING_DOCUMENT
2. Verify caller is the request owner (EMPLOYEE) or HR_MANAGER
3. Set supportingDocumentId on request
4. Transition status from PENDING_DOCUMENT → PENDING_APPROVAL
5. Publish DocumentAttached
Document visibility (FR-ABS-052): - Approver (MANAGER) sees document filename only — cannot download/view content - HR_MANAGER and DG can view document content (download link) - Document stored via platform's MinIO basic attachment API (not managed by this module)
API Endpoints¶
| Method | Path | Request | Response | Auth |
|---|---|---|---|---|
| PATCH | /api/v1/absence/leave-requests/{id}/attach-document |
AttachDocumentRequest |
LeaveRequestResponse |
EMPLOYEE (own), HR_MANAGER |
Note: POST for creating sick leave requests uses the same endpoint as AB-010 (POST /api/v1/absence/leave-requests), extended to handle leaveType=SICK.
Validation Rules¶
- FR-ABS-050: SICK leave type → document required for approval; if missing →
PENDING_DOCUMENT - FR-ABS-051: Document upload via platform MinIO API — max 10 MB, accepted formats: image/*, application/pdf
- FR-ABS-052: Document visibility — approver sees filename only; HR/DG can view content
- Cannot attach document to a request that is not in
PENDING_DOCUMENTstatus → 422 - Cannot attach document to another employee's request (unless HR_MANAGER) → 403
Multi-Tenant Considerations¶
- All queries include
tenantIdfromScopedValue<TenantContext> - Document storage in MinIO is tenant-scoped (tenant prefix in bucket path)
- Works across all 4 DB profiles
Frontend Scope¶
OFFLINE SYNC REMINDER (Papillon — CRITICAL): This story involves data mutations. The sick leave request form MUST work offline — save as DRAFT to IndexedDB. File attachments: store blob in IndexedDB, upload via Service Worker on reconnect. If the form is submitted without a document offline, the DRAFT includes a flag that document is pending. On sync: server creates request with PENDING_DOCUMENT status. See
docs/standards/offline-sync-guidelines.md.
Pages & Routes¶
No new routes. Extends the existing leave request form from AB-010.
| Route | Page | Description |
|---|---|---|
/absences/demande |
LeaveRequestPage (extended) | Form adapts when type=SICK |
/absences |
AbsenceHome (extended) | PENDING_DOCUMENT card shows attach button |
Components¶
S2 — Sick Form Variant (on LeaveRequestPage):
- FileUpload — Drop zone / tap zone for document upload (accept="image/*,.pdf", maxSize=10MB)
- FilePreview — Thumbnail preview of attached file
S1 — PENDING_DOCUMENT Card (on AbsenceHome):
- AttachDocumentButton — Prominent secondary button on PENDING_DOCUMENT request cards
UI States (ALL REQUIRED)¶
| 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" |
Responsive Behavior¶
Mobile (360px — Papillon primary):
- Form adapts when type=SICK: adds FileUpload zone prominently below date pickers
- Helper text below upload zone: "Joindre un document -- Photo, PDF -- max 10 Mo"
- Can submit without document → explicit caption below submit button: "Vous pouvez envoyer sans document. La demande restera en attente du justificatif."
- On AbsenceHome: PENDING_DOCUMENT card shows amber badge "En attente de justificatif" + prominent "Joindre certificat" button (secondary, sm)
- Touch targets >= 48px on upload zone and buttons
Desktop (1024px+ — Enterprise primary): - FileUpload zone in form modal/panel, drag-and-drop enabled - PENDING_DOCUMENT row in DataTable shows attach icon button in actions column
Interactions¶
- Select type=SICK → FileUpload zone appears with slide-down animation
- Tap/click upload zone → native file picker (image/*, .pdf)
- Drag-and-drop file onto zone (desktop)
- File selected → preview thumbnail + filename displayed
- Remove file → tap X on preview
- Submit without file → warning toast, request created as PENDING_DOCUMENT
- On AbsenceHome: tap "Joindre certificat" → opens file picker → uploads → badge updates
French Micro-Copy¶
sick.upload_label: "Certificat medical"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 a votre responsable"sick.submit_no_doc: "Demande envoyee ! N'oubliez pas de joindre votre certificat medical."sick.file_too_large: "Fichier trop volumineux -- max 10 Mo"
Acceptance Criteria¶
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)
OHADA & Regulatory Rules¶
- FR-ABS-050 (CT Art. 26.1): Sick leave requires a medical certificate (certificat medical). Without it, the request remains in
PENDING_DOCUMENTstatus and is not forwarded for approval. - FR-ABS-051: Document upload via MinIO basic attachment API. Max 10 MB. Accepted formats: images (JPEG, PNG) and PDF.
- FR-ABS-052 (CT Art. 26.2): Document visibility rules — the approving manager sees the document filename only (privacy protection); HR_MANAGER and DG can view the document content for verification purposes.
- FR-ABS-080 (CCI Art. 32): Reference to CCI indemnization table for sick leave pay calculation — full pay, then half pay, then zero pay, based on employee category and seniority. Actual calculation is in AB-017 (payroll integration), not this 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
Testing Requirements¶
Unit Tests¶
LeaveRequestService.createRequest(): SICK + no document → status=PENDING_DOCUMENTLeaveRequestService.createRequest(): SICK + document provided → status=PENDING_APPROVALLeaveRequestService.attachDocument(): PENDING_DOCUMENT + valid doc → PENDING_APPROVALLeaveRequestService.attachDocument(): status != PENDING_DOCUMENT → 422LeaveRequestService.attachDocument(): different employee's request → 403- Document visibility: MANAGER role → filename only in response
- Document visibility: HR_MANAGER role → full document reference in response
Integration Tests¶
- POST
/api/v1/absence/leave-requestswith SICK type, no doc → 201, status=PENDING_DOCUMENT - PATCH
/api/v1/absence/leave-requests/{id}/attach-document→ 200, status transitions to PENDING_APPROVAL - PATCH on non-PENDING_DOCUMENT request → 422
- Multi-tenant: document attachment in tenant A not accessible from tenant B
What QA Will Validate¶
- Select SICK type → FileUpload zone appears
- Submit without file → warning toast, request saved as PENDING_DOCUMENT
- On AbsenceHome: PENDING_DOCUMENT card shows amber badge + attach button
- Tap attach → file picker → upload → badge updates to "En attente d'approbation"
- File too large (>10MB) → inline error
- Offline: form works, file stored in IndexedDB, synced on reconnect
- Desktop: drag-and-drop works on FileUpload zone
Out of Scope¶
- CCI indemnization table lookup engine (AB-017)
- Document versioning (ADVDOC module)
- Medical certificate content viewing restrictions at API level (managed by document service)
- Sick leave balance tracking (sick leave uses rolling 12-month counter, not annual balance)
- Extended sick leave rules (>6 months)
Definition of Done¶
- [ ] All backend endpoints implemented and returning correct responses
- [ ] All frontend pages render correctly at 360px, 768px, 1024px
- [ ] All 5 UI states implemented (loading, empty, error, offline, success)
- [ ] French micro-copy matches design spec exactly
- [ ] All acceptance criteria pass manually
- [ ] Unit tests written and passing
- [ ] Integration tests written and passing
- [ ] Multi-tenant isolation verified — document attachment scoped to tenant, RBAC enforced
- [ ] Offline write+sync works (Papillon) / N/A (Enterprise) — File blob stored in IndexedDB, uploaded on sync, form auto-saves
- [ ] No TypeScript
any - [ ] No Java raw types — all generics explicit
- [ ] API responses follow standard envelope format
- [ ] Audit trail entries for every data mutation — DocumentAttached published, status transitions logged