Aller au contenu

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_DOCUMENTPENDING_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_DOCUMENT status → 422
  • Cannot attach document to another employee's request (unless HR_MANAGER) → 403

Multi-Tenant Considerations

  • All queries include tenantId from ScopedValue<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_DOCUMENT status 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 Events
  • docs/standards/database-guidelines.md — §Multi-tenant repository pattern
  • docs/standards/api-guidelines.md — §PATCH endpoint patterns, §ApiResponse envelope, §File upload patterns
  • docs/standards/react-typescript-guidelines.md — §Component patterns, §CSS Modules, §i18n, §File upload handling
  • docs/standards/design-system.md — §FileUpload, §Badge, §Toast, §Button
  • docs/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_DOCUMENT
  • LeaveRequestService.createRequest(): SICK + document provided → status=PENDING_APPROVAL
  • LeaveRequestService.attachDocument(): PENDING_DOCUMENT + valid doc → PENDING_APPROVAL
  • LeaveRequestService.attachDocument(): status != PENDING_DOCUMENT → 422
  • LeaveRequestService.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-requests with 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