Aller au contenu

Story QR-014: Teletravail Request + Calendar

Module: qr-attendance Slice: QR-T4-002 (partial: request + limits) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 14 Depends on: QR-006 (DailyAttendance), QR-004 (config for remote work limits) Can develop concurrently with: QR-011, QR-016 Merge order: Before QR-015 Estimated complexity: M PRD User Stories: US-QR-007 (AC-001, AC-002, AC-003)


Objective

Implement the teletravail (remote work) request flow and calendar view for employees. Employees can request teletravail for future dates via an interactive calendar, with configurable advance notice requirements and weekly/monthly limit warnings. Short-notice requests require a mandatory reason. Limits warn but do not block — HR retains final approval authority.


Backend Scope

Entities & Records

RemoteWorkRequest: - id (UUID) - tenantId (UUID) - employeeId (UUID) - requestDate (LocalDate) - reason (String, nullable) - status (enum: PENDING, APPROVED, REJECTED, CANCELLED) - approvedBy (UUID, nullable) - approvedAt (Instant, nullable) - rejectionReason (String, nullable) - createdAt (Instant) - updatedAt (Instant)

Flyway Migrations

V001_016__attendance_remote_work_request.sql:

CREATE TABLE attendance_remote_work_request (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    employee_id UUID NOT NULL,
    request_date DATE NOT NULL,
    reason TEXT,
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED', 'CANCELLED')),
    approved_by UUID,
    approved_at TIMESTAMP,
    rejection_reason TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    UNIQUE(tenant_id, employee_id, request_date)
);
CREATE INDEX idx_remote_work_tenant_emp ON attendance_remote_work_request(tenant_id, employee_id, request_date);
CREATE INDEX idx_remote_work_tenant_status ON attendance_remote_work_request(tenant_id, status, request_date);

Repository Layer

RemoteWorkRepository: - findByTenantIdAndEmployeeIdAndRequestDate(tenantId, employeeId, date) — check uniqueness - findByTenantIdAndStatus(tenantId, status) — list by status - countByTenantIdAndEmployeeIdAndStatusAndWeek(tenantId, employeeId, statuses, weekStart, weekEnd) — weekly limit check - countByTenantIdAndEmployeeIdAndStatusAndMonth(tenantId, employeeId, statuses, monthStart, monthEnd) — monthly limit check

Service Layer

RemoteWorkService: - createRequest(tenantId, employeeId, request): - Validates date is in the future (not today or past) - Checks unique constraint (one request per employee per date) - Advance notice check: if requestDate < today + minRemoteWorkAdvanceDays (from config), reason is mandatory - Limit check: counts approved+pending for current week/month. If >= max, includes warning in response (does NOT block) - Creates request with status=PENDING - Publishes audit event - cancel(tenantId, requestId, employeeId): - Only own PENDING requests - Only before the request date - Sets status=CANCELLED - Publishes audit event

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/qrcontr/remote-work CreateRemoteWorkRequest ApiResponse<RemoteWorkResponse> Employee (P4)
GET /api/v1/qrcontr/remote-work ?status=&employeeId=&dateFrom=&dateTo=&cursor=&size= ApiResponse<CursorPage<RemoteWorkResponse>> HR, Manager (team), Employee (own)
GET /api/v1/qrcontr/remote-work/{id} ApiResponse<RemoteWorkResponse> HR, Manager, Employee (own)
POST /api/v1/qrcontr/remote-work/{id}/cancel ApiResponse<RemoteWorkResponse> Employee (own, PENDING only)

DTOs

CreateRemoteWorkRequest: - date (LocalDate, required) — must be future - reason (String, conditional) — mandatory if short notice

RemoteWorkResponse: - All entity fields + employeeName (String) - limitWarning (String, nullable) — e.g., "1 jour teletravail cette semaine (max 2)"

Validation Rules

  • date must be a future date (not today or past)
  • If date < today + minRemoteWorkAdvanceDays: reason is mandatory → error "Raison obligatoire pour les demandes de derniere minute"
  • Unique constraint: one request per employee per date → error "Une demande existe deja pour cette date"
  • Cancel: only own PENDING requests, only before the request date

Multi-Tenant Considerations

All queries filter by tenant_id. Configuration (advance notice days, weekly/monthly limits) is tenant-scoped via AttendanceModuleConfig from QR-004. Employee can only see own requests; Manager sees team; HR sees all.


Frontend Scope

Pages & Routes

  • /qrcontr/teletravail — Teletravail calendar + request form

