Aller au contenu

Story CP-005: Organization Structure + Reference Data

Module: control-plane Slice: Slice 5 from architecture Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 5 Depends on: CP-001, CP-002, CP-004 Estimated complexity: M


Objective

Deliver the organizational backbone and reference data that every HR module depends on: the hierarchical org unit tree (Entity → Sites → Departments), the position catalog with escape-hatch creation, employee categories, and contract types. This slice establishes the master data structures. The Employee entity (CRUD, matricule, offline) is in CP-017 and CP-018.


Backend Scope

Entities & Records

OrgUnit entity (tenant DB):

Field Type Constraints
id UUID v7 PK
tenant_id UUID NOT NULL
name String NOT NULL, min 2, max 200
type OrgUnitType enum NOT NULL
parent_id UUID Nullable (null = root Entity)
status OrgUnitStatus enum NOT NULL, default ACTIVE
created_date Instant NOT NULL
deactivated_date Instant Nullable

OrgUnitType: ENTITY, SITE, DEPARTMENT OrgUnitStatus: ACTIVE, INACTIVE

Position entity (tenant DB):

Field Type Constraints
id UUID v7 PK
tenant_id UUID NOT NULL
title String NOT NULL, min 2, max 200
description String Nullable, max 500
category_id UUID NOT NULL, FK to EmployeeCategory
department_id UUID Nullable, FK to OrgUnit (DEPARTMENT)
status String NOT NULL, default "ACTIVE"
is_custom boolean NOT NULL, default false
created_date Instant NOT NULL

EmployeeCategory entity (tenant DB):

Field Type Constraints
id UUID v7 PK
tenant_id UUID NOT NULL
country_code String(2) NOT NULL
name String NOT NULL, max 100
code String NOT NULL, max 10
description String Nullable
sort_order int NOT NULL
is_system boolean NOT NULL, default true
created_date Instant NOT NULL

CI seed data: Cadre (CAD), Agent de maîtrise (AM), Employé (EMP), Ouvrier (OUV)

ContractType entity (tenant DB):

Field Type Constraints
id UUID v7 PK
tenant_id UUID NOT NULL
country_code String(2) NOT NULL
name String NOT NULL, max 200
code String NOT NULL, max 10
max_duration_months Integer Nullable
description String Nullable
is_system boolean NOT NULL, default true
status String NOT NULL, default "ACTIVE"
created_date Instant NOT NULL

CI seed data: CDI, CDD (max 24), Stage (max 24), Apprentissage (max 36), Journalier, CTT (max 24)

Note: Employee entity, ManagerHistory entity, and all Employee DTOs are in CP-017. EmployeeStatus enum (PRE_HIRE, ACTIVE, SUSPENDED, TERMINATED) is defined in CP-017.

DTOs:

// OrgUnit
public record OrgUnitTreeResponse(UUID id, String name, OrgUnitType type, String status,
    int employeeCount, List<OrgUnitTreeResponse> children) {}
public record CreateOrgUnitRequest(String name, OrgUnitType type, @Nullable UUID parentId) {}

// Position
public record PositionResponse(UUID id, String title, String categoryCode, String categoryName,
    @Nullable String departmentName, boolean isCustom) {}
public record CreatePositionRequest(String title, UUID categoryId, @Nullable UUID departmentId,
    @Nullable String description) {}

// Category
public record CategoryResponse(UUID id, String name, String code, String description, int sortOrder, boolean isSystem) {}

// ContractType
public record ContractTypeResponse(UUID id, String name, String code, @Nullable Integer maxDurationMonths,
    String description, boolean isSystem, String status) {}

Repository Layer

  • OrgUnitRepository.java — Tenant-aware: findByParentId, findAll, save
  • PositionRepository.java — Tenant-aware: findAll, findByTitle(search), save
  • EmployeeCategoryRepository.java — Tenant-aware: findAll, findByCode
  • ContractTypeRepository.java — Tenant-aware: findAll, findByCode

Note: EmployeeRepository and ManagerHistoryRepository are in CP-017.

Service Layer

OrgUnitService.java: - getTree(UUID tenantId) → List<OrgUnitTreeResponse> — Returns full tree with employee counts - createUnit(CreateOrgUnitRequest) → OrgUnitTreeResponse — Validates hierarchy rules (Site parent = Entity, Dept parent = Entity or Site), name unique within parent - deactivateUnit(UUID unitId) → void — Sets status=INACTIVE. Does NOT affect existing employee assignments. Blocks new assignments. Publishes OrgUnitDeactivated.

