Aller au contenu

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.md
  • docs/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 — DailyAttendance has earlyDepartureMinutes set 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 test is green
  • [ ] ./gradlew spotlessApply passes