Compare commits

...

19 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
dahoud
1496c83b6f docs: ajouter openapi.yaml généré depuis le backend de production (v1.0.5) 2026-04-21 16:28:31 +00:00
dahoud
ee6e945bdb feat: DTOs KYC/messagerie/mutuelle/parts/versement/payment + enrichissement paiement
## Nouveaux packages DTO / enums
- dto/kyc/ : NiveauRisqueKyc, TypePieceIdentite, KycDossier DTOs
- dto/messagerie/ : conversation/message DTOs + enums (TypeConversation, etc.)
- dto/mutuelle/financier/ : ParametresFinanciersMutuelle DTOs
- dto/mutuelle/parts/ : ComptePartsSociales, TransactionPartsSociales DTOs
- dto/versement/ : Versement DTOs
- payment/ : PaymentProvider, PaymentStatus, PaymentException,
  CheckoutRequest, CheckoutSession, PaymentEvent (SPI paiement unifié)

## Paiement enrichi
- CreatePaiementRequest, DeclarerPaiementManuelRequest, InitierPaiementEnLigneRequest
- IntentionStatutResponse, PaiementGatewayResponse, PaiementResponse, PaiementSummaryResponse
- Tests correspondants mis à jour

## Tests nouveaux
- DTOs agricole, ayant-droit, backup tests
- Tous les nouveaux DTOs KYC/messagerie/mutuelle/versement/payment

## Build
- pom.xml : version 1.0.5, parent-pom.xml aligné
- script/publish-api.sh : version bumped

164 fichiers, +8 481 insertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:44:49 +00:00
dahoud
eac66ce25d refactor: supprimer alias doublons typeAssociation/typeLibelle/numeroRegistre dans OrganisationResponse
- Supprimer typeAssociation (doublon mort de typeOrganisation)
- Supprimer typeLibelle (doublon de typeOrganisationLibelle)
- Supprimer alias getNumeroRegistre/setNumeroRegistre (doublon de numeroEnregistrement)
- Supprimer alias getNomOrganisationParente/setNomOrganisationParente (doublon de organisationParenteNom)
- Réécrire OrganisationResponseTest pour tester les vrais champs
2026-04-17 19:53:25 +00:00
dahoud
69f740d6ed test: corriger ADMIN_ENTITE→ADMIN_ORGANISATION dans LoginRequestTest 2026-04-17 19:19:57 +00:00
dahoud
1b7700a368 test: corriger OrganisationSummaryResponseTest pour ville + pays (12 args)
Constructeur passé de 10 à 12 arguments avec l'ajout de ville et pays.
Tests mis à jour + nouveau test testSettersVillePays.
2026-04-16 12:34:05 +00:00
dahoud
c9615f349e fix(dto): ajouter ville + pays à OrganisationSummaryResponse
Le XHTML liste.xhtml affichait #{org.ville} et #{org.pays} mais le DTO
Summary n'avait que id/nom/type/statut/nombreMembres — pas de localisation.
L'EL résolvait null → colonnes vides dans le DataTable.
2026-04-16 12:28:26 +00:00
dahoud
ba31d6802e test(MembreSummaryResponse): cover isActif(false) branch for 100% Jacoco
isActif() had 1 of 4 branches missed: Boolean=false was never tested.
Added explicit test for actif=false (distinct from null).
2026-04-11 02:50:33 +00:00
dahoud
d66c013d0b fix(tests): replace remaining record-style accessors with getters
ErrorResponseTest and CompteAdherentResponseTest were using record
accessors (message(), error(), numeroMembre(), etc.) on @Data classes.
2026-04-11 02:45:24 +00:00
dahoud
ae7ada6b91 fix(tests): replace record-style accessors with JavaBean getters in server-api tests
All response DTOs were converted from records to @Data classes for JSF/EL
compatibility. Updated 8 test files to use getters instead of record
component accessors. Also added isActif() method to MembreSummaryResponse
(Boolean wrapper needs explicit isActif() since Lombok generates getActif()).
Fixed ConversationResponseTest builder calls: .isMuted() → .muted(),
and MessageResponseTest builder calls: .isEdited() → .edited(), .isDeleted() → .deleted().
2026-04-11 02:21:08 +00:00
dahoud
43678c8ae9 fix(api): 1.0.4 — OrganisationSummaryResponse record→@Data (compat JSF/EL), version bump 2026-04-11 02:00:07 +00:00
614 changed files with 75198 additions and 50252 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

16414
docs/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,96 +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.0</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.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.version>3.15.1</quarkus.platform.version>
<lombok.version>1.18.34</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>
<source>17</source>
<target>17</target>
<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>

53
pom.xml
View File

@@ -4,36 +4,50 @@
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.0</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>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<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.15.1</quarkus.platform.version>
<jackson.version>2.17.0</jackson.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>
<!-- Versions des plugins de qualité -->
<jacoco.version>0.8.11</jacoco.version>
<jacoco.version>0.8.12</jacoco.version>
<checkstyle.version>10.12.4</checkstyle.version>
<maven-checkstyle-plugin.version>3.3.1</maven-checkstyle-plugin.version>
<junit.version>5.10.1</junit.version>
<mockito.version>5.7.0</mockito.version>
<assertj.version>3.24.2</assertj.version>
<junit.version>5.11.4</junit.version>
<mockito.version>5.14.2</mockito.version>
<assertj.version>3.27.3</assertj.version>
<!-- Seuils de couverture Jacoco - 100% obligatoire -->
<jacoco.line.coverage.minimum>1.00</jacoco.line.coverage.minimum>
@@ -115,7 +129,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
@@ -126,16 +140,15 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
<release>21</release>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.36</version>
</path>
</annotationProcessorPaths>
</configuration>

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.0 \
-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

