Story CP-018: Employee Offline + Manager Hierarchy + PRE_HIRE Transition¶
Module: control-plane Slice: Slice 5 from architecture (split from CP-005) Brand context: Papillon HR Suite (mobile-first, offline-first, warm UX) Priority: 5 Depends on: CP-017 Estimated complexity: M
⚠️ Pre-existing event stub (2026-04-22) — The record class
ManagerChangedwas created ahead of this story on branchfeat/control-plane-employee-eventsto unblock consumers (AB-003 approval chain, QR-002 attendance cache). This story still owns publishing it — wiring into the manager-hierarchy service,@TransactionalEventListenercontract, and integration tests for old→new manager transitions. Do not re-create the record.
Objective¶
Complete the employee experience with offline-first creation (IndexedDB queue + sync), manager hierarchy historization, offline employee list, and the scheduled PRE_HIRE-to-ACTIVE automatic transition. This story makes the employee module work without connectivity and ensures manager changes are auditable with date ranges.
Backend Scope¶
Entities & Records¶
ManagerHistory entity (tenant DB):
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK |
| tenant_id | UUID | NOT NULL |
| employee_id | UUID | NOT NULL, FK to Employee |
| manager_id | UUID | NOT NULL, FK to Employee |
| start_date | LocalDate | NOT NULL |
| end_date | LocalDate | Nullable |
Repository Layer¶
ManagerHistoryRepository.java— Tenant-aware:findByEmployeeId(UUID) → List<ManagerHistory>findByEmployeeIdAndDate(UUID, LocalDate) → Optional<ManagerHistory>— Find active manager at a given date (end_date is null or >= date)
Service Layer¶
EmployeeService.java (additions to CP-017):
- update(UUID, UpdateEmployeeRequest) — Enhanced: If manager changed, creates ManagerHistory entry (closes previous with end_date=today, creates new with start_date=today). Publishes ManagerChanged event in addition to EmployeeUpdated.
PreHireTransitionJob.java (@Scheduled):
- Runs daily at 00:01 tenant local time (per FR-CP-051)
- Queries: SELECT * FROM employees WHERE status = 'PRE_HIRE' AND hire_date <= CURRENT_DATE AND tenant_id = ?
- For each matching employee: updates status to ACTIVE, sets status_effective_date = hire_date
- Publishes EmployeeStatusChanged event for each transition
- Logs: "Transitioned {count} employees from PRE_HIRE to ACTIVE for tenant {tenantId}"
- Must run for ALL tenants (iterate tenant list)
Flyway Migrations (Tenant DB)¶
V006__create_manager_history.sql—manager_historytable
Multi-Tenant Considerations¶
- ManagerHistory is in the tenant DB and tenant-scoped
- PRE_HIRE transition job must iterate all active tenants and run within each tenant's context
Frontend Scope¶
Offline Employee Creation¶
Employee creation (offline): 1. Same form as CP-017. UUID v7 generated client-side. 2. Submit --> saved to IndexedDB sync queue. Matricule shows "Numero en attente". 3. Toast: "Employe enregistre hors ligne" 4. On reconnect --> sync queue sends POST /api/v1/employees --> server assigns matricule --> IndexedDB updated --> matricule displayed
Sync queue behavior:
- Uses the offline sync queue pattern from docs/standards/offline-sync-guidelines.md
- Employee creation requests queued in IndexedDB with syncStatus: 'pending'
- On connectivity restored: queue processed FIFO
- On sync success: local record updated with server response (matricule, etc.)
- On sync failure (409 conflict): mark as syncStatus: 'conflict' for manual resolution
Employee List Offline Mode¶
Offline employee list: - Employee list reads from IndexedDB when offline - Orange header badge: "Hors ligne -- donnees locales" - All filtering and search works locally against IndexedDB data - Pending-sync employees show orange left border + small sync icon
UI States (additions to CP-017)¶
| State | Behavior | French Copy |
|---|---|---|
| Offline (list) | Full list from IndexedDB, orange header badge | "Hors ligne -- donnees locales" |
| Success (create offline) | Orange toast | "Employe enregistre hors ligne" |
| Pending sync | Employee card: orange left border + sync icon | — |
| Sync success | Green toast | "Employe synchronise" |
| Sync conflict | Red badge on employee card | "Conflit de synchronisation" |
Employee card states (addition to CP-017):
| State | Visual |
|---|---|
| Pending sync | Orange left border + small sync icon |
Components (additions)¶
No new page components. Enhancements to existing CP-017 components:
- EmployeeListPage.tsx — Add offline data source, orange badge, sync status indicators
- EmployeeCard.tsx — Add pending-sync visual state
- EmployeeFormPage.tsx — Add offline save path (IndexedDB queue)
Acceptance Criteria¶
AC-035: Create employee offline
Given I am RH Manager and offline
When I submit the employee form with valid data
Then the employee is saved to IndexedDB with UUID v7
And the matricule shows "Numero en attente"
And I see the toast "Employe enregistre hors ligne"
And when connectivity returns, the employee syncs and receives a server-assigned matricule
AC-039: Manager hierarchy historization
Given employee "Jean" has manager "Moussa"
When I change Jean's manager to "Aminata"
Then ManagerHistory for Moussa gets end_date=today
And a new ManagerHistory for Aminata is created with start_date=today
And a ManagerChanged event is published
AC-044: Employee list works offline
Given I am offline
When I navigate to the employee list
Then I see employees loaded from IndexedDB
And I see an orange "Hors ligne" indicator
AC-045: PRE_HIRE automatic transition
Given employee "Jean" has status=PRE_HIRE and hire_date=2026-03-15
When the daily batch job runs on 2026-03-15
Then Jean's status transitions to ACTIVE automatically
OHADA & Regulatory Rules¶
- Code du Travail Art. 92.3 (Registre d'employeur): Manager history supports the traceability requirement for organizational reporting.
- Employee data is personal data (Loi 2013-450): Offline-stored employee data in IndexedDB is PII. The PWA must not persist data after logout. IndexedDB is cleared on user sign-out.
- AUDCIF Art. 24: Manager changes are historized (ManagerHistory) and produce audit entries, satisfying the traceability requirement.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Application Events, §Scheduled jobsdocs/standards/database-guidelines.md— §Self-referencing FKsdocs/standards/offline-sync-guidelines.md— §IndexedDB sync queue, §UUID v7 client generation, §Conflict detection, §"Numero en attente" patterndocs/standards/react-typescript-guidelines.md— §Offline indicators, §Sync status badges
Testing Requirements¶
Unit Tests¶
- ManagerHistory: correct start/end date management on manager change
- ManagerHistory: first manager assignment creates entry with no previous to close
- PreHireTransitionJob: transitions matching employees to ACTIVE
- PreHireTransitionJob: skips employees with future hire_date
- PreHireTransitionJob: publishes EmployeeStatusChanged event
Integration Tests¶
- PATCH /api/v1/employees/{id} with manager change → ManagerHistory entries created correctly
- PreHireTransitionJob: end-to-end with real DB
- Offline sync: client-generated UUID v7 accepted by server
- Offline sync: conflict detection on stale version
What QA Will Validate¶
- Employee creation form works offline (saved to IndexedDB)
- Matricule shows "Numero en attente" offline, real number after sync
- Employee list works offline with orange badge
- Pending-sync employees show orange border + sync icon
- After reconnect, employees sync and matricule appears
- Manager change creates history entries (verify via audit trail)
- PRE_HIRE employees transition to ACTIVE on hire_date
Out of Scope¶
- Sync conflict resolution UI (ConflictResolver component) — described in design §16.2, implement when needed. For now, conflicts are flagged but not auto-resolved.
- Full employee dossier (photo, emergency contacts, bank info, documents) — HR Core module
- Bulk employee import — V2
Definition of Done¶
- [ ] Offline employee creation saves to IndexedDB and syncs on reconnect
- [ ] ManagerHistory entries created correctly on manager change
- [ ] PRE_HIRE→ACTIVE scheduled job runs daily and transitions matching employees
- [ ] Employee list works offline with IndexedDB data source
- [ ] All acceptance criteria pass manually
- [ ] Unit tests written and passing
- [ ] Integration tests written and passing
- [ ] Multi-tenant isolation verified (no cross-tenant data leaks)
- [ ] IndexedDB cleared on sign-out (no PII persistence)
- [ ] No TypeScript
any— all types explicit - [ ] No Java raw types — all generics explicit
- [ ] Audit trail entries created for manager changes and status transitions