Aller au contenu

Story AB-016: Maternity Declaration (Backend + Frontend)

Module: absence-management Slice: VS-AB-009 from architecture Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 16 Depends on: AB-012 Can develop concurrently with: AB-014, AB-015 Merge order: After AB-012 Estimated complexity: M PRD User Stories: US-ABS-004


Objective

Enable HR managers and DGs to create maternity declarations that auto-generate a 14-week leave block: 6 semaines prénatales + 8 semaines postnatales (Art. 25.8 Code du Travail, 98 jours calendaires total). The leave is AUTO_APPROVED — no manager approval step. Overlapping annual leaves are detected and credited back automatically via BalanceMutationService.


Backend Scope

Entities & Records

MaternityDeclaration (from AB-002):

public class MaternityDeclaration {
    private UUID id;
    private UUID tenantId;
    private UUID employeeId;
    private LocalDate expectedDeliveryDate;
    private LocalDate actualDeliveryDate;       // nullable — set via PATCH
    private LocalDate startDate;                 // computed: expectedDeliveryDate - 42 days
    private LocalDate endDate;                   // computed: expectedDeliveryDate + 56 days (or actualDeliveryDate + 56 days after update)
    private UUID leaveRequestId;                 // FK to auto-created LeaveRequest
    private UUID supportingDocumentId;           // attestation medicale — required
    private Instant createdAt;
    private Instant updatedAt;
}

LeaveRequest (status=AUTO_APPROVED, type=MATERNITY) — uses existing entity from AB-002.

Request records:

public record CreateMaternityDeclarationRequest(
    @NotNull UUID employeeId,
    @NotNull LocalDate expectedDeliveryDate,
    @NotNull UUID supportingDocumentId          // attestation medicale — hard block
) {}

public record UpdateMaternityDeclarationRequest(
    @NotNull LocalDate actualDeliveryDate
) {}

Repository Layer

MaternityDeclarationRepository.java: - save(MaternityDeclaration declaration) — insert or update - findById(UUID id) — single declaration lookup - findByTenantIdAndEmployeeId(UUID tenantId, UUID employeeId) — all declarations for an employee - existsOverlapping(UUID tenantId, UUID employeeId, LocalDate startDate, LocalDate endDate) — checks for overlapping maternity declarations

Service Layer

MaternityService.java:

  • createDeclaration(UUID employeeId, LocalDate expectedDeliveryDate, UUID supportingDocumentId):
  • Calculate period: startDate = expectedDeliveryDate - 42 days, endDate = expectedDeliveryDate + 56 days
  • Validate no overlapping maternity declarations exist for the employee
  • Create LeaveRequest with status=AUTO_APPROVED, type=MATERNITY
  • Detect overlapping APPROVED annual leaves within the maternity period
  • For each overlap: credit back overlapping annual leave days via OVERLAP_CREDIT ledger entry using BalanceMutationService
  • Notify employee + manager (publish notification event)
  • Publish LeaveApproved with { leaveType=MATERNITY, isPaid=true, payRate=100 }
  • Persist MaternityDeclaration and return response

  • updateActualDeliveryDate(UUID declarationId, LocalDate actualDate):

  • Load existing declaration
  • Set actualDeliveryDate
  • Recalculate post-delivery period: newEndDate = actualDate + 56 days
  • Pre-delivery period stays as originally taken (FR-ABS-093)
  • Update the associated LeaveRequest end date
  • Re-evaluate overlap credits if end date extended
  • Publish updated LeaveApproved

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/absence/maternity-declarations CreateMaternityDeclarationRequest ApiResponse<MaternityDeclarationResponse> (201) HR_MANAGER, DG
PATCH /api/v1/absence/maternity-declarations/:id UpdateMaternityDeclarationRequest ApiResponse<MaternityDeclarationResponse> HR_MANAGER, DG

Validation Rules

  • Employee must be ACTIVE (validated via employee service lookup)
  • expectedDeliveryDate must be in the future — 422 "La date presumee d'accouchement doit etre dans le futur."
  • supportingDocumentId (attestation medicale) is required — 422 "L'attestation medicale est obligatoire."
  • Cannot create overlapping maternity declarations for same employee — 409 "Une declaration de maternite existe deja pour cette periode."
  • actualDeliveryDate on PATCH must be a valid date (past or today) — 422 "La date d'accouchement doit etre dans le passe ou aujourd'hui."
  • Gender check is NOT enforced by the system (inclusive design)