@@ -1,13 +1,22 @@
package dev.lions.unionflow.server.api.dto.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO d'erreur unifié retourné par tous les endpoints REST.
* Remplace les ErrorResponse locales dupliquées dans chaque Resource.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ErrorResponse(String message, String error) {
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
private String message;
private String error;
/** Constructeur pratique avec message uniquement (cas le plus courant). */
public static ErrorResponse of(String message) {

View File

@@ -1,7 +1,10 @@
package dev.lions.unionflow.server.api.dto.communication.response;
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@@ -14,20 +17,23 @@ import java.util.UUID;
* @version 1.0
* @since 2026-03-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public record ConversationResponse(
UUID id,
String name,
String description,
ConversationType type,
List<UUID> participantIds,
UUID organisationId,
MessageResponse lastMessage,
int unreadCount,
boolean isMuted,
boolean isPinned,
boolean isArchived,
LocalDateTime createdAt,
LocalDateTime updatedAt,
String avatarUrl
) {}
public class ConversationResponse {
private UUID id;
private String name;
private String description;
private ConversationType type;
private List<UUID> participantIds;
private UUID organisationId;
private MessageResponse lastMessage;
private int unreadCount;
private boolean muted;
private boolean pinned;
private boolean archived;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String avatarUrl;
}

View File

@@ -3,7 +3,10 @@ package dev.lions.unionflow.server.api.dto.communication.response;
import dev.lions.unionflow.server.api.enums.communication.MessagePriority;
import dev.lions.unionflow.server.api.enums.communication.MessageStatus;
import dev.lions.unionflow.server.api.enums.communication.MessageType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@@ -16,24 +19,27 @@ import java.util.UUID;
* @version 1.0
* @since 2026-03-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public record MessageResponse(
UUID id,
UUID conversationId,
UUID senderId,
String senderName,
String senderAvatar,
String content,
MessageType type,
MessageStatus status,
MessagePriority priority,
List<UUID> recipientIds,
List<String> recipientRoles,
UUID organisationId,
LocalDateTime createdAt,
LocalDateTime readAt,
List<String> attachments,
boolean isEdited,
LocalDateTime editedAt,
boolean isDeleted
) {}
public class MessageResponse {
private UUID id;
private UUID conversationId;
private UUID senderId;
private String senderName;
private String senderAvatar;
private String content;
private MessageType type;
private MessageStatus status;
private MessagePriority priority;
private List<UUID> recipientIds;
private List<String> recipientRoles;
private UUID organisationId;
private LocalDateTime createdAt;
private LocalDateTime readAt;
private List<String> attachments;
private boolean edited;
private LocalDateTime editedAt;
private boolean deleted;
}

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

@@ -1,5 +1,9 @@
package dev.lions.unionflow.server.api.dto.cotisation.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
@@ -7,29 +11,32 @@ import java.util.UUID;
/**
* Réponse simplifiée pour les listes de cotisations.
*
* @param id Identifiant unique.
* @param numeroReference Référence unique.
* @param nomMembre Nom du membre.
* @param montantDu Montant total dû.
* @param montantPaye Montant déjà réglé.
* @param statut Statut actuel (code).
* @param statutLibelle Libellé du statut pour l'UI.
* @param dateEcheance Date limite.
* @param annee Année concernée.
* @param actif Indique si l'entité est active.
* @author UnionFlow Team
* @version 1.0
* @since 2026-02-22
*/
public record CotisationSummaryResponse(
UUID id,
String numeroReference,
String nomMembre,
BigDecimal montantDu,
BigDecimal montantPaye,
String statut,
String statutLibelle,
LocalDate dateEcheance,
Integer annee,
Boolean actif) {
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CotisationSummaryResponse {
/** Identifiant unique. */
private UUID id;
/** Référence unique. */
private String numeroReference;
/** Nom du membre. */
private String nomMembre;
/** Montant total dû. */
private BigDecimal montantDu;
/** Montant déjà réglé. */
private BigDecimal montantPaye;
/** Statut actuel (code). */
private String statut;
/** Libellé du statut pour l'UI. */
private String statutLibelle;
/** Date limite. */
private LocalDate dateEcheance;
/** Année concernée. */
private Integer annee;
/** Indique si l'entité est active. */
private Boolean actif;
}

View File

@@ -1,5 +1,9 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -10,39 +14,42 @@ import java.time.LocalDate;
* @author UnionFlow Team
* @version 1.0
*/
public record MembreDashboardSyntheseResponse(
String prenom,
String nom,
LocalDate dateInscription,
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MembreDashboardSyntheseResponse implements Serializable {
// Cotisations
BigDecimal mesCotisationsPaiement,
/** Total des cotisations payées sur l'année en cours (pour affichage dashboard). */
BigDecimal totalCotisationsPayeesAnnee,
/** Total des cotisations payées tout temps (pour la carte « Contribution Totale »). */
BigDecimal totalCotisationsPayeesToutTemps,
/** Nombre de cotisations PAYÉES (pour la carte « Cotisations »). */
Integer nombreCotisationsPayees,
String statutCotisations,
/** Taux de cotisation en % (0-100). Calculé sur l'année courante ou toutes années si pas de cotisation 2026. */
Integer tauxCotisationsPerso,
/** Nombre TOTAL de cotisations (toutes années, tous statuts) — pour calcul du taux d'engagement. */
Integer nombreCotisationsTotal,
private String prenom;
private String nom;
private LocalDate dateInscription;
// Epargne
BigDecimal monSoldeEpargne,
BigDecimal evolutionEpargneNombre,
String evolutionEpargne,
Integer objectifEpargne,
// Cotisations
private BigDecimal mesCotisationsPaiement;
/** Total des cotisations payées sur l'année en cours (pour affichage dashboard). */
private BigDecimal totalCotisationsPayeesAnnee;
/** Total des cotisations payées tout temps (pour la carte « Contribution Totale »). */
private BigDecimal totalCotisationsPayeesToutTemps;
/** Nombre de cotisations PAYÉES (pour la carte « Cotisations »). */
private Integer nombreCotisationsPayees;
private String statutCotisations;
/** Taux de cotisation en % (0-100). Calculé sur l'année courante ou toutes années si pas de cotisation 2026. */
private Integer tauxCotisationsPerso;
/** Nombre TOTAL de cotisations (toutes années, tous statuts) — pour calcul du taux d'engagement. */
private Integer nombreCotisationsTotal;
// Evenements
Integer mesEvenementsInscrits,
Integer evenementsAVenir,
Integer tauxParticipationPerso,
// Epargne
private BigDecimal monSoldeEpargne;
private BigDecimal evolutionEpargneNombre;
private String evolutionEpargne;
private Integer objectifEpargne;
// Aides
Integer mesDemandesAide,
Integer aidesEnCours,
Integer tauxAidesApprouvees) implements Serializable {
// Evenements
private Integer mesEvenementsInscrits;
private Integer evenementsAVenir;
private Integer tauxParticipationPerso;
// Aides
private Integer mesDemandesAide;
private Integer aidesEnCours;
private Integer tauxAidesApprouvees;
}

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,54 @@
package dev.lions.unionflow.server.api.dto.kyc;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDate;
/**
* DTO de création ou mise à jour d'un dossier KYC/AML.
*/
@Data
@Builder
public class KycDossierRequest {
@NotNull
private String membreId;
@NotNull
private TypePieceIdentite typePiece;
@NotBlank
@Size(max = 50)
private String numeroPiece;
/** Date d'expiration de la pièce d'identité. */
private LocalDate dateExpirationPiece;
/** Justificatif de domicile — ID du fichier stocké (MinIO/S3). */
@Size(max = 500)
private String justifDomicileFileId;
/** Pièce d'identité recto — ID du fichier stocké. */
@Size(max = 500)
private String pieceIdentiteRectoFileId;
/** Pièce d'identité verso — ID du fichier stocké. */
@Size(max = 500)
private String pieceIdentiteVersoFileId;
/** Notes du validateur lors d'une approbation/refus manuel. */
@Size(max = 1000)
private String notesValidateur;
/** Indique si le membre est une Personne Exposée Politiquement (PEP). */
private Boolean estPep;
/** Nationalité déclarée (ISO 3166-1 alpha-2). */
@Size(max = 5)
private String nationalite;
}

View File

@@ -0,0 +1,46 @@
package dev.lions.unionflow.server.api.dto.kyc;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.membre.NiveauRisqueKyc;
import dev.lions.unionflow.server.api.enums.membre.StatutKyc;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* DTO de réponse pour un dossier KYC/AML.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class KycDossierResponse extends BaseDTO {
private UUID membreId;
private String membreNomComplet;
private String membreEmail;
private TypePieceIdentite typePiece;
private String numeroPiece;
private LocalDate dateExpirationPiece;
private String justifDomicileFileId;
private String pieceIdentiteRectoFileId;
private String pieceIdentiteVersoFileId;
private StatutKyc statut;
private NiveauRisqueKyc niveauRisque;
private int scoreRisque;
private boolean estPep;
private String nationalite;
private LocalDateTime dateVerification;
private UUID validateurId;
private String notesValidateur;
/** Année de référence pour la rétention (partitionnement logique). */
private int anneeReference;
}

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

@@ -1,5 +1,9 @@
package dev.lions.unionflow.server.api.dto.membre;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -18,57 +22,55 @@ import java.time.LocalDate;
* <li>Capacité d'emprunt estimée (3× l'épargne — règle mutuelle classique)</li>
* </ul>
*
* @param numeroMembre Numéro unique du membre sur la plateforme (ex: MUF-2026-001)
* @param nomComplet Nom et prénom du membre
* @param organisationNom Nom de l'organisation principale (ou null si aucune)
* @param dateAdhesion Date d'inscription sur la plateforme
* @param statutCompte Statut actuel du compte (ACTIF, SUSPENDU, etc.)
*
* @param soldeCotisations Total des cotisations payées (toutes années confondues)
* @param soldeEpargne Solde disponible sur l'ensemble des comptes épargne actifs
* @param soldeBloque Montant bloqué (garantie de prêt)
* @param soldeTotalDisponible soldeCotisations + soldeEpargne - soldeBloque
* @param encoursCreditTotal Montant total des prêts en cours (0 si fonctionnalité non encore activée)
* @param capaciteEmprunt Capacité d'emprunt estimée (3 × soldeEpargne selon règle mutuelle standard)
*
* @param nombreCotisationsPayees Nombre de cotisations payées (historique complet)
* @param nombreCotisationsTotal Nombre total de cotisations (payées + en attente + retard)
* @param nombreCotisationsEnRetard Nombre de cotisations en retard
* @param tauxEngagement Taux de paiement global en % (0-100)
*
* @param nombreComptesEpargne Nombre de comptes épargne actifs
* @param dateCalcul Date/heure du calcul (pour information client)
*
* @author UnionFlow Team
* @version 1.0
*/
public record CompteAdherentResponse(
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CompteAdherentResponse implements Serializable {
// ── Identité ──────────────────────────────────────────────────────────
String numeroMembre,
String nomComplet,
String organisationNom,
LocalDate dateAdhesion,
String statutCompte,
// ── Identité ──────────────────────────────────────────────────────────
/** Numéro unique du membre sur la plateforme (ex: MUF-2026-001) */
private String numeroMembre;
/** Nom et prénom du membre */
private String nomComplet;
/** Nom de l'organisation principale (ou null si aucune) */
private String organisationNom;
/** Date d'inscription sur la plateforme */
private LocalDate dateAdhesion;
/** Statut actuel du compte (ACTIF, SUSPENDU, etc.) */
private String statutCompte;
// ── Soldes ────────────────────────────────────────────────────────────
BigDecimal soldeCotisations,
BigDecimal soldeEpargne,
BigDecimal soldeBloque,
BigDecimal soldeTotalDisponible,
BigDecimal encoursCreditTotal,
BigDecimal capaciteEmprunt,
// ── Soldes ────────────────────────────────────────────────────────────
/** Total des cotisations payées (toutes années confondues) */
private BigDecimal soldeCotisations;
/** Solde disponible sur l'ensemble des comptes épargne actifs */
private BigDecimal soldeEpargne;
/** Montant bloqué (garantie de prêt) */
private BigDecimal soldeBloque;
/** soldeCotisations + soldeEpargne - soldeBloque */
private BigDecimal soldeTotalDisponible;
/** Montant total des prêts en cours (0 si fonctionnalité non encore activée) */
private BigDecimal encoursCreditTotal;
/** Capacité d'emprunt estimée (3 × soldeEpargne selon règle mutuelle standard) */
private BigDecimal capaciteEmprunt;
// ── Cotisations ───────────────────────────────────────────────────────
Integer nombreCotisationsPayees,
Integer nombreCotisationsTotal,
Integer nombreCotisationsEnRetard,
Integer tauxEngagement,
// ── Cotisations ───────────────────────────────────────────────────────
/** Nombre de cotisations payées (historique complet) */
private Integer nombreCotisationsPayees;
/** Nombre total de cotisations (payées + en attente + retard) */
private Integer nombreCotisationsTotal;
/** Nombre de cotisations en retard */
private Integer nombreCotisationsEnRetard;
/** Taux de paiement global en % (0-100) */
private Integer tauxEngagement;
// ── Épargne ───────────────────────────────────────────────────────────
Integer nombreComptesEpargne,
// ── Épargne ───────────────────────────────────────────────────────────
/** Nombre de comptes épargne actifs */
private Integer nombreComptesEpargne;
// ── Méta ──────────────────────────────────────────────────────────────
LocalDate dateCalcul
) implements Serializable {}
// ── Méta ──────────────────────────────────────────────────────────────
/** Date/heure du calcul (pour information client) */
private LocalDate dateCalcul;
}

View File

@@ -1,5 +1,9 @@
package dev.lions.unionflow.server.api.dto.membre.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
@@ -7,81 +11,56 @@ import java.util.UUID;
/**
* DTO de réponse résumé pour Membre (listes et optimisations).
*
* <p>Ce record expose des méthodes calculées pour la compatibilité JSF EL :
* {@link #nomComplet()}, {@link #statut()}, {@link #typeMembre()}, etc.
* <p>Classe JavaBean pour compatibilité JSF EL (EL cherche getXxx()).
* Expose également des propriétés calculées (nomComplet, statut, typeMembre, etc.)
*/
public record MembreSummaryResponse(
UUID id,
String numeroMembre,
String prenom,
String nom,
String email,
String telephone,
String profession,
String statutCompte,
String statutCompteLibelle,
String statutCompteSeverity,
Boolean actif,
List<String> roles,
UUID organisationId,
String organisationNom,
LocalDate dateAdhesion) {
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MembreSummaryResponse {
// ── Getters JavaBean pour compatibilité JSF EL (EL cherche getXxx()) ──────
public UUID getId() { return id; }
public String getNumeroMembre() { return numeroMembre; }
public String getPrenom() { return prenom; }
public String getNom() { return nom; }
public String getEmail() { return email; }
public String getTelephone() { return telephone; }
public String getProfession() { return profession; }
public String getStatutCompte() { return statutCompte; }
public String getStatutCompteLibelle() { return statutCompteLibelle; }
public String getStatutCompteSeverity() { return statutCompteSeverity; }
public Boolean getActif() { return actif; }
public boolean isActif() { return Boolean.TRUE.equals(actif); }
public List<String> getRoles() { return roles; }
public UUID getOrganisationId() { return organisationId; }
public String getOrganisationNom() { return organisationNom; }
public LocalDate getDateAdhesion() { return dateAdhesion; }
// Getters JavaBean pour les propriétés calculées
public String getNomComplet() { return nomComplet(); }
public String getStatut() { return statut(); }
public String getStatutLibelle() { return statutLibelle(); }
public String getStatutSeverity() { return statutSeverity(); }
public String getStatutIcon() { return statutIcon(); }
public String getTypeMembre() { return typeMembre(); }
public String getTypeSeverity() { return typeSeverity(); }
public String getTypeIcon() { return typeIcon(); }
private UUID id;
private String numeroMembre;
private String prenom;
private String nom;
private String email;
private String telephone;
private String profession;
private String statutCompte;
private String statutCompteLibelle;
private String statutCompteSeverity;
private Boolean actif;
private List<String> roles;
private UUID organisationId;
private String organisationNom;
private LocalDate dateAdhesion;
// ── Propriétés calculées pour la compatibilité JSF EL ──────────────────
/** Prénom + Nom concaténés. */
public String nomComplet() {
public String getNomComplet() {
String p = prenom != null ? prenom.trim() : "";
String n = nom != null ? nom.trim() : "";
return (p + " " + n).trim();
}
/** Alias de {@link #statutCompte()} pour #{membre.statut}. */
public String statut() {
/** Alias de {@link #getStatutCompte()} pour #{membre.statut}. */
public String getStatut() {
return statutCompte;
}
/** Alias de {@link #statutCompteLibelle()} pour #{membre.statutLibelle}. */
public String statutLibelle() {
/** Alias de {@link #getStatutCompteLibelle()} pour #{membre.statutLibelle}. */
public String getStatutLibelle() {
return statutCompteLibelle;
}
/** Alias de {@link #statutCompteSeverity()} pour #{membre.statutSeverity}. */
public String statutSeverity() {
/** Alias de {@link #getStatutCompteSeverity()} pour #{membre.statutSeverity}. */
public String getStatutSeverity() {
return statutCompteSeverity;
}
/** Icône PrimeIcons calculée depuis {@link #statutCompte()}. */
public String statutIcon() {
/** Icône PrimeIcons calculée depuis {@link #getStatutCompte()}. */
public String getStatutIcon() {
if (statutCompte == null) return "pi pi-question-circle";
return switch (statutCompte) {
case "ACTIF" -> "pi pi-check-circle";
@@ -92,8 +71,8 @@ public record MembreSummaryResponse(
};
}
/** Libellé du rôle principal calculé depuis {@link #roles()}. */
public String typeMembre() {
/** Libellé du rôle principal calculé depuis {@link #getRoles()}. */
public String getTypeMembre() {
if (roles == null || roles.isEmpty()) return "Membre";
if (roles.contains("PRESIDENT")) return "Président";
if (roles.contains("VICE_PRESIDENT")) return "Vice-Président";
@@ -104,8 +83,8 @@ public record MembreSummaryResponse(
return "Membre";
}
/** Severity PrimeUI calculée depuis {@link #roles()}. */
public String typeSeverity() {
/** Severity PrimeUI calculée depuis {@link #getRoles()}. */
public String getTypeSeverity() {
if (roles == null || roles.isEmpty()) return "secondary";
if (roles.contains("PRESIDENT")) return "primary";
if (roles.contains("VICE_PRESIDENT")) return "primary";
@@ -116,8 +95,13 @@ public record MembreSummaryResponse(
return "secondary";
}
/** Icône PrimeIcons calculée depuis {@link #roles()}. */
public String typeIcon() {
/** Retourne true si le membre est actif (null traité comme false). */
public boolean isActif() {
return actif != null && actif;
}
/** Icône PrimeIcons calculée depuis {@link #getRoles()}. */
public String getTypeIcon() {
if (roles == null || roles.isEmpty()) return "pi pi-user";
if (roles.contains("PRESIDENT")) return "pi pi-star";
if (roles.contains("VICE_PRESIDENT")) return "pi pi-star";

View File

@@ -0,0 +1,30 @@
package dev.lions.unionflow.server.api.dto.messagerie.request;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour bloquer un membre dans une organisation.
*
* <p>Le blocage est unilatéral et limité à une organisation.
* Le membre bloqué ne peut plus envoyer de messages au bloqueur
* dans cette organisation.
*
* @param membreABloquerId ID du membre à bloquer (obligatoire)
* @param organisationId Organisation dans laquelle le blocage s'applique (obligatoire)
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record BloquerMembreRequest(
@NotNull(message = "L'ID du membre à bloquer est obligatoire")
UUID membreABloquerId,
@NotNull(message = "L'ID de l'organisation est obligatoire")
UUID organisationId
) {}

View File

@@ -0,0 +1,35 @@
package dev.lions.unionflow.server.api.dto.messagerie.request;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Builder;
/**
* Démarre une conversation directe 1-1 entre le membre connecté
* et un autre membre de la même organisation.
*
* <p>Si une conversation directe entre ces deux membres existe déjà
* dans l'organisation, elle est réutilisée (idempotente).
*
* @param destinataireId ID du membre destinataire (obligatoire)
* @param organisationId ID de l'organisation commune (obligatoire)
* @param contenuInitial Premier message optionnel
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record DemarrerConversationDirecteRequest(
@NotNull(message = "L'ID du destinataire est obligatoire")
UUID destinataireId,
@NotNull(message = "L'ID de l'organisation est obligatoire")
UUID organisationId,
@Size(max = 2000, message = "Le message initial ne doit pas dépasser 2000 caractères")
String contenuInitial
) {}

View File

@@ -0,0 +1,45 @@
package dev.lions.unionflow.server.api.dto.messagerie.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Builder;
/**
* Démarre une conversation avec un rôle officiel de l'organisation.
*
* <p>Le membre ne contacte pas une personne spécifique mais le "canal"
* du rôle (ex: "Contacter le Trésorier"). Tous les membres portant ce
* rôle voient et peuvent répondre — si le trésorier change, la conversation
* continue avec le nouveau trésorier.
*
* <p>Si un canal pour ce rôle existe déjà dans l'organisation, il est réutilisé.
*
* @param organisationId ID de l'organisation (obligatoire)
* @param roleCible Rôle visé : PRESIDENT | TRESORIER | SECRETAIRE | VICE_PRESIDENT | ADMIN
* @param contenuInitial Premier message (obligatoire pour un canal rôle)
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record DemarrerConversationRoleRequest(
@NotNull(message = "L'ID de l'organisation est obligatoire")
UUID organisationId,
@NotBlank(message = "Le rôle cible est obligatoire")
@Pattern(
regexp = "^(PRESIDENT|TRESORIER|SECRETAIRE|VICE_PRESIDENT|ADMIN|ADMIN_ORGANISATION)$",
message = "Rôle invalide. Valeurs : PRESIDENT, TRESORIER, SECRETAIRE, VICE_PRESIDENT, ADMIN, ADMIN_ORGANISATION"
)
String roleCible,
@NotBlank(message = "Le message initial est obligatoire pour contacter un rôle")
@Size(max = 2000, message = "Le message ne doit pas dépasser 2000 caractères")
String contenuInitial
) {}

View File

@@ -0,0 +1,47 @@
package dev.lions.unionflow.server.api.dto.messagerie.request;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour envoyer un message dans une conversation existante.
*
* <p>La validation contextuelle (contenu OU urlFichier selon le type)
* est effectuée dans le service.
*
* @param typeMessage TEXTE (défaut) | VOCAL | IMAGE | SYSTEME
* @param contenu Texte du message (obligatoire pour TEXTE)
* @param urlFichier URL du fichier (obligatoire pour VOCAL/IMAGE)
* @param dureeAudio Durée en secondes (obligatoire pour VOCAL)
* @param messageParentId ID du message auquel on répond (optionnel)
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record EnvoyerMessageRequest(
@Pattern(
regexp = "^(TEXTE|VOCAL|IMAGE|SYSTEME)$",
message = "Type de message invalide. Valeurs : TEXTE, VOCAL, IMAGE, SYSTEME"
)
String typeMessage,
@Size(max = 4000, message = "Le message ne doit pas dépasser 4000 caractères")
String contenu,
@Size(max = 500, message = "L'URL du fichier ne doit pas dépasser 500 caractères")
String urlFichier,
@Min(value = 1, message = "La durée audio doit être supérieure à 0")
@Max(value = 300, message = "La durée audio ne peut pas dépasser 300 secondes")
Integer dureeAudio,
UUID messageParentId
) {}

View File

@@ -0,0 +1,34 @@
package dev.lions.unionflow.server.api.dto.messagerie.request;
import jakarta.validation.constraints.Pattern;
import lombok.Builder;
/**
* Mise à jour de la politique de communication d'une organisation.
* Réservée aux administrateurs.
*
* @param typePolitique OUVERT | BUREAU_SEULEMENT | GROUPES_INTERNES
* @param autoriserMembreVersMembre Autorise les échanges entre membres
* @param autoriserMembreVersRole Autorise les échanges vers les rôles du bureau
* @param autoriserNotesVocales Autorise les notes vocales
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record MettreAJourPolitiqueRequest(
@Pattern(
regexp = "^(OUVERT|BUREAU_SEULEMENT|GROUPES_INTERNES)$",
message = "Politique invalide. Valeurs : OUVERT, BUREAU_SEULEMENT, GROUPES_INTERNES"
)
String typePolitique,
Boolean autoriserMembreVersMembre,
Boolean autoriserMembreVersRole,
Boolean autoriserNotesVocales
) {}

View File

@@ -0,0 +1,31 @@
package dev.lions.unionflow.server.api.dto.messagerie.response;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO de réponse pour la politique de communication d'une organisation.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ContactPolicyResponse {
private UUID id;
private UUID organisationId;
/** OUVERT | BUREAU_SEULEMENT | GROUPES_INTERNES */
private String typePolitique;
private boolean autoriserMembreVersMembre;
private boolean autoriserMembreVersRole;
private boolean autoriserNotesVocales;
}

View File

@@ -0,0 +1,68 @@
package dev.lions.unionflow.server.api.dto.messagerie.response;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO de réponse complète pour une conversation.
* Inclut les derniers messages et la liste des participants.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ConversationResponse {
private UUID id;
/** DIRECTE | ROLE_CANAL | GROUPE */
private String typeConversation;
/** Titre affiché (nom de l'autre participant ou du rôle). */
private String titre;
/** ACTIVE | ARCHIVEE */
private String statut;
/** Rôle cible pour les canaux rôle. */
private String roleCible;
private UUID organisationId;
private String organisationNom;
private LocalDateTime dateCreation;
private LocalDateTime dernierMessageAt;
private int nombreMessages;
/** Participants de la conversation. */
private List<ParticipantResponse> participants;
/** Derniers messages (page 0). */
private List<MessageResponse> messages;
/** Nombre de messages non lus pour le membre connecté. */
private long nonLus;
// ── DTO interne pour les participants ─────────────────────────────────────
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class ParticipantResponse {
private UUID membreId;
private String prenom;
private String nom;
private String roleDansConversation;
private LocalDateTime luJusqua;
}
}

View File

@@ -0,0 +1,52 @@
package dev.lions.unionflow.server.api.dto.messagerie.response;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO résumé d'une conversation — utilisé dans la liste des conversations.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ConversationSummaryResponse {
private UUID id;
/** DIRECTE | ROLE_CANAL | GROUPE */
private String typeConversation;
/**
* Titre affiché dans la liste :
* - Pour DIRECTE : "Prénom Nom" de l'autre participant
* - Pour ROLE_CANAL : "Trésorier", "Président", etc.
*/
private String titre;
/** Statut : ACTIVE | ARCHIVEE */
private String statut;
/** Aperçu du dernier message (max 100 caractères). */
private String dernierMessageApercu;
/** Type du dernier message (pour afficher l'icône vocal/image). */
private String dernierMessageType;
/** Horodatage du dernier message. */
private LocalDateTime dernierMessageAt;
/** Nombre de messages non lus pour le membre connecté. */
private long nonLus;
/** ID de l'organisation de la conversation. */
private UUID organisationId;
}

View File

@@ -0,0 +1,52 @@
package dev.lions.unionflow.server.api.dto.messagerie.response;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO de réponse pour un message.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MessageResponse {
private UUID id;
/** TEXTE | VOCAL | IMAGE | SYSTEME */
private String typeMessage;
/** Texte du message (null si VOCAL/IMAGE). */
private String contenu;
/** URL du fichier audio ou image. */
private String urlFichier;
/** Durée en secondes (notes vocales uniquement). */
private Integer dureeAudio;
/** True si le message a été supprimé par son auteur. */
private boolean supprime;
// ── Expéditeur ────────────────────────────────────────────────────────────
private UUID expediteurId;
private String expediteurNom;
private String expediteurPrenom;
// ── Réponse à un message ──────────────────────────────────────────────────
private UUID messageParentId;
/** Aperçu du message parent (premiers 100 caractères). */
private String messageParentApercu;
// ── Horodatage ────────────────────────────────────────────────────────────
private LocalDateTime dateEnvoi;
}

View File

@@ -0,0 +1,50 @@
package dev.lions.unionflow.server.api.dto.mutuelle.financier;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Paramètres financiers de la mutuelle")
public class ParametresFinanciersMutuellRequest {
@NotBlank
@Schema(description = "UUID de l'organisation")
private String organisationId;
@NotNull
@DecimalMin(value = "1.0")
@Schema(description = "Valeur nominale par défaut d'une part sociale (XOF)", example = "5000")
private BigDecimal valeurNominaleParDefaut;
@NotNull
@DecimalMin(value = "0.0")
@DecimalMax(value = "1.0")
@Schema(description = "Taux d'intérêt annuel sur l'épargne (ex: 0.03 = 3%)", example = "0.03")
private BigDecimal tauxInteretAnnuelEpargne;
@NotNull
@DecimalMin(value = "0.0")
@DecimalMax(value = "1.0")
@Schema(description = "Taux de dividende annuel sur les parts sociales (ex: 0.05 = 5%)", example = "0.05")
private BigDecimal tauxDividendePartsAnnuel;
@NotBlank
@Schema(description = "Périodicité du calcul des intérêts: MENSUEL, TRIMESTRIEL, ANNUEL", example = "MENSUEL")
private String periodiciteCalcul;
@DecimalMin(value = "0.0")
@Schema(description = "Solde minimum pour bénéficier des intérêts sur épargne", example = "10000")
private BigDecimal seuilMinEpargneInterets;
}

View File

@@ -0,0 +1,34 @@
package dev.lions.unionflow.server.api.dto.mutuelle.financier;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDate;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Paramètres financiers d'une mutuelle")
public class ParametresFinanciersMutuellResponse extends BaseDTO {
private String organisationId;
private String organisationNom;
private BigDecimal valeurNominaleParDefaut;
private BigDecimal tauxInteretAnnuelEpargne;
private BigDecimal tauxDividendePartsAnnuel;
private String periodiciteCalcul;
private BigDecimal seuilMinEpargneInterets;
@Schema(description = "Date du prochain calcul automatique des intérêts")
private LocalDate prochaineCalculInterets;
@Schema(description = "Date du dernier calcul effectué")
private LocalDate dernierCalculInterets;
@Schema(description = "Nombre de comptes épargne traités lors du dernier calcul")
private Integer dernierNbComptesTraites;
}

View File

@@ -0,0 +1,41 @@
package dev.lions.unionflow.server.api.dto.mutuelle.parts;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Ouverture d'un compte de parts sociales")
public class ComptePartsSocialesRequest {
@NotBlank
@Schema(description = "UUID du membre souscripteur")
private String membreId;
@NotBlank
@Schema(description = "UUID de l'organisation / mutuelle")
private String organisationId;
@NotNull
@Min(0)
@Schema(description = "Nombre de parts souscrites à l'ouverture (0 = compte sans parts initiales)", example = "1")
private Integer nombreParts;
@Schema(description = "Valeur nominale par part (XOF). Si absent, utilise la valeur configurée pour l'organisation.")
@DecimalMin(value = "1.0")
private BigDecimal valeurNominale;
@Schema(description = "Notes d'ouverture")
private String notes;
}

View File

@@ -0,0 +1,42 @@
package dev.lions.unionflow.server.api.dto.mutuelle.parts;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.StatutComptePartsSociales;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDate;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Compte de parts sociales d'un membre")
public class ComptePartsSocialesResponse extends BaseDTO {
private String membreId;
private String membreNomComplet;
private String organisationId;
@Schema(description = "Numéro de compte (ex: PS-MEG-00042)")
private String numeroCompte;
@Schema(description = "Nombre de parts actuellement détenues")
private Integer nombreParts;
@Schema(description = "Valeur nominale par part en devise de l'organisation")
private BigDecimal valeurNominale;
@Schema(description = "Montant total du capital = nombreParts × valeurNominale")
private BigDecimal montantTotal;
@Schema(description = "Cumul des dividendes reçus")
private BigDecimal totalDividendesRecus;
private StatutComptePartsSociales statut;
private LocalDate dateOuverture;
private LocalDate dateDerniereOperation;
private String notes;
}

View File

@@ -0,0 +1,44 @@
package dev.lions.unionflow.server.api.dto.mutuelle.parts;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.TypeTransactionPartsSociales;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Transaction sur un compte de parts sociales")
public class TransactionPartsSocialesRequest {
@NotBlank
@Schema(description = "UUID du compte de parts sociales cible")
private String compteId;
@NotNull
private TypeTransactionPartsSociales typeTransaction;
@NotNull
@Min(1)
@Schema(description = "Nombre de parts concernées par la transaction")
private Integer nombreParts;
@Schema(description = "Montant total en devise. Si absent, calculé depuis nombreParts × valeurNominale du compte.")
@DecimalMin(value = "0.01")
private BigDecimal montant;
@Schema(description = "Motif / libellé de la transaction")
private String motif;
@Schema(description = "Référence externe (n° de reçu, bordereau, etc.)")
private String referenceExterne;
}

View File

@@ -0,0 +1,39 @@
package dev.lions.unionflow.server.api.dto.mutuelle.parts;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.TypeTransactionPartsSociales;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Transaction sur un compte de parts sociales")
public class TransactionPartsSocialesResponse extends BaseDTO {
private String compteId;
private String numeroCompte;
private TypeTransactionPartsSociales typeTransaction;
private String typeTransactionLibelle;
@Schema(description = "Nombre de parts impliquées")
private Integer nombreParts;
@Schema(description = "Montant en devise")
private BigDecimal montant;
@Schema(description = "Solde en parts avant la transaction")
private Integer soldePartsAvant;
@Schema(description = "Solde en parts après la transaction")
private Integer soldePartsApres;
private String motif;
private String referenceExterne;
private LocalDateTime dateTransaction;
}

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

@@ -46,28 +46,13 @@ public class OrganisationResponse extends BaseResponse {
// ── Classification ─────────────────────────
private String typeOrganisation;
/** Alias pour tri/filtre (type d'organisation) */
private String typeAssociation;
private String typeOrganisationLibelle;
/** Libellé du type pour affichage (alias typeOrganisationLibelle, ex. p:tag) */
private String typeLibelle;
private String statut;
private String statutLibelle;
private String statutSeverity;
private LocalDate dateFondation;
private String numeroEnregistrement;
/**
* Alias pour la vue (detail.xhtml, organisation-form.xhtml) : même valeur que numeroEnregistrement.
*/
public String getNumeroRegistre() {
return getNumeroEnregistrement();
}
public void setNumeroRegistre(String numeroRegistre) {
setNumeroEnregistrement(numeroRegistre);
}
// ── Géographie ─────────────────────────────
private String adresse;
private String quartier;
@@ -81,13 +66,6 @@ public class OrganisationResponse extends BaseResponse {
// ── Hiérarchie ─────────────────────────────
private UUID organisationParenteId;
private String organisationParenteNom;
/** Alias pour la vue (detail.xhtml) : même valeur que organisationParenteNom. */
public String getNomOrganisationParente() {
return getOrganisationParenteNom();
}
public void setNomOrganisationParente(String nomOrganisationParente) {
setOrganisationParenteNom(nomOrganisationParente);
}
private Integer niveauHierarchique;
private Boolean estOrganisationRacine;
@@ -113,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

@@ -1,23 +1,33 @@
package dev.lions.unionflow.server.api.dto.organisation.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.UUID;
/**
* Résumé d'une organisation pour les listes.
* Classe JavaBean (pas un record) pour compatibilité JSF/EL (getNom() requis).
*
* @author UnionFlow Team
* @version 3.0
* @version 3.1
* @since 2026-02-22
*/
public record OrganisationSummaryResponse(
UUID id,
String nom,
String nomCourt,
String typeOrganisation,
String typeOrganisationLibelle,
String statut,
String statutLibelle,
String statutSeverity,
Integer nombreMembres,
Boolean actif) {
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrganisationSummaryResponse {
private UUID id;
private String nom;
private String nomCourt;
private String typeOrganisation;
private String typeOrganisationLibelle;
private String statut;
private String statutLibelle;
private String statutSeverity;
private Integer nombreMembres;
private Boolean actif;
private String ville;
private String pays;
}

View File

@@ -3,29 +3,34 @@ package dev.lions.unionflow.server.api.dto.paiement.request;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Builder;
/**
* Requete de création d'un paiement.
* Requête de création d'un paiement.
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Builder
public record CreatePaiementRequest(
@NotBlank(message = "Le numéro de référence est obligatoire") String numeroReference,
@NotNull(message = "Le montant est obligatoire") @DecimalMin(value = "0.0", message = "Le montant doit être positif") BigDecimal montant,
@NotBlank(message = "Le numéro de référence est obligatoire")
String numeroReference,
@NotBlank(message = "Le code devise est obligatoire") @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") String codeDevise,
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.01", message = "Le montant doit être supérieur à 0")
BigDecimal montant,
@NotBlank(message = "La méthode de paiement est obligatoire") String methodePaiement,
@NotBlank(message = "Le code devise est obligatoire")
String codeDevise,
String commentaire,
@NotBlank(message = "La méthode de paiement est obligatoire")
String methodePaiement,
@NotNull(message = "Le membre payeur est obligatoire") UUID membreId) {
}
String commentaire,
UUID membreId
) {}

View File

@@ -2,45 +2,25 @@ package dev.lions.unionflow.server.api.dto.paiement.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour déclarer un paiement manuel (espèces, virement, chèque).
* Le paiement est créé avec le statut EN_ATTENTE_VALIDATION.
* Le trésorier devra le valider via une page admin.
*
* @param cotisationId ID de la cotisation payée
* @param methodePaiement Méthode de paiement (ESPECES, VIREMENT, CHEQUE, AUTRE)
* @param reference Référence du paiement (numéro de transaction, numéro de chèque, etc.)
* @param commentaire Commentaire optionnel
* @param origineFonds Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré
* @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire
* Requête de déclaration d'un paiement manuel (espèces, virement, chèque).
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-02
* @version 3.0
* @since 2025-01-29
*/
@Builder
public record DeclarerPaiementManuelRequest(
@NotNull(message = "L'ID de la cotisation est obligatoire")
UUID cotisationId,
@NotBlank(message = "La méthode de paiement est obligatoire")
@Pattern(regexp = "^(ESPECES|VIREMENT|CHEQUE|AUTRE)$",
message = "Méthode de paiement invalide. Valeurs autorisées : ESPECES, VIREMENT, CHEQUE, AUTRE")
String methodePaiement,
@Size(max = 100, message = "La référence ne doit pas dépasser 100 caractères")
/** Référence externe (numéro de chèque, référence virement, etc.) */
String reference,
@Size(max = 500, message = "Le commentaire ne doit pas dépasser 500 caractères")
String commentaire,
String origineFonds,
String justificationLcbFt
) {
}
String commentaire
) {}

View File

@@ -1,40 +1,22 @@
package dev.lions.unionflow.server.api.dto.paiement.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour initier un paiement en ligne via un gateway (Wave, Orange, Free Money, Carte).
*
* @param cotisationId ID de la cotisation à payer
* @param methodePaiement Méthode de paiement (WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE)
* @param numeroTelephone Numéro de téléphone pour Wave/Orange/Free (format: 221771234567)
* @param origineFonds Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré
* @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire
* Requête d'initiation de paiement en ligne via Wave, Orange Money, Free Money ou carte bancaire.
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-02
* @version 3.0
* @since 2025-01-29
*/
@Builder
public record InitierPaiementEnLigneRequest(
@NotNull(message = "L'ID de la cotisation est obligatoire")
UUID cotisationId,
@NotBlank(message = "La méthode de paiement est obligatoire")
@Pattern(regexp = "^(WAVE|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$",
message = "Méthode de paiement invalide. Valeurs autorisées : WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE")
@NotNull(message = "La méthode de paiement est obligatoire")
String methodePaiement,
/** Optionnel sur le web (QR code sans restriction de payeur). Obligatoire sur mobile. */
@Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)")
String numeroTelephone,
String origineFonds,
String justificationLcbFt
) {
}
String numeroTelephone
) {}

View File

@@ -2,23 +2,52 @@ package dev.lions.unionflow.server.api.dto.paiement.response;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Réponse du polling de statut d'une IntentionPaiement Wave.
* Utilisée par le web pour savoir si le paiement est confirmé.
* Réponse au polling du statut d'une intention de paiement Wave.
*
* <p>Utilisée par le deep link de retour (mobile) et le polling web (QR code).
* Quand {@code statut} vaut {@code COMPLETEE}, la cotisation est automatiquement
* marquée PAYEE et {@code confirme} vaut {@code true}.
*
* @author UnionFlow Team
* @version 3.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public record IntentionStatutResponse(
UUID intentionId,
public class IntentionStatutResponse {
/** UUID de l'intention de paiement (clé de polling) */
private UUID intentionId;
/** INITIEE | EN_COURS | COMPLETEE | EXPIREE | ECHOUEE */
String statut,
/** URL à encoder en QR code (wave_launch_url Wave Checkout) */
String waveLaunchUrl,
String waveCheckoutSessionId,
private String statut;
/** Raccourci : {@code true} si statut == COMPLETEE */
private boolean confirme;
/** URL Wave — pour régénérer le QR code si la session est toujours active */
private String waveLaunchUrl;
/** ID de session Wave (cos-xxx) */
private String waveCheckoutSessionId;
/** ID de transaction Wave (TCN...) — disponible quand COMPLETEE */
String waveTransactionId,
BigDecimal montant,
String referenceCotisation,
String message
) {}
private String waveTransactionId;
/** Montant du paiement */
private BigDecimal montant;
/** Référence de la cotisation concernée */
private String referenceCotisation;
/** Message lisible pour l'utilisateur */
private String message;
}

View File

@@ -2,76 +2,45 @@ package dev.lions.unionflow.server.api.dto.paiement.response;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO de réponse pour l'initiation d'un paiement en ligne via un gateway.
* Retourne l'URL de redirection vers le gateway (Wave, Orange, Free Money, Carte).
* DTO de réponse pour l'initiation d'un paiement en ligne (Wave, Orange Money, etc.).
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-02
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class PaiementGatewayResponse {
/**
* ID de la transaction créée
*/
/** ID interne du paiement ou de l'intention créé. */
private UUID transactionId;
/**
* URL de redirection vers le gateway de paiement
*/
/** URL de redirection vers la page de paiement du gateway. */
private String redirectUrl;
/**
* Montant à payer (en FCFA)
*/
private BigDecimal montant;
/**
* Statut de la transaction (EN_ATTENTE, VALIDE, ECHOUE)
*/
private String statut;
/**
* Méthode de paiement (WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE)
*/
private String methodePaiement;
/**
* Référence de la cotisation
*/
private String referenceCotisation;
/**
* Message d'information pour l'utilisateur
*/
private String message;
/**
* URL Wave pour ouvrir l'app Wave (redirection automatique membre).
* Présent lorsque methodePaiement = WAVE.
*/
/** URL spécifique Wave (wave_launch_url) pour deep-link vers l'app Wave. */
private String waveLaunchUrl;
/**
* ID de session Checkout Wave (cos-xxx). Pour réconciliation / webhook.
*/
/** ID de la session Wave Checkout. */
private String waveCheckoutSessionId;
/**
* Référence client (UUID intention) pour le deep link de retour.
*/
/** Référence client envoyée à Wave (intention UUID). */
private String clientReference;
private BigDecimal montant;
/** Statut initial (EN_ATTENTE). */
private String statut;
private String methodePaiement;
/** Numéro de référence de la cotisation associée. */
private String referenceCotisation;
/** Message d'information à afficher à l'utilisateur. */
private String message;
}

View File

@@ -16,36 +16,39 @@ import lombok.Setter;
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class PaiementResponse extends BaseResponse {
private String numeroReference;
private BigDecimal montant;
private String codeDevise;
private String methodePaiement;
private String methodePaiementLibelle;
private String statutPaiement;
private String statutPaiementLibelle;
private String statutPaiementSeverity;
private LocalDateTime datePaiement;
private LocalDateTime dateValidation;
private String validateur;
private String referenceExterne;
private String urlPreuve;
private String commentaire;
// Informations relatives au payeur
private UUID membreId;
private String membreNom;
// Intégration Wave
private UUID transactionWaveId;
// LCB-FT (Lutte Contre le Blanchiment et Financement du Terrorisme)
private String origineFonds;
private String justificationLcbFt;
}

View File

@@ -5,20 +5,20 @@ import java.time.LocalDateTime;
import java.util.UUID;
/**
* DTO de réponse résumé pour un paiement.
* Utilisé principalement pour l'affichage de listes.
* DTO résumé pour les listes de paiements.
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public record PaiementSummaryResponse(
UUID id,
String numeroReference,
BigDecimal montant,
String codeDevise,
String methodePaiementLibelle,
String statutPaiement,
String statutPaiementLibelle,
String statutPaiementSeverity,
LocalDateTime datePaiement) {
}
UUID id,
String numeroReference,
BigDecimal montant,
String codeDevise,
String methodePaiementLibelle,
String statutPaiement,
String statutPaiementLibelle,
String statutPaiementSeverity,
LocalDateTime datePaiement
) {}

View File

@@ -52,5 +52,17 @@ public record CreateTypeReferenceRequest(
Boolean estSysteme,
/** UUID de l'organisation (null = global). */
UUID organisationId) {
UUID organisationId,
/**
* Catégorie fonctionnelle (ex: FINANCIER_SOLIDAIRE).
* Obligatoire pour domaine=TYPE_ORGANISATION.
*/
@Size(max = 50) String categorie,
/**
* Modules métier CSV (ex: "MEMBRES,COTISATIONS,TONTINE,FINANCE").
* Obligatoire pour domaine=TYPE_ORGANISATION.
*/
String modulesRequis) {
}

View File

@@ -41,5 +41,11 @@ public record UpdateTypeReferenceRequest(
Boolean estDefaut,
/** Nouvel état actif/inactif (optionnel). */
Boolean actif) {
Boolean actif,
/** Nouvelle catégorie fonctionnelle (optionnel). */
@Size(max = 50) String categorie,
/** Nouveaux modules requis CSV (optionnel). */
String modulesRequis) {
}

View File

@@ -60,4 +60,19 @@ public class TypeReferenceResponse extends BaseResponse {
/** UUID de l'organisation (null = global). */
private UUID organisationId;
/**
* Catégorie fonctionnelle du type d'organisation.
* Valeurs: ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION.
* Uniquement pertinent pour domaine=TYPE_ORGANISATION.
*/
private String categorie;
/**
* Modules métier activés pour ce type d'organisation (CSV).
* Exemple: "MEMBRES,COTISATIONS,TONTINE,FINANCE"
* Utilisé pour initialiser Organisation.modulesActifs à la création.
* Uniquement pertinent pour domaine=TYPE_ORGANISATION.
*/
private String modulesRequis;
}

View File

@@ -0,0 +1,50 @@
package dev.lions.unionflow.server.api.dto.versement.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour déclarer un versement manuel (espèces, virement, chèque).
*
* <p>Le versement est créé avec le statut {@code EN_ATTENTE_VALIDATION}.
* Le trésorier de l'organisation devra le valider via la page admin.
*
* @param cotisationId ID de la cotisation réglée (obligatoire)
* @param methodePaiement Mode de règlement : ESPECES | VIREMENT | CHEQUE | AUTRE
* @param reference Référence libre (numéro de chèque, bordereau de virement…)
* @param commentaire Commentaire optionnel
* @param origineFonds Origine des fonds — obligatoire au-delà du seuil LCB-FT
* @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record DeclarerVersementManuelRequest(
@NotNull(message = "L'ID de la cotisation est obligatoire")
UUID cotisationId,
@NotBlank(message = "La méthode de règlement est obligatoire")
@Pattern(
regexp = "^(ESPECES|VIREMENT|CHEQUE|AUTRE)$",
message = "Méthode invalide. Valeurs autorisées : ESPECES, VIREMENT, CHEQUE, AUTRE"
)
String methodePaiement,
@Size(max = 100, message = "La référence ne doit pas dépasser 100 caractères")
String reference,
@Size(max = 500, message = "Le commentaire ne doit pas dépasser 500 caractères")
String commentaire,
String origineFonds,
String justificationLcbFt
) {}

View File

@@ -0,0 +1,43 @@
package dev.lions.unionflow.server.api.dto.versement.request;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour initier un dépôt sur compte épargne via Wave.
*
* <p>Même flux que le versement de cotisation : Wave s'ouvre sur le même téléphone,
* le membre confirme avec son PIN Wave, puis retour automatique dans UnionFlow.
*
* @param compteId ID du compte épargne cible (obligatoire)
* @param montant Montant à déposer, en FCFA (obligatoire, &gt; 0)
* @param numeroTelephone Numéro Wave du membre, pré-rempli depuis le profil
* @param origineFonds Origine des fonds — obligatoire au-delà du seuil LCB-FT
* @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record InitierDepotEpargneRequest(
@NotNull(message = "L'ID du compte épargne est obligatoire")
UUID compteId,
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.01", message = "Le montant doit être supérieur à 0")
BigDecimal montant,
@Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)")
String numeroTelephone,
String origineFonds,
String justificationLcbFt
) {}

View File

@@ -0,0 +1,39 @@
package dev.lions.unionflow.server.api.dto.versement.request;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.util.UUID;
import lombok.Builder;
/**
* Requête pour initier un versement via Wave Mobile Money.
*
* <p>Wave est la seule méthode de paiement en ligne active. Le numéro de
* téléphone est pré-rempli depuis le profil du membre (même téléphone qu'UnionFlow),
* ce qui permet à Wave de s'ouvrir directement avec toutes les informations.
*
* @param cotisationId ID de la cotisation à régler (obligatoire)
* @param numeroTelephone Numéro Wave du membre, pré-rempli depuis le profil
* (9-15 chiffres, optionnel sur web QR code)
* @param origineFonds Origine des fonds — obligatoire au-delà du seuil LCB-FT
* @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Builder
public record InitierVersementWaveRequest(
@NotNull(message = "L'ID de la cotisation est obligatoire")
UUID cotisationId,
/** Optionnel sur web (QR code sans restriction de payeur). Obligatoire sur mobile. */
@Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)")
String numeroTelephone,
String origineFonds,
String justificationLcbFt
) {}

View File

@@ -0,0 +1,59 @@
package dev.lions.unionflow.server.api.dto.versement.response;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Réponse à l'initiation d'un versement Wave.
*
* <p>Contient le {@code waveLaunchUrl} permettant à {@code url_launcher}
* d'ouvrir l'application Wave directement sur le téléphone du membre avec
* le montant et le numéro pré-remplis. Le {@code clientReference} (UUID
* de l'intention) est utilisé dans le deep link de retour :
* {@code unionflow://payment?result=success&ref={clientReference}}.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class VersementGatewayResponse {
/** ID du versement créé */
private UUID versementId;
/** URL Wave pour {@code url_launcher} — ouvre l'app Wave sur le même téléphone */
private String waveLaunchUrl;
/** ID de session Checkout Wave (cos-xxx) — pour le polling et la réconciliation webhook */
private String waveCheckoutSessionId;
/**
* UUID de l'intention — utilisé comme paramètre {@code ref} dans le deep link retour
* {@code unionflow://payment?result=success&ref={clientReference}}
*/
private String clientReference;
/** Montant du versement en FCFA */
private BigDecimal montant;
/** Statut initial : EN_ATTENTE */
private String statut;
/** Référence de la cotisation concernée */
private String referenceCotisation;
/** Message d'information pour l'utilisateur */
private String message;
}

View File

@@ -0,0 +1,85 @@
package dev.lions.unionflow.server.api.dto.versement.response;
import dev.lions.unionflow.server.api.dto.base.BaseResponse;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO de réponse détaillée pour un versement.
*
* <p>Un versement est l'acte de régler une cotisation. Il peut être effectué
* via Wave (deep link natif) ou manuellement (espèces, virement, chèque).
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class VersementResponse extends BaseResponse {
/** Référence unique du versement (ex: VRS-2026-XXXXXXXXXXXX) */
private String numeroReference;
// ── Montant ───────────────────────────────────────────────────────────────
private BigDecimal montant;
private String codeDevise;
// ── Méthode ───────────────────────────────────────────────────────────────
/** WAVE | ESPECES | VIREMENT | CHEQUE | AUTRE */
private String methodePaiement;
private String methodePaiementLibelle;
// ── Statut ────────────────────────────────────────────────────────────────
/** EN_ATTENTE | EN_COURS | CONFIRME | ECHEC | EN_ATTENTE_VALIDATION | ANNULE */
private String statutPaiement;
private String statutPaiementLibelle;
private String statutPaiementSeverity;
// ── Dates ─────────────────────────────────────────────────────────────────
private LocalDateTime datePaiement;
private LocalDateTime dateValidation;
// ── Validation (paiement manuel) ──────────────────────────────────────────
private String validateur;
// ── Traçabilité ───────────────────────────────────────────────────────────
/** ID transaction Wave (TCN...) ou référence chèque/virement */
private String referenceExterne;
private String urlPreuve;
private String commentaire;
// ── Payeur ────────────────────────────────────────────────────────────────
private UUID membreId;
private String membreNom;
/** Numéro Wave utilisé pour ce versement */
private String numeroTelephone;
// ── Intégration Wave ─────────────────────────────────────────────────────
private UUID transactionWaveId;
// ── LCB-FT ───────────────────────────────────────────────────────────────
private String origineFonds;
private String justificationLcbFt;
}

View File

@@ -0,0 +1,53 @@
package dev.lions.unionflow.server.api.dto.versement.response;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Réponse au polling du statut d'un versement Wave.
*
* <p>Utilisée par le deep link de retour (mobile) et le polling web.
* Quand {@code statut} vaut {@code COMPLETEE}, la cotisation est automatiquement
* marquée PAYEE et {@code confirme} vaut {@code true}.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class VersementStatutResponse {
/** UUID de l'intention Wave (clé de polling) */
private UUID intentionId;
/** INITIEE | EN_COURS | COMPLETEE | EXPIREE | ECHOUEE */
private String statut;
/** Raccourci : {@code true} si statut == COMPLETEE */
private boolean confirme;
/** URL Wave — pour régénérer le QR code si la session est toujours active */
private String waveLaunchUrl;
/** ID de session Wave (cos-xxx) */
private String waveCheckoutSessionId;
/** ID de transaction Wave (TCN...) — disponible quand COMPLETEE */
private String waveTransactionId;
/** Montant du versement */
private BigDecimal montant;
/** Référence de la cotisation concernée */
private String referenceCotisation;
/** Message lisible pour l'utilisateur */
private String message;
}

View File

@@ -0,0 +1,30 @@
package dev.lions.unionflow.server.api.dto.versement.response;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO résumé d'un versement — utilisé dans les listes.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VersementSummaryResponse {
private UUID id;
private String numeroReference;
private BigDecimal montant;
private String codeDevise;
private String methodePaiementLibelle;
private String statutPaiement;
private String statutPaiementLibelle;
private String statutPaiementSeverity;
private LocalDateTime datePaiement;
}

View File

@@ -0,0 +1,43 @@
package dev.lions.unionflow.server.api.enums.membre;
/**
* Niveau de risque LCB-FT calculé par le service KYC/AML.
* Détermine les contrôles additionnels applicables au membre.
*/
public enum NiveauRisqueKyc {
/** Risque faible — profil standard, pas de signalement. */
FAIBLE("Risque faible", 0, 39),
/** Risque moyen — surveillance accrue, revue annuelle. */
MOYEN("Risque moyen", 40, 69),
/** Risque élevé — due diligence renforcée, approbation manuelle. */
ELEVE("Risque élevé", 70, 89),
/** Risque très élevé — blocage automatique, signalement GIABA. */
CRITIQUE("Risque critique", 90, 100);
private final String libelle;
private final int scoreMin;
private final int scoreMax;
NiveauRisqueKyc(String libelle, int scoreMin, int scoreMax) {
this.libelle = libelle;
this.scoreMin = scoreMin;
this.scoreMax = scoreMax;
}
public String getLibelle() { return libelle; }
public int getScoreMin() { return scoreMin; }
public int getScoreMax() { return scoreMax; }
public static NiveauRisqueKyc fromScore(int score) {
for (NiveauRisqueKyc niveau : values()) {
if (score >= niveau.scoreMin && score <= niveau.scoreMax) {
return niveau;
}
}
return CRITIQUE;
}
}

View File

@@ -0,0 +1,24 @@
package dev.lions.unionflow.server.api.enums.membre;
/**
* Type de pièce d'identité pour le dossier KYC — conformité GIABA/BCEAO LCB-FT.
*/
public enum TypePieceIdentite {
CNI("Carte Nationale d'Identité"),
PASSEPORT("Passeport"),
TITRE_SEJOUR("Titre de séjour"),
CARTE_CONSULAIRE("Carte consulaire"),
PERMIS_CONDUIRE("Permis de conduire"),
AUTRE("Autre pièce officielle");
private final String libelle;
TypePieceIdentite(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,17 @@
package dev.lions.unionflow.server.api.enums.messagerie;
/**
* Statut d'une conversation.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
public enum StatutConversation {
/** Conversation active — messages entrants autorisés. */
ACTIVE,
/** Conversation archivée — lecture seule, plus de nouveaux messages. */
ARCHIVEE
}

View File

@@ -0,0 +1,35 @@
package dev.lions.unionflow.server.api.enums.messagerie;
/**
* Type de contenu d'un message.
*
* <p>Les notes vocales (VOCAL) sont critiques pour l'inclusivité :
* les membres analphabètes peuvent communiquer par audio exactement
* comme sur WhatsApp (bouton maintenir pour parler).
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
public enum TypeContenu {
/** Message texte classique. */
TEXTE,
/**
* Note vocale — fichier audio Opus/AAC stocké sur object storage.
* Champs associés : {@code urlFichier} + {@code dureeAudio} (secondes).
*/
VOCAL,
/**
* Image — JPEG/PNG stockée sur object storage.
* Champ associé : {@code urlFichier}.
*/
IMAGE,
/**
* Message système généré automatiquement (ex: "La conversation a été archivée").
*/
SYSTEME
}

View File

@@ -0,0 +1,31 @@
package dev.lions.unionflow.server.api.enums.messagerie;
/**
* Type de conversation dans la messagerie instantanée.
*
* <p>La politique de l'organisation détermine quels types sont autorisés.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
public enum TypeConversation {
/**
* Conversation directe 1-1 entre deux membres de la même organisation.
* Autorisée par défaut dans toutes les organisations.
*/
DIRECTE,
/**
* Canal vers un rôle officiel (PRESIDENT, TRESORIER, SECRETAIRE…).
* Le membre contacte la "boîte" du rôle, pas une personne spécifique.
* Tous les membres du rôle voient et peuvent répondre.
*/
ROLE_CANAL,
/**
* Conversation de groupe — réservé V2.
*/
GROUPE
}

View File

@@ -0,0 +1,38 @@
package dev.lions.unionflow.server.api.enums.messagerie;
/**
* Politique de communication configurable par l'administrateur d'organisation.
*
* <p>Valeurs par défaut selon le type d'organisation :
* <ul>
* <li>Tontine (≤30 membres) → {@code OUVERT}</li>
* <li>Association (30500) → {@code OUVERT}</li>
* <li>Mutuelle (505000) → {@code BUREAU_SEULEMENT}</li>
* <li>Coopérative → {@code OUVERT}</li>
* </ul>
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
public enum TypePolitiqueCommunication {
/**
* Tous les membres peuvent se contacter entre eux et contacter le bureau.
* Mode par défaut pour les petites structures (tontines, associations).
*/
OUVERT,
/**
* Les membres ne peuvent contacter que les rôles du bureau
* (Président, Trésorier, Secrétaire).
* Recommandé pour les mutuelles et grandes organisations.
*/
BUREAU_SEULEMENT,
/**
* Communication uniquement au sein des groupes/comités internes.
* Pour les coopératives avec plusieurs sections.
*/
GROUPES_INTERNES
}

View File

@@ -0,0 +1,12 @@
package dev.lions.unionflow.server.api.enums.mutuelle.parts;
public enum StatutComptePartsSociales {
ACTIF("Compte actif"),
SUSPENDU("Compte suspendu"),
CLOS("Compte clôturé — parts rachetées");
private final String libelle;
StatutComptePartsSociales(String libelle) { this.libelle = libelle; }
public String getLibelle() { return libelle; }
}

View File

@@ -0,0 +1,15 @@
package dev.lions.unionflow.server.api.enums.mutuelle.parts;
public enum TypeTransactionPartsSociales {
SOUSCRIPTION("Souscription de parts sociales"),
SOUSCRIPTION_IMPORT("Import historique — souscription"),
CESSION_PARTIELLE("Cession partielle de parts"),
RACHAT_TOTAL("Rachat total (clôture)"),
PAIEMENT_DIVIDENDE("Versement de dividendes / intérêts sur parts"),
CORRECTION("Correction administrative");
private final String libelle;
TypeTransactionPartsSociales(String libelle) { this.libelle = libelle; }
public String getLibelle() { return libelle; }
}

View File

@@ -0,0 +1,25 @@
package dev.lions.unionflow.server.api.payment;
import java.math.BigDecimal;
import java.util.Map;
/**
* Requête d'initiation d'un checkout — abstraction provider-agnostique.
*/
public record CheckoutRequest(
BigDecimal amount,
String currency,
String customerPhone,
String customerEmail,
/** Référence interne UnionFlow (souscription, cotisation, transaction épargne...). */
String reference,
String successUrl,
String cancelUrl,
Map<String, String> metadata
) {
public CheckoutRequest {
if (amount == null || amount.signum() <= 0) throw new IllegalArgumentException("amount doit être positif");
if (currency == null || currency.isBlank()) throw new IllegalArgumentException("currency obligatoire");
if (reference == null || reference.isBlank()) throw new IllegalArgumentException("reference obligatoire");
}
}

View File

@@ -0,0 +1,14 @@
package dev.lions.unionflow.server.api.payment;
import java.time.Instant;
import java.util.Map;
/**
* Résultat d'une initiation de session de paiement.
*/
public record CheckoutSession(
String externalId,
String checkoutUrl,
Instant expiresAt,
Map<String, String> providerMetadata
) {}

View File

@@ -0,0 +1,16 @@
package dev.lions.unionflow.server.api.payment;
import java.math.BigDecimal;
import java.time.Instant;
/**
* Événement de paiement normalisé reçu via webhook ou polling.
*/
public record PaymentEvent(
String externalId,
String reference,
PaymentStatus status,
BigDecimal amountConfirmed,
String transactionCode,
Instant occurredAt
) {}

View File

@@ -0,0 +1,25 @@
package dev.lions.unionflow.server.api.payment;
/**
* Exception levée par les implémentations PaymentProvider.
*/
public class PaymentException extends RuntimeException {
private final String providerCode;
private final int httpStatus;
public PaymentException(String providerCode, String message, int httpStatus) {
super("[" + providerCode + "] " + message);
this.providerCode = providerCode;
this.httpStatus = httpStatus;
}
public PaymentException(String providerCode, String message, int httpStatus, Throwable cause) {
super("[" + providerCode + "] " + message, cause);
this.providerCode = providerCode;
this.httpStatus = httpStatus;
}
public String getProviderCode() { return providerCode; }
public int getHttpStatus() { return httpStatus; }
}

View File

@@ -0,0 +1,55 @@
package dev.lions.unionflow.server.api.payment;
import java.util.Map;
import java.util.Set;
/**
* Abstraction unifiée pour tous les providers de paiement UnionFlow.
*
* <p>Implémentations disponibles : Wave, Orange Money, MTN MoMo, Moov Money, PI-SPI (BCEAO).
* Chaque provider est un bean CDI {@code @ApplicationScoped} découvert via
* {@code @Any Instance<PaymentProvider>} dans le registry.
*/
public interface PaymentProvider {
/** Code unique du provider — utilisé comme discriminant dans la config et les routes webhook. */
String getProviderCode();
/**
* Initie une session de paiement et retourne l'URL de checkout.
*
* @throws PaymentException si le provider est indisponible ou la requête invalide
*/
CheckoutSession initiateCheckout(CheckoutRequest request) throws PaymentException;
/**
* Interroge le statut d'un paiement par son identifiant provider.
*
* @throws PaymentException si le provider est indisponible
*/
PaymentStatus getStatus(String externalId) throws PaymentException;
/**
* Traite un webhook entrant et vérifie sa signature.
*
* @param rawBody payload brut HTTP body
* @param headers en-têtes HTTP (inclut la signature)
* @throws PaymentException si la signature est invalide ou le payload malformé
*/
PaymentEvent processWebhook(String rawBody, Map<String, String> headers) throws PaymentException;
/** Devises supportées — par défaut XOF uniquement. */
default Set<String> getSupportedCurrencies() {
return Set.of("XOF");
}
/** Indique si ce provider supporte les remboursements. */
default boolean supportsRefund() {
return false;
}
/** Health check rapide (ping API provider). */
default boolean isAvailable() {
return true;
}
}

View File

@@ -0,0 +1,10 @@
package dev.lions.unionflow.server.api.payment;
public enum PaymentStatus {
INITIATED,
PROCESSING,
SUCCESS,
FAILED,
CANCELLED,
EXPIRED
}

View File

@@ -0,0 +1,45 @@
package dev.lions.unionflow.server.api.dto.agricole;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class CampagneAgricoleDTOTest {
@Test
void testConstructeurParDefaut() {
assertThat(new CampagneAgricoleDTO()).isNotNull();
}
@Test
void testSettersEtGetters() {
CampagneAgricoleDTO dto = new CampagneAgricoleDTO();
dto.setOrganisationCoopId("org-1");
dto.setDesignation("Campagne Arachide 2026");
dto.setTypeCulturePrincipale("Arachide");
dto.setSurfaceTotaleEstimeeHectares(new BigDecimal("100.5"));
dto.setVolumePrevisionnelTonnes(new BigDecimal("50"));
dto.setVolumeReelTonnes(new BigDecimal("48"));
dto.setStatut(StatutCampagneAgricole.RECOLTE);
assertThat(dto.getOrganisationCoopId()).isEqualTo("org-1");
assertThat(dto.getDesignation()).isEqualTo("Campagne Arachide 2026");
assertThat(dto.getTypeCulturePrincipale()).isEqualTo("Arachide");
assertThat(dto.getSurfaceTotaleEstimeeHectares()).isEqualByComparingTo("100.5");
assertThat(dto.getVolumePrevisionnelTonnes()).isEqualByComparingTo("50");
assertThat(dto.getVolumeReelTonnes()).isEqualByComparingTo("48");
assertThat(dto.getStatut()).isEqualTo(StatutCampagneAgricole.RECOLTE);
}
@Test
void testBuilder() {
CampagneAgricoleDTO dto = CampagneAgricoleDTO.builder()
.designation("Test")
.statut(StatutCampagneAgricole.PREPARATION)
.build();
assertThat(dto.getDesignation()).isEqualTo("Test");
assertThat(dto.getStatut()).isEqualTo(StatutCampagneAgricole.PREPARATION);
}
}

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

@@ -58,7 +58,7 @@ class LoginRequestTest {
LoginRequest request = LoginRequest.builder()
.username("test@test.com")
.password("test123")
.typeCompte("ADMIN_ENTITE")
.typeCompte("ADMIN_ORGANISATION")
.rememberMe(false)
.build();

View File

@@ -0,0 +1,53 @@
package dev.lions.unionflow.server.api.dto.ayantdroit;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
class AyantDroitRequestTest {
@Test
void testConstructeurParDefaut() {
assertThat(new AyantDroitRequest()).isNotNull();
}
@Test
void testSettersEtGetters() {
AyantDroitRequest req = new AyantDroitRequest();
req.setMembrePrincipalId("m-1");
req.setPrenom("Alice");
req.setNom("Dupont");
req.setDateNaissance(LocalDate.of(2000, 1, 15));
req.setSexe("F");
req.setPieceIdentite("CNI-123");
req.setLienParente(LienParente.CONJOINT);
req.setJustificatifLienId("doc-1");
assertThat(req.getMembrePrincipalId()).isEqualTo("m-1");
assertThat(req.getPrenom()).isEqualTo("Alice");
assertThat(req.getNom()).isEqualTo("Dupont");
assertThat(req.getDateNaissance()).isEqualTo(LocalDate.of(2000, 1, 15));
assertThat(req.getSexe()).isEqualTo("F");
assertThat(req.getLienParente()).isEqualTo(LienParente.CONJOINT);
}
@Test
void testBuilder() {
AyantDroitRequest req = AyantDroitRequest.builder()
.membrePrincipalId("m-2")
.prenom("Bob")
.lienParente(LienParente.ENFANT)
.build();
assertThat(req.getMembrePrincipalId()).isEqualTo("m-2");
assertThat(req.getLienParente()).isEqualTo(LienParente.ENFANT);
}
@Test
void testEqualsHashCode() {
AyantDroitRequest r1 = AyantDroitRequest.builder().membrePrincipalId("x").prenom("A").build();
AyantDroitRequest r2 = AyantDroitRequest.builder().membrePrincipalId("x").prenom("A").build();
assertThat(r1).isEqualTo(r2);
}
}

View File

@@ -0,0 +1,48 @@
package dev.lions.unionflow.server.api.dto.ayantdroit;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente;
import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
class AyantDroitResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new AyantDroitResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
AyantDroitResponse resp = new AyantDroitResponse();
resp.setMembrePrincipalId("m-1");
resp.setNumeroCarteBeneficiaire("CB-001");
resp.setPrenom("Alice");
resp.setNom("Dupont");
resp.setDateNaissance(LocalDate.of(2000, 1, 1));
resp.setAgeActuel(26);
resp.setLienParente(LienParente.ENFANT);
resp.setPourcentageCouvertureSante(new BigDecimal("80.0"));
resp.setStatut(StatutAyantDroit.ACTIF);
assertThat(resp.getMembrePrincipalId()).isEqualTo("m-1");
assertThat(resp.getNumeroCarteBeneficiaire()).isEqualTo("CB-001");
assertThat(resp.getPrenom()).isEqualTo("Alice");
assertThat(resp.getAgeActuel()).isEqualTo(26);
assertThat(resp.getLienParente()).isEqualTo(LienParente.ENFANT);
assertThat(resp.getStatut()).isEqualTo(StatutAyantDroit.ACTIF);
}
@Test
void testBuilder() {
AyantDroitResponse resp = AyantDroitResponse.builder()
.prenom("Bob")
.statut(StatutAyantDroit.INACTIF)
.build();
assertThat(resp.getPrenom()).isEqualTo("Bob");
assertThat(resp.getStatut()).isEqualTo(StatutAyantDroit.INACTIF);
}
}

View File

@@ -0,0 +1,53 @@
package dev.lions.unionflow.server.api.dto.backup.request;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Tests CreateBackupRequest")
class CreateBackupRequestTest {
@Test
void testConstructeurParDefaut() {
assertThat(new CreateBackupRequest()).isNotNull();
}
@Test
void testSettersEtGetters() {
CreateBackupRequest req = new CreateBackupRequest();
req.setName("backup-2026");
req.setDescription("Full backup");
req.setType("MANUAL");
req.setIncludeDatabase(true);
req.setIncludeFiles(false);
req.setIncludeConfiguration(true);
assertThat(req.getName()).isEqualTo("backup-2026");
assertThat(req.getDescription()).isEqualTo("Full backup");
assertThat(req.getType()).isEqualTo("MANUAL");
assertThat(req.getIncludeDatabase()).isTrue();
assertThat(req.getIncludeFiles()).isFalse();
assertThat(req.getIncludeConfiguration()).isTrue();
}
@Test
void testBuilder() {
CreateBackupRequest req = CreateBackupRequest.builder()
.name("test")
.type("AUTO")
.includeDatabase(true)
.build();
assertThat(req.getName()).isEqualTo("test");
assertThat(req.getType()).isEqualTo("AUTO");
assertThat(req.getIncludeDatabase()).isTrue();
}
@Test
void testEqualsHashCode() {
CreateBackupRequest r1 = CreateBackupRequest.builder().name("x").build();
CreateBackupRequest r2 = CreateBackupRequest.builder().name("x").build();
assertThat(r1).isEqualTo(r2);
assertThat(r1.hashCode()).isEqualTo(r2.hashCode());
}
}

View File

@@ -0,0 +1,55 @@
package dev.lions.unionflow.server.api.dto.backup.request;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Tests RestoreBackupRequest")
class RestoreBackupRequestTest {
@Test
void testConstructeurParDefaut() {
assertThat(new RestoreBackupRequest()).isNotNull();
}
@Test
void testSettersEtGetters() {
UUID id = UUID.randomUUID();
RestoreBackupRequest req = new RestoreBackupRequest();
req.setBackupId(id);
req.setRestoreDatabase(true);
req.setRestoreFiles(false);
req.setRestoreConfiguration(true);
req.setCreateRestorePoint(false);
assertThat(req.getBackupId()).isEqualTo(id);
assertThat(req.getRestoreDatabase()).isTrue();
assertThat(req.getRestoreFiles()).isFalse();
assertThat(req.getRestoreConfiguration()).isTrue();
assertThat(req.getCreateRestorePoint()).isFalse();
}
@Test
void testBuilder() {
UUID id = UUID.randomUUID();
RestoreBackupRequest req = RestoreBackupRequest.builder()
.backupId(id)
.restoreDatabase(true)
.createRestorePoint(true)
.build();
assertThat(req.getBackupId()).isEqualTo(id);
assertThat(req.getRestoreDatabase()).isTrue();
assertThat(req.getCreateRestorePoint()).isTrue();
}
@Test
void testEqualsHashCode() {
UUID id = UUID.randomUUID();
RestoreBackupRequest r1 = RestoreBackupRequest.builder().backupId(id).build();
RestoreBackupRequest r2 = RestoreBackupRequest.builder().backupId(id).build();
assertThat(r1).isEqualTo(r2);
assertThat(r1.hashCode()).isEqualTo(r2.hashCode());
}
}

View File

@@ -0,0 +1,49 @@
package dev.lions.unionflow.server.api.dto.backup.request;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Tests UpdateBackupConfigRequest")
class UpdateBackupConfigRequestTest {
@Test
void testConstructeurParDefaut() {
assertThat(new UpdateBackupConfigRequest()).isNotNull();
}
@Test
void testSettersEtGetters() {
UpdateBackupConfigRequest req = new UpdateBackupConfigRequest();
req.setAutoBackupEnabled(true);
req.setFrequency("DAILY");
req.setRetention("30 jours");
req.setRetentionDays(30);
req.setBackupTime("02:00");
req.setIncludeDatabase(true);
req.setIncludeFiles(true);
req.setIncludeConfiguration(false);
assertThat(req.getAutoBackupEnabled()).isTrue();
assertThat(req.getFrequency()).isEqualTo("DAILY");
assertThat(req.getRetention()).isEqualTo("30 jours");
assertThat(req.getRetentionDays()).isEqualTo(30);
assertThat(req.getBackupTime()).isEqualTo("02:00");
assertThat(req.getIncludeDatabase()).isTrue();
assertThat(req.getIncludeFiles()).isTrue();
assertThat(req.getIncludeConfiguration()).isFalse();
}
@Test
void testBuilder() {
UpdateBackupConfigRequest req = UpdateBackupConfigRequest.builder()
.autoBackupEnabled(false)
.frequency("WEEKLY")
.retentionDays(90)
.build();
assertThat(req.getAutoBackupEnabled()).isFalse();
assertThat(req.getFrequency()).isEqualTo("WEEKLY");
assertThat(req.getRetentionDays()).isEqualTo(90);
}
}

View File

@@ -0,0 +1,59 @@
package dev.lions.unionflow.server.api.dto.backup.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Tests BackupConfigResponse")
class BackupConfigResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new BackupConfigResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
LocalDateTime last = LocalDateTime.now().minusDays(1);
LocalDateTime next = LocalDateTime.now().plusDays(1);
BackupConfigResponse resp = new BackupConfigResponse();
resp.setAutoBackupEnabled(true);
resp.setFrequency("DAILY");
resp.setRetention("7 jours");
resp.setRetentionDays(7);
resp.setBackupTime("03:00");
resp.setIncludeDatabase(true);
resp.setIncludeFiles(false);
resp.setIncludeConfiguration(true);
resp.setLastBackup(last);
resp.setNextScheduledBackup(next);
resp.setTotalBackups(5);
resp.setTotalSizeBytes(1024L);
resp.setTotalSizeFormatted("1 KB");
assertThat(resp.getAutoBackupEnabled()).isTrue();
assertThat(resp.getFrequency()).isEqualTo("DAILY");
assertThat(resp.getRetention()).isEqualTo("7 jours");
assertThat(resp.getRetentionDays()).isEqualTo(7);
assertThat(resp.getBackupTime()).isEqualTo("03:00");
assertThat(resp.getIncludeDatabase()).isTrue();
assertThat(resp.getIncludeFiles()).isFalse();
assertThat(resp.getLastBackup()).isEqualTo(last);
assertThat(resp.getNextScheduledBackup()).isEqualTo(next);
assertThat(resp.getTotalBackups()).isEqualTo(5);
assertThat(resp.getTotalSizeBytes()).isEqualTo(1024L);
assertThat(resp.getTotalSizeFormatted()).isEqualTo("1 KB");
}
@Test
void testBuilder() {
BackupConfigResponse resp = BackupConfigResponse.builder()
.frequency("HOURLY")
.totalBackups(10)
.build();
assertThat(resp.getFrequency()).isEqualTo("HOURLY");
assertThat(resp.getTotalBackups()).isEqualTo(10);
}
}

View File

@@ -0,0 +1,68 @@
package dev.lions.unionflow.server.api.dto.backup.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Tests BackupResponse")
class BackupResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new BackupResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
UUID id = UUID.randomUUID();
LocalDateTime created = LocalDateTime.now();
BackupResponse resp = new BackupResponse();
resp.setId(id);
resp.setName("full-backup");
resp.setDescription("Full backup");
resp.setType("MANUAL");
resp.setSizeBytes(2048L);
resp.setSizeFormatted("2 KB");
resp.setStatus("COMPLETED");
resp.setCreatedAt(created);
resp.setCreatedBy("admin");
resp.setIncludesDatabase(true);
resp.setIncludesFiles(false);
resp.setIncludesConfiguration(true);
resp.setFilePath("/backups/test.tar.gz");
resp.setErrorMessage(null);
assertThat(resp.getId()).isEqualTo(id);
assertThat(resp.getName()).isEqualTo("full-backup");
assertThat(resp.getType()).isEqualTo("MANUAL");
assertThat(resp.getSizeBytes()).isEqualTo(2048L);
assertThat(resp.getSizeFormatted()).isEqualTo("2 KB");
assertThat(resp.getStatus()).isEqualTo("COMPLETED");
assertThat(resp.getCreatedAt()).isEqualTo(created);
assertThat(resp.getCreatedBy()).isEqualTo("admin");
assertThat(resp.getIncludesDatabase()).isTrue();
assertThat(resp.getIncludesFiles()).isFalse();
assertThat(resp.getFilePath()).isEqualTo("/backups/test.tar.gz");
assertThat(resp.getErrorMessage()).isNull();
}
@Test
void testBuilder() {
BackupResponse resp = BackupResponse.builder()
.name("test")
.status("PENDING")
.build();
assertThat(resp.getName()).isEqualTo("test");
assertThat(resp.getStatus()).isEqualTo("PENDING");
}
@Test
void testEqualsHashCode() {
BackupResponse r1 = BackupResponse.builder().name("a").build();
BackupResponse r2 = BackupResponse.builder().name("a").build();
assertThat(r1).isEqualTo(r2);
}
}

View File

@@ -0,0 +1,45 @@
package dev.lions.unionflow.server.api.dto.collectefonds;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class CampagneCollecteResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new CampagneCollecteResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
CampagneCollecteResponse resp = new CampagneCollecteResponse();
resp.setOrganisationId("org-1");
resp.setTitre("Collecte Ramadan");
resp.setCourteDescription("Aide aux familles");
resp.setObjectifFinancier(new BigDecimal("1000000"));
resp.setMontantCollecteActuel(new BigDecimal("500000"));
resp.setNombreDonateurs(50);
resp.setStatut(StatutCampagneCollecte.EN_COURS);
resp.setEstPublique(true);
assertThat(resp.getOrganisationId()).isEqualTo("org-1");
assertThat(resp.getTitre()).isEqualTo("Collecte Ramadan");
assertThat(resp.getObjectifFinancier()).isEqualByComparingTo("1000000");
assertThat(resp.getMontantCollecteActuel()).isEqualByComparingTo("500000");
assertThat(resp.getNombreDonateurs()).isEqualTo(50);
assertThat(resp.getStatut()).isEqualTo(StatutCampagneCollecte.EN_COURS);
assertThat(resp.getEstPublique()).isTrue();
}
@Test
void testBuilder() {
CampagneCollecteResponse resp = CampagneCollecteResponse.builder()
.titre("Test")
.statut(StatutCampagneCollecte.ATTEINTE)
.build();
assertThat(resp.getTitre()).isEqualTo("Test");
}
}

View File

@@ -0,0 +1,41 @@
package dev.lions.unionflow.server.api.dto.collectefonds;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class ContributionCollecteDTOTest {
@Test
void testConstructeurParDefaut() {
assertThat(new ContributionCollecteDTO()).isNotNull();
}
@Test
void testSettersEtGetters() {
ContributionCollecteDTO dto = new ContributionCollecteDTO();
dto.setCampagneId("camp-1");
dto.setMembreDonateurId("m-1");
dto.setAliasDonateur("Donateur Anonyme");
dto.setEstAnonyme(true);
dto.setMontantSoutien(new BigDecimal("10000"));
dto.setMessageSoutien("Bon courage");
assertThat(dto.getCampagneId()).isEqualTo("camp-1");
assertThat(dto.getMembreDonateurId()).isEqualTo("m-1");
assertThat(dto.getAliasDonateur()).isEqualTo("Donateur Anonyme");
assertThat(dto.getEstAnonyme()).isTrue();
assertThat(dto.getMontantSoutien()).isEqualByComparingTo("10000");
assertThat(dto.getMessageSoutien()).isEqualTo("Bon courage");
}
@Test
void testBuilder() {
ContributionCollecteDTO dto = ContributionCollecteDTO.builder()
.campagneId("c-1")
.montantSoutien(new BigDecimal("5000"))
.build();
assertThat(dto.getCampagneId()).isEqualTo("c-1");
}
}

View File

@@ -0,0 +1,32 @@
package dev.lions.unionflow.server.api.dto.common;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class ErrorResponseTest {
@Test
void testOf_messageUniquement() {
ErrorResponse response = ErrorResponse.of("Une erreur est survenue");
assertThat(response.getMessage()).isEqualTo("Une erreur est survenue");
assertThat(response.getError()).isNull();
}
@Test
void testOfError_errorUniquement() {
ErrorResponse response = ErrorResponse.ofError("NOT_FOUND");
assertThat(response.getMessage()).isNull();
assertThat(response.getError()).isEqualTo("NOT_FOUND");
}
@Test
void testConstructeurComplet() {
ErrorResponse response = new ErrorResponse("Message d'erreur", "VALIDATION_ERROR");
assertThat(response.getMessage()).isEqualTo("Message d'erreur");
assertThat(response.getError()).isEqualTo("VALIDATION_ERROR");
}
}

View File

@@ -40,28 +40,28 @@ class ConversationResponseTest {
.organisationId(orgId)
.lastMessage(lastMessage)
.unreadCount(5)
.isMuted(true)
.isPinned(false)
.isArchived(false)
.muted(true)
.pinned(false)
.archived(false)
.createdAt(createdAt)
.updatedAt(updatedAt)
.avatarUrl("https://example.com/avatar.jpg")
.build();
assertThat(response.id()).isEqualTo(id);
assertThat(response.name()).isEqualTo("Conversation Test");
assertThat(response.description()).isEqualTo("Description test");
assertThat(response.type()).isEqualTo(ConversationType.GROUP);
assertThat(response.participantIds()).containsExactly(participant1, participant2);
assertThat(response.organisationId()).isEqualTo(orgId);
assertThat(response.lastMessage()).isEqualTo(lastMessage);
assertThat(response.unreadCount()).isEqualTo(5);
assertThat(response.getId()).isEqualTo(id);
assertThat(response.getName()).isEqualTo("Conversation Test");
assertThat(response.getDescription()).isEqualTo("Description test");
assertThat(response.getType()).isEqualTo(ConversationType.GROUP);
assertThat(response.getParticipantIds()).containsExactly(participant1, participant2);
assertThat(response.getOrganisationId()).isEqualTo(orgId);
assertThat(response.getLastMessage()).isEqualTo(lastMessage);
assertThat(response.getUnreadCount()).isEqualTo(5);
assertThat(response.isMuted()).isTrue();
assertThat(response.isPinned()).isFalse();
assertThat(response.isArchived()).isFalse();
assertThat(response.createdAt()).isEqualTo(createdAt);
assertThat(response.updatedAt()).isEqualTo(updatedAt);
assertThat(response.avatarUrl()).isEqualTo("https://example.com/avatar.jpg");
assertThat(response.getCreatedAt()).isEqualTo(createdAt);
assertThat(response.getUpdatedAt()).isEqualTo(updatedAt);
assertThat(response.getAvatarUrl()).isEqualTo("https://example.com/avatar.jpg");
}
@Test
@@ -69,20 +69,20 @@ class ConversationResponseTest {
void testBuilderWithNulls() {
ConversationResponse response = ConversationResponse.builder().build();
assertThat(response.id()).isNull();
assertThat(response.name()).isNull();
assertThat(response.description()).isNull();
assertThat(response.type()).isNull();
assertThat(response.participantIds()).isNull();
assertThat(response.organisationId()).isNull();
assertThat(response.lastMessage()).isNull();
assertThat(response.unreadCount()).isEqualTo(0);
assertThat(response.getId()).isNull();
assertThat(response.getName()).isNull();
assertThat(response.getDescription()).isNull();
assertThat(response.getType()).isNull();
assertThat(response.getParticipantIds()).isNull();
assertThat(response.getOrganisationId()).isNull();
assertThat(response.getLastMessage()).isNull();
assertThat(response.getUnreadCount()).isEqualTo(0);
assertThat(response.isMuted()).isFalse();
assertThat(response.isPinned()).isFalse();
assertThat(response.isArchived()).isFalse();
assertThat(response.createdAt()).isNull();
assertThat(response.updatedAt()).isNull();
assertThat(response.avatarUrl()).isNull();
assertThat(response.getCreatedAt()).isNull();
assertThat(response.getUpdatedAt()).isNull();
assertThat(response.getAvatarUrl()).isNull();
}
@Test

View File

@@ -51,28 +51,28 @@ class MessageResponseTest {
.createdAt(createdAt)
.readAt(readAt)
.attachments(List.of("file1.pdf", "file2.png"))
.isEdited(true)
.edited(true)
.editedAt(editedAt)
.isDeleted(false)
.deleted(false)
.build();
assertThat(response.id()).isEqualTo(id);
assertThat(response.conversationId()).isEqualTo(conversationId);
assertThat(response.senderId()).isEqualTo(senderId);
assertThat(response.senderName()).isEqualTo("John Doe");
assertThat(response.senderAvatar()).isEqualTo("https://example.com/avatar.jpg");
assertThat(response.content()).isEqualTo("Message content");
assertThat(response.type()).isEqualTo(MessageType.INDIVIDUAL);
assertThat(response.status()).isEqualTo(MessageStatus.READ);
assertThat(response.priority()).isEqualTo(MessagePriority.HIGH);
assertThat(response.recipientIds()).containsExactly(recipient1, recipient2);
assertThat(response.recipientRoles()).containsExactly("ADMIN", "USER");
assertThat(response.organisationId()).isEqualTo(orgId);
assertThat(response.createdAt()).isEqualTo(createdAt);
assertThat(response.readAt()).isEqualTo(readAt);
assertThat(response.attachments()).containsExactly("file1.pdf", "file2.png");
assertThat(response.getId()).isEqualTo(id);
assertThat(response.getConversationId()).isEqualTo(conversationId);
assertThat(response.getSenderId()).isEqualTo(senderId);
assertThat(response.getSenderName()).isEqualTo("John Doe");
assertThat(response.getSenderAvatar()).isEqualTo("https://example.com/avatar.jpg");
assertThat(response.getContent()).isEqualTo("Message content");
assertThat(response.getType()).isEqualTo(MessageType.INDIVIDUAL);
assertThat(response.getStatus()).isEqualTo(MessageStatus.READ);
assertThat(response.getPriority()).isEqualTo(MessagePriority.HIGH);
assertThat(response.getRecipientIds()).containsExactly(recipient1, recipient2);
assertThat(response.getRecipientRoles()).containsExactly("ADMIN", "USER");
assertThat(response.getOrganisationId()).isEqualTo(orgId);
assertThat(response.getCreatedAt()).isEqualTo(createdAt);
assertThat(response.getReadAt()).isEqualTo(readAt);
assertThat(response.getAttachments()).containsExactly("file1.pdf", "file2.png");
assertThat(response.isEdited()).isTrue();
assertThat(response.editedAt()).isEqualTo(editedAt);
assertThat(response.getEditedAt()).isEqualTo(editedAt);
assertThat(response.isDeleted()).isFalse();
}
@@ -81,23 +81,23 @@ class MessageResponseTest {
void testBuilderWithNulls() {
MessageResponse response = MessageResponse.builder().build();
assertThat(response.id()).isNull();
assertThat(response.conversationId()).isNull();
assertThat(response.senderId()).isNull();
assertThat(response.senderName()).isNull();
assertThat(response.senderAvatar()).isNull();
assertThat(response.content()).isNull();
assertThat(response.type()).isNull();
assertThat(response.status()).isNull();
assertThat(response.priority()).isNull();
assertThat(response.recipientIds()).isNull();
assertThat(response.recipientRoles()).isNull();
assertThat(response.organisationId()).isNull();
assertThat(response.createdAt()).isNull();
assertThat(response.readAt()).isNull();
assertThat(response.attachments()).isNull();
assertThat(response.getId()).isNull();
assertThat(response.getConversationId()).isNull();
assertThat(response.getSenderId()).isNull();
assertThat(response.getSenderName()).isNull();
assertThat(response.getSenderAvatar()).isNull();
assertThat(response.getContent()).isNull();
assertThat(response.getType()).isNull();
assertThat(response.getStatus()).isNull();
assertThat(response.getPriority()).isNull();
assertThat(response.getRecipientIds()).isNull();
assertThat(response.getRecipientRoles()).isNull();
assertThat(response.getOrganisationId()).isNull();
assertThat(response.getCreatedAt()).isNull();
assertThat(response.getReadAt()).isNull();
assertThat(response.getAttachments()).isNull();
assertThat(response.isEdited()).isFalse();
assertThat(response.editedAt()).isNull();
assertThat(response.getEditedAt()).isNull();
assertThat(response.isDeleted()).isFalse();
}

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,46 @@
package dev.lions.unionflow.server.api.dto.comptabilite.response;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class CompteComptableResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new CompteComptableResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
CompteComptableResponse resp = new CompteComptableResponse();
resp.setNumeroCompte("401000");
resp.setLibelle("Fournisseurs");
resp.setTypeCompte(TypeCompteComptable.PASSIF);
resp.setClasseComptable(4);
resp.setSoldeInitial(new BigDecimal("0"));
resp.setSoldeActuel(new BigDecimal("500000"));
resp.setCompteCollectif(true);
resp.setCompteAnalytique(false);
resp.setDescription("Compte fournisseurs");
assertThat(resp.getNumeroCompte()).isEqualTo("401000");
assertThat(resp.getLibelle()).isEqualTo("Fournisseurs");
assertThat(resp.getTypeCompte()).isEqualTo(TypeCompteComptable.PASSIF);
assertThat(resp.getClasseComptable()).isEqualTo(4);
assertThat(resp.getSoldeActuel()).isEqualByComparingTo("500000");
assertThat(resp.getCompteCollectif()).isTrue();
assertThat(resp.getCompteAnalytique()).isFalse();
}
@Test
void testBuilder() {
CompteComptableResponse resp = CompteComptableResponse.builder()
.numeroCompte("501000")
.typeCompte(TypeCompteComptable.ACTIF)
.build();
assertThat(resp.getNumeroCompte()).isEqualTo("501000");
}
}

View File

@@ -0,0 +1,48 @@
package dev.lions.unionflow.server.api.dto.comptabilite.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class EcritureComptableResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new EcritureComptableResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
UUID journalId = UUID.randomUUID();
EcritureComptableResponse resp = new EcritureComptableResponse();
resp.setNumeroPiece("PIECE-001");
resp.setDateEcriture(LocalDate.of(2026, 1, 15));
resp.setLibelle("Cotisation membre");
resp.setReference("REF-001");
resp.setPointe(false);
resp.setMontantDebit(new BigDecimal("50000"));
resp.setMontantCredit(new BigDecimal("50000"));
resp.setJournalId(journalId);
resp.setLignes(List.of());
assertThat(resp.getNumeroPiece()).isEqualTo("PIECE-001");
assertThat(resp.getDateEcriture()).isEqualTo(LocalDate.of(2026, 1, 15));
assertThat(resp.getLibelle()).isEqualTo("Cotisation membre");
assertThat(resp.getMontantDebit()).isEqualByComparingTo("50000");
assertThat(resp.getJournalId()).isEqualTo(journalId);
assertThat(resp.getLignes()).isEmpty();
}
@Test
void testBuilder() {
EcritureComptableResponse resp = EcritureComptableResponse.builder()
.numeroPiece("P-002")
.montantDebit(new BigDecimal("1000"))
.build();
assertThat(resp.getNumeroPiece()).isEqualTo("P-002");
}
}

