Aller au contenu

Story CP-016: Audit Trail UI + Access Control

Module: control-plane Slice: Slice 4 from architecture (split from CP-004) Brand context: [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority: 4 Depends on: CP-004 Estimated complexity: M


Objective

Build the audit trail viewer UI and the query endpoint with filters. The DG can browse, filter, and inspect audit entries. Non-DG users are denied access. This story builds on the AuditService and AuditEntry infrastructure from CP-004.


Backend Scope

Entities & Records

No new entities. Uses AuditEntry entity from CP-004.

DTOs (from CP-004, used by the query endpoint):

public record AuditEntryResponse(
    UUID id, UUID userId, String userFullName, Instant timestamp,
    String entityType, UUID entityId, String moduleCode,
    AuditAction action, JsonNode beforeValue, JsonNode afterValue,
    JsonNode metadata
) {}

public record AuditPageResponse(
    List<AuditEntryResponse> entries, int totalCount, int page, int pageSize
) {}

public record AuditQueryParams(
    @Nullable String entityType,
    @Nullable UUID entityId,
    @Nullable UUID userId,
    @Nullable AuditAction action,
    @Nullable String moduleCode,
    @Nullable Instant dateFrom,
    @Nullable Instant dateTo,
    int page,    // default 0
    int pageSize // default 50
) {}

Repository Layer

Uses AuditRepository from CP-004: - findByFilters(UUID tenantId, AuditQueryParams) → Page<AuditEntry> — Paginated query with optional filters on entity_type, entity_id, user_id, action, module_code, date range

Service Layer

AuditService.java (addition to CP-004's AuditService): - query(AuditQueryParams params) → AuditPageResponse — Tenant-scoped query for the UI. Automatically scopes by TenantContext.current().tenantId().

API Endpoints

Method Path Request Body Response Auth
GET /api/v1/audit Query params (filters) AuditPageResponse ROLE_DG

Validation Rules

  • entity_type max 100 chars
  • module_code max 20 chars
  • Query date range: date_from must be before date_to
  • Page size: 1–100 (default 50)
  • Only ROLE_DG can access this endpoint (403 for all other roles)

Frontend Scope

Pages & Routes

  • /admin/audit — Audit Trail viewer page

Components

  • AuditTrailPage.tsx — Filterable, paginated audit log
  • AuditEntryCard.tsx — Single audit entry (mobile card view)
  • AuditEntryRow.tsx — Table row (desktop view)
  • AuditDetailSheet.tsx — Bottom sheet (mobile) / side panel (desktop) showing full before/after diff
  • AuditFilterBar.tsx — Filter chips: Module, Action, Date range

UI States (ALL REQUIRED)

State Behavior French Copy
Loading Skeleton: 5 entry card placeholders
Empty Illustration + message "Aucune activité enregistrée pour cette période."
Error Red toast "Une erreur est survenue. Veuillez réessayer."
Offline Full page offline message "Le journal d'audit n'est pas disponible hors ligne."
Success Entries displayed with pagination
Unauthorized Redirect or message "Accès non autorisé"

Responsive Behavior

  • 360px (mobile): Timeline list. Each entry card shows: date/time, user name, human-readable action label, entity name in quotes, module badge, "Détails -->" link.
  • 768px (tablet): Compact table view.
  • 1024px+ (desktop): Full-width table with columns: Date/Heure, Utilisateur, Action, Entite, Module, Resume. Expandable rows showing before/after diff inline. Advanced filters: date range picker, module dropdown, action type checkboxes, user search.

Interactions

  • Filters: Module dropdown, Action type (Creation/Modification/Suppression), Date range picker. Filters stack as chips.
  • Pagination: "Charger plus..." button at bottom (mobile). Page numbers (desktop).
  • Detail view: Tap "Détails -->" on any entry --> bottom sheet (mobile) / side panel (desktop) showing:
  • Action, Entite, Par (user), Date
  • "Modifications" section showing field-by-field before/after
  • Before/After display: Human-readable format:
    Nom
      Avant: Production
      Apres: Prod.
    

French micro-copy for audit UI:

Key French Text
audit.title "Journal d'audit"
audit.filter.module "Module"
audit.filter.action "Action"
audit.filter.date "Période"
audit.action.create "Création"
audit.action.update "Modification"
audit.action.delete "Suppression"
audit.detail.title "Détail de l'audit"
audit.detail.action "Action"
audit.detail.entity "Entité"
audit.detail.by "Par"
audit.detail.date "Date"
audit.detail.changes "Modifications"
audit.detail.before "Avant"
audit.detail.after "Après"
audit.load_more "Charger plus..."
empty.audit "Aucune activité enregistrée pour cette période."

Acceptance Criteria

AC-029: Audit query with filters
Given 50 audit entries exist for my tenant
When I call GET /api/v1/audit?entityType=Employee&action=CREATE&dateFrom=2026-02-01
Then I receive only audit entries matching all three filters

AC-031: Audit UI displays timeline
Given I am DG and navigate to /admin/audit
When the page loads
Then I see a chronological list of audit entries with filters at the top

AC-032: Audit detail view shows before/after
Given an UPDATE audit entry for a department rename
When I tap "Détails -->" on the entry
Then I see the before/after diff in human-readable format: "Nom -- Avant: Production -- Apres: Prod."

AC-033: Audit accessible only to DG
Given I am authenticated with role EMPLOYE
When I navigate to /admin/audit
Then I am redirected or shown "Acces non autorise"

OHADA & Regulatory Rules

  • AUDCIF Art. 24: The audit viewer provides the DG with traceability of all data mutations, satisfying the 10-year retention visibility requirement.
  • AUDCIF Art. 22: Audit entries displayed are immutable — the UI is read-only. No edit or delete actions exist.
  • Loi 2013-450: Audit trail viewer restricted to DG role enforces accountability for personal data access.

Standards & Conventions

  • docs/standards/react-typescript-guidelines.md — §List/table components, §Filter pattern, §Bottom sheet detail, §Pagination
  • docs/standards/api-guidelines.md — §Pagination, §Query parameter conventions

Testing Requirements

Unit Tests

  • AuditService.query(): filter combinations (entity_type, action, date range, module)
  • AuditService.query(): pagination (page, pageSize)
  • Before/after diff generation for human-readable display

Integration Tests

  • GET /api/v1/audit — with multiple filter combinations
  • GET /api/v1/audit — pagination (50 per page, next page)
  • GET /api/v1/audit — tenant isolation (tenant A queries, no tenant B data)
  • GET /api/v1/audit — EMPLOYE role → 403
  • GET /api/v1/audit — DG role → 200

What QA Will Validate

  • Audit page loads with entries in reverse chronological order
  • Filters work: Module, Action type, Date range
  • Detail view shows before/after diff correctly
  • Pagination ("Charger plus...") loads next page
  • Page shows offline message when disconnected
  • DG can access audit page; EMPLOYE cannot
  • All breakpoints render correctly (360px, 768px, 1024px)

Out of Scope

  • CSV export of audit trail — V2
  • Cross-tenant audit query (for platform admin) — CP-012
  • Audit trail for admin console actions — CP-012

Definition of Done

  • [ ] GET /api/v1/audit endpoint with filters returns correct paginated data
  • [ ] All frontend pages render correctly at 360px, 768px, 1024px
  • [ ] All 6 UI states implemented (loading, empty, error, offline, success, unauthorized)
  • [ ] French micro-copy matches design spec exactly
  • [ ] All acceptance criteria pass manually
  • [ ] Unit tests written and passing
  • [ ] Integration tests written and passing
  • [ ] DG-only access enforced (403 for other roles)
  • [ ] No TypeScript any — all types explicit
  • [ ] No Java raw types — all generics explicit
  • [ ] API responses follow standard envelope format