Aller au contenu

Story QR-012: Regularization Approval (Manager + HR)

Module: qr-attendance Slice: QR-T4-001 (partial: approve/reject/override) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 12 Depends on: QR-011 (regularization requests exist) Can develop concurrently with: QR-015 Merge order: Before QR-013, QR-017 Estimated complexity: M PRD User Stories: US-QR-005


Objective

Implement the approval workflow for regularization requests: managers can approve or reject requests from their direct reports, HR can approve/reject any request and override manager-approved regularizations. On approval, the corresponding DailyAttendance record is recalculated using the corrected times while preserving the original clock events for audit. This completes the regularization lifecycle started in QR-011.


Backend Scope

Entities & Records

No new tables. Extends RegularizationRequest from QR-011.

Flyway Migrations

None.

Repository Layer

No new repositories. Uses existing RegularizationRepository.

Service Layer

Extend RegularizationService:

approve(id, approverId): - Validates: request is PENDING, approver has authority (manager for direct reports, HR for any) - Sets status=APPROVED, approvedBy, approvedAt - Calls DailyAttendanceService.recalculateForRegularization(regularization): - Uses corrected clock-in/clock-out times - Recalculates: workedMinutes, scheduledMinutes, overtimeMinutes, lateMinutes, earlyDepartureMinutes - Original clock events PRESERVED — regularization is additive - If correcting auto-closed missed clock-out (QR-010): overtime may now be credited - If the affected day's week has a WeeklyOvertimeSummary, flag for re-classification - Sets DailyAttendance.regularizationId - Notifies employee

reject(id, rejecterId, reason): - Sets status=REJECTED - Notifies employee with reason

override(id, hrId): - HR only. Overrides a previously manager-approved regularization. - Creates new RegularizationRequest with overrideOf pointing to original - Reverts DailyAttendance to original clock events calculation - Notifies manager and employee

Authority Matrix

Role Approve/Reject Override Scope
Manager Yes No Direct reports only (CachedEmployeeData.currentManagerId == authenticatedUserId)
HR Yes Yes Any employee

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/qrcontr/regularizations/{id}/approve ApiResponse<RegularizationResponse> Manager (team), HR
POST /api/v1/qrcontr/regularizations/{id}/reject {reason: string} ApiResponse<RegularizationResponse> Manager (team), HR
POST /api/v1/qrcontr/regularizations/{id}/override ApiResponse<RegularizationResponse> HR (P2)

Validation Rules

  • Approve/reject only on PENDING requests
  • Override only on APPROVED requests (HR only)
  • Manager can only act on direct reports
  • Optimistic locking via version field prevents concurrent approval (409 Conflict)
  • Reject reason is mandatory

Multi-Tenant Considerations

All queries filter by tenant_id. Authority checks use tenant-scoped employee data. Notification events include tenant context.


Frontend Scope

Components

ApprovalPage (design spec wireframe 3.8): - List of pending requests (regularizations tab)

ApprovalCard: - Employee name, type, date, reason, corrected times - Approve button (green) + Reject button (red) - Badge count in navigation

RejectReasonDialog: - Popup for rejection reason input

OverrideDialog: - HR confirmation: "Cette regularisation a ete approuvee par {manager}. Souhaitez-vous l'annuler ?"

UI States

State Behavior French Copy
Loading Skeleton cards
Empty Celebration illustration "Aucune demande en attente"
Error Inline retry "Impossible de charger les demandes. Reessayez."
Offline Cached requests, approve/reject queued "Actions sauvegardees — seront synchronisees."
Success (approve) Green flash -> card slides out left (300ms) "Regularisation approuvee."
Success (reject) Red flash -> card slides out (300ms) "Regularisation refusee."

Responsive Behavior

  • 360px: Full-width stacked approval cards. Swipe right = approve (green trail), swipe left = reject (red trail, requires reason)
  • 768px: 2-column approval cards grid
  • 1024px+: Table view with batch approve (Enterprise), side detail panel

Interactions

  • Swipe right on card (mobile): quick approve (green trail animation)
  • Swipe left: quick reject -> requires reason in popup
  • Undo: toast with 5-second undo window after action. "Action annulee" on undo.
  • Badge count decrements on approval/rejection

Acceptance Criteria

AC-001: Manager sees only direct reports' requests
Given I am a manager
When I view pending regularizations
Then I see only requests from my direct reports

AC-002: HR sees all pending requests
Given I am HR
When I view pending regularizations
Then I see all pending requests across the company

AC-003: Approval recalculates attendance
Given I approve a regularization
When the system processes it
Then the attendance record for that day is updated in display/calculation (original record preserved for audit)

AC-004: HR override of manager approval
Given I am HR
When I see a manager-approved regularization that is incorrect
Then I can override/reject it

OHADA & Regulatory Rules

  • FR-QR-064: All actions (approve, reject, override) logged in audit trail
  • Original clock events NEVER modified — regularizations are additive (ADR-10)
  • Optimistic locking via version field prevents concurrent approval (409 Conflict)

Standards & Conventions

  • docs/standards/java-spring-guidelines.md
  • docs/standards/api-guidelines.md
  • docs/standards/react-typescript-guidelines.md
  • docs/standards/offline-sync-guidelines.md

Testing Requirements

Unit Tests

  • Authority check: manager can approve direct report, manager cannot approve non-report, HR can approve any
  • DailyAttendance recalculation with corrected times: workedMinutes, overtimeMinutes, lateMinutes updated
  • Override logic: new request created with overrideOf, original DA reverted
  • Reject reason mandatory
  • Optimistic locking: concurrent approve returns 409

Integration Tests

  • POST /regularizations/{id}/approve -> 200 + DA recalculated
  • POST /regularizations/{id}/reject with reason -> 200
  • POST /regularizations/{id}/override as HR -> 200
  • Manager approve non-report -> 403
  • Approve already-approved request -> 409 or 400
  • Tenant isolation: requests from tenant A cannot be approved by tenant B users

What QA Will Validate

  • Approve/reject flow on mobile (swipe gestures)
  • Verify attendance record updated after approval
  • Verify override dialog and DA revert
  • Test undo within 5-second window
  • Verify badge count updates on approval/rejection

Out of Scope

  • HR direct-create regularization (QR-013)
  • Batch approve (Enterprise V2)
  • Notification delivery implementation (publish event only)

Definition of Done

  • [ ] All approval/reject/override endpoints implemented
  • [ ] DailyAttendance recalculated on approval
  • [ ] Approval page with swipe actions on mobile
  • [ ] All UI states implemented (loading, empty, error, offline, approve success, reject success)
  • [ ] French micro-copy matches design spec
  • [ ] All acceptance criteria pass manually
  • [ ] Unit tests and integration tests passing
  • [ ] Multi-tenant isolation verified
  • [ ] Offline: approve/reject queued for sync
  • [ ] No TypeScript any, no Java raw types
  • [ ] Audit trail entries for all approval actions
  • [ ] ./gradlew test is green
  • [ ] ./gradlew spotlessApply passes