Story CP-006: Country Config: Tax Brackets + Social Contributions¶
Module: control-plane Slice: Slice 6a from architecture (split from original CP-006) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 6 Depends on: CP-001, CP-002 Estimated complexity: S
Objective¶
Build the tax bracket and social contribution configuration for the country-specific configuration engine. This slice creates the CountryTaxBracket and CountrySocialContribution entities, the CountryConfigService (tax + social methods), and GET endpoints for tax-brackets and social-contributions. Rates are versioned per fiscal year, updatable without redeployment, and flagged as verified/unverified. For MVP, only Côte d'Ivoire (CI) data is seeded.
Backend Scope¶
Entities & Records¶
CountryTaxBracket entity (platform DB — reference data):
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK |
| country_code | String(2) | NOT NULL |
| fiscal_year | int | NOT NULL |
| bracket_lower | BigDecimal | NOT NULL |
| bracket_upper | BigDecimal | Nullable (null = infinity) |
| rate_percent | BigDecimal | NOT NULL |
| effective_date | LocalDate | NOT NULL |
| is_verified | boolean | NOT NULL, default false |
CI ITS brackets (Ordonnance n° 2023-719 — temporarily validated):
| Lower (FCFA/month) | Upper (FCFA/month) | Rate |
|---|---|---|
| 0 | 75,000 | 0% |
| 75,001 | 240,000 | 16% |
| 240,001 | 800,000 | 21% |
| 800,001 | 2,400,000 | 24% |
| 2,400,001 | 8,000,000 | 28% |
| 8,000,001 | ∞ | 32% |
CountrySocialContribution entity (platform DB):
| Field | Type | Constraints |
|---|---|---|
| id | UUID v7 | PK |
| country_code | String(2) | NOT NULL |
| fiscal_year | int | NOT NULL |
| branch_code | String(10) | NOT NULL (PF, MAT, ATMP, AV) |
| branch_name | String | NOT NULL |
| employer_rate | BigDecimal | NOT NULL |
| employee_rate | BigDecimal | NOT NULL |
| ceiling_monthly | BigDecimal | Nullable (null = no ceiling) |
| risk_class | String | Nullable (only for ATMP) |
| effective_date | LocalDate | NOT NULL |
| is_verified | boolean | NOT NULL, default false |
CI CNPS rates (temporarily validated):
| Branch | Code | Employer | Employee | Ceiling | Notes |
|---|---|---|---|---|---|
| Prestations Familiales | PF | 5.00% | 0% | No ceiling | |
| Maternité | MAT | 0.75% | 0% | No ceiling | |
| AT/MP | ATMP | 2.00%–5.00% | 0% | No ceiling | Rate by risk class |
| Assurance Vieillesse | AV | 7.70% | 6.30% | 3,375,000 FCFA/month | Ceiling unverified |
Repository Layer¶
CountryTaxBracketRepository.java— Platform-scoped:findByCountryCodeAndFiscalYear(String, int) → List<CountryTaxBracket>CountrySocialContributionRepository.java— Platform-scoped:findByCountryCodeAndFiscalYear(String, int) → List<CountrySocialContribution>findByCountryCodeAndFiscalYearAndBranchCode(String, int, String) → Optional<CountrySocialContribution>
Service Layer¶
CountryConfigService.java:
- getTaxBrackets(String countryCode, int fiscalYear) → List<CountryTaxBracket> — Returns brackets ordered by bracket_lower
- getSocialContributions(String countryCode, int fiscalYear) → List<CountrySocialContribution>
- No caching in this slice (caching added in CP-019)
API Endpoints¶
| Method | Path | Request Body | Response | Auth |
|---|---|---|---|---|
| GET | /api/v1/country-config/{countryCode}/tax-brackets?fiscalYear={y} | — | List |
Any authenticated |
| GET | /api/v1/country-config/{countryCode}/social-contributions?fiscalYear={y} | — | 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}/tax-brackets | PLATFORM_ADMIN |
| PUT | /api/v1/admin/country-config/{countryCode}/social-contributions | PLATFORM_ADMIN |
Validation Rules¶
country_code: valid ISO 3166-1 alpha-2 (MVP: only "CI" accepted)fiscal_year: >= 2020, <= current year + 2- All rate fields: BigDecimal, >= 0, <= 100 (percentages)
- All monetary fields: BigDecimal, >= 0, zero decimal places (XOF)
- Tax brackets must not overlap for same country/year
Multi-Tenant Considerations¶
- Country config is in the platform DB — shared across all tenants
- NOT tenant-scoped (same CI tax rates apply to all CI tenants)
- Read endpoints accessible to any authenticated user (their country_code from JWT determines relevant data)
- Write endpoints restricted to PLATFORM_ADMIN role
Flyway Migration¶
V003__create_country_config_tax_social.sql— Platform DB: CountryTaxBracket + CountrySocialContribution tables + seed CI ITS brackets and CNPS rates for fiscal year 2026
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-046: Get CI tax brackets
Given CI ITS brackets are seeded for fiscal year 2026
When I call GET /api/v1/country-config/CI/tax-brackets?fiscalYear=2026
Then I receive 6 brackets ordered by bracket_lower
And the first bracket has rate=0% (0–75,000 FCFA)
AC-047: Get CI CNPS social contributions
Given CI CNPS rates are seeded for fiscal year 2026
When I call GET /api/v1/country-config/CI/social-contributions?fiscalYear=2026
Then I receive 4 branches: PF, MAT, ATMP, AV
And each includes employer_rate, employee_rate, ceiling (nullable), is_verified flag
AC-049: Unverified rates flagged
Given CNPS AV ceiling is seeded with is_verified=false
When any module reads the rate
Then the response includes is_verified=false
And the admin console can display it with an "⚠ Non vérifié" badge (CP-012)
OHADA & Regulatory Rules¶
- ITS brackets: Ordonnance n° 2023-719 defines CI income tax brackets. Currently temporarily validated (TV-CP-05) — is_verified=false. Must be confirmed with official DGI publications before Payroll module goes live.
- CNPS rates: Based on CNPS Communiqué 2023 — temporarily validated (TV-CP-01 through TV-CP-04). Ceiling for AV branch (3,375,000 FCFA/month) needs official confirmation.
- SYSCOHADA chart: Standard across all 17 OHADA countries. Seeded for the Accounting module.
- Sector salary grids: CCI defines minimum salaries per category per sector. Placeholder values seeded; final values needed before Payroll.
- BigDecimal ONLY: All monetary and percentage values use BigDecimal. Never double/float for financial data.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Entities, §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: bracket lookup by country + fiscal year
- CountryConfigService: social contribution lookup by country + fiscal year
- Validation: overlapping bracket detection
Integration Tests¶
- GET endpoints for tax-brackets and social-contributions
- Seed data verification: CI brackets and CNPS rates match expected values
- 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¶
- Public holidays, salary grids, caching, country code validation — CP-019
- Admin console editor UI — CP-012
- Countries other than CI — Post-MVP (architecture supports multi-country from day one)
- RICF (Réduction d'Impôt pour Charges de Famille) — Open question OQ-CP-02, needed before Payroll
- 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