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¶
datemust be a future date (not today or past)- If
date < today + minRemoteWorkAdvanceDays:reasonis 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.mddocs/standards/api-guidelines.md— cursor-based pagination (ADR-12)docs/standards/react-typescript-guidelines.mddocs/standards/offline-sync-guidelines.mddocs/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 reachedRemoteWorkService.createRequest: duplicate date rejectedRemoteWorkService.cancel: only own PENDING, only before date
Integration Tests¶
- POST
/remote-work→ 201 PENDING - POST
/remote-workwithout reason on short notice → 400 - POST
/remote-workwith limit reached → 201 with warning in response - POST
/remote-work/{id}/cancel→ 200 CANCELLED - POST
/remote-work/{id}/cancelon non-own → 403 - GET
/remote-workwith 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 testis green - [ ]
./gradlew spotlessApplypasses