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.mddocs/standards/api-guidelines.mddocs/standards/react-typescript-guidelines.mddocs/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
DailyAttendancerecalculation 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}/rejectwith reason -> 200 - POST
/regularizations/{id}/overrideas 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 testis green - [ ]
./gradlew spotlessApplypasses