PositionService.java: - list(UUID tenantId, @Nullable String search) → List<PositionResponse> — Search by title - create(CreatePositionRequest) → PositionResponse — Creates a position. Sets is_custom=true for escape-hatch created positions.

Note: EmployeeService, MatriculeService, and all employee endpoints are in CP-017.

API Endpoints

Method Path Request Body Response Auth
GET /api/v1/org-units/tree List Any authenticated
POST /api/v1/org-units CreateOrgUnitRequest OrgUnitTreeResponse ROLE_DG, ROLE_RH_MANAGER
PATCH /api/v1/org-units/{id}/deactivate 204 ROLE_DG, ROLE_RH_MANAGER
GET /api/v1/positions ?search= List Any authenticated
POST /api/v1/positions CreatePositionRequest PositionResponse ROLE_DG, ROLE_RH_MANAGER
GET /api/v1/categories List Any authenticated
GET /api/v1/contract-types List Any authenticated

Note: All employee endpoints (/api/v1/employees/*) are in CP-017.

Validation Rules

  • OrgUnit: Site parent must be ENTITY. Department parent must be ENTITY or SITE. Name unique within parent.
  • Position: title min 2, max 200. category_id required and must exist.
  • EmployeeCategory: system categories (is_system=true) cannot be deleted, only deactivated.
  • Financial calculations: N/A for this slice (no money fields).

Multi-Tenant Considerations

  • ALL entities in this slice are in the tenant DB and tenant-scoped
  • Every repository extends TenantAwareRepository

Flyway Migrations (Tenant DB)

  • V003__create_org_unit.sqlorg_units table
  • V004__create_position_category.sqlpositions, employee_categories, contract_types tables + seed CI data

Note: V005__create_employee.sql (employees, matricule_counters) is in CP-017. V006__create_manager_history.sql is in CP-018.


Frontend Scope

Pages & Routes

  • /admin/settings/org-structure — Org tree page
  • /admin/settings/categories — Category management
  • /admin/settings/positions — Position catalog
  • /admin/settings/contract-types — Contract type list

Note: All employee pages (/rh/employees/*) are in CP-017.

Components

  • OrgTreePage.tsx, OrgTreeNode.tsx (component spec in design §19.4)
  • CategoryListPage.tsx
  • PositionCatalogPage.tsx, PositionCombobox.tsx (component spec in design §19.5)
  • ContractTypeListPage.tsx

Note: EmployeeListPage, EmployeeCard, EmployeeRow, EmployeeDetailPage, EmployeeFormPage are in CP-017.

UI States (ALL REQUIRED)

Note: Employee List UI states are in CP-017.

Org Structure:

State Behavior French Copy
Loading Skeleton: tree outline
Empty Message + CTA "Votre structure organisationnelle est vide.\nCommencez par ajouter des départements." CTA: "Ajouter un département"
Error Red toast "Une erreur est survenue. Veuillez réessayer."
Offline (read) Tree from IndexedDB, orange badge "Hors ligne — données locales"
Offline (write) Add unit queues for sync "Unité enregistrée hors ligne ●"

Position Catalog:

State Behavior French Copy
Loading Skeleton: 5 position items
Empty Message "Aucun poste défini.\nLes postes seront créés automatiquement lorsque vous ajouterez des employés."
Error Red toast "Une erreur est survenue. Veuillez réessayer."
Offline List from IndexedDB "Hors ligne — données locales"

Responsive Behavior

Note: Employee List responsive behavior is in CP-017.

Org Structure: - 360px: Indented list with expand/collapse. Long-press → add child. Tap → bottom sheet with details. - 1024px+: Master (40%, tree with search filter) + detail panel (60%, selected unit info, inline edit, "Ajouter un sous-département" button).

Interactions

Note: Employee creation interactions (online + offline) are in CP-017 and CP-018.

Org tree interactions (mobile): - Tap node → toggle expand/collapse - Long-press parent → "Ajouter sous [name]" action - Tap node name → bottom sheet: name, type badge, status, employee count - In bottom sheet: edit name, deactivate toggle

Note: Employee card states (Active/Pre-hire/Suspended/Terminated/Pending sync) are in CP-017.


Acceptance Criteria

AC-036: Position escape-hatch creation
Given I am on the position catalog or employee form, position field
When I type "Technicien réseau" and no matching position exists
Then I see an option "Technicien réseau — Ajouter comme nouveau poste"
When I select it
Then the position is auto-created in the catalog (is_custom=true)
And I can select a category for the new position

AC-038: Org tree hierarchy rules
Given the root Entity "Acme SARL" exists
When I try to add a Site with parent = a Department
Then the validation rejects: Site parent must be the Entity

AC-042: Deactivated org unit blocks new assignments
Given department "Informatique" is deactivated
When I try to assign a new employee to "Informatique"
Then the validation rejects: cannot assign to inactive org unit
And existing employees in "Informatique" are NOT affected

AC-043: System categories cannot be deleted
Given the system category "Cadre" (is_system=true) exists
When I attempt to delete it
Then the operation is rejected
And I can only deactivate it

Moved ACs: AC-034, AC-037, AC-040, AC-041 → CP-017. AC-035, AC-039, AC-044, AC-045 → CP-018.


OHADA & Regulatory Rules

  • Code du Travail Art. 92.3 (Registre d'employeur): The Employee entity stores baseline fields required for the employer register. Full register → HR Core module.
  • CDD 2-year limit (Art. 15.10): CDD contracts exceeding 2 years cumulative automatically become CDI. The rule is stored in ContractType.max_duration_months. Alerts at 90/30/7 days — enforcement in Employee Management module, but the data model supports it from day one.
  • Convention Collective Interprofessionnelle (CCI): Defines 4 employee categories (Cadre, AM, Employé, Ouvrier). Seeded as system categories. Affects CNPS contribution rates.
  • Décret 2024-901: Companies with >10 workers must elect staff representatives (délégués du personnel). Employee count tracking enables this threshold check.
  • Employee data is personal data (Loi 2013-450): first_name, last_name, email, phone are PII. Audit trail tracks all modifications. Terminated employees are never hard-deleted (AUDCIF Art. 24).

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §Entities, §UUID v7, §Optimistic locking, §Application Events, §Tree structures
  • docs/standards/database-guidelines.md — §Self-referencing FKs, §Counter tables (FOR UPDATE), §Tenant-scoped unique constraints
  • docs/standards/api-guidelines.md — §CRUD patterns, §Pagination, §Filtering
  • docs/standards/react-typescript-guidelines.md — §List/card/table components, §Form components, §Combobox pattern, §FAB
  • docs/standards/offline-sync-guidelines.md — §IndexedDB sync queue, §UUID v7 client generation, §Conflict detection, §"Numéro en attente" pattern

Testing Requirements

Unit Tests

  • OrgUnitService: hierarchy validation (Site parent = Entity, Dept parent = Entity/Site)
  • OrgUnitService: name uniqueness within parent
  • OrgUnitService: deactivation does not affect existing assignments
  • PositionService: escape-hatch creation sets is_custom=true
  • EmployeeCategoryService: system categories cannot be deleted

Integration Tests

  • GET /api/v1/org-units/tree — correct tree structure with employee counts
  • POST /api/v1/org-units — hierarchy validation
  • POST /api/v1/positions — escape-hatch creation
  • GET /api/v1/categories — seed data present
  • GET /api/v1/contract-types — seed data present
  • Tenant isolation: all entities scoped to tenant

What QA Will Validate

  • Org tree: expand/collapse, add child, deactivate
  • Position catalog: search, escape-hatch creation
  • Category list: system categories cannot be deleted
  • Contract type list displays correctly
  • All pages render correctly at 360px, 768px, 1024px

Out of Scope

  • Employee CRUD + Matricule — CP-017
  • Employee Offline + Manager Hierarchy + PRE_HIRE Transition — CP-018
  • Full employee dossier (photo, emergency contacts, bank info, documents) — HR Core module
  • Employee onboarding/offboarding workflows — Employee Management module
  • Bulk employee import — V2
  • Employee self-service profile view — V2
  • Org tree reordering (drag & drop) — V2
  • Multi-entity support (multiple legal entities per tenant) — V2

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)
  • [ ] Seed data present (CI categories + contract types)
  • [ ] 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