Aller au contenu

Story AB-002 : Modèle de données + Migrations Flyway

Module : absence-management Slice : VS-AB-002 from architecture Brand context : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority : 2 Depends on : AB-001 Can develop concurrently with : Aucune Merge order : Après AB-001, Avant AB-003 Estimated complexity : M PRD User Stories : N/A - Technical Story


Objectif

Créer toutes les tables de base de données et les entités Spring Data JDBC pour le module Absence Management via des migrations Flyway, établissant la fondation de données pour toutes les stories suivantes. Ce slice définit le schéma canonique pour les demandes de congé, les soldes, les profils employé, les tables de configuration et les jours fériés — chaque story AB à partir de AB-003 dépend de ce schéma.


Périmètre Backend

Entités & Records

1. LeaveRequest

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
employee_id UUID NOT NULL
leave_type_code VARCHAR(20) NOT NULL
event_type_code VARCHAR(30) Nullable (pour le sous-type de congé exceptionnel)
start_date DATE NOT NULL
end_date DATE NOT NULL
half_day_start BOOLEAN NOT NULL, default false
half_day_end BOOLEAN NOT NULL, default false
number_of_days DECIMAL NOT NULL (jours ouvrables)
number_of_days_ouvrables DECIMAL NOT NULL
status VARCHAR(30) NOT NULL (DRAFT, PENDING_APPROVAL, APPROVED, REJECTED, CANCELLED)
reason TEXT Nullable
rejection_reason TEXT Nullable
cancellation_reason TEXT Nullable
approver_id UUID Nullable, FK conceptuel vers User
is_retroactive BOOLEAN NOT NULL, default false
supporting_document_id UUID Nullable
submitted_offline BOOLEAN NOT NULL, default false
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
version INT NOT NULL, default 0 (optimistic locking)
created_by UUID NOT NULL

2. LeaveEmployeeProfile

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
employee_id UUID NOT NULL, UNIQUE par tenant
annual_balance_ouvrables DECIMAL NOT NULL, default 0
exceptional_days_used_this_period DECIMAL NOT NULL, default 0
sick_days_current_rolling_12m DECIMAL NOT NULL, default 0
sick_leave_phase VARCHAR(20) Nullable (FULL_PAY, HALF_PAY, UNPAID)
number_of_children_under_21 INTEGER NOT NULL, default 0
reference_period_start DATE NOT NULL
reference_period_end DATE NOT NULL
last_accrual_date DATE Nullable
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
version INT NOT NULL, default 0

3. LeaveBalanceLedger

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
employee_id UUID NOT NULL
leave_type VARCHAR(20) NOT NULL
transaction_type VARCHAR(30) NOT NULL (MONTHLY_ACCRUAL, SENIORITY_BONUS, CHILDREN_BONUS, DEDUCTION, CARRY_OVER, MANUAL_ADJUSTMENT)
amount DECIMAL NOT NULL (signé : positif = crédit, négatif = débit)
amount_calendaires DECIMAL NOT NULL
amount_ouvres DECIMAL NOT NULL
reference_id UUID Nullable (FK vers LeaveRequest ou autre source)
reason TEXT NOT NULL
created_at TIMESTAMP WITH TIME ZONE NOT NULL
created_by UUID NOT NULL

Index : idx_balance_ledger_lookup(tenant_id, employee_id, leave_type, created_at)

4. MaternityDeclaration

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
employee_id UUID NOT NULL
expected_delivery_date DATE NOT NULL
actual_delivery_date DATE Nullable
leave_request_id UUID NOT NULL, FK vers LeaveRequest
created_at TIMESTAMP WITH TIME ZONE NOT NULL
created_by UUID NOT NULL

5. ExceptionalLeaveConfig

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
event_type_code VARCHAR(30) NOT NULL
duration_days DECIMAL NOT NULL
cci_minimum_days DECIMAL NOT NULL
country_code VARCHAR(2) NOT NULL
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL

Unique : (tenant_id, event_type_code)

