Story AB-021: HR Employee Balance Detail (Backend + Frontend)¶
Module: absence-management Slice: VS-AB-010 (HR extension) + VS-AB-014 (dashboard link) from architecture Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 21 Depends on: AB-009 Concurrent with: AB-020 Merge order: Any order with AB-020 Estimated complexity: S PRD User Stories: US-ABS-008
Objective¶
Provide HR managers and DGs with a detailed view of any employee's leave balances, including the full ledger of accruals, deductions, adjustments, carry-overs, and sick leave phase tracking. Accessible from the HR Dashboard and team calendar. This is the primary HR tool for understanding an employee's leave history and current entitlements.
Backend Scope¶
Entities & Records¶
No new entities. This story extends the query layer over existing models from AB-009 (LeaveEmployeeProfile, ledger entries).
DTOs:
public record EmployeeBalanceDetailResponse(
// Employee profile
String employeeName,
LocalDate hireDate,
int seniorityYears,
String category, // cadre / non-cadre
int childrenUnder21Count,
// Annual leave breakdown
BigDecimal acquisBase, // base accrual (JC)
BigDecimal senioriteBonusDays, // anciennete bonus
BigDecimal childrenBonusDays, // children bonus
BigDecimal totalAcquis, // total acquired
BigDecimal taken, // total taken
BigDecimal available, // net available (JC)
// Exceptional leave
BigDecimal exceptionalUsed, // days used
int exceptionalCap, // 10 days
int exceptionalPercentage, // progress bar %
// Sick leave (rolling 12 months)
BigDecimal sickDaysUsed,
String currentPhase, // FULL_PAY, HALF_PAY, ZERO_PAY
int daysRemainingBeforeNextPhase
) {}
public record LedgerEntryResponse(
UUID id,
LocalDate date,
String transactionType, // enum code
String transactionLabel, // French label
BigDecimal amount, // signed (+/-)
String unit, // "JC"
@Nullable BigDecimal runningBalance,
@Nullable String reason
) {}
public record LedgerEntriesResponse(
List<LedgerEntryResponse> entries,
@Nullable String nextCursor,
int totalCount
) {}
Repository Layer¶
No new repositories. Uses existing repositories from AB-009 with extended query methods: - Ledger query with pagination (cursor-based), date range filter, transaction type filter
Service Layer¶
BalanceQueryService.java (extend from AB-009):
- getEmployeeBalanceDetail(UUID tenantId, UUID employeeId) -> EmployeeBalanceDetailResponse — Full balance breakdown with employee profile info, annual/exceptional/sick leave summaries
- getLedgerEntries(UUID tenantId, UUID employeeId, LedgerFilter filters) -> LedgerEntriesResponse — Paginated ledger with filters:
- from / to: date range (optional)
- type: transaction type filter (optional, e.g., "ANNUAL")
- cursor: cursor-based pagination
- Default sort: chronological descending (most recent first)
API Endpoints¶
| Method | Path | Request Body | Response | Auth |
|---|---|---|---|---|
| GET | /api/v1/absence/employees/{employeeId}/balance | ?includeHistory=true | EmployeeBalanceDetailResponse (+ optional ledger preview) | HR_MANAGER, DG |
| GET | /api/v1/absence/employees/{employeeId}/balance/ledger | ?from=...&to=...&type=ANNUAL&cursor=... | LedgerEntriesResponse (paginated) | HR_MANAGER, DG |
Validation Rules¶
employeeIdmust exist and belong to the current tenantfrom/todate filters:frommust be before or equal tototypefilter must be a valid transaction type- Only HR_MANAGER or DG roles can access these endpoints
Multi-Tenant Considerations¶
- All queries scoped by tenant_id from TenantResolutionFilter
- Employee must belong to the authenticated user's tenant
- Ledger entries are tenant-isolated by design (FK to tenant-scoped LeaveEmployeeProfile)
Flyway Migrations¶
No new migrations. Uses existing ledger tables from AB-009/AB-012.
Frontend Scope¶
Pages & Routes¶
/absences/employes/:id/solde— HR Employee Balance Detail page- Access: HR_MANAGER, DG
Components¶
EmployeeBalanceDetailPage.tsx— Main page orchestrating all sectionsEmployeeProfileCard.tsx— Employee identity: name, hire date, seniority, category, children countAnnualLeaveSection.tsx— Hero balance display (JC) + breakdown (acquis base, anciennete, enfants, pris, disponible)ExceptionalLeaveSection.tsx— Progress bar "Utilisees : {n} / 10 jours" with percentageSickLeaveSection.tsx— Days used, current phase badge (variant by phase), days remainingLedgerSection.tsx— Chronological ledger list with "Voir tout" linkLedgerEntryRow.tsx— Single ledger entry: date, label, signed amount with +/- and JC unitLedgerFullPage.tsx— Full paginated ledger view (route:/absences/employes/:id/solde/grand-livre)
UI States (ALL REQUIRED)¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton: profile card + 3 balance sections + ledger list | — |
| Empty (new employee) | Balance shows 0, ledger section empty state | "Aucune transaction enregistree" |
| Error | Toast + retry button | "Impossible de charger le solde de l'employe." |
| Offline | Cached data displayed + offline banner | "Hors connexion -- derniere synchro : {time}" |
| Success | N/A (read-only page) | — |
Responsive Behavior¶
- 360px (mobile): Single column, stacked sections:
- Employee profile card with avatar icon, name, hire date, seniority badge, category, children count
- "Conge annuel" section: large hero balance number (JC), breakdown rows below
- "Permissions exceptionnelles" section: progress bar with label
- "Conge maladie (12 mois glissants)" section: phase badge + days info
- "Grand livre" section: last 5 ledger entries, "Voir tout" link at bottom
- "Ajuster le solde" button (secondary, full-width) at bottom
- 768px (tablet): Same stacked layout, wider cards.
- 1024px+ (desktop): 2-column layout:
- Left column: employee profile card + balance sections
- Right column: ledger entries in DataTable with sort/filter columns (date, type, amount)
- "Ajuster le solde" button in page header area
Interactions¶
- Navigate to adjustment: Tap "Ajuster le solde" button to navigate to
/absences/employes/:id/ajustement(AB-020), pre-filling employee context - Ledger "Voir tout": Navigates to full paginated ledger view with date range filter and transaction type filter
- Offline cache: Page loads from IndexedDB cache when offline. Banner shows last sync time. Data may be stale.
- Phase badge colors:
- FULL_PAY: Badge variant=success (green)
- HALF_PAY: Badge variant=warning (amber)
- ZERO_PAY: Badge variant=error (red)
Ledger transaction type labels (French):
| Transaction Type | French Label |
|---|---|
| MONTHLY_ACCRUAL | "Acquisition mensuelle" |
| SENIORITY_BONUS | "Bonus anciennete" |
| CHILDREN_BONUS | "Bonus enfants" |
| LEAVE_DEBIT | "Deduction conge" |
| CANCEL_CREDIT | "Credit annulation" |
| EARLY_RETURN_CREDIT | "Credit retour anticipe" |
| OVERLAP_CREDIT | "Credit chevauchement" |
| CARRY_OVER | "Report" |
| EXPIRY | "Expiration" |
| MANUAL_ADJUSTMENT | "Ajustement manuel" |
| SETTLEMENT | "Solde de tout compte" |
Acceptance Criteria¶
AC-067: HR can view employee profile and balance breakdown
Given I am authenticated as HR_MANAGER or DG
When I navigate to /absences/employes/{id}/solde for an employee in my tenant
Then I see: employee profile (name, hire date, seniority, category, children under 21)
And annual leave breakdown (acquis base, anciennete bonus, children bonus, taken, available)
And exceptional leave (used / 10 cap with progress bar)
And sick leave (days used, current phase with color-coded badge, days remaining before next phase)
AC-068: Full ledger history is visible
Given I view an employee's balance detail
When I expand the ledger section or navigate to the full ledger view
Then I see the chronological list of all ledger transactions (accruals, deductions, adjustments, carry-overs)
And each entry shows: date, French label, signed amount in JC, and optional reason
And entries are paginated with cursor-based navigation
AC-069: Navigate to adjustment from detail
Given I am viewing an employee's balance detail
When I tap "Ajuster le solde"
Then I navigate to /absences/employes/{id}/ajustement (AB-020)
And the employee context (id, name, current balance) is pre-filled
OHADA & Regulatory Rules¶
- FR-ABS-120: Ledger transaction types — all types must be displayed with correct French labels
- FR-ABS-121: Ledger entry fields — date, transaction_type, amount, reason must be visible to HR
- FR-ABS-122: Ledger is append-only and immutable — UI must not offer edit/delete on ledger entries
- FR-ABS-123: Corrections are new entries — if a MANUAL_ADJUSTMENT corrects a prior error, it appears as a separate entry in the ledger
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— Service layer, cursor-based paginationdocs/standards/database-guidelines.md— Read queries, tenant isolation, BigDecimal for balancesdocs/standards/api-guidelines.md— GET with query parameters, standard ApiResponse envelope, cursor pagination formatdocs/standards/react-typescript-guidelines.md— Page composition, skeleton loading, offline cache displaydocs/standards/design-system.md— Badge (variant=success/warning/error), ProgressBar, Card, DataTable, Button componentsdocs/standards/offline-sync-guidelines.md— Read-only cache pattern (display cached data offline with staleness banner)docs/standards/multi-datasource-patterns.md— Tenant-scoped data access via JdbcClient
Testing Requirements¶
Unit Tests¶
- BalanceQueryService: getEmployeeBalanceDetail returns correct breakdown
- BalanceQueryService: getLedgerEntries with date range filter
- BalanceQueryService: getLedgerEntries with transaction type filter
- BalanceQueryService: cursor-based pagination returns correct page
- BalanceQueryService: sick leave phase calculation (FULL_PAY, HALF_PAY, ZERO_PAY)
Integration Tests¶
- GET /api/v1/absence/employees/{id}/balance — full response structure validation
- GET /api/v1/absence/employees/{id}/balance/ledger — pagination and filtering
- GET endpoint — 403 for EMPLOYE role (unauthorized for HR detail view)
- Tenant isolation: employee balance from tenant A not visible to tenant B HR
What QA Will Validate¶
- Balance detail page renders correctly on mobile (360px), tablet (768px), desktop (1024px+)
- Desktop 2-column layout with DataTable on right
- Phase badges show correct colors (green/amber/red)
- Progress bar for exceptional leave displays correct percentage
- Ledger entries show French labels for all transaction types
- "Voir tout" navigates to full paginated ledger
- "Ajuster le solde" navigates to AB-020 adjustment form
- Offline: cached data displayed with staleness banner
- Empty state: new employee with no transactions shows "Aucune transaction enregistree"
Out of Scope¶
- Editing employee profile data — HR Core module responsibility
- Exporting ledger to CSV/PDF — Enterprise V2
- Employee self-service balance view — AB-009 (different route, different auth scope)
- Ledger entry edit/delete — not allowed per FR-ABS-122 (immutable ledger)
- Historical balance snapshots — V2 (point-in-time balance reconstruction)
Definition of Done¶
- [ ] All backend endpoints implemented and returning correct responses
- [ ] All frontend pages render 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 (no cross-tenant data leaks)
- [ ] Offline write+sync works (Papillon) — read-only cache with staleness banner
- [ ] No TypeScript
any— all types explicit - [ ] No Java raw types — all generics explicit
- [ ] API responses follow standard envelope format
- [ ] Audit trail entries created for every data mutation — N/A (read-only endpoints)