Aller au contenu

Story QR-018: Overtime Report + Weekly Classification

Module: qr-attendance Slice: QR-T4-003 + QR-T5-003 Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 18 Depends on: QR-006 (DailyAttendance), QR-004 (overtime config) Can develop concurrently with: QR-017 Merge order: After QR-004, QR-006 Estimated complexity: M PRD User Stories: US-QR-012


Objective

Implement weekly overtime classification and the overtime report. A scheduled batch job runs weekly to classify overtime hours into regulatory bands (BAND_1: 40-46h, BAND_2: 46-48h, BAND_3: >48h) with supplementary flags for night work (21:00-05:00) and Sunday/holiday work. The overtime report presents per-employee per-week band breakdowns for HR and accountants to use in payroll reconciliation. Overtime is stored as minutes (not monetary) — payroll applies rates separately.


Backend Scope

Entities & Records

WeeklyOvertimeSummary: - id (UUID) - tenantId (UUID) - employeeId (UUID) - weekStartDate (LocalDate) - weekEndDate (LocalDate) - totalWorkedMinutes (int) - band1Minutes (int) - band2Minutes (int) - band3Minutes (int) - nightMinutes (int) - sundayHolidayMinutes (int) - totalOvertimeMinutes (int) - createdAt (Instant) - updatedAt (Instant)

Flyway Migrations

V001_017__attendance_weekly_overtime_summary.sql:

CREATE TABLE attendance_weekly_overtime_summary (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    employee_id UUID NOT NULL,
    week_start_date DATE NOT NULL,
    week_end_date DATE NOT NULL,
    total_worked_minutes INTEGER NOT NULL DEFAULT 0,
    band_1_minutes INTEGER NOT NULL DEFAULT 0,
    band_2_minutes INTEGER NOT NULL DEFAULT 0,
    band_3_minutes INTEGER NOT NULL DEFAULT 0,
    night_minutes INTEGER NOT NULL DEFAULT 0,
    sunday_holiday_minutes INTEGER NOT NULL DEFAULT 0,
    total_overtime_minutes INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    UNIQUE(tenant_id, employee_id, week_start_date)
);
CREATE INDEX idx_weekly_overtime_tenant ON attendance_weekly_overtime_summary(tenant_id, employee_id, week_start_date);

Repository Layer

WeeklyOvertimeSummaryRepository: - findByTenantIdAndEmployeeIdAndWeekStartDate(tenantId, employeeId, weekStart) — for upsert - findByTenantIdAndWeekStartDateBetween(tenantId, from, to) — for report - findByTenantIdAndEmployeeIdAndWeekStartDateBetween(tenantId, employeeId, from, to) — per employee

Service Layer

OvertimeClassificationService: - classifyWeek(tenantId, employeeId, weekStart, weekEnd): - Total weekly worked = sum of DailyAttendance.workedMinutes for the week - If total > 2400 min (40h): overtime = total - 2400 - Classify into bands (from OvertimeConfig): - BAND_1: first 360 min of overtime (40h-46h) - BAND_2: next 120 min (46h-48h) - BAND_3: remaining (>48h) - NIGHT: hours worked between nightStartTime-nightEndTime (21:00-05:00 default) — supplementary flag (added on top of band classification) - SUNDAY_HOLIDAY: hours on Sunday or public holiday — supplementary flag - Upsert WeeklyOvertimeSummary - Publish WeeklyOvertimeComputed - Week runs from configured start day (default Monday 00:00) to day before next week start

OvertimeClassificationScheduledJob: - @Scheduled weekly (Monday 00:30) - Iterates active tenants with QRCONTR module enabled - Classifies previous week for all employees with clock events during that week

OvertimeReportService: - generateOvertimeReport(tenantId, month, weekStart, employeeId, departmentId): - Returns OvertimeReport — per employee per week band breakdown - Uses WeeklyOvertimeSummary table

Retroactive recalculation: - When a regularization (QR-012) affects a past week, re-run classifyWeek for that week - Listens to RegularizationApplied and checks if affected DailyAttendance falls in a past week

API Endpoints

Method Path Request Body Response Auth
GET /api/v1/qrcontr/reports/overtime ?month=2026-03&weekStart=&employeeId=&departmentId= ApiResponse<OvertimeReport> HR, Comptable
GET /api/v1/qrcontr/reports/export ?type=overtime&month=&format=xlsx\|csv File download (Content-Disposition) HR, Comptable

DTOs

OvertimeReport: - month (YearMonth) - employees (List): - employeeId (UUID) - name (String) - weekNumber (int) - weekStart (LocalDate) - band1Minutes (int) - band2Minutes (int) - band3Minutes (int) - nightMinutes (int) - sundayHolidayMinutes (int) - totalOvertimeMinutes (int)

Validation Rules

  • month format: YYYY-MM (required for report)
  • format must be xlsx or csv for export
  • Band configuration read from tenant's OvertimeConfig (QR-004)