6. PublicHoliday

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID Nullable (null = valeur par défaut au niveau plateforme)
country_code VARCHAR(2) NOT NULL
year INTEGER NOT NULL
date DATE NOT NULL
name_fr VARCHAR(100) NOT NULL
holiday_code VARCHAR(30) NOT NULL
is_fixed BOOLEAN NOT NULL
source VARCHAR(20) NOT NULL (SYSTEM, TENANT_CUSTOM)
created_at TIMESTAMP WITH TIME ZONE NOT NULL

Unique : (tenant_id, country_code, year, holiday_code) — Note : tenant_id nullable dans la contrainte unique, utiliser COALESCE ou index partiel.

7. CachedEmployeeData

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
employee_id UUID NOT NULL
employee_matricule VARCHAR NOT NULL
first_name VARCHAR NOT NULL
last_name VARCHAR NOT NULL
category VARCHAR NOT NULL
hire_date DATE NOT NULL
org_unit_id UUID NOT NULL
current_manager_id UUID Nullable
employee_status VARCHAR NOT NULL
synced_at TIMESTAMP WITH TIME ZONE NOT NULL

Index : idx_cached_employee_lookup(tenant_id, employee_id)

8. LeaveTypeConfig

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
code VARCHAR(20) NOT NULL
name_fr VARCHAR(100) NOT NULL
is_paid BOOLEAN NOT NULL
requires_document BOOLEAN NOT NULL
requires_approval BOOLEAN NOT NULL
accrual_based BOOLEAN NOT NULL
max_days_per_year DECIMAL Nullable
is_system BOOLEAN NOT NULL, default true
is_active BOOLEAN NOT NULL, default true
country_code VARCHAR(2) NOT NULL
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL

Unique : (tenant_id, code)

9. AbsenceSettings

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL, UNIQUE
deduction_unit VARCHAR NOT NULL (OUVRABLES, OUVRES, CALENDAIRES)
reference_period_type VARCHAR NOT NULL (CALENDAR_YEAR, CUSTOM)
carry_over_policy VARCHAR NOT NULL (NO_CARRY_OVER, FULL_CARRY_OVER, PARTIAL_CARRY_OVER)
partial_carry_over_max_days DECIMAL Nullable (requis quand carry_over_policy = PARTIAL_CARRY_OVER)
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL

10. SickLeaveIndemnizationRule

Field Type Constraints
id UUID v7 PK, server-generated
tenant_id UUID NOT NULL
country_code VARCHAR(2) NOT NULL
employee_category VARCHAR NOT NULL
seniority_min_years INT NOT NULL
seniority_max_years INT Nullable
full_pay_months INT NOT NULL
half_pay_months INT NOT NULL
total_months INT NOT NULL
created_at TIMESTAMP WITH TIME ZONE NOT NULL
updated_at TIMESTAMP WITH TIME ZONE NOT NULL

Unique : (tenant_id, country_code, employee_category, seniority_min_years)

Couche Repository

