Story CP-019: Country Config: Holidays + Caching + Validation¶
Module: control-plane Slice: Slice 6b from architecture (split from CP-006) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 6 (after CP-006) Depends on: CP-006 Estimated complexity: S
Objective¶
Extend the country configuration engine with public holidays, sector salary grids, Caffeine caching for all country config queries, and country code validation. This slice adds the PublicHoliday and CountrySectorSalaryGrid entities, their endpoints, caching across all CountryConfigService methods (including those from CP-006), and rejects invalid country codes.
Backend Scope¶
Entities & Records¶
PublicHoliday entity (platform DB):
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK |
| country_code | String(2) | NOT NULL |
| year | int | NOT NULL |
| date | LocalDate | NOT NULL |
| name | String | NOT NULL |
CountrySectorSalaryGrid entity (platform DB):
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK |
| country_code | String(2) | NOT NULL |
| sector_code | String(10) | NOT NULL (COM, BTP, IND, SVC, AGR) |
| sector_name | String | NOT NULL |
| category_code | String(10) | NOT NULL |
| minimum_salary_monthly | BigDecimal | NOT NULL |
| effective_date | LocalDate | NOT NULL |
Sectors: Commerce (COM), BTP, Industrie (IND), Services (SVC), Agriculture (AGR)
Repository Layer¶
PublicHolidayRepository.java— Platform-scoped:findByCountryCodeAndYear(String, int) → List<PublicHoliday>CountrySectorSalaryGridRepository.java— Platform-scoped:findByCountryCodeAndSectorCode(String, String) → List<CountrySectorSalaryGrid>
Service Layer¶
CountryConfigService.java (extend from CP-006):
- getPublicHolidays(String countryCode, int year) → List<PublicHoliday> — NEW method
- getSalaryGrids(String countryCode, String sectorCode) → List<CountrySectorSalaryGrid> — NEW method
- Add Caffeine caching to ALL methods (TTL: 1 hour, invalidated on update):
- getTaxBrackets (from CP-006) — add @Cacheable
- getSocialContributions (from CP-006) — add @Cacheable
- getPublicHolidays — add @Cacheable
- getSalaryGrids — add @Cacheable
- Add country code validation: reject any code not in supported list (MVP: only "CI"). Return HTTP 400 with message "Country code not supported" (Code pays non pris en charge).
API Endpoints¶
| Method | Path | Request Body | Response | Auth |
|---|---|---|---|---|
| GET | /api/v1/country-config/{countryCode}/holidays/{year} | — | List |
Any authenticated |
| GET | /api/v1/country-config/{countryCode}/salary-grids?sectorCode={s} | — | List |
Any authenticated |
Admin CRUD endpoints (for platform admin — consumed by Admin Console CP-012):
| Method | Path | Auth |
|---|---|---|
| PUT | /api/v1/admin/country-config/{countryCode}/holidays/{year} | PLATFORM_ADMIN |
Validation Rules¶
country_code: valid ISO 3166-1 alpha-2 (MVP: only "CI" accepted). Invalid codes → HTTP 400.year: >= 2020, <= current year + 2sector_code: must be one of COM, BTP, IND, SVC, AGR- All monetary fields: BigDecimal, >= 0, zero decimal places (XOF)
Multi-Tenant Considerations¶
- Country config is in the platform DB — shared across all tenants
- NOT tenant-scoped (same CI holidays apply to all CI tenants)
- Read endpoints accessible to any authenticated user
- Write endpoints restricted to PLATFORM_ADMIN role
- Cache is shared across all tenants (reference data is the same)
Flyway Migration¶
V004__create_country_config_holidays_grids.sql— Platform DB: PublicHoliday + CountrySectorSalaryGrid tables + seed CI holidays 2026-2027 + salary grid placeholders
Frontend Scope¶
No tenant-facing frontend in this slice. Country config is read-only reference data for tenants. The Admin Console (CP-012) provides the editor UI.
Pages & Routes¶
N/A for tenant frontend. Admin console editor is in CP-012.
UI States (ALL REQUIRED)¶
N/A — Backend-only slice for tenant side. Admin UI in CP-012.
Responsive Behavior¶
N/A
Interactions¶
N/A
Acceptance Criteria¶
AC-048: Get CI public holidays
Given CI holidays are seeded for 2026
When I call GET /api/v1/country-config/CI/holidays/2026
Then I receive all CI public holidays for 2026
AC-050: Country config caching
Given tax brackets have been fetched once
When the same request is made within 1 hour
Then the response is served from Caffeine cache (no DB query)
AC-051: Invalid country code rejected
Given country code "XX" does not exist
When I call GET /api/v1/country-config/XX/tax-brackets?fiscalYear=2026
Then I receive HTTP 400 with message "Country code not supported"
OHADA & Regulatory Rules¶
- CI public holidays: Fixed holidays (Fete du Travail, Fete de l'Independance, etc.) plus variable Islamic holidays. Holiday list must be updatable annually without redeployment.
- Sector salary grids: CCI defines minimum salaries per category per sector. Placeholder values seeded; final values needed before Payroll.
- BigDecimal ONLY: All monetary values use BigDecimal. Never double/float for financial data.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Entities, §Caffeine caching, §BigDecimal for financialsdocs/standards/database-guidelines.md— §Platform DB reference data, §Seed data in migrationsdocs/standards/api-guidelines.md— §Read-only reference endpoints, §Admin-only write endpoints
Testing Requirements¶
Unit Tests¶
- CountryConfigService: holiday lookup by country + year
- CountryConfigService: salary grid lookup by country + sector
- CountryConfigService: cache hit/miss/invalidation (all 4 methods)
- Validation: invalid country code rejection
Integration Tests¶
- GET endpoints for holidays and salary-grids
- Seed data verification: CI holidays match expected values
- Cache invalidation on admin update
- Country code "XX" → 400 (on all endpoints including tax-brackets and social-contributions from CP-006)
- Authorization: tenant user can read, cannot write (403)
What QA Will Validate¶
- N/A for tenant UI (backend-only for tenants)
- Admin console (CP-012) will validate the editor UI
Out of Scope¶
- Admin console editor UI — CP-012
- Countries other than CI — Post-MVP (architecture supports multi-country from day one)
- Automatic holiday import API — Backlog OQ-CP-06
- SYSCOHADA chart of accounts — Seed in migration, full accounting module consumes it later
- Leave type reference data — Seeded here, detailed in Absence Management PRD
Definition of Done¶
- [ ] All backend endpoints implemented and returning correct responses
- [ ] All frontend pages render correctly at 360px, 768px, 1024px — N/A
- [ ] All 5 UI states implemented — N/A (backend-only for tenant side)
- [ ] French micro-copy matches design spec exactly — N/A
- [ ] All acceptance criteria pass manually
- [ ] Unit tests written and passing
- [ ] Integration tests written and passing
- [ ] Multi-tenant isolation verified — N/A (reference data is shared)
- [ ] Offline write+sync works — N/A
- [ ] No TypeScript
any— N/A - [ ] No Java raw types — all generics explicit
- [ ] API responses follow standard envelope format
- [ ] Audit trail entries created for every data mutation — Admin updates to config generate audit entries