Multi-Tenant Considerations

  • All queries include tenantId from ScopedValue<TenantContext>
  • MaternityDeclaration table has tenant_id column
  • Overlap detection is scoped to tenant + employee
  • Works across all 4 DB profiles (designed for BYO-DB first)

Frontend Scope

OFFLINE SYNC REMINDER (Papillon — CRITICAL): Maternity declaration is an online-only action — it requires HR/DG access and document upload. Form is blocked when offline with clear messaging.

Pages & Routes

Route Page Description
/absences/maternite/nouvelle MaternityDeclarationPage HR/DG-only maternity declaration form

Components

  • InfoBanner — Info banner (bg=info-light) explaining maternity rights
  • FormField + Select (searchable) — Employee search
  • FormField + DatePicker — Expected delivery date (DPA)
  • MaternityPeriodSummary — Auto-calculated summary Card (bg=primary-subtle) showing start, end, duration
  • OverlapNotice — Amber info text for overlapping annual leave credit-back
  • FileUpload — Attestation medicale upload zone (required)
  • Button — Submit button (primary, lg, fullWidth)
  • HelperText — Notification helper below submit

UI States (ALL REQUIRED)

State Behavior French Copy
Loading Skeleton for employee select
Empty N/A (form always available)
Error (validation) Inline field errors Various validation messages
Error (API) Toast "Impossible d'enregistrer la declaration."
Offline Form blocked (online-only action) "Cette action necessite une connexion internet"
Success Confetti animation + Toast + navigate back "Declaration enregistree — conge cree automatiquement"

Responsive Behavior

Mobile (360px — Papillon primary): - Full-screen form layout, single column - Info banner at top (bg=info-light): "La maternite est un droit legal. Ce formulaire cree automatiquement un conge de 14 semaines." - FormField + Select (searchable) for employee: placeholder "Rechercher une employee..." - FormField + DatePicker for DPA: label "Date presumee d'accouchement" - Auto-calculated summary Card (bg=primary-subtle): "Debut : {date} (DPA - 42 jours) / Fin : {date} (DPA + 56 jours) / Duree : 14 semaines (98 jours)" - If overlap detected: amber info text: "{n} jours de conge annuel approuve seront recredites automatiquement." - FileUpload zone: label "Attestation medicale" (required) - Submit button: "Enregistrer la declaration" (primary, lg, fullWidth) - Helper: "L'employee et son manager seront notifies automatiquement." - All touch targets >= 48px

Desktop (1024px+ — Enterprise primary): - Modal or side panel, same form flow, max-width 600px - More compact spacing, same component order

Interactions

  • Employee search: type-ahead search with debounce (300ms), shows employee name + matricule
  • DPA selection: when date selected, MaternityPeriodSummary auto-calculates and renders
  • Overlap detection: client checks if overlap warning is needed (API provides overlap count on DPA change)
  • FileUpload: tap to open file picker (mobile), drag-drop also supported (desktop)
  • Submit: validates all required fields client-side, then POST to API
  • Success: confetti animation (200ms), toast, navigate to /absences

French Micro-Copy

  • maternity.info: "La maternite est un droit legal. Ce formulaire cree automatiquement un conge de 14 semaines."
  • maternity.employee: "Employee"
  • maternity.search: "Rechercher une employee..."
  • maternity.dpa: "Date presumee d'accouchement"
  • maternity.summary: "Resume du conge"
  • maternity.start: "Debut : {date} (DPA - 42 jours)"
  • maternity.end: "Fin : {date} (DPA + 56 jours)"
  • maternity.duration: "Duree : 14 semaines (98 jours)"
  • maternity.overlap: "{n} jours de conge annuel approuve seront recredites automatiquement."
  • maternity.attestation: "Attestation medicale"
  • maternity.submit: "Enregistrer la declaration"
  • maternity.notify: "L'employee et son manager seront notifies automatiquement."
  • maternity.error.future_date: "La date presumee d'accouchement doit etre dans le futur."
  • maternity.error.attestation_required: "L'attestation medicale est obligatoire."
  • maternity.error.overlap: "Une declaration de maternite existe deja pour cette periode."
  • maternity.error.save: "Impossible d'enregistrer la declaration."
  • maternity.offline: "Cette action necessite une connexion internet"
  • maternity.success: "Declaration enregistree — conge cree automatiquement"