View File

@@ -0,0 +1,41 @@
package dev.lions.unionflow.server.api.dto.comptabilite.response;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
class JournalComptableResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new JournalComptableResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
JournalComptableResponse resp = new JournalComptableResponse();
resp.setCode("JC-001");
resp.setLibelle("Journal Caisse");
resp.setTypeJournal(TypeJournalComptable.CAISSE);
resp.setDateDebut(LocalDate.of(2026, 1, 1));
resp.setDateFin(LocalDate.of(2026, 12, 31));
resp.setStatut("OUVERT");
resp.setDescription("Journal de caisse principale");
assertThat(resp.getCode()).isEqualTo("JC-001");
assertThat(resp.getLibelle()).isEqualTo("Journal Caisse");
assertThat(resp.getTypeJournal()).isEqualTo(TypeJournalComptable.CAISSE);
assertThat(resp.getStatut()).isEqualTo("OUVERT");
}
@Test
void testBuilder() {
JournalComptableResponse resp = JournalComptableResponse.builder()
.code("JB-001")
.typeJournal(TypeJournalComptable.BANQUE)
.build();
assertThat(resp.getCode()).isEqualTo("JB-001");
}
}

View File

@@ -0,0 +1,44 @@
package dev.lions.unionflow.server.api.dto.comptabilite.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import java.util.UUID;
import org.junit.jupiter.api.Test;
class LigneEcritureResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new LigneEcritureResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
UUID ecritureId = UUID.randomUUID();
UUID compteId = UUID.randomUUID();
LigneEcritureResponse resp = new LigneEcritureResponse();
resp.setNumeroLigne(1);
resp.setMontantDebit(new BigDecimal("25000"));
resp.setMontantCredit(BigDecimal.ZERO);
resp.setLibelle("Ligne débit");
resp.setReference("REF-L1");
resp.setEcritureId(ecritureId);
resp.setCompteComptableId(compteId);
assertThat(resp.getNumeroLigne()).isEqualTo(1);
assertThat(resp.getMontantDebit()).isEqualByComparingTo("25000");
assertThat(resp.getLibelle()).isEqualTo("Ligne débit");
assertThat(resp.getEcritureId()).isEqualTo(ecritureId);
assertThat(resp.getCompteComptableId()).isEqualTo(compteId);
}
@Test
void testBuilder() {
LigneEcritureResponse resp = LigneEcritureResponse.builder()
.numeroLigne(2)
.montantCredit(new BigDecimal("25000"))
.build();
assertThat(resp.getNumeroLigne()).isEqualTo(2);
}
}

