Compare commits

..

9 Commits

Author SHA1 Message Date
dahoud
db36727001 feat(sprint-17 api 2026-04-25): bump 1.0.10 + DTO KpiPublicSnapshot pour partage KPI public signé
Sous-ensemble strictement filtré de ComplianceSnapshot — vue read-only pour autorités
externes (BCEAO, ARTCI, CENTIF). N'expose que les indicateurs agrégés, jamais
d'informations sensibles (complianceOfficerId, payloads, IDs membres, PEP details).

Champs : organisationNom, referentielComptable, scoreGlobal, complianceOfficerDesigne,
agAnnuelleStatut, rapportAirmsStatut, dirigeantsAvecCmu, tauxKycAJourPct,
tauxFormationLbcFtPct, couvertureUboPct, commissaireAuxComptesStatut, dateGeneration.

Tests Jacoco 100% (3 tests : builder all-fields, null defaults, equals/hash/toString)

ACTION USER : `mvn clean install` puis `mvn deploy` pour 1.0.10 sur Gitea.
2026-04-25 16:47:54 +00:00
dahoud
7a596f68bd feat(sprint-13.A api 2026-04-25): bump 1.0.9 + OrganisationResponse expose referentielComptable + complianceOfficerId
Pendant pour Sprint 10 — UpdateOrganisationRequest avait déjà ces 2 champs (Sprint 10),
mais OrganisationResponse ne les exposait pas en lecture. Le formulaire d'édition web
ne pouvait donc pas afficher leur valeur initiale lors du load.