Acceptance Criteria

AC-051: Maternity auto-creates 14-week leave block
Given I enter an expected delivery date
When I save the maternity declaration
Then the system creates a 14-week leave block (6 weeks before expected delivery + 8 weeks after)

AC-052: Maternity leave is auto-approved
Given maternity leave is created
When I view it
Then it shows status AUTO_APPROVED — no manager approval step

AC-053: Overlapping annual leave days are credited back
Given the employee has other approved leaves overlapping with the maternity period
When maternity is created
Then overlapping annual leave days are credited back to the employee's balance

OHADA & Regulatory Rules

  • FR-ABS-090: Maternity = 14 weeks (6 before + 8 after delivery)
  • FR-ABS-091: HR enters expected delivery date; system auto-calculates the leave period
  • FR-ABS-092: Auto-calculation and auto-creation with overlap resolution
  • FR-ABS-093: Actual delivery date update recalculates post-delivery period only — pre-delivery period stays as originally taken
  • FR-ABS-094: Employer pays 100%, claims CNPS reimbursement (TV-ABS-01)
  • FR-ABS-095: Employee protected from dismissal during entire maternity period

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §REST controller patterns, §Application Events, §Service layer patterns
  • docs/standards/database-guidelines.md — §Multi-tenant repository pattern, §Tenant isolation in every query
  • docs/standards/api-guidelines.md — §POST/PATCH endpoint patterns, §ApiResponse envelope, §201 Created, §422 validation errors
  • docs/standards/react-typescript-guidelines.md — §Component patterns, §CSS Modules, §i18n, §Form patterns
  • docs/standards/design-system.md — §Select (searchable), §DatePicker, §FileUpload, §Button, §Card, §InfoBanner
  • docs/standards/offline-sync-guidelines.md — §Online-only actions, §Offline blocking pattern
  • docs/standards/multi-datasource-patterns.md — §Tenant-scoped repository queries

Testing Requirements

Unit Tests

  • MaternityService.createDeclaration(): valid input → creates LeaveRequest with AUTO_APPROVED + MaternityDeclaration
  • MaternityService.createDeclaration(): calculates start = DPA - 42 days, end = DPA + 56 days
  • MaternityService.createDeclaration(): overlapping approved annual leave (5 days) → 5 OVERLAP_CREDIT ledger entries
  • MaternityService.createDeclaration(): no overlapping annual leave → no credit-back entries
  • MaternityService.createDeclaration(): overlapping maternity declaration exists → 409
  • MaternityService.createDeclaration(): expectedDeliveryDate in the past → 422
  • MaternityService.createDeclaration(): missing supportingDocumentId → 422
  • MaternityService.updateActualDeliveryDate(): recalculates end = actualDate + 56 days, start unchanged
  • MaternityService.updateActualDeliveryDate(): extended end date triggers re-evaluation of overlap credits

Integration Tests

  • POST valid maternity declaration → 201, LeaveRequest created with AUTO_APPROVED
  • POST with past expectedDeliveryDate → 422
  • POST without supportingDocumentId → 422
  • POST with existing overlapping declaration → 409
  • PATCH with actualDeliveryDate → 200, end date recalculated
  • Multi-tenant: tenant A declaration does not affect tenant B

What QA Will Validate

  • Form renders at 360px with info banner, employee search, date picker, file upload
  • DPA selection triggers auto-calculation of period summary
  • Overlap notice appears when applicable
  • Submit with valid data → confetti + toast + navigation
  • Submit without attestation → inline error
  • Offline → form blocked with "Cette action necessite une connexion internet"
  • Desktop: modal/panel layout, max-width 600px

Out of Scope

  • CNPS reimbursement tracking (accounting process)
  • Dismissal protection enforcement (Employee Management module)
  • Paternity leave flow (uses standard request flow with 2-day auto-fill via AB-010)
  • Maternity extension for medical complications (V2)
  • Breastfeeding hour reduction tracking (V2)

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 — declarations scoped to tenant, overlap check per tenant+employee
  • [ ] Offline behavior verified — form blocked with message (Papillon) / N/A (Enterprise)
  • [ ] No TypeScript any
  • [ ] No Java raw types — all generics explicit
  • [ ] API responses follow standard envelope format
  • [ ] Audit trail entries for every data mutation — LeaveApproved published with leaveType=MATERNITY