View File

@@ -0,0 +1,46 @@
package dev.lions.unionflow.server.api.dto.config.request;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class ParametresLcbFtRequestTest {
@Test
void testConstructeurParDefaut() {
assertThat(new ParametresLcbFtRequest()).isNotNull();
}
@Test
void testSettersEtGetters() {
ParametresLcbFtRequest req = new ParametresLcbFtRequest();
req.setOrganisationId("org-1");
req.setMontantSeuilJustification(new BigDecimal("500000"));
req.setMontantSeuilValidationManuelle(new BigDecimal("1000000"));
req.setCodeDevise("XOF");
req.setNotes("Paramètres UEMOA");
assertThat(req.getOrganisationId()).isEqualTo("org-1");
assertThat(req.getMontantSeuilJustification()).isEqualByComparingTo("500000");
assertThat(req.getMontantSeuilValidationManuelle()).isEqualByComparingTo("1000000");
assertThat(req.getCodeDevise()).isEqualTo("XOF");
assertThat(req.getNotes()).isEqualTo("Paramètres UEMOA");
}
@Test
void testBuilder() {
ParametresLcbFtRequest req = ParametresLcbFtRequest.builder()
.codeDevise("XOF")
.montantSeuilJustification(new BigDecimal("300000"))
.build();
assertThat(req.getCodeDevise()).isEqualTo("XOF");
}
@Test
void testEqualsHashCode() {
ParametresLcbFtRequest r1 = ParametresLcbFtRequest.builder().codeDevise("XOF").build();
ParametresLcbFtRequest r2 = ParametresLcbFtRequest.builder().codeDevise("XOF").build();
assertThat(r1).isEqualTo(r2);
}
}

