Story QR-007: Clock-Out + Validation Rules (Duplicate, Terminated, Early Departure)¶
@assignee: @m-kouassi
Module: qr-attendance Slice: QR-T2-001 (extension) + QR-T3-001 (early departure) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 7 Depends on: QR-006 Can develop concurrently with: None (extends QR-006 directly) Merge order: Before QR-009 Estimated complexity: M PRD User Stories: US-QR-001 (AC-004, AC-005), US-QR-002 (AC-001, AC-003)
Objective¶
Extend the clock event pipeline from QR-006 with three critical validation rules: duplicate scan detection (within a 15-minute window), terminated/offboarded employee rejection, and early departure flagging. These rules ensure data integrity and compliance without requiring new tables or endpoints — they layer onto the existing POST /api/v1/qrcontr/clock-events flow.
Backend Scope¶
Entities & Records¶
No new tables or entities. Extends existing Clock and DailyAttendance from QR-006.
Flyway Migrations¶
None — no new tables.
Repository Layer¶
No new repositories.
Service Layer¶
DuplicateDetector:
- IF two scans of the same event type within 15 minutes for the same employee: keep earliest, mark later as is_duplicate=true
- Log in audit: "Doublon detecte (fenetre 15 min)."
- If >15 min apart, treat as separate events
Extend ClockEventService.recordClockEvent:
- Check employee status via CachedEmployeeData — if TERMINATED or OFFBOARDED, reject with "Compte desactive — contactez les RH" and log attempt in audit
- On clock-OUT: check if clock-out before scheduled end time — flag DailyAttendance as DEPART_ANTICIPE, compute early_departure_minutes = scheduled end - clock-out time
EarlyDepartureDetector:
- Compares last clock-out with employee's schedule end time
- No tolerance for early departures (any departure before scheduled end is flagged)
API Endpoints¶
No new endpoints. Existing POST /api/v1/qrcontr/clock-events now applies these rules.
Validation Rules¶
| Rule | Condition | Outcome |
|---|---|---|
| Duplicate | Same employee + same event type + within 15 min | is_duplicate=true on later event |
| Terminated | employee_status in (TERMINATED, OFFBOARDED) |
403 with message "Compte desactive — contactez les RH" |
| Early departure | Clock-out time < schedule end time | DailyAttendance.earlyDepartureMinutes set, status includes DEPART_ANTICIPE flag |
Multi-Tenant Considerations¶
All validation queries include tenant_id filtering. CachedEmployeeData is already tenant-scoped.
Frontend Scope¶
No new pages or routes. Extends QR Scanner states from QR-006.
Components to Extend¶
QRScanner — add duplicate info state:
- Amber (not red): "Pointage deja enregistre a HH:MM" (auto-dismiss 2s)
QRScanner — add terminated error state:
- Red shake + "Compte desactive — contactez les RH"
ClockConfirmation — for clock-out:
- Show "Depart — [Site name]" instead of "Arrivee"
UI States¶
| State | Behavior | French Copy |
|---|---|---|
| Duplicate | Amber info, no red | "Pointage deja enregistre a {time}." |
| Terminated | Red shake, distinct message | "Compte desactive — contactez les RH" |
| Clock-out success | Same celebration as clock-in | "Pointage enregistre" + "HH:MM" + "Depart — [Site name]" |
Responsive Behavior¶
Same as QR-006 (360px mobile-first, 768px tablet, 1024px+ desktop).
Acceptance Criteria¶
AC-001: Duplicate scan detection within 15 minutes
Given I scan the same QR code twice within 15 minutes
When the system processes the scans
Then the second scan is discarded as a duplicate
AC-002: Terminated employee scan rejection
Given I am a terminated employee
When I try to scan
Then the scan is rejected with message "Compte desactive — contactez les RH"
AC-003: Clock-out event recording
Given I have a clock-in recorded for today
When I scan the QR code
Then a clock-out event is recorded
AC-004: Early departure detection
Given I clock out before my scheduled end time
When the system processes the event
Then an early departure (depart anticipe) is flagged
OHADA & Regulatory Rules¶
- Clock events are immutable audit records (ADR-10). Duplicates are marked but never deleted.
- Terminated employee rejection logged in audit trail (FR-QR-013).
Standards & Conventions¶
docs/standards/java-spring-guidelines.mddocs/standards/api-guidelines.md
Testing Requirements¶
Unit Tests¶
DuplicateDetector: within 15 min window (duplicate), outside 15 min window (not duplicate), same vs different event type- Terminated employee check: TERMINATED status rejected, OFFBOARDED status rejected, ACTIVE status allowed
- Early departure calculation: clock-out before schedule end, clock-out at schedule end (no flag), clock-out after schedule end (no flag)
Integration Tests¶
- POST clock-event with duplicate within 15 min — second event marked
is_duplicate=true - POST clock-event with terminated employee — 403 response
- POST clock-out before schedule end —
DailyAttendancehasearlyDepartureMinutesset and DEPART_ANTICIPE status - Tenant isolation: duplicate detection does not cross tenants
What QA Will Validate¶
- Scan twice quickly, verify duplicate message (amber, not red)
- Verify clock-out shows "Depart — [Site name]" instead of "Arrivee"
- Verify early departure is flagged in attendance records
- Verify terminated employee sees red shake with correct message
Out of Scope¶
- Late arrival detection (QR-009)
- Auto-close for missed clock-out (QR-010)
- Shared device anomaly detection (QR-010)
Definition of Done¶
- [ ] All backend validation rules implemented (duplicate, terminated, early departure)
- [ ] Frontend scanner states updated (duplicate amber, terminated red, clock-out success)
- [ ] All 5 UI states from QR-006 still work + new states added
- [ ] 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 - [ ] Audit trail entries for duplicates and rejected scans
- [ ]
./gradlew testis green - [ ]
./gradlew spotlessApplypasses