Quarkus inchangé (3.27.3 — décision flotte LTS sur 3.27 jusqu'à fin août 2026).

Modifications
- OrganisationResponse : 2 nouveaux champs String referentielComptable + UUID complianceOfficerId
- Tests : 2 tests Jacoco 100% (null par défaut, builder, setter ; pour les 2 nouveaux champs)
- Bump version 1.0.8 → 1.0.9

ACTION USER : `mvn clean install` puis `mvn deploy` pour publier 1.0.9 sur Gitea.
2026-04-25 15:22:26 +00:00
dahoud
f263d4f51a revert(sprint-14 api): rollback 1.0.9 → 1.0.8 — flotte reste sur Quarkus 3.27.3 LTS
Décision stratégique : rester sur Quarkus 3.27.3 pour TOUTE la flotte Lions
jusqu'à fin août 2026 (1 mois avant EOL 3.27 = 24 sept 2026).

Justification :
- 3.27.3 LTS = 5 mois de runway, pas urgent
- Cohérence flotte (10 projets actuellement sur 3.27.3)
- Évite saut imprévu PrimeFaces 14 → 15 (extension quarkus-primefaces 4.15.14
  cible Quarkus 3.32.2 et impose PF15.0.14 — incompat thème Freya 5.0.0 acheté)
- LUM en prod 44 jours — pas de re-déploiement risqué
- 3.39 LTS attendue ~sept 2026 (juste avant EOL 3.27) → migration directe
  3.27 → 3.39 prévue avec écosystème PF15+Freya mature

Restaurer pom.xml 1.0.8 + Quarkus 3.27.3 (état post-Sprint 10).
1.0.9 (commit 6f23319) jamais publié sur Gitea — fix local seulement.
2026-04-25 15:10:55 +00:00
dahoud
6f233193d4 build(sprint-14 api 2026-04-25): bump 1.0.8 → 1.0.9 + Quarkus 3.27.3 → 3.33.0 LTS
Migration LTS dans la fenêtre officielle. Module DTOs records purs — aucun runtime
Quarkus consommé directement, le bump de quarkus.platform.version est documentaire
(aligned-with) pour cohérence flotte.

Aucun breaking change applicable (records + Bean Validation + tests JUnit 5).
Tests existants : 3530 + jacoco 100% — doivent rester verts au build.

ACTION USER : `mvn clean install` puis `mvn deploy` pour publier 1.0.9 sur Gitea.
2026-04-25 15:00:34 +00:00
dahoud
13635e269b test(sprint-10 api): tests DTOs Sprint 10 pour Jacoco 100%
Build api 1.0.8 a échoué sur jacoco:check (97-99% < 100% requis). Ajout des tests
manquants pour les 6 nouveaux DTOs + extension UpdateOrganisationRequestTest.

Tests ajoutés
- CreateBeneficiaireEffectifRequestTest : 8 tests (builder all-fields, validation 5 champs obligatoires, nationalité ISO-3, natureControle regex, pourcentage 0-100, paysResidence vide accepté, valide complet, equals/hash/toString)
- UpdateBeneficiaireEffectifRequestTest : 7 tests (builder, vide valide, pourcentage > 100, nationalité format, natureControle invalide / vide, equals/hash/toString)
- BeneficiaireEffectifResponseTest : 3 tests (all-fields, null defaults, equals)
- AuditTrailOperationResponseTest : 3 tests (all-fields, null defaults, equals)
- CreateRoleDelegationRequestTest : 6 tests (builder, 6 champs obligatoires, role regex, dateFin future, valide, equals)
- RoleDelegationResponseTest : 3 tests (all-fields, null, equals)
- UpdateOrganisationRequestTest : 4 tests ajoutés (referentielComptable valide × 3, vide accepté, invalide, complianceOfficerId)

Total +34 tests, attendus pour rétablir Jacoco 100% bundle.
2026-04-25 12:42:06 +00:00
dahoud
65a9d03c00 feat(sprint-10 api 2026-04-25): bump 1.0.8 + DTOs UBO/audit-trail/délégation + UpdateOrganisationRequest enrichi
Pré-requis pour exposer les features Sprints 1-2 via REST. Architecture stricte :
DTOs en module api (contrat public), aucune logique métier.

Version : 1.0.7 → 1.0.8

DTOs UBO (Instr. BCEAO 003-03-2025)
- CreateBeneficiaireEffectifRequest : validation pays ISO-3, regex natureControle (5 valeurs), pourcentages 0-100
- UpdateBeneficiaireEffectifRequest : tous optionnels (PATCH partiel)
- BeneficiaireEffectifResponse : vue read-only

DTOs audit trail (Sprint 1)
- AuditTrailOperationResponse : payloadAvant/payloadApres/metadata en String JSONB

DTOs délégation rôles (Sprint 2)
- CreateRoleDelegationRequest : regex rôle uppercase + dates futures
- RoleDelegationResponse : statut + estActive (calculé)

Enrich UpdateOrganisationRequest
- referentielComptable (regex SYSCOHADA|SYCEBNL|PCSFD_UMOA)
- complianceOfficerId UUID (Instr. BCEAO 001-03-2025)

À publier sur Gitea via script/publish-api.sh — user action requise.
2026-04-25 12:31:43 +00:00
2d21a580de docs: bump published version ref 2.0.0→1.0.7 (ligne publication Gitea Maven 2026-04-24, module autonome) 2026-04-24 18:06:09 +00:00
3feab6518e chore(quarkus-327): drop parent-pom.xml (all modules autonomous), clean publish-api scripts 2026-04-23 15:50:22 +00:00
7fa862b755 chore(quarkus-327): bump to Quarkus 3.27.3 LTS, make pom autonomous, bump to 1.0.7 2026-04-23 14:42:55 +00:00
434 changed files with 49336 additions and 48405 deletions

View File

@@ -95,10 +95,12 @@ Ajouter à votre `pom.xml` :
<dependency>
<groupId>dev.lions.unionflow</groupId>
<artifactId>unionflow-server-api</artifactId>
<version>2.0.0</version>
<version>1.0.7</version>
</dependency>
```
> **Version publiée Gitea Maven (2026-04-24)** : `1.0.7` — module autonome (pas de `unionflow-parent`), aligné Quarkus 3.27.3 LTS.
### Repository Gitea (Maven Registry)
Configurer `~/.m2/settings.xml` :
@@ -543,6 +545,6 @@ Propriétaire - © 2026 Lions Club Côte d'Ivoire
---
**Version** : 2.0.0
**Dernière mise à jour** : 2026-03-14
**Version publiée Gitea Maven** : 1.0.7 (2026-04-24)
**Dernière mise à jour** : 2026-04-24
**Auteur** : Équipe UnionFlow

View File

@@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.lions.unionflow</groupId>
<artifactId>unionflow-parent</artifactId>
<version>1.0.5</version>
<packaging>pom</packaging>
<name>UnionFlow - Parent</name>
<description>Plateforme complète de gestion d'union — POM parent partagé</description>
<distributionManagement>
<repository>
<id>gitea-lionsdev</id>
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
</repository>
<snapshotRepository>
<id>gitea-lionsdev</id>
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>gitea-lionsdev</id>
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.version>3.20.0</quarkus.platform.version>
<lombok.version>1.18.36</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.lions.unionflow</groupId>
<artifactId>unionflow-server-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
<encoding>UTF-8</encoding>
<parameters>true</parameters>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

32
pom.xml
View File

@@ -4,23 +4,39 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.lions.unionflow</groupId>
<artifactId>unionflow-parent</artifactId>
<version>1.0.5</version>
<relativePath>parent-pom.xml</relativePath>
</parent>
<groupId>dev.lions.unionflow</groupId>
<artifactId>unionflow-server-api</artifactId>
<version>1.0.10</version>
<packaging>jar</packaging>
<name>UnionFlow Server API</name>
<description>API définitions pour le serveur UnionFlow</description>
<distributionManagement>
<repository>
<id>gitea-lionsdev</id>
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
</repository>
</distributionManagement>
<repositories>
<repository>
<id>gitea-lionsdev</id>
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.release>${java.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<quarkus.platform.version>3.20.0</quarkus.platform.version>
<quarkus.platform.version>3.27.3</quarkus.platform.version>
<lombok.version>1.18.38</lombok.version>
<jackson.version>2.18.2</jackson.version>
<validation-api.version>3.0.2</validation-api.version>
<microprofile-openapi.version>3.1.1</microprofile-openapi.version>

View File

@@ -1,28 +1,15 @@
@echo off
REM Publie le parent pom + server-api sur le Gitea Package Registry
REM Publie unionflow-server-api sur le Gitea Package Registry
REM Usage : script\publish-api.bat
REM Depuis : n'importe où dans le repo server-api
REM Prérequis: credentials dans %USERPROFILE%\.m2\settings.xml (server id: gitea-lionsdev)
set REGISTRY_URL=https://git.lions.dev/api/packages/lionsdev/maven
set REGISTRY_ID=gitea-lionsdev
REM Depuis : n'importe ou dans le repo server-api
REM Prerequis: credentials dans %USERPROFILE%\.m2\settings.xml (server id: gitea-lionsdev)
REM
REM Note : unionflow-parent n'existe plus (modules autonomes depuis Quarkus 3.27.3).
cd /d "%~dp0\.."
echo.
echo [1/2] Publication du parent pom...
call mvn deploy:deploy-file ^
-DgroupId=dev.lions.unionflow ^
-DartifactId=unionflow-parent ^
-Dversion=1.0.0 ^
-Dpackaging=pom ^
-Dfile=parent-pom.xml ^
-DrepositoryId=%REGISTRY_ID% ^
-Durl=%REGISTRY_URL%
if errorlevel 409 echo [WARN] Parent pom deja publie pour cette version, on continue.
echo.
echo [2/2] Publication du server-api...
echo Publication du server-api...
call mvn deploy -DskipTests
if errorlevel 409 echo [WARN] Server-api deja publie - incrementer la version pour republier.

View File

@@ -1,30 +1,18 @@
#!/bin/bash
# Publie le parent pom + server-api sur le Gitea Package Registry
# Publie unionflow-server-api sur le Gitea Package Registry
# Usage : ./script/publish-api.sh
# Depuis : n'importe où dans le repo server-api
# Prérequis: credentials dans ~/.m2/settings.xml (server id: gitea-lionsdev)
#
# Note : unionflow-parent n'existe plus (les 3 modules sont autonomes
# depuis la migration Quarkus 3.27.3). Ne publier que le module server-api.
set -e
REGISTRY_URL="https://git.lions.dev/api/packages/lionsdev/maven"
REGISTRY_ID="gitea-lionsdev"
cd "$(dirname "$0")/.."
echo ""
echo "[1/2] Publication du parent pom..."
mvn deploy:deploy-file \
-DgroupId=dev.lions.unionflow \
-DartifactId=unionflow-parent \
-Dversion=1.0.5 \
-Dpackaging=pom \
-Dfile=parent-pom.xml \
-DrepositoryId="${REGISTRY_ID}" \
-Durl="${REGISTRY_URL}" \
|| echo "[WARN] Parent pom déjà publié pour cette version (409), on continue."
echo ""
echo "[2/2] Publication du server-api..."
echo "Publication du server-api..."
mvn deploy -DskipTests \
|| echo "[WARN] Server-api déjà publié pour cette version (409) - incrémenter la version pour republier."

View File

@@ -0,0 +1,36 @@
package dev.lions.unionflow.server.api.dto.audit.response;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Builder;
/**
* Vue d'une opération d'audit trail enrichi (Sprint 1, exposée Sprint 10).
*
* <p>Les payloads JSONB ({@code payloadAvant}, {@code payloadApres}, {@code metadata})
* sont sérialisés en string ; le frontend les parse selon les besoins.
*
* @since 2026-04-25 (Sprint 10)
*/
@Builder
public record AuditTrailOperationResponse(
UUID id,
UUID userId,
String userEmail,
String roleActif,
UUID organisationActiveId,
String actionType,
String entityType,
UUID entityId,
String description,
String ipAddress,
String userAgent,
UUID requestId,
String payloadAvant,
String payloadApres,
String metadata,
Boolean sodCheckPassed,
String sodViolations,
LocalDateTime operationAt
) {
}

View File

@@ -0,0 +1,57 @@
package dev.lions.unionflow.server.api.dto.compliance.response;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Builder;
/**
* Snapshot KPI public (Sprint 17) — vue read-only pour autorités externes (BCEAO, ARTCI, CENTIF).
*
* <p>Sous-ensemble strictement filtré de la conformité : indicateurs agrégés uniquement,
* jamais de données sensibles ({@code complianceOfficerId}, payloads détaillés, IDs membres,
* informations PEP). L'autorité voit le score, l'état des obligations, les pourcentages
* de couverture — pas l'intimité opérationnelle.
*
* <p>Diffusé via URL signée temporairement (HMAC-SHA256, expiry stricte).
*
* @since 2026-04-25 (Sprint 17 — transparency réglementaire publique)
*/
@Builder
public record KpiPublicSnapshot(
/** Nom de l'organisation. */
String organisationNom,
/** Référentiel comptable applicable (SYSCOHADA / SYCEBNL / PCSFD_UMOA). */
String referentielComptable,
/** Score conformité agrégé 0-100. */
int scoreGlobal,
/** Compliance Officer désigné (présence uniquement, pas l'identité). */
boolean complianceOfficerDesigne,
/** Statut AG annuelle : OK / EN_ATTENTE / RETARD. */
String agAnnuelleStatut,
/** Statut rapport AIRMS : OK / EN_ATTENTE. */
String rapportAirmsStatut,
/** Nombre de dirigeants enrôlés CMU (sans liste). */
int dirigeantsAvecCmu,
/** Pourcentage KYC à jour (membres). */
BigDecimal tauxKycAJourPct,
/** Pourcentage formation LBC/FT (membres formés dans les 12 derniers mois). */
BigDecimal tauxFormationLbcFtPct,
/** Pourcentage couverture UBO (Bénéficiaires Effectifs documentés). */
BigDecimal couvertureUboPct,
/** Statut commissaire aux comptes (OPTIONNEL / OBLIGATOIRE). */
String commissaireAuxComptesStatut,
/** Date de génération du snapshot. */
LocalDateTime dateGeneration
) {
}

View File

@@ -0,0 +1,42 @@
package dev.lions.unionflow.server.api.dto.delegation.request;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Builder;
/**
* Requête de création d'une délégation de rôle temporaire.
*
* @since 2026-04-25 (Sprint 10 — service existant Sprint 2)
*/
@Builder
public record CreateRoleDelegationRequest(
@NotNull(message = "L'organisation est obligatoire")
UUID organisationId,
@NotNull(message = "Le déléguant est obligatoire")
UUID delegantUserId,
@NotNull(message = "Le délégataire est obligatoire")
UUID delegataireUserId,
@NotBlank(message = "Le rôle délégué est obligatoire")
@Pattern(regexp = "^[A-Z_]{3,50}$", message = "Format de rôle invalide (uppercase + underscore)")
String roleDelegue,
@NotNull(message = "La date de début est obligatoire")
LocalDateTime dateDebut,
@NotNull(message = "La date de fin est obligatoire")
@Future(message = "La date de fin doit être future")
LocalDateTime dateFin,
@Size(max = 500)
String motif
) {
}

View File

@@ -0,0 +1,27 @@
package dev.lions.unionflow.server.api.dto.delegation.response;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Builder;
/**
* Vue d'une délégation de rôle.
*
* @since 2026-04-25 (Sprint 10)
*/
@Builder
public record RoleDelegationResponse(
UUID id,
UUID organisationId,
UUID delegantUserId,
UUID delegataireUserId,
String roleDelegue,
LocalDateTime dateDebut,
LocalDateTime dateFin,
String motif,
String statut,
LocalDateTime dateRevocation,
LocalDateTime dateCreation,
boolean estActive
) {
}

View File

@@ -0,0 +1,79 @@
package dev.lions.unionflow.server.api.dto.kyc.request;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import lombok.Builder;
/**
* Création d'un Bénéficiaire Effectif (UBO) — Instruction BCEAO 003-03-2025.
*
* <p>Lié à un {@code KycDossier} d'entreprise. Au moins un identifiant cible est requis :
* {@code kycDossierId} pour KYC personne morale, ou {@code organisationCibleId} +
* {@code membreId} pour membre individuel à risque.
*
* @since 2026-04-25 (Sprint 10)
*/
@Builder
public record CreateBeneficiaireEffectifRequest(
UUID kycDossierId,
UUID organisationCibleId,
UUID membreId,
@NotBlank(message = "Le nom est obligatoire")
@Size(max = 100)
String nom,
@NotBlank(message = "Les prénoms sont obligatoires")
@Size(max = 200)
String prenoms,
@NotNull(message = "La date de naissance est obligatoire")
LocalDate dateNaissance,
@Size(max = 200)
String lieuNaissance,
@NotBlank(message = "La nationalité est obligatoire (ISO-3)")
@Pattern(regexp = "^[A-Z]{3}$", message = "Code ISO-3 (ex: CIV, FRA)")
String nationalite,
@Pattern(regexp = "^[A-Z]{3}$|^$", message = "Code ISO-3")
String paysResidence,
TypePieceIdentite typePieceIdentite,
@Size(max = 50)
String numeroPieceIdentite,
LocalDate dateExpirationPiece,
@DecimalMin(value = "0.00")
@DecimalMax(value = "100.00")
BigDecimal pourcentageCapital,
@DecimalMin(value = "0.00")
@DecimalMax(value = "100.00")
BigDecimal pourcentageDroitsVote,
@NotBlank(message = "La nature du contrôle est obligatoire")
@Pattern(regexp = "^(DETENTION_CAPITAL|DROITS_VOTE|CONTROLE_DE_FAIT|BENEFICIAIRE_ULTIME|MANDAT_REPRESENTATION)$",
message = "Nature du contrôle invalide")
String natureControle,
Boolean estPep,
@Size(max = 100) String pepCategorie,
@Pattern(regexp = "^[A-Z]{3}$|^$") String pepPays,
@Size(max = 200) String pepFonction,
Boolean presenceListesSanctions,
@Size(max = 1000) String detailsListesSanctions
) {
}

View File

@@ -0,0 +1,40 @@
package dev.lions.unionflow.server.api.dto.kyc.request;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Builder;
/**
* Mise à jour partielle d'un Bénéficiaire Effectif (UBO).
* Tous les champs sont optionnels — seul ce qui est fourni est mis à jour.
*
* @since 2026-04-25 (Sprint 10)
*/
@Builder
public record UpdateBeneficiaireEffectifRequest(
@Size(max = 100) String nom,
@Size(max = 200) String prenoms,
LocalDate dateNaissance,
@Size(max = 200) String lieuNaissance,
@Pattern(regexp = "^[A-Z]{3}$|^$") String nationalite,
@Pattern(regexp = "^[A-Z]{3}$|^$") String paysResidence,
@DecimalMin("0.00") @DecimalMax("100.00") BigDecimal pourcentageCapital,
@DecimalMin("0.00") @DecimalMax("100.00") BigDecimal pourcentageDroitsVote,
@Pattern(regexp = "^(DETENTION_CAPITAL|DROITS_VOTE|CONTROLE_DE_FAIT|BENEFICIAIRE_ULTIME|MANDAT_REPRESENTATION)?$")
String natureControle,
Boolean estPep,
@Size(max = 100) String pepCategorie,
@Pattern(regexp = "^[A-Z]{3}$|^$") String pepPays,
@Size(max = 200) String pepFonction,
Boolean presenceListesSanctions,
@Size(max = 1000) String detailsListesSanctions
) {
}

View File

@@ -0,0 +1,43 @@
package dev.lions.unionflow.server.api.dto.kyc.response;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Builder;
/**
* Vue d'un Bénéficiaire Effectif (UBO).
*
* @since 2026-04-25 (Sprint 10)
*/
@Builder
public record BeneficiaireEffectifResponse(
UUID id,
UUID kycDossierId,
UUID organisationCibleId,
UUID membreId,
String nom,
String prenoms,
LocalDate dateNaissance,
String lieuNaissance,
String nationalite,
String paysResidence,
TypePieceIdentite typePieceIdentite,
String numeroPieceIdentite,
LocalDate dateExpirationPiece,
BigDecimal pourcentageCapital,
BigDecimal pourcentageDroitsVote,
String natureControle,
boolean estPep,
String pepCategorie,
String pepPays,
String pepFonction,
boolean presenceListesSanctions,
String detailsListesSanctions,
LocalDateTime dateCreation,
LocalDateTime dateModification,
Boolean actif
) {
}

View File

@@ -2,9 +2,11 @@ package dev.lions.unionflow.server.api.dto.organisation.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import lombok.Builder;
@@ -51,5 +53,13 @@ public record UpdateOrganisationRequest(
@Size(max = 100) String pays,
@Size(max = 20) String codePostal,
Boolean organisationPublique,
Boolean accepteNouveauxMembres) {
Boolean accepteNouveauxMembres,
/** Référentiel comptable applicable (Sprint 1) — SYSCOHADA / SYCEBNL / PCSFD_UMOA. */
@Pattern(regexp = "^(SYSCOHADA|SYCEBNL|PCSFD_UMOA)?$",
message = "Référentiel comptable invalide")
String referentielComptable,
/** UUID du Compliance Officer désigné (Instr. BCEAO 001-03-2025). */
UUID complianceOfficerId) {
}

View File

@@ -91,4 +91,11 @@ public class OrganisationResponse extends BaseResponse {
// ── Paramètres ─────────────────────────────
private Boolean organisationPublique;
private Boolean accepteNouveauxMembres;
// ── Conformité (Sprint 1 — Instr. BCEAO + OHADA) ─────────────────────
/** Référentiel comptable applicable : SYSCOHADA, SYCEBNL, PCSFD_UMOA. */
private String referentielComptable;
/** UUID du Compliance Officer désigné (Instr. BCEAO 001-03-2025). */
private UUID complianceOfficerId;
}

View File

@@ -0,0 +1,67 @@
package dev.lions.unionflow.server.api.dto.audit.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class AuditTrailOperationResponseTest {
@Test
void testBuilder_AllFields() {
UUID id = UUID.randomUUID();
UUID userId = UUID.randomUUID();
UUID orgId = UUID.randomUUID();
UUID entityId = UUID.randomUUID();
UUID requestId = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
AuditTrailOperationResponse r = AuditTrailOperationResponse.builder()
.id(id).userId(userId).userEmail("a@b.com").roleActif("TRESORIER")
.organisationActiveId(orgId)
.actionType("PAYMENT_INITIATED").entityType("Paiement").entityId(entityId)
.description("Paiement initié")
.ipAddress("192.168.1.1").userAgent("Mozilla").requestId(requestId)
.payloadAvant("{}").payloadApres("{\"k\":1}").metadata("{\"src\":\"web\"}")
.sodCheckPassed(true).sodViolations(null)
.operationAt(now)
.build();
assertThat(r.id()).isEqualTo(id);
assertThat(r.userId()).isEqualTo(userId);
assertThat(r.userEmail()).isEqualTo("a@b.com");
assertThat(r.roleActif()).isEqualTo("TRESORIER");
assertThat(r.organisationActiveId()).isEqualTo(orgId);
assertThat(r.actionType()).isEqualTo("PAYMENT_INITIATED");
assertThat(r.entityType()).isEqualTo("Paiement");
assertThat(r.entityId()).isEqualTo(entityId);
assertThat(r.description()).isEqualTo("Paiement initié");
assertThat(r.ipAddress()).isEqualTo("192.168.1.1");
assertThat(r.userAgent()).isEqualTo("Mozilla");
assertThat(r.requestId()).isEqualTo(requestId);
assertThat(r.payloadAvant()).isEqualTo("{}");
assertThat(r.payloadApres()).isEqualTo("{\"k\":1}");
assertThat(r.metadata()).isEqualTo("{\"src\":\"web\"}");
assertThat(r.sodCheckPassed()).isTrue();
assertThat(r.sodViolations()).isNull();
assertThat(r.operationAt()).isEqualTo(now);
}
@Test
void testBuilder_NullFields() {
AuditTrailOperationResponse r = AuditTrailOperationResponse.builder().build();
assertThat(r.id()).isNull();
assertThat(r.sodCheckPassed()).isNull();
}
@Test
void testEqualsHashCodeToString() {
UUID id = UUID.randomUUID();
AuditTrailOperationResponse a = AuditTrailOperationResponse.builder().id(id).build();
AuditTrailOperationResponse b = AuditTrailOperationResponse.builder().id(id).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("AuditTrailOperationResponse");
}
}

View File

@@ -0,0 +1,64 @@
package dev.lions.unionflow.server.api.dto.compliance.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
class KpiPublicSnapshotTest {
@Test
void testBuilder_AllFields() {
LocalDateTime now = LocalDateTime.of(2026, 4, 25, 12, 0);
KpiPublicSnapshot s = KpiPublicSnapshot.builder()
.organisationNom("Mutuelle Test")
.referentielComptable("SYCEBNL")
.scoreGlobal(82)
.complianceOfficerDesigne(true)
.agAnnuelleStatut("OK")
.rapportAirmsStatut("EN_ATTENTE")
.dirigeantsAvecCmu(3)
.tauxKycAJourPct(new BigDecimal("85.5"))
.tauxFormationLbcFtPct(new BigDecimal("70.0"))
.couvertureUboPct(new BigDecimal("60.0"))
.commissaireAuxComptesStatut("OPTIONNEL")
.dateGeneration(now)
.build();
assertThat(s.organisationNom()).isEqualTo("Mutuelle Test");
assertThat(s.referentielComptable()).isEqualTo("SYCEBNL");
assertThat(s.scoreGlobal()).isEqualTo(82);
assertThat(s.complianceOfficerDesigne()).isTrue();
assertThat(s.agAnnuelleStatut()).isEqualTo("OK");
assertThat(s.rapportAirmsStatut()).isEqualTo("EN_ATTENTE");
assertThat(s.dirigeantsAvecCmu()).isEqualTo(3);
assertThat(s.tauxKycAJourPct()).isEqualByComparingTo("85.5");
assertThat(s.tauxFormationLbcFtPct()).isEqualByComparingTo("70.0");
assertThat(s.couvertureUboPct()).isEqualByComparingTo("60.0");
assertThat(s.commissaireAuxComptesStatut()).isEqualTo("OPTIONNEL");
assertThat(s.dateGeneration()).isEqualTo(now);
}
@Test
void testBuilder_NullDefaults() {
KpiPublicSnapshot empty = KpiPublicSnapshot.builder().build();
assertThat(empty.organisationNom()).isNull();
assertThat(empty.scoreGlobal()).isZero();
assertThat(empty.complianceOfficerDesigne()).isFalse();
assertThat(empty.dirigeantsAvecCmu()).isZero();
assertThat(empty.tauxKycAJourPct()).isNull();
}
@Test
void testEqualsHashCodeToString() {
LocalDateTime t = LocalDateTime.of(2026, 4, 25, 10, 0);
KpiPublicSnapshot a = KpiPublicSnapshot.builder()
.organisationNom("Org").scoreGlobal(80).dateGeneration(t).build();
KpiPublicSnapshot b = KpiPublicSnapshot.builder()
.organisationNom("Org").scoreGlobal(80).dateGeneration(t).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("KpiPublicSnapshot");
}
}

View File

@@ -0,0 +1,106 @@
package dev.lions.unionflow.server.api.dto.delegation.request;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CreateRoleDelegationRequestTest {
private Validator validator;
@BeforeEach
void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
private CreateRoleDelegationRequest.CreateRoleDelegationRequestBuilder valide() {
return CreateRoleDelegationRequest.builder()
.organisationId(UUID.randomUUID())
.delegantUserId(UUID.randomUUID())
.delegataireUserId(UUID.randomUUID())
.roleDelegue("TRESORIER")
.dateDebut(LocalDateTime.now().plusHours(1))
.dateFin(LocalDateTime.now().plusDays(14));
}
@Test
void testBuilder_AllFields() {
UUID org = UUID.randomUUID();
UUID delegant = UUID.randomUUID();
UUID delegataire = UUID.randomUUID();
LocalDateTime debut = LocalDateTime.of(2026, 5, 1, 9, 0);
LocalDateTime fin = LocalDateTime.of(2026, 5, 15, 18, 0);
CreateRoleDelegationRequest req = CreateRoleDelegationRequest.builder()
.organisationId(org).delegantUserId(delegant).delegataireUserId(delegataire)
.roleDelegue("TRESORIER").dateDebut(debut).dateFin(fin).motif("Congé")
.build();
assertThat(req.organisationId()).isEqualTo(org);
assertThat(req.delegantUserId()).isEqualTo(delegant);
assertThat(req.delegataireUserId()).isEqualTo(delegataire);
assertThat(req.roleDelegue()).isEqualTo("TRESORIER");
assertThat(req.dateDebut()).isEqualTo(debut);
assertThat(req.dateFin()).isEqualTo(fin);
assertThat(req.motif()).isEqualTo("Congé");
}
@Test
void testValidation_ChampsObligatoires() {
CreateRoleDelegationRequest req = CreateRoleDelegationRequest.builder().build();
Set<ConstraintViolation<CreateRoleDelegationRequest>> v = validator.validate(req);
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("organisationId"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("delegantUserId"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("delegataireUserId"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("roleDelegue"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("dateDebut"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("dateFin"));
}
@Test
void testValidation_RoleFormatInvalide() {
CreateRoleDelegationRequest req = valide().roleDelegue("admin-org").build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("roleDelegue"));
}
@Test
void testValidation_DateFinPassee() {
CreateRoleDelegationRequest req = valide()
.dateFin(LocalDateTime.now().minusDays(1)).build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("dateFin"));
}
@Test
void testValidation_Valide() {
assertThat(validator.validate(valide().motif("OK").build())).isEmpty();
}
@Test
void testEqualsHashCodeToString() {
UUID org = UUID.randomUUID();
UUID d1 = UUID.randomUUID();
UUID d2 = UUID.randomUUID();
LocalDateTime debut = LocalDateTime.now().plusHours(1);
LocalDateTime fin = LocalDateTime.now().plusDays(7);
CreateRoleDelegationRequest a = CreateRoleDelegationRequest.builder()
.organisationId(org).delegantUserId(d1).delegataireUserId(d2)
.roleDelegue("TRESORIER").dateDebut(debut).dateFin(fin).build();
CreateRoleDelegationRequest b = CreateRoleDelegationRequest.builder()
.organisationId(org).delegantUserId(d1).delegataireUserId(d2)
.roleDelegue("TRESORIER").dateDebut(debut).dateFin(fin).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("CreateRoleDelegationRequest");
}
}

View File

@@ -0,0 +1,59 @@
package dev.lions.unionflow.server.api.dto.delegation.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class RoleDelegationResponseTest {
@Test
void testBuilder_AllFields() {
UUID id = UUID.randomUUID();
UUID org = UUID.randomUUID();
UUID d1 = UUID.randomUUID();
UUID d2 = UUID.randomUUID();
LocalDateTime debut = LocalDateTime.of(2026, 5, 1, 9, 0);
LocalDateTime fin = LocalDateTime.of(2026, 5, 15, 18, 0);
LocalDateTime revoc = LocalDateTime.of(2026, 5, 10, 12, 0);
LocalDateTime cree = LocalDateTime.of(2026, 4, 25, 12, 0);
RoleDelegationResponse r = RoleDelegationResponse.builder()
.id(id).organisationId(org).delegantUserId(d1).delegataireUserId(d2)
.roleDelegue("TRESORIER").dateDebut(debut).dateFin(fin).motif("Congé")
.statut("REVOQUEE").dateRevocation(revoc).dateCreation(cree).estActive(false)
.build();
assertThat(r.id()).isEqualTo(id);
assertThat(r.organisationId()).isEqualTo(org);
assertThat(r.delegantUserId()).isEqualTo(d1);
assertThat(r.delegataireUserId()).isEqualTo(d2);
assertThat(r.roleDelegue()).isEqualTo("TRESORIER");
assertThat(r.dateDebut()).isEqualTo(debut);
assertThat(r.dateFin()).isEqualTo(fin);
assertThat(r.motif()).isEqualTo("Congé");
assertThat(r.statut()).isEqualTo("REVOQUEE");
assertThat(r.dateRevocation()).isEqualTo(revoc);
assertThat(r.dateCreation()).isEqualTo(cree);
assertThat(r.estActive()).isFalse();
}
@Test
void testBuilder_NullFields() {
RoleDelegationResponse r = RoleDelegationResponse.builder().build();
assertThat(r.id()).isNull();
assertThat(r.statut()).isNull();
assertThat(r.estActive()).isFalse();
}
@Test
void testEqualsHashCodeToString() {
UUID id = UUID.randomUUID();
RoleDelegationResponse a = RoleDelegationResponse.builder().id(id).build();
RoleDelegationResponse b = RoleDelegationResponse.builder().id(id).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("RoleDelegationResponse");
}
}

View File

@@ -0,0 +1,133 @@
package dev.lions.unionflow.server.api.dto.kyc.request;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CreateBeneficiaireEffectifRequestTest {
private Validator validator;
@BeforeEach
void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
private CreateBeneficiaireEffectifRequest.CreateBeneficiaireEffectifRequestBuilder valide() {
return CreateBeneficiaireEffectifRequest.builder()
.kycDossierId(UUID.randomUUID())
.nom("Dupont")
.prenoms("Jean Pierre")
.dateNaissance(LocalDate.of(1980, 5, 15))
.nationalite("CIV")
.natureControle("DETENTION_CAPITAL");
}
@Test
void testBuilder_AllFields() {
UUID kyc = UUID.randomUUID();
UUID org = UUID.randomUUID();
UUID membre = UUID.randomUUID();
CreateBeneficiaireEffectifRequest req = CreateBeneficiaireEffectifRequest.builder()
.kycDossierId(kyc).organisationCibleId(org).membreId(membre)
.nom("Doe").prenoms("John")
.dateNaissance(LocalDate.of(1980, 1, 1))
.lieuNaissance("Abidjan")
.nationalite("CIV").paysResidence("FRA")
.typePieceIdentite(TypePieceIdentite.PASSEPORT)
.numeroPieceIdentite("AB123")
.dateExpirationPiece(LocalDate.of(2030, 1, 1))
.pourcentageCapital(new BigDecimal("30"))
.pourcentageDroitsVote(new BigDecimal("25"))
.natureControle("BENEFICIAIRE_ULTIME")
.estPep(true).pepCategorie("MINISTRE").pepPays("CIV").pepFonction("Min Finance")
.presenceListesSanctions(true).detailsListesSanctions("Liste UN 2024")
.build();
assertThat(req.kycDossierId()).isEqualTo(kyc);
assertThat(req.organisationCibleId()).isEqualTo(org);
assertThat(req.membreId()).isEqualTo(membre);
assertThat(req.nom()).isEqualTo("Doe");
assertThat(req.prenoms()).isEqualTo("John");
assertThat(req.lieuNaissance()).isEqualTo("Abidjan");
assertThat(req.paysResidence()).isEqualTo("FRA");
assertThat(req.typePieceIdentite()).isEqualTo(TypePieceIdentite.PASSEPORT);
assertThat(req.numeroPieceIdentite()).isEqualTo("AB123");
assertThat(req.dateExpirationPiece()).isEqualTo(LocalDate.of(2030, 1, 1));
assertThat(req.pourcentageCapital()).isEqualByComparingTo("30");
assertThat(req.pourcentageDroitsVote()).isEqualByComparingTo("25");
assertThat(req.natureControle()).isEqualTo("BENEFICIAIRE_ULTIME");
assertThat(req.estPep()).isTrue();
assertThat(req.pepCategorie()).isEqualTo("MINISTRE");
assertThat(req.pepPays()).isEqualTo("CIV");
assertThat(req.pepFonction()).isEqualTo("Min Finance");
assertThat(req.presenceListesSanctions()).isTrue();
assertThat(req.detailsListesSanctions()).isEqualTo("Liste UN 2024");
}
@Test
void testValidation_ChampsObligatoires() {
CreateBeneficiaireEffectifRequest req = CreateBeneficiaireEffectifRequest.builder().build();
Set<ConstraintViolation<CreateBeneficiaireEffectifRequest>> v = validator.validate(req);
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("nom"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("prenoms"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("dateNaissance"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("nationalite"));
assertThat(v).anyMatch(x -> x.getPropertyPath().toString().equals("natureControle"));
}
@Test
void testValidation_NationaliteFormatInvalide() {
CreateBeneficiaireEffectifRequest req = valide().nationalite("FR").build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("nationalite"));
}
@Test
void testValidation_NatureControleInvalide() {
CreateBeneficiaireEffectifRequest req = valide().natureControle("AUTRE").build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("natureControle"));
}
@Test
void testValidation_PourcentageHorsBornes() {
CreateBeneficiaireEffectifRequest req = valide().pourcentageCapital(new BigDecimal("150")).build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("pourcentageCapital"));
}
@Test
void testValidation_PaysResidenceVideAccepte() {
CreateBeneficiaireEffectifRequest req = valide().paysResidence("").build();
assertThat(validator.validate(req))
.noneMatch(v -> v.getPropertyPath().toString().equals("paysResidence"));
}
@Test
void testValidation_Valide() {
assertThat(validator.validate(valide().build())).isEmpty();
}
@Test
void testEqualsHashCodeToString() {
CreateBeneficiaireEffectifRequest a = valide().build();
CreateBeneficiaireEffectifRequest b = CreateBeneficiaireEffectifRequest.builder()
.kycDossierId(a.kycDossierId()).nom(a.nom()).prenoms(a.prenoms())
.dateNaissance(a.dateNaissance()).nationalite(a.nationalite())
.natureControle(a.natureControle()).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("CreateBeneficiaireEffectifRequest");
}
}

View File

@@ -0,0 +1,104 @@
package dev.lions.unionflow.server.api.dto.kyc.request;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class UpdateBeneficiaireEffectifRequestTest {
private Validator validator;
@BeforeEach
void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
void testBuilder_AllFields() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
.nom("Doe").prenoms("Jane")
.dateNaissance(LocalDate.of(1990, 6, 1))
.lieuNaissance("Dakar")
.nationalite("SEN").paysResidence("USA")
.pourcentageCapital(new BigDecimal("45"))
.pourcentageDroitsVote(new BigDecimal("50"))
.natureControle("DROITS_VOTE")
.estPep(false).pepCategorie("EX-PEP").pepPays("SEN").pepFonction("Ex-député")
.presenceListesSanctions(false).detailsListesSanctions("RAS")
.build();
assertThat(req.nom()).isEqualTo("Doe");
assertThat(req.prenoms()).isEqualTo("Jane");
assertThat(req.dateNaissance()).isEqualTo(LocalDate.of(1990, 6, 1));
assertThat(req.lieuNaissance()).isEqualTo("Dakar");
assertThat(req.nationalite()).isEqualTo("SEN");
assertThat(req.paysResidence()).isEqualTo("USA");
assertThat(req.pourcentageCapital()).isEqualByComparingTo("45");
assertThat(req.pourcentageDroitsVote()).isEqualByComparingTo("50");
assertThat(req.natureControle()).isEqualTo("DROITS_VOTE");
assertThat(req.estPep()).isFalse();
assertThat(req.pepCategorie()).isEqualTo("EX-PEP");
assertThat(req.pepPays()).isEqualTo("SEN");
assertThat(req.pepFonction()).isEqualTo("Ex-député");
assertThat(req.presenceListesSanctions()).isFalse();
assertThat(req.detailsListesSanctions()).isEqualTo("RAS");
}
@Test
void testBuilder_VideValide() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder().build();
Set<ConstraintViolation<UpdateBeneficiaireEffectifRequest>> v = validator.validate(req);
assertThat(v).isEmpty();
}
@Test
void testValidation_PourcentageDepasse100() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
.pourcentageCapital(new BigDecimal("110")).build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("pourcentageCapital"));
}
@Test
void testValidation_NationaliteFormatInvalide() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
.nationalite("FR").build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("nationalite"));
}
@Test
void testValidation_NatureControleInvalide() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
.natureControle("INVALIDE").build();
assertThat(validator.validate(req))
.anyMatch(v -> v.getPropertyPath().toString().equals("natureControle"));
}
@Test
void testValidation_NatureControleVideAccepte() {
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
.natureControle("").build();
assertThat(validator.validate(req))
.noneMatch(v -> v.getPropertyPath().toString().equals("natureControle"));
}
@Test
void testEqualsHashCodeToString() {
UpdateBeneficiaireEffectifRequest a = UpdateBeneficiaireEffectifRequest.builder()
.nom("X").build();
UpdateBeneficiaireEffectifRequest b = UpdateBeneficiaireEffectifRequest.builder()
.nom("X").build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("UpdateBeneficiaireEffectifRequest");
}
}

View File

@@ -0,0 +1,80 @@
package dev.lions.unionflow.server.api.dto.kyc.response;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class BeneficiaireEffectifResponseTest {
@Test
void testBuilder_AllFields() {
UUID id = UUID.randomUUID();
UUID kyc = UUID.randomUUID();
UUID org = UUID.randomUUID();
UUID membre = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
BeneficiaireEffectifResponse r = BeneficiaireEffectifResponse.builder()
.id(id).kycDossierId(kyc).organisationCibleId(org).membreId(membre)
.nom("Test").prenoms("User")
.dateNaissance(LocalDate.of(1990, 1, 1)).lieuNaissance("Abidjan")
.nationalite("CIV").paysResidence("FRA")
.typePieceIdentite(TypePieceIdentite.PASSEPORT)
.numeroPieceIdentite("AB123").dateExpirationPiece(LocalDate.of(2030, 1, 1))
.pourcentageCapital(new BigDecimal("25"))
.pourcentageDroitsVote(new BigDecimal("30"))
.natureControle("DETENTION_CAPITAL")
.estPep(true).pepCategorie("MIN").pepPays("CIV").pepFonction("Min")
.presenceListesSanctions(false).detailsListesSanctions("")
.dateCreation(now).dateModification(now).actif(true)
.build();
assertThat(r.id()).isEqualTo(id);
assertThat(r.kycDossierId()).isEqualTo(kyc);
assertThat(r.organisationCibleId()).isEqualTo(org);
assertThat(r.membreId()).isEqualTo(membre);
assertThat(r.nom()).isEqualTo("Test");
assertThat(r.prenoms()).isEqualTo("User");
assertThat(r.dateNaissance()).isEqualTo(LocalDate.of(1990, 1, 1));
assertThat(r.lieuNaissance()).isEqualTo("Abidjan");
assertThat(r.nationalite()).isEqualTo("CIV");
assertThat(r.paysResidence()).isEqualTo("FRA");
assertThat(r.typePieceIdentite()).isEqualTo(TypePieceIdentite.PASSEPORT);
assertThat(r.numeroPieceIdentite()).isEqualTo("AB123");
assertThat(r.dateExpirationPiece()).isEqualTo(LocalDate.of(2030, 1, 1));
assertThat(r.pourcentageCapital()).isEqualByComparingTo("25");
assertThat(r.pourcentageDroitsVote()).isEqualByComparingTo("30");
assertThat(r.natureControle()).isEqualTo("DETENTION_CAPITAL");
assertThat(r.estPep()).isTrue();
assertThat(r.pepCategorie()).isEqualTo("MIN");
assertThat(r.pepPays()).isEqualTo("CIV");
assertThat(r.pepFonction()).isEqualTo("Min");
assertThat(r.presenceListesSanctions()).isFalse();
assertThat(r.detailsListesSanctions()).isEqualTo("");
assertThat(r.dateCreation()).isEqualTo(now);
assertThat(r.dateModification()).isEqualTo(now);
assertThat(r.actif()).isTrue();
}
@Test
void testBuilder_NullFields() {
BeneficiaireEffectifResponse r = BeneficiaireEffectifResponse.builder().build();
assertThat(r.id()).isNull();
assertThat(r.actif()).isNull();
}
@Test
void testEqualsHashCodeToString() {
UUID id = UUID.randomUUID();
BeneficiaireEffectifResponse a = BeneficiaireEffectifResponse.builder().id(id).build();
BeneficiaireEffectifResponse b = BeneficiaireEffectifResponse.builder().id(id).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
assertThat(a.toString()).contains("BeneficiaireEffectifResponse");
}
}

View File

@@ -255,4 +255,49 @@ class UpdateOrganisationRequestTest {
assertThat(toString).contains("TTO");
assertThat(toString).contains("INACTIF");
}
// ──────────────────────────────────────────────────────────────────────
// Sprint 10 — referentielComptable + complianceOfficerId
// ──────────────────────────────────────────────────────────────────────
@Test
void testReferentielComptable_ValeursValides() {
for (String r : new String[] {"SYSCOHADA", "SYCEBNL", "PCSFD_UMOA"}) {
UpdateOrganisationRequest req = UpdateOrganisationRequest.builder()
.nom("Org").email("x@y.z").referentielComptable(r).build();
Set<ConstraintViolation<UpdateOrganisationRequest>> violations = validator.validate(req);
assertThat(violations).noneMatch(
v -> v.getPropertyPath().toString().equals("referentielComptable"));
}
}
@Test
void testReferentielComptable_VideAccepte() {
UpdateOrganisationRequest req = UpdateOrganisationRequest.builder()
.nom("Org").email("x@y.z").referentielComptable("").build();
Set<ConstraintViolation<UpdateOrganisationRequest>> violations = validator.validate(req);
assertThat(violations).noneMatch(
v -> v.getPropertyPath().toString().equals("referentielComptable"));
}
@Test
void testReferentielComptable_Invalide() {
UpdateOrganisationRequest req = UpdateOrganisationRequest.builder()
.nom("Org").email("x@y.z").referentielComptable("IFRS").build();
Set<ConstraintViolation<UpdateOrganisationRequest>> violations = validator.validate(req);
assertThat(violations).anyMatch(
v -> v.getPropertyPath().toString().equals("referentielComptable"));
}
@Test
void testComplianceOfficerId_Optionnel() {
java.util.UUID id = java.util.UUID.randomUUID();
UpdateOrganisationRequest req = UpdateOrganisationRequest.builder()
.nom("Org").email("x@y.z").complianceOfficerId(id).build();
assertThat(req.complianceOfficerId()).isEqualTo(id);
UpdateOrganisationRequest reqNull = UpdateOrganisationRequest.builder()
.nom("Org").email("x@y.z").build();
assertThat(reqNull.complianceOfficerId()).isNull();
}
}

View File

@@ -52,4 +52,38 @@ class OrganisationResponseTest {
r.setOrganisationParenteNom("Parent Org");
assertThat(r.getOrganisationParenteNom()).isEqualTo("Parent Org");
}
// ── Sprint 13 — Conformité (referentielComptable + complianceOfficerId) ─────
@Test
@DisplayName("referentielComptable null par défaut, builder + setter fonctionnent")
void referentielComptable_nullDefaultAndBuilderSetter() {
OrganisationResponse empty = OrganisationResponse.builder().build();
assertThat(empty.getReferentielComptable()).isNull();
OrganisationResponse builderSet = OrganisationResponse.builder()
.referentielComptable("SYCEBNL").build();
assertThat(builderSet.getReferentielComptable()).isEqualTo("SYCEBNL");
OrganisationResponse setterSet = OrganisationResponse.builder().build();
setterSet.setReferentielComptable("PCSFD_UMOA");
assertThat(setterSet.getReferentielComptable()).isEqualTo("PCSFD_UMOA");
}
@Test
@DisplayName("complianceOfficerId null par défaut, builder + setter fonctionnent")
void complianceOfficerId_nullDefaultAndBuilderSetter() {
OrganisationResponse empty = OrganisationResponse.builder().build();
assertThat(empty.getComplianceOfficerId()).isNull();
java.util.UUID id1 = java.util.UUID.randomUUID();
OrganisationResponse builderSet = OrganisationResponse.builder()
.complianceOfficerId(id1).build();
assertThat(builderSet.getComplianceOfficerId()).isEqualTo(id1);
java.util.UUID id2 = java.util.UUID.randomUUID();
OrganisationResponse setterSet = OrganisationResponse.builder().build();
setterSet.setComplianceOfficerId(id2);
assertThat(setterSet.getComplianceOfficerId()).isEqualTo(id2);
}
}