View File

@@ -0,0 +1,44 @@
package dev.lions.unionflow.server.api.dto.config.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map;
import org.junit.jupiter.api.Test;
class ConfigurationResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new ConfigurationResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
ConfigurationResponse resp = new ConfigurationResponse();
resp.setCle("smtp.host");
resp.setValeur("mail.example.com");
resp.setType("STRING");
resp.setCategorie("EMAIL");
resp.setDescription("Serveur SMTP");
resp.setModifiable(true);
resp.setVisible(true);
resp.setMetadonnees(Map.of("env", "prod"));
assertThat(resp.getCle()).isEqualTo("smtp.host");
assertThat(resp.getValeur()).isEqualTo("mail.example.com");
assertThat(resp.getType()).isEqualTo("STRING");
assertThat(resp.getCategorie()).isEqualTo("EMAIL");
assertThat(resp.getModifiable()).isTrue();
assertThat(resp.getMetadonnees()).containsEntry("env", "prod");
}
@Test
void testBuilder() {
ConfigurationResponse resp = ConfigurationResponse.builder()
.cle("key1")
.valeur("val1")
.build();
assertThat(resp.getCle()).isEqualTo("key1");
assertThat(resp.getValeur()).isEqualTo("val1");
}
}

View File

@@ -0,0 +1,42 @@
package dev.lions.unionflow.server.api.dto.config.response;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class ParametresLcbFtResponseTest {
@Test
void testConstructeurParDefaut() {
assertThat(new ParametresLcbFtResponse()).isNotNull();
}
@Test
void testSettersEtGetters() {
ParametresLcbFtResponse resp = new ParametresLcbFtResponse();
resp.setOrganisationId("org-1");
resp.setOrganisationNom("Association Test");
resp.setMontantSeuilJustification(new BigDecimal("500000"));
resp.setMontantSeuilValidationManuelle(new BigDecimal("1000000"));
resp.setCodeDevise("XOF");
resp.setNotes("Notes");
resp.setEstParametrePlateforme(false);
assertThat(resp.getOrganisationId()).isEqualTo("org-1");
assertThat(resp.getOrganisationNom()).isEqualTo("Association Test");
assertThat(resp.getMontantSeuilJustification()).isEqualByComparingTo("500000");
assertThat(resp.getCodeDevise()).isEqualTo("XOF");
assertThat(resp.getEstParametrePlateforme()).isFalse();
}
@Test
void testBuilder() {
ParametresLcbFtResponse resp = ParametresLcbFtResponse.builder()
.codeDevise("XOF")
.estParametrePlateforme(true)
.build();
assertThat(resp.getCodeDevise()).isEqualTo("XOF");
assertThat(resp.getEstParametrePlateforme()).isTrue();
}
}