Tous les repositories sont des interfaces Spring Data JDBC dans le package com.altarys.papillon.modules.absence.repository :

  • LeaveRequestRepository extends CrudRepository<LeaveRequest, UUID> :
  • findByTenantIdAndEmployeeId(UUID tenantId, UUID employeeId) -> List<LeaveRequest>
  • findByTenantIdAndStatus(UUID tenantId, String status) -> List<LeaveRequest>
  • findByTenantIdAndApproverId(UUID tenantId, UUID approverId) -> List<LeaveRequest>

  • LeaveEmployeeProfileRepository extends CrudRepository<LeaveEmployeeProfile, UUID> :

  • findByTenantIdAndEmployeeId(UUID tenantId, UUID employeeId) -> Optional<LeaveEmployeeProfile>

  • LeaveBalanceLedgerRepository extends CrudRepository<LeaveBalanceLedger, UUID> :

  • findByTenantIdAndEmployeeIdAndLeaveType(UUID tenantId, UUID employeeId, String leaveType) -> List<LeaveBalanceLedger>

  • MaternityDeclarationRepository extends CrudRepository<MaternityDeclaration, UUID>

  • ExceptionalLeaveConfigRepository extends CrudRepository<ExceptionalLeaveConfig, UUID> :

  • findByTenantIdAndEventTypeCode(UUID tenantId, String eventTypeCode) -> Optional<ExceptionalLeaveConfig>

  • PublicHolidayRepository extends CrudRepository<PublicHoliday, UUID> :

  • findByCountryCodeAndYear(String countryCode, int year) -> List<PublicHoliday>
  • findByTenantIdAndCountryCodeAndYear(UUID tenantId, String countryCode, int year) -> List<PublicHoliday>

  • CachedEmployeeDataRepository extends CrudRepository<CachedEmployeeData, UUID> :

  • findByTenantIdAndEmployeeId(UUID tenantId, UUID employeeId) -> Optional<CachedEmployeeData>

  • LeaveTypeConfigRepository extends CrudRepository<LeaveTypeConfig, UUID> :

  • findByTenantIdAndCode(UUID tenantId, String code) -> Optional<LeaveTypeConfig>
  • findByTenantIdAndIsActiveTrue(UUID tenantId) -> List<LeaveTypeConfig>

  • AbsenceSettingsRepository extends CrudRepository<AbsenceSettings, UUID> :

  • findByTenantId(UUID tenantId) -> Optional<AbsenceSettings>

  • SickLeaveIndemnizationRuleRepository extends CrudRepository<SickLeaveIndemnizationRule, UUID> :

  • findByTenantIdAndCountryCodeAndEmployeeCategory(UUID tenantId, String countryCode, String category) -> List<SickLeaveIndemnizationRule>

Couche Service

N/A — Pas de logique métier dans ce slice. Uniquement schéma + entités + repositories.

Endpoints API

N/A — Aucun endpoint dans ce slice.

Règles de validation

  • Tous les champs DECIMAL doivent utiliser BigDecimal en Java — jamais double ou float
  • Toutes les colonnes DATE correspondent à java.time.LocalDate dans les entités
  • Toutes les colonnes TIMESTAMP WITH TIME ZONE correspondent à java.time.Instant dans les entités
  • tenant_id est NOT NULL sur toutes les entités sauf PublicHoliday (nullable pour les valeurs par défaut au niveau plateforme)
  • Le champ version sur LeaveRequest et LeaveEmployeeProfile active l'optimistic locking via @Version
  • Les contraintes d'unicité doivent inclure tenant_id pour l'isolation tenant

Considérations multi-tenant

  • Chaque table inclut une colonne tenant_id (sauf PublicHoliday où elle est nullable pour les valeurs par défaut de la plateforme)
  • Toutes les contraintes d'unicité incluent tenant_id pour éviter les collisions inter-tenant
  • Toutes les méthodes de requête des repositories incluent le paramètre tenantId
  • Le schéma fonctionne sur les 4 profils DB : shared (filtrage par colonne tenant_id), schema-per-tenant (colonne tenant_id + isolation par schéma), DB-per-tenant (colonne tenant_id + isolation par DB), BYO-DB (colonne tenant_id + DB externe)

Migrations Flyway

Toutes les migrations dans db/tenant-migration/ (base de données tenant) :

  • V001__create_leave_request_table.sql
  • V002__create_leave_employee_profile_table.sql
  • V003__create_leave_balance_ledger_table.sql
  • V004__create_maternity_declaration_table.sql
  • V005__create_exceptional_leave_config_table.sql
  • V006__create_public_holiday_table.sql
  • V007__create_cached_employee_data_table.sql
  • V008__create_indexes.sql
  • V009__create_leave_type_config_table.sql
  • V010__create_absence_settings_table.sql
  • V011__create_sick_leave_indemnization_rule_table.sql

Périmètre Frontend

N/A — Story d'infrastructure backend.

Pages & Routes

N/A

Composants

N/A

États UI (TOUS REQUIS)

N/A — Backend uniquement.

Comportement responsive

N/A

Interactions

N/A


Critères d'acceptation

AC-004: All tables created with correct schema
Given the application starts with Flyway enabled
When all migrations run
Then all 10 tables are created with correct columns, types, constraints, and indexes