Components

TeletravailPage (design spec wireframe 3.7): - Month calendar view with request indicators + request form

TeletravailCalendar: - Interactive calendar component - Past dates greyed out, holidays disabled - Existing requests shown as colored dots: green=approved, amber=pending, red=rejected - Selected date: amber circle highlight - Month navigation: left/right arrows + swipe gesture on mobile

TeletravailRequestForm: - Date field (pre-filled from calendar selection) - Reason field (conditional: mandatory label when short notice, optional otherwise) - Submit button

LimitWarning: - Inline warning component displayed below form when weekly/monthly limit reached - Format: "i 1 jour teletravail cette semaine (max 2)"

UI States

State Behavior French Copy
Loading Calendar skeleton shimmer
Empty Calendar with no requests, today highlighted "Selectionnez une date pour demander le teletravail."
Error Inline validation "Selectionnez une date future." / "Raison obligatoire pour les demandes de derniere minute"
Offline Full form works, queued "Demande sauvegardee localement."
Success Date gets pending amber dot, toast "Demande soumise."

Responsive Behavior

  • 360px: Full-width calendar, form below
  • 768px: Calendar + side panel for form
  • 1024px+: Calendar left, detail right (split view)

Interactions

  • Date tap: amber circle highlight; disabled dates (past, holidays) don't respond
  • Month navigation: left/right arrows + swipe gesture on mobile
  • Limit warning: info badge below form when limit reached
  • Cancel: from pending request detail, confirmation dialog: "Etes-vous sur de vouloir annuler cette demande ?"
  • Offline: form submits to local queue, syncs when online

Acceptance Criteria

AC-001: Advance notice teletravail request
Given I request teletravail for a future date
When the date is more than the configured advance notice (default 2 days)
Then the request is submitted without mandatory reason

AC-002: Short-notice teletravail requires reason
Given I request same-day or short-notice teletravail
When I submit the request
Then a reason field is mandatory (e.g., 'Enfant malade', 'Probleme de transport')

AC-003: Weekly limit warning (not blocker)
Given my company has a maximum teletravail days/week configuration
When I already have the maximum approved for this week
Then I am warned before submitting (not blocked — HR can still approve)

OHADA & Regulatory Rules

  • Teletravail is regulated by tenant policy, not by law in current CI scope
  • Conservative approach for pending requests: treated as absent until approved (handled in QR-015)
  • Advance notice and limit configurations are tenant-specific

Standards & Conventions

  • docs/standards/java-spring-guidelines.md
  • docs/standards/api-guidelines.md — cursor-based pagination (ADR-12)
  • docs/standards/react-typescript-guidelines.md
  • docs/standards/offline-sync-guidelines.md
  • docs/standards/database-guidelines.md — Flyway migration naming

Testing Requirements

Unit Tests

  • RemoteWorkService.createRequest: advance notice check (reason mandatory vs optional)
  • RemoteWorkService.createRequest: limit warning included in response when limit reached
  • RemoteWorkService.createRequest: duplicate date rejected
  • RemoteWorkService.cancel: only own PENDING, only before date

Integration Tests

  • POST /remote-work → 201 PENDING
  • POST /remote-work without reason on short notice → 400
  • POST /remote-work with limit reached → 201 with warning in response
  • POST /remote-work/{id}/cancel → 200 CANCELLED
  • POST /remote-work/{id}/cancel on non-own → 403
  • GET /remote-work with filters → correct results
  • Tenant isolation verified

What QA Will Validate

  • Calendar interaction: select date, verify form appears
  • Submit with/without advance notice, verify reason requirement
  • Verify limit warning appears when limit reached
  • Cancel flow from pending request detail
  • Offline: form works and syncs when reconnected
  • Month navigation with arrows and swipe

Out of Scope

  • Approval/rejection workflow (QR-015)
  • Day-of status handling (QR-015)
  • DailyAttendance integration for teletravail (QR-015)

Definition of Done

  • [ ] All backend endpoints implemented and returning correct responses
  • [ ] Flyway migration applied successfully
  • [ ] Calendar component renders correctly at 360px, 768px, 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 written and passing
  • [ ] Integration tests written and passing
  • [ ] Multi-tenant isolation verified
  • [ ] Offline write+sync works
  • [ ] Audit trail entries for request creation and cancellation
  • [ ] No TypeScript any, no Java raw types
  • [ ] API responses follow standard envelope format
  • [ ] ./gradlew test is green
  • [ ] ./gradlew spotlessApply passes