View File

@@ -228,6 +228,28 @@ class CotisationResponseTest {
}
}
@Nested
@DisplayName("Alias getMethodePaiementLibelle")
class AliasMethodePaiement {
@Test
@DisplayName("getMethodePaiementLibelle retourne modePaiementLibelle")
void getMethodePaiementLibelle_retourneModePaiementLibelle() {
CotisationResponse r = CotisationResponse.builder()
.modePaiementLibelle("Wave Money")
.build();
assertThat(r.getMethodePaiementLibelle()).isEqualTo("Wave Money");
}
@Test
@DisplayName("getMethodePaiementLibelle retourne null si non renseigné")
void getMethodePaiementLibelle_null() {
CotisationResponse r = CotisationResponse.builder().build();
assertThat(r.getMethodePaiementLibelle()).isNull();
}
}
@Nested
@DisplayName("Builder complet")
class BuilderComplet {

View File

@@ -10,7 +10,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests unitaires pour CotisationSummaryResponse (record).
* Tests unitaires pour CotisationSummaryResponse.
*
* @author UnionFlow Team
* @version 1.0
@@ -51,16 +51,16 @@ class CotisationSummaryResponseTest {
);
assertThat(response).isNotNull();
assertThat(response.id()).isEqualTo(id);
assertThat(response.numeroReference()).isEqualTo("COT-2026-001");
assertThat(response.nomMembre()).isEqualTo("Jean Dupont");
assertThat(response.montantDu()).isEqualTo(new BigDecimal("50000.00"));
assertThat(response.montantPaye()).isEqualTo(new BigDecimal("30000.00"));
assertThat(response.statut()).isEqualTo("EN_COURS");
assertThat(response.statutLibelle()).isEqualTo("Paiement en cours");
assertThat(response.dateEcheance()).isEqualTo(LocalDate.of(2026, 12, 31));
assertThat(response.annee()).isEqualTo(2026);
assertThat(response.actif()).isTrue();
assertThat(response.getId()).isEqualTo(id);
assertThat(response.getNumeroReference()).isEqualTo("COT-2026-001");
assertThat(response.getNomMembre()).isEqualTo("Jean Dupont");
assertThat(response.getMontantDu()).isEqualTo(new BigDecimal("50000.00"));
assertThat(response.getMontantPaye()).isEqualTo(new BigDecimal("30000.00"));
assertThat(response.getStatut()).isEqualTo("EN_COURS");
assertThat(response.getStatutLibelle()).isEqualTo("Paiement en cours");
assertThat(response.getDateEcheance()).isEqualTo(LocalDate.of(2026, 12, 31));
assertThat(response.getAnnee()).isEqualTo(2026);
assertThat(response.getActif()).isTrue();
}
@Test
@@ -80,16 +80,16 @@ class CotisationSummaryResponseTest {
);
assertThat(response).isNotNull();
assertThat(response.id()).isNull();
assertThat(response.numeroReference()).isNull();
assertThat(response.nomMembre()).isNull();
assertThat(response.montantDu()).isNull();
assertThat(response.montantPaye()).isNull();
assertThat(response.statut()).isNull();
assertThat(response.statutLibelle()).isNull();
assertThat(response.dateEcheance()).isNull();
assertThat(response.annee()).isNull();
assertThat(response.actif()).isNull();
assertThat(response.getId()).isNull();
assertThat(response.getNumeroReference()).isNull();
assertThat(response.getNomMembre()).isNull();
assertThat(response.getMontantDu()).isNull();
assertThat(response.getMontantPaye()).isNull();
assertThat(response.getStatut()).isNull();
assertThat(response.getStatutLibelle()).isNull();
assertThat(response.getDateEcheance()).isNull();
assertThat(response.getAnnee()).isNull();
assertThat(response.getActif()).isNull();
}
}
@@ -213,8 +213,8 @@ class CotisationSummaryResponseTest {
true
);
assertThat(response.montantDu()).isEqualTo(response.montantPaye());
assertThat(response.statut()).isEqualTo("PAYE");
assertThat(response.getMontantDu()).isEqualTo(response.getMontantPaye());
assertThat(response.getStatut()).isEqualTo("PAYE");
}
@Test
@@ -233,8 +233,8 @@ class CotisationSummaryResponseTest {
true
);
assertThat(response.montantPaye()).isLessThan(response.montantDu());
assertThat(response.statut()).isEqualTo("EN_COURS");
assertThat(response.getMontantPaye()).isLessThan(response.getMontantDu());
assertThat(response.getStatut()).isEqualTo("EN_COURS");
}
@Test
@@ -253,8 +253,8 @@ class CotisationSummaryResponseTest {
true
);
assertThat(response.montantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
assertThat(response.statut()).isEqualTo("IMPAYE");
assertThat(response.getMontantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
assertThat(response.getStatut()).isEqualTo("IMPAYE");
}
@Test
@@ -275,8 +275,8 @@ class CotisationSummaryResponseTest {
true
);
assertThat(response.dateEcheance()).isBefore(LocalDate.now());
assertThat(response.statut()).isEqualTo("RETARD");
assertThat(response.getDateEcheance()).isBefore(LocalDate.now());
assertThat(response.getStatut()).isEqualTo("RETARD");
}
}
}