AC-005: Spring Data JDBC entities map correctly
Given the Spring Data JDBC repositories are defined
When a LeaveRequest entity is saved and retrieved
Then all fields round-trip correctly including BigDecimal amounts and DATE types

AC-006: Migrations are reversible
Given a migration has been applied
When a rollback is needed
Then each migration has a corresponding undo script or the migration is safely re-runnable

Règles OHADA & réglementaires

  • Types de données : Tous les champs monétaires et de comptage de jours utilisent DECIMAL / BigDecimal conformément aux exigences de calcul financier de la CI — jamais float ou double
  • Système à trois unités : LeaveBalanceLedger stocke amount (unité principale, ouvrables), amount_calendaires et amount_ouvres pour supporter les trois unités de calcul selon le droit du travail ivoirien
  • Jours ouvrables : Le champ number_of_days_ouvrables sur LeaveRequest est l'unité canonique de déduction selon le Code du Travail Art. 25.2

Standards & conventions

  • docs/standards/java-spring-guidelines.md — §Records & sealed types, §Spring Data JDBC entities
  • docs/standards/database-guidelines.md — §Flyway migration naming, §Column types, §Timestamp types (utiliser TIMESTAMP WITH TIME ZONE, pas TIMESTAMP), §Multi-tenant unique constraints
  • docs/standards/multi-datasource-patterns.md — §Tenant-scoped repositories, §Avoid CrudRepository for platform tables
  • docs/standards/api-guidelines.md — §UUID v7 generation

Exigences de test

Tests unitaires

  • Mapping des champs d'entité : tous les champs BigDecimal initialisés correctement
  • Validation d'entité : le champ version commence à 0
  • Valeurs d'enum : les enums status, transaction_type, deduction_unit contiennent toutes les valeurs attendues

Tests d'intégration

  • Les migrations Flyway s'exécutent sans erreur sur une base de données vierge
  • Les 10 tables existent après migration avec les colonnes et types corrects
  • Le round-trip save + findById de LeaveRequest préserve correctement tous les champs (y compris la précision BigDecimal)
  • La contrainte d'unicité de LeaveEmployeeProfile sur (tenant_id, employee_id) rejette les doublons
  • L'index de LeaveBalanceLedger est utilisé pour les requêtes typiques (EXPLAIN ANALYZE)

Ce que la QA validera

  • N/A — Pas de fonctionnalité visible par l'utilisateur. Le développeur vérifie les migrations et les mappings d'entités via les tests d'intégration.

Hors périmètre

  • Logique métier, services ou endpoints API — uniquement tables + entités + repositories
  • Données de seed pour LeaveTypeConfig ou ExceptionalLeaveConfig (fait dans les stories de configuration)
  • Configuration frontend de quelque nature que ce soit
  • Event listeners (AB-003)
  • Logique d'accumulation (AB-004)

Définition de Done

  • [ ] Tous les endpoints backend implémentés et retournant les réponses correctes — N/A (pas d'endpoints)
  • [ ] Toutes les pages frontend s'affichent correctement à 360px, 768px, 1024px — N/A (backend uniquement)
  • [ ] Les 5 états UI implémentés (loading, empty, error, offline, success) — N/A
  • [ ] Le micro-copy français correspond exactement à la spec de design — N/A
  • [ ] Tous les critères d'acceptation passent manuellement
  • [ ] Tests unitaires écrits et passants
  • [ ] Tests d'intégration écrits et passants
  • [ ] Isolation multi-tenant vérifiée — Les contraintes d'unicité incluent tenant_id
  • [ ] L'écriture + synchronisation offline fonctionne (Papillon) / N/A (Enterprise) — N/A (backend uniquement)
  • [ ] Pas de TypeScript any — N/A
  • [ ] Pas de Java raw types — tous les generics explicites
  • [ ] Les réponses API suivent le format d'enveloppe standard — N/A (pas d'endpoints)
  • [ ] Entrées de piste d'audit pour chaque mutation de données — N/A (pas de mutation de données dans ce slice)