Aller au contenu

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% (075,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 financials
  • docs/standards/database-guidelines.md — §Platform DB reference data, §Seed data in migrations
  • docs/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