View File

@@ -0,0 +1,44 @@
package dev.lions.unionflow.server.api.dto.culte;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
class DonReligieuxDTOTest {
@Test
void testConstructeurParDefaut() {
assertThat(new DonReligieuxDTO()).isNotNull();
}
@Test
void testSettersEtGetters() {
LocalDateTime date = LocalDateTime.now();
DonReligieuxDTO dto = new DonReligieuxDTO();
dto.setInstitutionId("mosque-1");
dto.setFideleId("m-1");
dto.setTypeDon(TypeDonReligieux.ZAKAT);
dto.setMontant(new BigDecimal("25000"));
dto.setDateEncaissement(date);
dto.setPeriodeOuNatureAssociee("Ramadan 2026");
assertThat(dto.getInstitutionId()).isEqualTo("mosque-1");
assertThat(dto.getFideleId()).isEqualTo("m-1");
assertThat(dto.getTypeDon()).isEqualTo(TypeDonReligieux.ZAKAT);
assertThat(dto.getMontant()).isEqualByComparingTo("25000");
assertThat(dto.getDateEncaissement()).isEqualTo(date);
assertThat(dto.getPeriodeOuNatureAssociee()).isEqualTo("Ramadan 2026");
}
@Test
void testBuilder() {
DonReligieuxDTO dto = DonReligieuxDTO.builder()
.institutionId("church-1")
.typeDon(TypeDonReligieux.DIME)
.build();
assertThat(dto.getInstitutionId()).isEqualTo("church-1");
}
}

