feat(p0-2026-04-25): multi-référentiel comptable + UBO + audit trail + SoD + seuils AML
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m11s
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m11s
Sprint 1 P0 (consolidation 2026-04-25, ETAT_PROJET_METIER_2026-04-25.md) : P0-NEW-9/10/11 — Multi-référentiel comptable - enum ReferentielComptable (SYSCOHADA / SYCEBNL / PCSFD_UMOA) - Organisation.referentielComptable + mapping defaultFor(typeOrganisation) - V43 : colonne + check + index + mapping initial des orgs existantes P0-NEW-13 — Bénéficiaires effectifs (UBO) — Instruction BCEAO 003-03-2025 - Entité BeneficiaireEffectif + repository - V44 : table beneficiaires_effectifs (FK kyc_dossier, UBO + PEP + sanctions) - Conservation 10 ans (directive 02/2015/CM/UEMOA) P0-NEW-14 — Compliance Officer (Instruction BCEAO 001-03-2025) - Organisation.complianceOfficerId + V43 colonne + index P0-NEW-15 — Seuils AML alignés (Instruction BCEAO 002-03-2025) - AmlSeuils : 10M FCFA intra-UEMOA / 5M FCFA entrée-sortie / 1M FCFA espèce - Liste pays UEMOA ISO 3166-1 - Méthodes seuilApplicable() / depasseSeuil() / depasseSeuilEspece() P0-NEW-17/18 — Rôles PRESIDENT + CONTROLEUR_INTERNE + suppléants - V45 seed : PRESIDENT, VICE_PRESIDENT, CONTROLEUR_INTERNE, ANIMATEUR_ZONE, SECRETAIRE_ADJOINT, TRESORIER_ADJOINT - Catégories GOUVERNANCE / CONTROLE / OPERATIONNEL P0-NEW-19 — Audit trail enrichi (SYSCOHADA + AUDSCGIE) - V45 : table audit_trail_operations (acteur, action, contexte multi-org, payload JSONB, SoD) - Entité AuditTrailOperation + AuditTrailOperationRepository - AuditTrailService (log avec contexte automatique depuis OrganisationContextHolder) - OrganisationContextHolder enrichi (roleActif, currentUserId, currentUserEmail) P0-NEW-20 — SoD (Separation of Duties) — SYSCOHADA + AUDSCGIE + BCEAO Circulaire 03-2017 - SoDPermissionChecker.checkValidationDistinct() (4-eyes principle) - .checkRoleCombination() (combinaisons interdites : Trésorier+Président, etc.) - .checkComplianceOfficerEligibility() (Instruction BCEAO 001-03-2025) - SoDCheckResult record avec audit trail automatique P0-NEW-24 — Champ numero_cmu sur Membre (Loi 2014-131 CI) - Membre.numeroCMU + V43 colonne + check format 11 caractères + index - Auto-déclaration (pas d'API publique CNAM disponible) BUILD SUCCESS.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package dev.lions.unionflow.server.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.lions.unionflow.server.entity.AuditTrailOperation;
|
||||
import dev.lions.unionflow.server.repository.AuditTrailOperationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service d'audit trail enrichi (SYSCOHADA + AUDSCGIE OHADA + Instruction BCEAO 003-03-2025).
|
||||
*
|
||||
* <p>Enregistre toutes les opérations sensibles (financières, lifecycle membres, configurations)
|
||||
* avec leur contexte multi-org complet (rôle actif, organisation active, vérifications SoD).
|
||||
*
|
||||
* <p>Usage typique dans un service métier :
|
||||
*
|
||||
* <pre>{@code
|
||||
* @Inject AuditTrailService auditTrail;
|
||||
*
|
||||
* public Cotisation enregistrerPaiement(...) {
|
||||
* Cotisation c = ...
|
||||
* auditTrail.log("Cotisation", c.getId(), "PAYMENT_CONFIRMED",
|
||||
* "Paiement confirmé via " + provider, c);
|
||||
* return c;
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @since 2026-04-25
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AuditTrailService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AuditTrailService.class);
|
||||
|
||||
@Inject AuditTrailOperationRepository repository;
|
||||
@Inject OrganisationContextHolder context;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* Enregistre une entrée d'audit trail à partir du contexte courant.
|
||||
*
|
||||
* @param entityType nom de l'entité (ex: "Cotisation", "Membre", "EcritureComptable")
|
||||
* @param entityId UUID de l'entité ciblée (peut être null pour actions globales)
|
||||
* @param actionType type d'action (cf. CHECK SQL : CREATE, UPDATE, DELETE, APPROVE, ...)
|
||||
* @param description description libre courte (≤ 500 caractères)
|
||||
* @param payloadApres entité après modification (sérialisée JSON, peut être null)
|
||||
*/
|
||||
@Transactional
|
||||
public void log(String entityType, UUID entityId, String actionType, String description,
|
||||
Object payloadApres) {
|
||||
log(entityType, entityId, actionType, description, null, payloadApres, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une entrée d'audit trail avec snapshot avant/après et résultat SoD.
|
||||
*/
|
||||
@Transactional
|
||||
public void log(String entityType, UUID entityId, String actionType, String description,
|
||||
Object payloadAvant, Object payloadApres, Object metadata,
|
||||
Boolean sodCheckPassed, String sodViolations) {
|
||||
try {
|
||||
AuditTrailOperation entry = AuditTrailOperation.builder()
|
||||
.userId(context.getCurrentUserId())
|
||||
.userEmail(context.getCurrentUserEmail())
|
||||
.roleActif(context.getRoleActif())
|
||||
.organisationActiveId(context.getOrganisationId())
|
||||
.actionType(actionType)
|
||||
.entityType(entityType)
|
||||
.entityId(entityId)
|
||||
.description(description)
|
||||
.payloadAvant(toJson(payloadAvant))
|
||||
.payloadApres(toJson(payloadApres))
|
||||
.metadata(toJson(metadata))
|
||||
.sodCheckPassed(sodCheckPassed)
|
||||
.sodViolations(sodViolations)
|
||||
.operationAt(LocalDateTime.now())
|
||||
.build();
|
||||
repository.persist(entry);
|
||||
} catch (Exception e) {
|
||||
// Fail-soft : l'audit trail ne doit jamais bloquer une opération métier.
|
||||
// Les violations sont loguées et peuvent être détectées via les logs applicatifs.
|
||||
LOG.errorf(e,
|
||||
"Audit trail log failed: entityType=%s entityId=%s actionType=%s description=%s",
|
||||
entityType, entityId, actionType, description);
|
||||
}
|
||||
}
|
||||
|
||||
/** Variante sans payload — pour les actions simples (LOGIN, LOGOUT, EXPORT...). */
|
||||
@Transactional
|
||||
public void logSimple(String entityType, UUID entityId, String actionType, String description) {
|
||||
log(entityType, entityId, actionType, description, null);
|
||||
}
|
||||
|
||||
private String toJson(Object o) {
|
||||
if (o == null) return null;
|
||||
try {
|
||||
return objectMapper.writeValueAsString(o);
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Audit trail JSON serialization failed for %s : %s",
|
||||
o.getClass().getSimpleName(), e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user