Aller au contenu

Story QR-011: Regularization Request (Employee/Manager)

Module: qr-attendance Slice: QR-T4-001 (partial: create + submit) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 11 Depends on: QR-006 (DailyAttendance exists) Can develop concurrently with: QR-009, QR-014, QR-016 Merge order: Before QR-012, QR-013 Estimated complexity: M PRD User Stories: US-QR-004


Objective

Create the regularization request flow that allows employees and managers to submit corrections for missed or incorrect clock events. The request captures a reason (from a predefined list), corrected times, and enters a pending approval state. This is the first half of the regularization feature — creation and submission only; approval/rejection is handled in QR-012.


Backend Scope

Entities & Records

RegularizationRequest: - id (UUID) - tenantId (UUID) - employeeId (UUID) - requestDate (LocalDate) - reason (enum: OUBLI_POINTAGE, PROBLEME_TECHNIQUE, MISSION_EXTERIEURE, AUTRE) - reasonDetail (String, nullable) - correctedClockIn (LocalTime, nullable) - correctedClockOut (LocalTime, nullable) - status (enum: PENDING, APPROVED, REJECTED — default PENDING) - requestedBy (UUID) - approvedBy (UUID, nullable) - approvedAt (Timestamp, nullable) - isDirectCreate (boolean, default false) - overrideOf (UUID, nullable) - createdAt (Timestamp) - updatedAt (Timestamp)

Flyway Migrations

V001_015__attendance_regularization_request.sql:

CREATE TABLE attendance_regularization_request (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    employee_id UUID NOT NULL,
    request_date DATE NOT NULL,
    reason VARCHAR(30) NOT NULL CHECK (reason IN ('OUBLI_POINTAGE', 'PROBLEME_TECHNIQUE', 'MISSION_EXTERIEURE', 'AUTRE')),
    reason_detail TEXT,
    corrected_clock_in TIME,
    corrected_clock_out TIME,
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED')),
    requested_by UUID NOT NULL,
    approved_by UUID,
    approved_at TIMESTAMP,
    is_direct_create BOOLEAN NOT NULL DEFAULT false,
    override_of UUID,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_regularization_tenant_status ON attendance_regularization_request(tenant_id, status, created_at);

Repository Layer

RegularizationRepository: - findByTenantIdAndStatus(UUID tenantId, String status) — list by status - findByTenantIdAndEmployeeId(UUID tenantId, UUID employeeId) — list by employee - findByTenantIdAndId(UUID tenantId, UUID id) — single request

Service Layer

RegularizationService: - createRequest(CreateRegularizationRequest) — validates fields, creates with PENDING status, publishes notification event to manager + HR

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/qrcontr/regularizations CreateRegularizationRequest ApiResponse<RegularizationResponse> Employee, Manager
GET /api/v1/qrcontr/regularizations ?status=&employeeId=&cursor=&size= ApiResponse<CursorPage<RegularizationResponse>> HR, Manager (team)
GET /api/v1/qrcontr/regularizations/{id} ApiResponse<RegularizationResponse> HR, Manager, Employee (own)

DTOs

CreateRegularizationRequest: - employeeId (UUID) - date (LocalDate) - reason (enum) - reasonDetail (String, conditional) - correctedClockIn (LocalTime, optional) - correctedClockOut (LocalTime, optional)

RegularizationResponse: - All entity fields + employeeName (String), requesterName (String)

Validation Rules

  • reason required, must be one of the enum values
  • If reason=AUTRE, reasonDetail is mandatory -> error "Precisez la raison."
  • At least one of correctedClockIn / correctedClockOut must be provided -> error "Indiquez au moins une heure corrigee."
  • date must not be in the future
  • correctedClockIn must be before correctedClockOut when both provided

Multi-Tenant Considerations

All queries filter by tenant_id. Notification events include tenant context. Employee ID validation is tenant-scoped.


Frontend Scope

Components

RegularizationBottomSheet (design spec wireframe 3.6) — 3-step wizard on mobile: - Step 1: Select reason (radio buttons: Oubli de pointage, Probleme technique, Mission exterieure, Autre) - Step 2: Correct times (time pickers for arrival and/or departure) - Step 3: Confirmation summary

RegularizationForm — side panel on desktop (all fields visible)

UI States

State Behavior French Copy
Loading N/A (form is local)
Empty Pre-filled date from context, reason options visible
Error Inline field errors, red border, auto-focus first error "Indiquez au moins une heure corrigee." / "Precisez la raison."
Offline Form works, submit queued "Demande sauvegardee — sera envoyee des la reconnexion."
Success Step 3: animated checkmark + summary "Demande envoyee. Votre manager et les RH seront notifies."

Responsive Behavior

  • 360px: Bottom sheet wizard (3 steps), slide-left animation between steps (300ms)
  • 768px: Bottom sheet (2 steps: reason+times together, then confirm)
  • 1024px+: Side panel with all fields visible

Interactions

  • Drag handle on bottom sheet to dismiss (velocity threshold)
  • Time picker: native scroll pickers (hours:minutes), 15-min increments suggested, any minute selectable
  • Back button preserves entered data (no data loss)
  • Triggered from: employee history (tap day with problem -> "Demander une regularisation" button)

Acceptance Criteria

AC-001: Regularization request with reason and corrected times
Given I forgot to clock in/out
When I submit a regularization request
Then I must select a reason from the predefined list and specify the corrected time(s)

AC-002: Request enters pending status with notifications
Given I submit a request
When it is saved
Then it enters PENDING status and my manager and HR are notified

AC-003: Offline regularization request
Given I am offline
When I submit a request
Then it is queued for sync

OHADA & Regulatory Rules

  • FR-QR-064: Every regularization action logged with actor, timestamp, before/after values
  • Original clock events NEVER modified or deleted — regularizations are additive records

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

  • Validation: reason=AUTRE requires reasonDetail, at least one corrected time required, future date rejected, clockIn before clockOut when both provided
  • PENDING status set on creation
  • Notification event published on creation

Integration Tests

  • POST /regularizations with valid data -> 201 PENDING
  • POST /regularizations with reason=AUTRE and no detail -> 400
  • POST /regularizations with no corrected times -> 400
  • GET /regularizations?status=PENDING -> filtered list
  • GET /regularizations/{id} -> single request with employee name
  • Tenant isolation: requests from tenant A not visible to tenant B

What QA Will Validate

  • Complete wizard flow on mobile (3 steps: reason, times, confirm)
  • Verify offline queue (submit while offline, sync when online)
  • Verify notifications sent to manager and HR
  • Verify error messages for validation failures
  • Verify back button preserves entered data

Out of Scope

  • Approve/reject workflow (QR-012)
  • HR direct-create (QR-013)
  • DailyAttendance recalculation on approval (QR-012)
  • HR override of manager approval (QR-012)

Definition of Done

  • [ ] All backend endpoints implemented and returning correct responses
  • [ ] Bottom sheet wizard works on mobile (360px)
  • [ ] Side panel form works on desktop (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 and integration tests passing
  • [ ] Multi-tenant isolation verified
  • [ ] Offline write+sync works (IndexedDB queue)
  • [ ] No TypeScript any, no Java raw types
  • [ ] API responses follow standard envelope format
  • [ ] Audit trail entries for regularization creation
  • [ ] ./gradlew test is green
  • [ ] ./gradlew spotlessApply passes