View File

@@ -10,8 +10,8 @@ import org.junit.jupiter.api.Test;
class MembreDashboardSyntheseResponseTest {
@Test
@DisplayName("Record MembreDashboardSyntheseResponse : construction et accesseurs")
void record_constructionAndAccessors() {
@DisplayName("MembreDashboardSyntheseResponse : construction et getters")
void constructionAndGetters() {
MembreDashboardSyntheseResponse dto = new MembreDashboardSyntheseResponse(
"Jean",
"Dupont",
@@ -35,20 +35,20 @@ class MembreDashboardSyntheseResponseTest {
100
);
assertThat(dto.prenom()).isEqualTo("Jean");
assertThat(dto.nom()).isEqualTo("Dupont");
assertThat(dto.dateInscription()).isEqualTo(LocalDate.of(2023, 1, 15));
assertThat(dto.mesCotisationsPaiement()).isEqualByComparingTo(BigDecimal.valueOf(50000));
assertThat(dto.totalCotisationsPayeesAnnee()).isEqualByComparingTo(BigDecimal.valueOf(150000));
assertThat(dto.totalCotisationsPayeesToutTemps()).isEqualByComparingTo(BigDecimal.valueOf(200000));
assertThat(dto.nombreCotisationsPayees()).isEqualTo(5);
assertThat(dto.statutCotisations()).isEqualTo("À jour");
assertThat(dto.tauxCotisationsPerso()).isEqualTo(100);
assertThat(dto.monSoldeEpargne()).isEqualByComparingTo(BigDecimal.valueOf(250000));
assertThat(dto.objectifEpargne()).isEqualTo(500000);
assertThat(dto.mesEvenementsInscrits()).isEqualTo(3);
assertThat(dto.tauxParticipationPerso()).isEqualTo(75);
assertThat(dto.mesDemandesAide()).isEqualTo(1);
assertThat(dto.tauxAidesApprouvees()).isEqualTo(100);
assertThat(dto.getPrenom()).isEqualTo("Jean");
assertThat(dto.getNom()).isEqualTo("Dupont");
assertThat(dto.getDateInscription()).isEqualTo(LocalDate.of(2023, 1, 15));
assertThat(dto.getMesCotisationsPaiement()).isEqualByComparingTo(BigDecimal.valueOf(50000));
assertThat(dto.getTotalCotisationsPayeesAnnee()).isEqualByComparingTo(BigDecimal.valueOf(150000));
assertThat(dto.getTotalCotisationsPayeesToutTemps()).isEqualByComparingTo(BigDecimal.valueOf(200000));
assertThat(dto.getNombreCotisationsPayees()).isEqualTo(5);
assertThat(dto.getStatutCotisations()).isEqualTo("À jour");
assertThat(dto.getTauxCotisationsPerso()).isEqualTo(100);
assertThat(dto.getMonSoldeEpargne()).isEqualByComparingTo(BigDecimal.valueOf(250000));
assertThat(dto.getObjectifEpargne()).isEqualTo(500000);
assertThat(dto.getMesEvenementsInscrits()).isEqualTo(3);
assertThat(dto.getTauxParticipationPerso()).isEqualTo(75);
assertThat(dto.getMesDemandesAide()).isEqualTo(1);
assertThat(dto.getTauxAidesApprouvees()).isEqualTo(100);
}
}

View File

@@ -0,0 +1,54 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class MonthlyStatDTOTest {
@Test
void testConstructeurParDefaut() {
assertThat(new MonthlyStatDTO()).isNotNull();
}
@Test
void testSettersEtGetters() {
MonthlyStatDTO dto = new MonthlyStatDTO();
dto.setMonth("2026-01");
dto.setTotalMembers(150);
dto.setActiveMembers(120);
dto.setContributionAmount(500000.0);
dto.setEventsCount(3);
dto.setEngagementRate(80.0);
dto.setNewMembers(10);
dto.setContributionsCount(100);
assertThat(dto.getMonth()).isEqualTo("2026-01");
assertThat(dto.getTotalMembers()).isEqualTo(150);
assertThat(dto.getActiveMembers()).isEqualTo(120);
assertThat(dto.getContributionAmount()).isEqualTo(500000.0);
assertThat(dto.getEventsCount()).isEqualTo(3);
assertThat(dto.getEngagementRate()).isEqualTo(80.0);
assertThat(dto.getNewMembers()).isEqualTo(10);
assertThat(dto.getContributionsCount()).isEqualTo(100);
}
@Test
void testBuilder() {
MonthlyStatDTO dto = MonthlyStatDTO.builder()
.month("2026-03")
.totalMembers(200)
.contributionAmount(750000.0)
.build();
assertThat(dto.getMonth()).isEqualTo("2026-03");
assertThat(dto.getTotalMembers()).isEqualTo(200);
}
@Test
void testEqualsHashCode() {
MonthlyStatDTO d1 = MonthlyStatDTO.builder().month("2026-01").totalMembers(100).build();
MonthlyStatDTO d2 = MonthlyStatDTO.builder().month("2026-01").totalMembers(100).build();
assertThat(d1).isEqualTo(d2);
assertThat(d1.hashCode()).isEqualTo(d2.hashCode());
}
}

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");
}
}

Some files were not shown because too many files have changed in this diff Show More