Multi-Tenant Considerations

All queries filter by tenant_id. Scheduled job iterates tenants independently. Band thresholds are tenant-configurable. HR and Comptable see all within tenant.


Frontend Scope

Pages & Routes

  • /qrcontr/reports/overtime — Overtime report view

Components

OvertimeReportPage: - Month/week selector, employee/department filter, overtime table

OvertimeTable: - Per employee per week: Band 1, Band 2, Band 3, Night, Sunday/Holiday, Total - Sortable columns - Totals row at bottom

Reuses ExportButtons from QR-017: - "Exporter Excel" + "Exporter CSV"

UI States

State Behavior French Copy
Loading Skeleton table
Empty No overtime for period "Aucune heure supplementaire pour la periode selectionnee."
Error Inline retry "Impossible de generer le rapport. Reessayez."
Offline Cached summaries only "Rapport complet disponible en ligne uniquement."
Success Overtime table rendered

Responsive Behavior

  • 360px: Horizontal scroll table, 3 columns visible, sticky first column (employee name)
  • 768px: 5 columns visible
  • 1024px+: Full table with all columns

Interactions

  • Month/week selector: dropdown for month, week toggle within month
  • Employee/department filter: dropdown selectors
  • Export: reuses export flow from QR-017 (browser download)
  • Table: horizontal scroll on mobile, sticky first column

Acceptance Criteria

AC-001: Overtime report by band per employee per week
Given I generate the overtime report
When I view it
Then I see per employee per week: Band 1 hours, Band 2 hours, Band 3 hours, Night hours, Sunday/holiday hours, Total overtime hours

AC-002: Overtime report export for payroll
Given I export the report
When Payroll receives it
Then the data is structured for direct import (Excel/CSV with correct formatting)

OHADA & Regulatory Rules

  • CI Code du Travail Art. 21.2-21.3: Overtime regulated by decree, 40h/week base
  • CI defaults (TV-QR-002): BAND_1 40-46h, BAND_2 46-48h, BAND_3 >48h
  • NIGHT: 21:00-05:00 (supplementary classification, added on top of band)
  • SUNDAY_HOLIDAY: supplementary classification (added on top of band)
  • Bands are configurable per tenant — incorrect defaults correctable without code change
  • Overtime hours stored as INTEGER minutes (not monetary — payroll applies rates)

Standards & Conventions

  • docs/standards/java-spring-guidelines.md@Scheduled patterns
  • docs/standards/api-guidelines.md
  • docs/standards/react-typescript-guidelines.md
  • docs/standards/database-guidelines.md — Flyway migration naming

Testing Requirements

Unit Tests

  • OvertimeClassificationService: no overtime for <= 40h week (2400 min)
  • OvertimeClassificationService: 43h week → 3h BAND_1, 0 BAND_2, 0 BAND_3
  • OvertimeClassificationService: 47h week → 6h BAND_1, 1h BAND_2, 0 BAND_3
  • OvertimeClassificationService: 50h week → 6h BAND_1, 2h BAND_2, 2h BAND_3
  • OvertimeClassificationService: night detection (hours between 21:00-05:00)
  • OvertimeClassificationService: Sunday/holiday detection
  • OvertimeClassificationService: retroactive recalculation on regularization
  • OvertimeReportService: aggregation per employee per week

Integration Tests

  • Weekly job execution: runs for all tenants, classifies correctly
  • GET /reports/overtime?month=2026-03 → correct band breakdown
  • GET /reports/export?type=overtime&format=xlsx → file download
  • GET /reports/export?type=overtime&format=csv → file download with BOM
  • Non-HR/Comptable → 403
  • Tenant isolation: tenant A data does not appear in tenant B report

What QA Will Validate

  • Verify overtime bands match configuration (40h base, CI defaults)
  • Generate report for month with known overtime, verify band breakdown
  • Export Excel and CSV, verify formatting and data accuracy
  • Verify retroactive recalculation after regularization
  • Responsive table at all breakpoints

Out of Scope

  • Monetary overtime calculation (Payroll module)
  • Multi-country overtime rates (V2)
  • Collective agreement overrides (V2)
  • Real-time overtime alerts (V2)

Definition of Done

  • [ ] Weekly classification batch job runs correctly
  • [ ] Overtime bands classified according to tenant configuration
  • [ ] Retroactive recalculation on regularization works
  • [ ] WeeklyOvertimeComputed published after classification
  • [ ] Overtime report endpoint returns correct band breakdown
  • [ ] Export reuses Excel/CSV services from QR-017
  • [ ] Flyway migration applied successfully
  • [ ] Report view renders at all breakpoints (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
  • [ ] No TypeScript any, no Java raw types
  • [ ] API responses follow standard envelope format
  • [ ] ./gradlew test is green
  • [ ] ./gradlew spotlessApply passes