Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

143
unionflow-server-api/.gitignore vendored Normal file
View File

@@ -0,0 +1,143 @@
# ====================================
# GITIGNORE POUR UNIONFLOW SERVER API
# ====================================
# ===== MAVEN =====
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# ===== QUARKUS =====
.quarkus/
quarkus.log
hs_err_pid*
# ===== JAVA COMPILED FILES =====
*.class
*.jar
*.war
*.ear
*.nar
*.zip
*.tar.gz
*.rar
# ===== IDE - ECLIPSE =====
.project
.classpath
.settings/
.factorypath
.metadata/
bin/
.apt_generated
.springBeans
.sts4-cache
# ===== IDE - INTELLIJ IDEA =====
.idea/
*.iml
*.ipr
*.iws
out/
# ===== IDE - NETBEANS =====
nbproject/
nbbuild/
nbdist/
.nb-gradle/
nb-configuration.xml
nbactions.xml
# ===== IDE - VS CODE =====
.vscode/
*.code-workspace
# ===== OS SPECIFIC =====
# Mac
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux
*~
# ===== LOGS =====
*.log
*.log.*
logs/
log/
# ===== TEMPORARY FILES =====
*.tmp
*.temp
*.bak
*.backup
*.old
*.swp
*.swo
*.orig
*.rej
*~
# ===== ENVIRONMENT & CONFIGURATION =====
.env
.env.local
.env.*
*.local
application-local.properties
# ===== SECURITY - KEYS & CERTIFICATES =====
*.key
*.pem
*.crt
*.p12
*.jks
*.keystore
.certs/
.certificates/
# ===== TEST COVERAGE =====
.jacoco/
jacoco.exec
coverage/
.nyc_output/
# ===== GENERATED SOURCES =====
src/gen/
generated-sources/
generated-test-sources/
# ===== BUILD ARTIFACTS =====
dist/
build/
out/
# ===== NODE (if used for build tools) =====
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# ===== DOCKER =====
.dockerignore
# ===== MAVEN WRAPPER (if not using project wrapper) =====
# Uncomment if you don't want to commit Maven wrapper
# .mvn/
# mvnw
# mvnw.cmd

View File

@@ -0,0 +1,359 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Configuration Checkstyle pour UnionFlow
Basée sur Google Java Style Guide avec adaptations pour UnionFlow
Objectif : 100% de conformité (zéro violation)
@author UnionFlow Team
@version 1.0
@since 2025-01-10
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Suppressions pour les fichiers générés -->
<module name="SuppressionFilter">
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
default="checkstyle-suppressions.xml" />
<property name="optional" value="true"/>
</module>
<!-- Vérifications au niveau des fichiers -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="NewlineAtEndOfFile"/>
<!-- Vérifications au niveau des arbres syntaxiques -->
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="AnnotationUseStyle"/>
<module name="MissingOverride"/>
<!-- Blocs -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT"/>
</module>
<!-- Conception de classe -->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="OneTopLevelClass"/>
<module name="VisibilityModifier">
<property name="protectedAllowed" value="true"/>
<property name="packageAllowed" value="true"/>
</module>
<!-- Codage -->
<module name="ArrayTrailingComma"/>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="CovariantEquals"/>
<module name="DeclarationOrder"/>
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsAvoidNull"/>
<module name="EqualsHashCode"/>
<module name="ExplicitInitialization"/>
<module name="FallThrough"/>
<module name="IllegalInstantiation"/>
<module name="IllegalToken"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="InnerAssignment"/>
<module name="MagicNumber">
<property name="ignoreNumbers" value="-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000"/>
<property name="ignoreHashCodeMethod" value="true"/>
<property name="ignoreAnnotation" value="true"/>
<property name="ignoreFieldDeclaration" value="true"/>
</module>
<module name="MissingSwitchDefault"/>
<module name="ModifiedControlVariable"/>
<module name="MultipleStringLiterals">
<property name="allowedDuplicates" value="3"/>
</module>
<module name="MultipleVariableDeclarations"/>
<module name="NoClone"/>
<module name="NoFinalizer"/>
<module name="OneStatementPerLine"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="PackageDeclaration"/>
<module name="ParameterAssignment"/>
<module name="RequireThis">
<property name="checkFields" value="false"/>
<property name="checkMethods" value="false"/>
</module>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="UnnecessaryParentheses"/>
<module name="VariableDeclarationUsageDistance"/>
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="AvoidStaticImport">
<property name="excludes" value="org.assertj.core.api.Assertions.*,org.junit.jupiter.api.Assertions.*,org.mockito.Mockito.*"/>
</module>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE"/>
</module>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Javadoc -->
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="InvalidJavadocPosition"/>
<module name="JavadocMethod">
<property name="allowMissingParamTags" value="false"/>
<property name="allowMissingReturnTag" value="false"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>
</module>
<module name="JavadocParagraph"/>
<module name="JavadocStyle"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="JavadocType"/>
<module name="MissingJavadocMethod">
<property name="minLineCount" value="2"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF"/>
</module>
<module name="MissingJavadocType"/>
<module name="NonEmptyAtclauseDescription"/>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<!-- Métriques -->
<module name="BooleanExpressionComplexity">
<property name="max" value="7"/>
</module>
<module name="ClassDataAbstractionCoupling">
<property name="max" value="15"/>
</module>
<module name="ClassFanOutComplexity">
<property name="max" value="25"/>
</module>
<module name="CyclomaticComplexity">
<property name="max" value="15"/>
</module>
<module name="JavaNCSS">
<property name="methodMaximum" value="80"/>
<property name="classMaximum" value="2000"/>
</module>
<module name="NPathComplexity">
<property name="max" value="200"/>
</module>
<!-- Divers -->
<module name="ArrayTypeStyle"/>
<module name="CommentsIndentation"/>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="8"/>
<property name="lineWrappingIndentation" value="8"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="OuterTypeFilename"/>
<module name="TodoComment">
<property name="format" value="(TODO)|(FIXME)"/>
</module>
<module name="TrailingComment"/>
<module name="UncommentedMain">
<property name="excludedClasses" value="\.Main$"/>
</module>
<module name="UpperEll"/>
<!-- Modificateurs -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Conventions de nommage -->
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="4"/>
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF, PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF"/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ConstantName"/>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern" value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern" value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern" value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern" value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="StaticVariableName"/>
<module name="TypeName">
<message key="name.invalidPattern" value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- Taille -->
<module name="AnonInnerLength">
<property name="max" value="30"/>
</module>
<module name="ExecutableStatementCount">
<property name="max" value="50"/>
</module>
<module name="LineLength">
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="MethodCount">
<property name="maxTotal" value="50"/>
<property name="maxPrivate" value="30"/>
<property name="maxPackage" value="30"/>
<property name="maxProtected" value="30"/>
<property name="maxPublic" value="30"/>
</module>
<module name="MethodLength">
<property name="tokens" value="METHOD_DEF, CTOR_DEF"/>
<property name="max" value="100"/>
</module>
<module name="OuterTypeNumber"/>
<module name="ParameterNumber">
<property name="max" value="8"/>
<property name="ignoreOverriddenMethods" value="true"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF"/>
</module>
<!-- Espaces blancs -->
<module name="EmptyForIteratorPad"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="GenericWhitespace">
<message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="MethodParamPad"/>
<module name="NoLineWrap"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="ParenPad"/>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed" value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded" value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
</module>
</module>

View File

@@ -0,0 +1,236 @@
<?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-server-api</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>UnionFlow Server API</name>
<description>API définitions pour le serveur UnionFlow</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<quarkus.platform.version>3.15.1</quarkus.platform.version>
<jackson.version>2.17.0</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>
<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>
<!-- Seuils de couverture Jacoco - 100% obligatoire -->
<jacoco.line.coverage.minimum>1.00</jacoco.line.coverage.minimum>
<jacoco.branch.coverage.minimum>1.00</jacoco.branch.coverage.minimum>
<jacoco.instruction.coverage.minimum>1.00</jacoco.instruction.coverage.minimum>
<jacoco.method.coverage.minimum>1.00</jacoco.method.coverage.minimum>
<jacoco.class.coverage.minimum>1.00</jacoco.class.coverage.minimum>
</properties>
<dependencies>
<!-- Jackson pour la sérialisation JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Bean Validation -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<!-- MicroProfile OpenAPI pour la documentation -->
<dependency>
<groupId>org.eclipse.microprofile.openapi</groupId>
<artifactId>microprofile-openapi-api</artifactId>
<version>${microprofile-openapi.version}</version>
</dependency>
<!-- Dépendances de test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
<!-- Lombok pour génération automatique des getters/setters -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Plugin Jacoco pour la couverture de code -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.line.coverage.minimum}</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.branch.coverage.minimum}</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.instruction.coverage.minimum}</minimum>
</limit>
<limit>
<counter>METHOD</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.method.coverage.minimum}</minimum>
</limit>
<limit>
<counter>CLASS</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.class.coverage.minimum}</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- Plugin Checkstyle pour la qualité du code -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
</dependencies>
<configuration>
<configLocation>google_checks.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<violationSeverity>warning</violationSeverity>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<excludes>**/target/**/*</excludes>
</configuration>
<!-- Executions désactivées temporairement pour les tests
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
-->
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,622 @@
package dev.lions.unionflow.server.api.dto.abonnement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
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.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des abonnements UnionFlow Représente un abonnement d'une organisation à une
* formule
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class AbonnementDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de référence unique de l'abonnement */
@NotBlank(message = "Le numéro de référence est obligatoire")
@Pattern(
regexp = "^ABO-\\d{4}-[A-Z0-9]{8}$",
message = "Format de référence invalide (ABO-YYYY-XXXXXXXX)")
private String numeroReference;
/** Identifiant de l'organisation abonnée */
@NotNull(message = "L'identifiant de l'organisation est obligatoire")
private UUID organisationId;
/** Nom de l'organisation abonnée */
private String nomOrganisation;
/** Identifiant de la formule d'abonnement */
@NotNull(message = "L'identifiant de la formule est obligatoire")
private UUID formulaireId;
/** Code de la formule */
private String codeFormule;
/** Nom de la formule */
private String nomFormule;
/** Type de formule (BASIC, STANDARD, PREMIUM, ENTERPRISE) */
private String typeFormule;
/** Statut de l'abonnement ACTIF, SUSPENDU, EXPIRE, ANNULE, EN_ATTENTE_PAIEMENT */
@NotBlank(message = "Le statut est obligatoire")
@Pattern(
regexp = "^(ACTIF|SUSPENDU|EXPIRE|ANNULE|EN_ATTENTE_PAIEMENT)$",
message = "Statut invalide")
private String statut;
/** Type d'abonnement (MENSUEL, ANNUEL) */
@NotBlank(message = "Le type d'abonnement est obligatoire")
@Pattern(regexp = "^(MENSUEL|ANNUEL)$", message = "Le type doit être MENSUEL ou ANNUEL")
private String typeAbonnement;
/** Date de début de l'abonnement */
@NotNull(message = "La date de début est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateDebut;
/** Date de fin de l'abonnement */
@Future(message = "La date de fin doit être dans le futur")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFin;
/** Date de la prochaine facturation */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateProchainePeriode;
/** Montant de l'abonnement */
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.0", inclusive = false, message = "Le montant doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal montant;
/** Devise */
@NotBlank(message = "La devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String devise;
/** Remise appliquée (pourcentage) */
@DecimalMin(value = "0.0", message = "La remise doit être positive")
@DecimalMin(value = "100.0", message = "La remise ne peut pas dépasser 100%")
private BigDecimal remise;
/** Montant après remise */
@DecimalMin(value = "0.0", message = "Le montant final doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal montantFinal;
/** Renouvellement automatique */
private Boolean renouvellementAutomatique;
/** Période d'essai utilisée */
private Boolean periodeEssaiUtilisee;
/** Date de fin de la période d'essai */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFinEssai;
/** Nombre de membres autorisés */
private Integer maxMembres;
/** Nombre de membres actuels */
private Integer nombreMembresActuels;
/** Espace de stockage alloué (GB) */
private BigDecimal espaceStockageGB;
/** Espace de stockage utilisé (GB) */
private BigDecimal espaceStockageUtilise;
/** Support technique inclus */
private Boolean supportTechnique;
/** Niveau de support */
private String niveauSupport;
/** Fonctionnalités avancées activées */
private Boolean fonctionnalitesAvancees;
/** Accès API activé */
private Boolean apiAccess;
/** Rapports personnalisés activés */
private Boolean rapportsPersonnalises;
/** Intégrations tierces activées */
private Boolean integrationsTierces;
/** Date de dernière utilisation */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereUtilisation;
/** Nombre de connexions ce mois */
private Integer connexionsCeMois;
/** Identifiant du responsable de l'abonnement */
private UUID responsableId;
/** Nom du responsable */
private String nomResponsable;
/** Email du responsable */
private String emailResponsable;
/** Téléphone du responsable */
private String telephoneResponsable;
/** Mode de paiement préféré */
@Pattern(
regexp = "^(WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|VIREMENT|CHEQUE|AUTRE)$",
message = "Mode de paiement invalide")
private String modePaiementPrefere;
/** Numéro de téléphone pour paiement mobile */
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Format de numéro de téléphone invalide")
private String numeroPaiementMobile;
/** Historique des paiements (JSON) */
@Size(max = 5000, message = "L'historique ne peut pas dépasser 5000 caractères")
private String historiquePaiements;
/** Notes sur l'abonnement */
@Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères")
private String notes;
/** Alertes activées */
private Boolean alertesActivees;
/** Notifications par email */
private Boolean notificationsEmail;
/** Notifications par SMS */
private Boolean notificationsSMS;
/** Date de suspension (si applicable) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateSuspension;
/** Raison de la suspension */
@Size(max = 500, message = "La raison ne peut pas dépasser 500 caractères")
private String raisonSuspension;
/** Date d'annulation (si applicable) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateAnnulation;
/** Raison de l'annulation */
@Size(max = 500, message = "La raison ne peut pas dépasser 500 caractères")
private String raisonAnnulation;
// Constructeurs
public AbonnementDTO() {
super();
this.statut = "EN_ATTENTE_PAIEMENT";
this.devise = "XOF";
this.renouvellementAutomatique = true;
this.periodeEssaiUtilisee = false;
this.supportTechnique = true;
this.fonctionnalitesAvancees = false;
this.apiAccess = false;
this.rapportsPersonnalises = false;
this.integrationsTierces = false;
this.connexionsCeMois = 0;
this.alertesActivees = true;
this.notificationsEmail = true;
this.notificationsSMS = false;
this.numeroReference = genererNumeroReference();
}
public AbonnementDTO(UUID organisationId, String nomOrganisation, String typeFormule) {
this();
this.organisationId = organisationId;
this.nomOrganisation = nomOrganisation;
this.typeFormule = typeFormule;
}
// Getters et Setters
public String getNumeroReference() {
return numeroReference;
}
public void setNumeroReference(String numeroReference) {
this.numeroReference = numeroReference;
}
public UUID getOrganisationId() {
return organisationId;
}
public void setOrganisationId(UUID organisationId) {
this.organisationId = organisationId;
}
public String getNomOrganisation() {
return nomOrganisation;
}
public void setNomOrganisation(String nomOrganisation) {
this.nomOrganisation = nomOrganisation;
}
public UUID getFormulaireId() {
return formulaireId;
}
public void setFormulaireId(UUID formulaireId) {
this.formulaireId = formulaireId;
}
public String getCodeFormule() {
return codeFormule;
}
public void setCodeFormule(String codeFormule) {
this.codeFormule = codeFormule;
}
public String getNomFormule() {
return nomFormule;
}
public void setNomFormule(String nomFormule) {
this.nomFormule = nomFormule;
}
public String getTypeFormule() {
return typeFormule;
}
public void setTypeFormule(String typeFormule) {
this.typeFormule = typeFormule;
}
public String getStatut() {
return statut;
}
public void setStatut(String statut) {
this.statut = statut;
}
public String getTypeAbonnement() {
return typeAbonnement;
}
public void setTypeAbonnement(String typeAbonnement) {
this.typeAbonnement = typeAbonnement;
}
public LocalDate getDateDebut() {
return dateDebut;
}
public void setDateDebut(LocalDate dateDebut) {
this.dateDebut = dateDebut;
}
public LocalDate getDateFin() {
return dateFin;
}
public void setDateFin(LocalDate dateFin) {
this.dateFin = dateFin;
}
public LocalDate getDateProchainePeriode() {
return dateProchainePeriode;
}
public void setDateProchainePeriode(LocalDate dateProchainePeriode) {
this.dateProchainePeriode = dateProchainePeriode;
}
public BigDecimal getMontant() {
return montant;
}
public void setMontant(BigDecimal montant) {
this.montant = montant;
}
public String getDevise() {
return devise;
}
public void setDevise(String devise) {
this.devise = devise;
}
// Getters et setters restants (suite)
public BigDecimal getRemise() {
return remise;
}
public void setRemise(BigDecimal remise) {
this.remise = remise;
}
public BigDecimal getMontantFinal() {
return montantFinal;
}
public void setMontantFinal(BigDecimal montantFinal) {
this.montantFinal = montantFinal;
}
public Boolean getRenouvellementAutomatique() {
return renouvellementAutomatique;
}
public void setRenouvellementAutomatique(Boolean renouvellementAutomatique) {
this.renouvellementAutomatique = renouvellementAutomatique;
}
public Boolean getPeriodeEssaiUtilisee() {
return periodeEssaiUtilisee;
}
public void setPeriodeEssaiUtilisee(Boolean periodeEssaiUtilisee) {
this.periodeEssaiUtilisee = periodeEssaiUtilisee;
}
public LocalDate getDateFinEssai() {
return dateFinEssai;
}
public void setDateFinEssai(LocalDate dateFinEssai) {
this.dateFinEssai = dateFinEssai;
}
public Integer getMaxMembres() {
return maxMembres;
}
public void setMaxMembres(Integer maxMembres) {
this.maxMembres = maxMembres;
}
public Integer getNombreMembresActuels() {
return nombreMembresActuels;
}
public void setNombreMembresActuels(Integer nombreMembresActuels) {
this.nombreMembresActuels = nombreMembresActuels;
}
public BigDecimal getEspaceStockageGB() {
return espaceStockageGB;
}
public void setEspaceStockageGB(BigDecimal espaceStockageGB) {
this.espaceStockageGB = espaceStockageGB;
}
public BigDecimal getEspaceStockageUtilise() {
return espaceStockageUtilise;
}
public void setEspaceStockageUtilise(BigDecimal espaceStockageUtilise) {
this.espaceStockageUtilise = espaceStockageUtilise;
}
public Boolean getSupportTechnique() {
return supportTechnique;
}
public void setSupportTechnique(Boolean supportTechnique) {
this.supportTechnique = supportTechnique;
}
public String getNiveauSupport() {
return niveauSupport;
}
public void setNiveauSupport(String niveauSupport) {
this.niveauSupport = niveauSupport;
}
public Boolean getFonctionnalitesAvancees() {
return fonctionnalitesAvancees;
}
public void setFonctionnalitesAvancees(Boolean fonctionnalitesAvancees) {
this.fonctionnalitesAvancees = fonctionnalitesAvancees;
}
public Boolean getApiAccess() {
return apiAccess;
}
public void setApiAccess(Boolean apiAccess) {
this.apiAccess = apiAccess;
}
public Boolean getRapportsPersonnalises() {
return rapportsPersonnalises;
}
public void setRapportsPersonnalises(Boolean rapportsPersonnalises) {
this.rapportsPersonnalises = rapportsPersonnalises;
}
public Boolean getIntegrationsTierces() {
return integrationsTierces;
}
public void setIntegrationsTierces(Boolean integrationsTierces) {
this.integrationsTierces = integrationsTierces;
}
public LocalDateTime getDateDerniereUtilisation() {
return dateDerniereUtilisation;
}
public void setDateDerniereUtilisation(LocalDateTime dateDerniereUtilisation) {
this.dateDerniereUtilisation = dateDerniereUtilisation;
}
public Integer getConnexionsCeMois() {
return connexionsCeMois;
}
public void setConnexionsCeMois(Integer connexionsCeMois) {
this.connexionsCeMois = connexionsCeMois;
}
public UUID getResponsableId() {
return responsableId;
}
public void setResponsableId(UUID responsableId) {
this.responsableId = responsableId;
}
public String getNomResponsable() {
return nomResponsable;
}
public void setNomResponsable(String nomResponsable) {
this.nomResponsable = nomResponsable;
}
public String getEmailResponsable() {
return emailResponsable;
}
public void setEmailResponsable(String emailResponsable) {
this.emailResponsable = emailResponsable;
}
public String getTelephoneResponsable() {
return telephoneResponsable;
}
public void setTelephoneResponsable(String telephoneResponsable) {
this.telephoneResponsable = telephoneResponsable;
}
public String getModePaiementPrefere() {
return modePaiementPrefere;
}
public void setModePaiementPrefere(String modePaiementPrefere) {
this.modePaiementPrefere = modePaiementPrefere;
}
public String getNumeroPaiementMobile() {
return numeroPaiementMobile;
}
public void setNumeroPaiementMobile(String numeroPaiementMobile) {
this.numeroPaiementMobile = numeroPaiementMobile;
}
public String getHistoriquePaiements() {
return historiquePaiements;
}
public void setHistoriquePaiements(String historiquePaiements) {
this.historiquePaiements = historiquePaiements;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public Boolean getAlertesActivees() {
return alertesActivees;
}
public void setAlertesActivees(Boolean alertesActivees) {
this.alertesActivees = alertesActivees;
}
public Boolean getNotificationsEmail() {
return notificationsEmail;
}
public void setNotificationsEmail(Boolean notificationsEmail) {
this.notificationsEmail = notificationsEmail;
}
public Boolean getNotificationsSMS() {
return notificationsSMS;
}
public void setNotificationsSMS(Boolean notificationsSMS) {
this.notificationsSMS = notificationsSMS;
}
public LocalDateTime getDateSuspension() {
return dateSuspension;
}
public void setDateSuspension(LocalDateTime dateSuspension) {
this.dateSuspension = dateSuspension;
}
public String getRaisonSuspension() {
return raisonSuspension;
}
public void setRaisonSuspension(String raisonSuspension) {
this.raisonSuspension = raisonSuspension;
}
public LocalDateTime getDateAnnulation() {
return dateAnnulation;
}
public void setDateAnnulation(LocalDateTime dateAnnulation) {
this.dateAnnulation = dateAnnulation;
}
public String getRaisonAnnulation() {
return raisonAnnulation;
}
public void setRaisonAnnulation(String raisonAnnulation) {
this.raisonAnnulation = raisonAnnulation;
}
/**
* Génère un numéro de référence unique
*
* @return Le numéro de référence généré
*/
private String genererNumeroReference() {
return "ABO-"
+ LocalDate.now().getYear()
+ "-"
+ String.format("%08d", (int) (Math.random() * 100000000));
}
}

View File

@@ -0,0 +1,68 @@
package dev.lions.unionflow.server.api.dto.admin;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour les logs d'audit
* Représente une entrée dans le journal d'audit du système
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-17
*/
@Getter
@Setter
public class AuditLogDTO extends BaseDTO implements Serializable {
private static final long serialVersionUID = 1L;
/** Type d'action effectuée */
private String typeAction;
/** Sévérité de l'événement (SUCCESS, INFO, WARNING, ERROR, CRITICAL) */
private String severite;
/** Nom de l'utilisateur qui a effectué l'action */
private String utilisateur;
/** Rôle de l'utilisateur */
private String role;
/** Module concerné (AUTH, MEMBRES, COTISATIONS, EVENTS, etc.) */
private String module;
/** Description de l'action */
private String description;
/** Détails supplémentaires */
private String details;
/** Adresse IP de l'utilisateur */
private String ipAddress;
/** User-Agent du navigateur */
private String userAgent;
/** ID de session */
private String sessionId;
/** Date et heure de l'événement */
private LocalDateTime dateHeure;
/** Données avant modification (pour les actions de modification) */
private String donneesAvant;
/** Données après modification (pour les actions de modification) */
private String donneesApres;
/** ID de l'entité concernée (membre, cotisation, etc.) */
private String entiteId;
/** Type d'entité concernée */
private String entiteType;
}

View File

@@ -0,0 +1,82 @@
package dev.lions.unionflow.server.api.dto.adresse;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des adresses
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class AdresseDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Type d'adresse */
@NotNull(message = "Le type d'adresse est obligatoire")
private TypeAdresse typeAdresse;
/** Adresse complète */
private String adresse;
/** Complément d'adresse */
private String complementAdresse;
/** Code postal */
private String codePostal;
/** Ville */
private String ville;
/** Région */
private String region;
/** Pays */
private String pays;
/** Latitude */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6)
private BigDecimal latitude;
/** Longitude */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6)
private BigDecimal longitude;
/** Adresse principale */
private Boolean principale;
/** Libellé personnalisé */
private String libelle;
/** Notes et commentaires */
private String notes;
/** ID de l'organisation (si adresse d'organisation) */
private UUID organisationId;
/** ID du membre (si adresse de membre) */
private UUID membreId;
/** ID de l'événement (si adresse d'événement) */
private UUID evenementId;
/** Adresse complète formatée (calculée) */
private String adresseComplete;
}

View File

@@ -0,0 +1,259 @@
package dev.lions.unionflow.server.api.dto.analytics;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO pour les données analytics UnionFlow
*
* <p>Représente une donnée analytique avec sa valeur, sa métrique associée, sa période d'analyse et
* ses métadonnées.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalyticsDataDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Type de métrique analysée */
@NotNull(message = "Le type de métrique est obligatoire")
private TypeMetrique typeMetrique;
/** Période d'analyse */
@NotNull(message = "La période d'analyse est obligatoire")
private PeriodeAnalyse periodeAnalyse;
/** Valeur numérique de la métrique */
@NotNull(message = "La valeur est obligatoire")
@DecimalMin(value = "0.0", message = "La valeur doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur invalide")
private BigDecimal valeur;
/** Valeur précédente pour comparaison */
@DecimalMin(value = "0.0", message = "La valeur précédente doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur précédente invalide")
private BigDecimal valeurPrecedente;
/** Pourcentage d'évolution par rapport à la période précédente */
@Digits(integer = 6, fraction = 2, message = "Format de pourcentage d'évolution invalide")
private BigDecimal pourcentageEvolution;
/** Date de début de la période analysée */
@NotNull(message = "La date de début est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDebut;
/** Date de fin de la période analysée */
@NotNull(message = "La date de fin est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateFin;
/** Date de calcul de la métrique */
@NotNull(message = "La date de calcul est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateCalcul;
/** Identifiant de l'organisation (optionnel pour filtrage) */
private UUID organisationId;
/** Nom de l'organisation */
@Size(max = 200, message = "Le nom de l'organisation ne peut pas dépasser 200 caractères")
private String nomOrganisation;
/** Identifiant de l'utilisateur qui a demandé le calcul */
private UUID utilisateurId;
/** Nom de l'utilisateur qui a demandé le calcul */
@Size(max = 200, message = "Le nom de l'utilisateur ne peut pas dépasser 200 caractères")
private String nomUtilisateur;
/** Libellé personnalisé de la métrique */
@Size(max = 300, message = "Le libellé personnalisé ne peut pas dépasser 300 caractères")
private String libellePersonnalise;
/** Description ou commentaire sur la métrique */
@Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères")
private String description;
/** Données détaillées pour les graphiques (format JSON) */
@Size(max = 10000, message = "Les données détaillées ne peuvent pas dépasser 10000 caractères")
private String donneesDetaillees;
/** Configuration du graphique (couleurs, type, etc.) */
@Size(max = 2000, message = "La configuration graphique ne peut pas dépasser 2000 caractères")
private String configurationGraphique;
/** Métadonnées additionnelles */
private Map<String, Object> metadonnees;
/** Indicateur de fiabilité des données (0-100) */
@DecimalMin(value = "0.0", message = "L'indicateur de fiabilité doit être positif")
@DecimalMax(value = "100.0", message = "L'indicateur de fiabilité ne peut pas dépasser 100")
@Digits(integer = 3, fraction = 1, message = "Format d'indicateur de fiabilité invalide")
private BigDecimal indicateurFiabilite;
/** Nombre d'éléments analysés pour calculer cette métrique */
@DecimalMin(value = "0", message = "Le nombre d'éléments doit être positif")
private Integer nombreElementsAnalyses;
/** Temps de calcul en millisecondes */
@DecimalMin(value = "0", message = "Le temps de calcul doit être positif")
private Long tempsCalculMs;
/** Indicateur si la métrique est en temps réel */
@Builder.Default private Boolean tempsReel = false;
/** Indicateur si la métrique nécessite une mise à jour */
@Builder.Default private Boolean necessiteMiseAJour = false;
/** Niveau de priorité de la métrique (1=faible, 5=critique) */
@DecimalMin(value = "1", message = "Le niveau de priorité minimum est 1")
@DecimalMax(value = "5", message = "Le niveau de priorité maximum est 5")
private Integer niveauPriorite;
/** Tags pour catégoriser la métrique */
private List<String> tags;
// === MÉTHODES UTILITAIRES ===
/**
* Retourne le libellé à afficher (personnalisé ou par défaut)
*
* @return Le libellé à afficher
*/
public String getLibelleAffichage() {
return libellePersonnalise != null && !libellePersonnalise.trim().isEmpty()
? libellePersonnalise
: typeMetrique.getLibelle();
}
/**
* Retourne l'unité de mesure de la métrique
*
* @return L'unité de mesure
*/
public String getUnite() {
return typeMetrique.getUnite();
}
/**
* Retourne l'icône de la métrique
*
* @return L'icône Material Design
*/
public String getIcone() {
return typeMetrique.getIcone();
}
/**
* Retourne la couleur de la métrique
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return typeMetrique.getCouleur();
}
/**
* Vérifie si la métrique a évolué positivement
*
* @return true si l'évolution est positive
*/
public boolean hasEvolutionPositive() {
return pourcentageEvolution != null && pourcentageEvolution.compareTo(BigDecimal.ZERO) > 0;
}
/**
* Vérifie si la métrique a évolué négativement
*
* @return true si l'évolution est négative
*/
public boolean hasEvolutionNegative() {
return pourcentageEvolution != null && pourcentageEvolution.compareTo(BigDecimal.ZERO) < 0;
}
/**
* Vérifie si la métrique est stable (pas d'évolution)
*
* @return true si l'évolution est nulle
*/
public boolean isStable() {
return pourcentageEvolution != null && pourcentageEvolution.compareTo(BigDecimal.ZERO) == 0;
}
/**
* Retourne la tendance sous forme de texte
*
* @return "hausse", "baisse" ou "stable"
*/
public String getTendance() {
if (hasEvolutionPositive()) return "hausse";
if (hasEvolutionNegative()) return "baisse";
return "stable";
}
/**
* Vérifie si les données sont fiables (indicateur > 80)
*
* @return true si les données sont considérées comme fiables
*/
public boolean isDonneesFiables() {
return indicateurFiabilite != null
&& indicateurFiabilite.compareTo(new BigDecimal("80.0")) >= 0;
}
/**
* Vérifie si la métrique est critique (priorité >= 4)
*
* @return true si la métrique est critique
*/
public boolean isCritique() {
return niveauPriorite != null && niveauPriorite >= 4;
}
/**
* Constructeur avec les champs essentiels
*
* @param typeMetrique Le type de métrique
* @param periodeAnalyse La période d'analyse
* @param valeur La valeur de la métrique
*/
public AnalyticsDataDTO(
TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, BigDecimal valeur) {
super();
this.typeMetrique = typeMetrique;
this.periodeAnalyse = periodeAnalyse;
this.valeur = valeur;
this.dateCalcul = LocalDateTime.now();
this.dateDebut = periodeAnalyse.getDateDebut();
this.dateFin = periodeAnalyse.getDateFin();
this.tempsReel = false;
this.necessiteMiseAJour = false;
this.niveauPriorite = 3; // Priorité normale par défaut
this.indicateurFiabilite = new BigDecimal("95.0"); // Fiabilité élevée par défaut
}
}

View File

@@ -0,0 +1,338 @@
package dev.lions.unionflow.server.api.dto.analytics;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO pour les widgets de tableau de bord analytics UnionFlow
*
* <p>Représente un widget personnalisable affiché sur le tableau de bord avec sa configuration, sa
* position et ses données.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardWidgetDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Titre du widget */
@NotBlank(message = "Le titre du widget est obligatoire")
@Size(min = 3, max = 200, message = "Le titre du widget doit contenir entre 3 et 200 caractères")
private String titre;
/** Description du widget */
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
/** Type de widget (kpi, chart, table, gauge, progress, text) */
@NotBlank(message = "Le type de widget est obligatoire")
@Size(max = 50, message = "Le type de widget ne peut pas dépasser 50 caractères")
private String typeWidget;
/** Type de métrique affiché */
private TypeMetrique typeMetrique;
/** Période d'analyse */
private PeriodeAnalyse periodeAnalyse;
/** Identifiant de l'organisation (optionnel pour filtrage) */
private UUID organisationId;
/** Nom de l'organisation */
@Size(max = 200, message = "Le nom de l'organisation ne peut pas dépasser 200 caractères")
private String nomOrganisation;
/** Identifiant de l'utilisateur propriétaire */
@NotNull(message = "L'identifiant de l'utilisateur propriétaire est obligatoire")
private UUID utilisateurProprietaireId;
/** Nom de l'utilisateur propriétaire */
@Size(
max = 200,
message = "Le nom de l'utilisateur propriétaire ne peut pas dépasser 200 caractères")
private String nomUtilisateurProprietaire;
/** Position X du widget sur la grille */
@NotNull(message = "La position X est obligatoire")
@DecimalMin(value = "0", message = "La position X doit être positive ou nulle")
private Integer positionX;
/** Position Y du widget sur la grille */
@NotNull(message = "La position Y est obligatoire")
@DecimalMin(value = "0", message = "La position Y doit être positive ou nulle")
private Integer positionY;
/** Largeur du widget (en unités de grille) */
@NotNull(message = "La largeur est obligatoire")
@DecimalMin(value = "1", message = "La largeur minimum est 1")
@DecimalMax(value = "12", message = "La largeur maximum est 12")
private Integer largeur;
/** Hauteur du widget (en unités de grille) */
@NotNull(message = "La hauteur est obligatoire")
@DecimalMin(value = "1", message = "La hauteur minimum est 1")
@DecimalMax(value = "12", message = "La hauteur maximum est 12")
private Integer hauteur;
/** Ordre d'affichage (z-index) */
@DecimalMin(value = "0", message = "L'ordre d'affichage doit être positif ou nul")
@Builder.Default
private Integer ordreAffichage = 0;
/** Configuration visuelle du widget */
@Size(max = 5000, message = "La configuration visuelle ne peut pas dépasser 5000 caractères")
private String configurationVisuelle;
/** Couleur principale du widget */
@Size(max = 7, message = "La couleur doit être au format #RRGGBB")
private String couleurPrincipale;
/** Couleur secondaire du widget */
@Size(max = 7, message = "La couleur secondaire doit être au format #RRGGBB")
private String couleurSecondaire;
/** Icône du widget */
@Size(max = 50, message = "L'icône ne peut pas dépasser 50 caractères")
private String icone;
/** Indicateur si le widget est visible */
@Builder.Default private Boolean visible = true;
/** Indicateur si le widget est redimensionnable */
@Builder.Default private Boolean redimensionnable = true;
/** Indicateur si le widget est déplaçable */
@Builder.Default private Boolean deplacable = true;
/** Indicateur si le widget peut être supprimé */
@Builder.Default private Boolean supprimable = true;
/** Indicateur si le widget se met à jour automatiquement */
@Builder.Default private Boolean miseAJourAutomatique = true;
/** Fréquence de mise à jour en secondes */
@DecimalMin(value = "30", message = "La fréquence minimum est 30 secondes")
@Builder.Default
private Integer frequenceMiseAJourSecondes = 300; // 5 minutes par défaut
/** Date de dernière mise à jour des données */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereMiseAJour;
/** Prochaine mise à jour programmée */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime prochaineMiseAJour;
/** Données du widget (format JSON) */
@Size(max = 50000, message = "Les données du widget ne peuvent pas dépasser 50000 caractères")
private String donneesWidget;
/** Configuration des filtres */
private Map<String, Object> configurationFiltres;
/** Configuration des alertes */
private Map<String, Object> configurationAlertes;
/** Seuil d'alerte bas */
private Double seuilAlerteBas;
/** Seuil d'alerte haut */
private Double seuilAlerteHaut;
/** Indicateur si une alerte est active */
@Builder.Default private Boolean alerteActive = false;
/** Message d'alerte actuel */
@Size(max = 500, message = "Le message d'alerte ne peut pas dépasser 500 caractères")
private String messageAlerte;
/** Type d'alerte (info, warning, error, success) */
@Size(max = 20, message = "Le type d'alerte ne peut pas dépasser 20 caractères")
private String typeAlerte;
/** Permissions d'accès au widget */
@Size(max = 1000, message = "Les permissions ne peuvent pas dépasser 1000 caractères")
private String permissions;
/** Rôles autorisés à voir le widget */
@Size(max = 500, message = "Les rôles autorisés ne peuvent pas dépasser 500 caractères")
private String rolesAutorises;
/** Template personnalisé du widget */
@Size(max = 10000, message = "Le template personnalisé ne peut pas dépasser 10000 caractères")
private String templatePersonnalise;
/** CSS personnalisé du widget */
@Size(max = 5000, message = "Le CSS personnalisé ne peut pas dépasser 5000 caractères")
private String cssPersonnalise;
/** JavaScript personnalisé du widget */
@Size(max = 10000, message = "Le JavaScript personnalisé ne peut pas dépasser 10000 caractères")
private String javascriptPersonnalise;
/** Métadonnées additionnelles */
private Map<String, Object> metadonnees;
/** Nombre de vues du widget */
@DecimalMin(value = "0", message = "Le nombre de vues doit être positif")
@Builder.Default
private Long nombreVues = 0L;
/** Nombre d'interactions avec le widget */
@DecimalMin(value = "0", message = "Le nombre d'interactions doit être positif")
@Builder.Default
private Long nombreInteractions = 0L;
/** Temps moyen passé sur le widget (en secondes) */
@DecimalMin(value = "0", message = "Le temps moyen doit être positif")
private Integer tempsMoyenSecondes;
/** Taux d'erreur du widget (en pourcentage) */
@DecimalMin(value = "0.0", message = "Le taux d'erreur doit être positif")
@DecimalMax(value = "100.0", message = "Le taux d'erreur ne peut pas dépasser 100%")
@Builder.Default
private Double tauxErreur = 0.0;
/** Date de dernière erreur */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereErreur;
/** Message de dernière erreur */
@Size(max = 1000, message = "Le message d'erreur ne peut pas dépasser 1000 caractères")
private String messageDerniereErreur;
// === MÉTHODES UTILITAIRES ===
/**
* Retourne le libellé de la métrique si définie
*
* @return Le libellé de la métrique ou null
*/
public String getLibelleMetrique() {
return typeMetrique != null ? typeMetrique.getLibelle() : null;
}
/**
* Retourne l'unité de mesure si métrique définie
*
* @return L'unité de mesure ou chaîne vide
*/
public String getUnite() {
return typeMetrique != null ? typeMetrique.getUnite() : "";
}
/**
* Retourne l'icône de la métrique ou l'icône personnalisée
*
* @return L'icône à afficher
*/
public String getIconeAffichage() {
if (icone != null && !icone.trim().isEmpty()) {
return icone;
}
return typeMetrique != null ? typeMetrique.getIcone() : "dashboard";
}
/**
* Retourne la couleur de la métrique ou la couleur personnalisée
*
* @return La couleur à utiliser
*/
public String getCouleurAffichage() {
if (couleurPrincipale != null && !couleurPrincipale.trim().isEmpty()) {
return couleurPrincipale;
}
return typeMetrique != null ? typeMetrique.getCouleur() : "#757575";
}
/**
* Vérifie si le widget nécessite une mise à jour
*
* @return true si une mise à jour est nécessaire
*/
public boolean necessiteMiseAJour() {
return miseAJourAutomatique
&& prochaineMiseAJour != null
&& prochaineMiseAJour.isBefore(LocalDateTime.now());
}
/**
* Vérifie si le widget est interactif
*
* @return true si le widget permet des interactions
*/
public boolean isInteractif() {
return "chart".equals(typeWidget) || "table".equals(typeWidget) || "gauge".equals(typeWidget);
}
/**
* Vérifie si le widget affiche des données temps réel
*
* @return true si le widget est en temps réel
*/
public boolean isTempsReel() {
return frequenceMiseAJourSecondes != null && frequenceMiseAJourSecondes <= 60;
}
/**
* Retourne la taille du widget (surface occupée)
*
* @return La surface en unités de grille
*/
public int getTailleWidget() {
return largeur * hauteur;
}
/**
* Vérifie si le widget est grand (surface > 6)
*
* @return true si le widget est considéré comme grand
*/
public boolean isWidgetGrand() {
return getTailleWidget() > 6;
}
/**
* Vérifie si le widget a des erreurs récentes (< 24h)
*
* @return true si des erreurs récentes sont détectées
*/
public boolean hasErreursRecentes() {
return dateDerniereErreur != null
&& dateDerniereErreur.isAfter(LocalDateTime.now().minusHours(24));
}
/**
* Retourne le statut du widget
*
* @return "actif", "erreur", "inactif" ou "maintenance"
*/
public String getStatutWidget() {
if (hasErreursRecentes()) return "erreur";
if (!visible) return "inactif";
if (tauxErreur != null && tauxErreur > 10.0) return "maintenance";
return "actif";
}
}

View File

@@ -0,0 +1,309 @@
package dev.lions.unionflow.server.api.dto.analytics;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO pour les tendances et évolutions des KPI UnionFlow
*
* <p>Représente l'évolution d'un KPI dans le temps avec les points de données historiques pour
* générer des graphiques de tendance.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class KPITrendDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Type de métrique pour cette tendance */
@NotNull(message = "Le type de métrique est obligatoire")
private TypeMetrique typeMetrique;
/** Période d'analyse globale */
@NotNull(message = "La période d'analyse est obligatoire")
private PeriodeAnalyse periodeAnalyse;
/** Identifiant de l'organisation (optionnel) */
private UUID organisationId;
/** Nom de l'organisation */
@Size(max = 200, message = "Le nom de l'organisation ne peut pas dépasser 200 caractères")
private String nomOrganisation;
/** Date de début de la période analysée */
@NotNull(message = "La date de début est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDebut;
/** Date de fin de la période analysée */
@NotNull(message = "La date de fin est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateFin;
/** Points de données pour la tendance */
@NotNull(message = "Les points de données sont obligatoires")
private List<PointDonneeDTO> pointsDonnees;
/** Valeur actuelle du KPI */
@NotNull(message = "La valeur actuelle est obligatoire")
@DecimalMin(value = "0.0", message = "La valeur actuelle doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur actuelle invalide")
private BigDecimal valeurActuelle;
/** Valeur minimale sur la période */
@DecimalMin(value = "0.0", message = "La valeur minimale doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur minimale invalide")
private BigDecimal valeurMinimale;
/** Valeur maximale sur la période */
@DecimalMin(value = "0.0", message = "La valeur maximale doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur maximale invalide")
private BigDecimal valeurMaximale;
/** Valeur moyenne sur la période */
@DecimalMin(value = "0.0", message = "La valeur moyenne doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur moyenne invalide")
private BigDecimal valeurMoyenne;
/** Écart-type des valeurs */
@DecimalMin(value = "0.0", message = "L'écart-type doit être positif ou nul")
@Digits(integer = 15, fraction = 4, message = "Format d'écart-type invalide")
private BigDecimal ecartType;
/** Coefficient de variation (écart-type / moyenne) */
@DecimalMin(value = "0.0", message = "Le coefficient de variation doit être positif ou nul")
@Digits(integer = 6, fraction = 4, message = "Format de coefficient de variation invalide")
private BigDecimal coefficientVariation;
/** Tendance générale (pente de la régression linéaire) */
@Digits(integer = 10, fraction = 6, message = "Format de tendance invalide")
private BigDecimal tendanceGenerale;
/** Coefficient de corrélation R² */
@DecimalMin(value = "0.0", message = "Le coefficient de corrélation doit être positif ou nul")
@DecimalMax(value = "1.0", message = "Le coefficient de corrélation ne peut pas dépasser 1")
@Digits(integer = 1, fraction = 6, message = "Format de coefficient de corrélation invalide")
private BigDecimal coefficientCorrelation;
/** Pourcentage d'évolution depuis le début de la période */
@Digits(integer = 6, fraction = 2, message = "Format de pourcentage d'évolution invalide")
private BigDecimal pourcentageEvolutionGlobale;
/** Prédiction pour la prochaine période */
@DecimalMin(value = "0.0", message = "La prédiction doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de prédiction invalide")
private BigDecimal predictionProchainePeriode;
/** Marge d'erreur de la prédiction (en pourcentage) */
@DecimalMin(value = "0.0", message = "La marge d'erreur doit être positive ou nulle")
@DecimalMax(value = "100.0", message = "La marge d'erreur ne peut pas dépasser 100%")
@Digits(integer = 3, fraction = 2, message = "Format de marge d'erreur invalide")
private BigDecimal margeErreurPrediction;
/** Seuil d'alerte bas */
@DecimalMin(value = "0.0", message = "Le seuil d'alerte bas doit être positif ou nul")
@Digits(integer = 15, fraction = 4, message = "Format de seuil d'alerte bas invalide")
private BigDecimal seuilAlerteBas;
/** Seuil d'alerte haut */
@DecimalMin(value = "0.0", message = "Le seuil d'alerte haut doit être positif ou nul")
@Digits(integer = 15, fraction = 4, message = "Format de seuil d'alerte haut invalide")
private BigDecimal seuilAlerteHaut;
/** Indicateur si une alerte est active */
@Builder.Default private Boolean alerteActive = false;
/** Type d'alerte (bas, haut, anomalie) */
@Size(max = 50, message = "Le type d'alerte ne peut pas dépasser 50 caractères")
private String typeAlerte;
/** Message d'alerte */
@Size(max = 500, message = "Le message d'alerte ne peut pas dépasser 500 caractères")
private String messageAlerte;
/** Configuration du graphique (couleurs, style, etc.) */
@Size(max = 2000, message = "La configuration graphique ne peut pas dépasser 2000 caractères")
private String configurationGraphique;
/** Intervalle de regroupement des données */
@Size(max = 20, message = "L'intervalle de regroupement ne peut pas dépasser 20 caractères")
private String intervalleRegroupement;
/** Format d'affichage des dates */
@Size(max = 20, message = "Le format de date ne peut pas dépasser 20 caractères")
private String formatDate;
/** Date de dernière mise à jour */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereMiseAJour;
/** Fréquence de mise à jour en minutes */
@DecimalMin(value = "1", message = "La fréquence de mise à jour minimum est 1 minute")
private Integer frequenceMiseAJourMinutes;
// === CLASSES INTERNES ===
/** Classe interne représentant un point de données dans la tendance */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PointDonneeDTO {
/** Date du point de données */
@NotNull(message = "La date du point de données est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime date;
/** Valeur du point de données */
@NotNull(message = "La valeur du point de données est obligatoire")
@DecimalMin(value = "0.0", message = "La valeur du point doit être positive ou nulle")
@Digits(integer = 15, fraction = 4, message = "Format de valeur du point invalide")
private BigDecimal valeur;
/** Libellé du point (optionnel) */
@Size(max = 100, message = "Le libellé du point ne peut pas dépasser 100 caractères")
private String libelle;
/** Indicateur si le point est une anomalie */
@Builder.Default private Boolean anomalie = false;
/** Indicateur si le point est une prédiction */
@Builder.Default private Boolean prediction = false;
/** Métadonnées additionnelles du point */
private String metadonnees;
}
// === MÉTHODES UTILITAIRES ===
/**
* Retourne le libellé de la métrique
*
* @return Le libellé de la métrique
*/
public String getLibelleMetrique() {
return typeMetrique.getLibelle();
}
/**
* Retourne l'unité de mesure
*
* @return L'unité de mesure
*/
public String getUnite() {
return typeMetrique.getUnite();
}
/**
* Retourne l'icône de la métrique
*
* @return L'icône Material Design
*/
public String getIcone() {
return typeMetrique.getIcone();
}
/**
* Retourne la couleur de la métrique
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return typeMetrique.getCouleur();
}
/**
* Vérifie si la tendance est positive
*
* @return true si la tendance générale est positive
*/
public boolean isTendancePositive() {
return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) > 0;
}
/**
* Vérifie si la tendance est négative
*
* @return true si la tendance générale est négative
*/
public boolean isTendanceNegative() {
return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) < 0;
}
/**
* Vérifie si la tendance est stable
*
* @return true si la tendance générale est stable
*/
public boolean isTendanceStable() {
return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) == 0;
}
/**
* Retourne la volatilité du KPI (basée sur le coefficient de variation)
*
* @return "faible", "moyenne" ou "élevée"
*/
public String getVolatilite() {
if (coefficientVariation == null) return "inconnue";
BigDecimal cv = coefficientVariation;
if (cv.compareTo(new BigDecimal("0.1")) <= 0) return "faible";
if (cv.compareTo(new BigDecimal("0.3")) <= 0) return "moyenne";
return "élevée";
}
/**
* Vérifie si la prédiction est fiable (R² > 0.7)
*
* @return true si la prédiction est considérée comme fiable
*/
public boolean isPredictionFiable() {
return coefficientCorrelation != null
&& coefficientCorrelation.compareTo(new BigDecimal("0.7")) >= 0;
}
/**
* Retourne le nombre de points de données
*
* @return Le nombre de points de données
*/
public int getNombrePointsDonnees() {
return pointsDonnees != null ? pointsDonnees.size() : 0;
}
/**
* Vérifie si des anomalies ont été détectées
*
* @return true si au moins un point est marqué comme anomalie
*/
public boolean hasAnomalies() {
return pointsDonnees != null
&& pointsDonnees.stream().anyMatch(point -> Boolean.TRUE.equals(point.getAnomalie()));
}
}

View File

@@ -0,0 +1,327 @@
package dev.lions.unionflow.server.api.dto.analytics;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.analytics.FormatExport;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* DTO pour la configuration des rapports analytics UnionFlow
*
* <p>Représente la configuration d'un rapport personnalisé avec ses métriques, sa mise en forme et
* ses paramètres d'export.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReportConfigDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Nom du rapport */
@NotBlank(message = "Le nom du rapport est obligatoire")
@Size(min = 3, max = 200, message = "Le nom du rapport doit contenir entre 3 et 200 caractères")
private String nom;
/** Description du rapport */
@Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères")
private String description;
/** Type de rapport (executif, analytique, technique, operationnel) */
@NotBlank(message = "Le type de rapport est obligatoire")
@Size(max = 50, message = "Le type de rapport ne peut pas dépasser 50 caractères")
private String typeRapport;
/** Période d'analyse par défaut */
@NotNull(message = "La période d'analyse est obligatoire")
private PeriodeAnalyse periodeAnalyse;
/** Date de début personnalisée (si période personnalisée) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDebutPersonnalisee;
/** Date de fin personnalisée (si période personnalisée) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateFinPersonnalisee;
/** Identifiant de l'organisation (optionnel pour filtrage) */
private UUID organisationId;
/** Nom de l'organisation */
@Size(max = 200, message = "Le nom de l'organisation ne peut pas dépasser 200 caractères")
private String nomOrganisation;
/** Identifiant de l'utilisateur créateur */
@NotNull(message = "L'identifiant de l'utilisateur créateur est obligatoire")
private UUID utilisateurCreateurId;
/** Nom de l'utilisateur créateur */
@Size(max = 200, message = "Le nom de l'utilisateur créateur ne peut pas dépasser 200 caractères")
private String nomUtilisateurCreateur;
/** Métriques incluses dans le rapport */
@NotNull(message = "Les métriques sont obligatoires")
@Valid
private List<MetriqueConfigDTO> metriques;
/** Sections du rapport */
@Valid private List<SectionRapportDTO> sections;
/** Format d'export par défaut */
@NotNull(message = "Le format d'export est obligatoire")
private FormatExport formatExport;
/** Formats d'export autorisés */
private List<FormatExport> formatsExportAutorises;
/** Modèle de rapport à utiliser */
@Size(max = 100, message = "Le modèle de rapport ne peut pas dépasser 100 caractères")
private String modeleRapport;
/** Configuration de la mise en page */
@Size(
max = 2000,
message = "La configuration de mise en page ne peut pas dépasser 2000 caractères")
private String configurationMiseEnPage;
/** Logo personnalisé (URL ou base64) */
@Size(max = 5000, message = "Le logo personnalisé ne peut pas dépasser 5000 caractères")
private String logoPersonnalise;
/** Couleurs personnalisées du rapport */
private Map<String, String> couleursPersonnalisees;
/** Indicateur si le rapport est public */
@Builder.Default private Boolean rapportPublic = false;
/** Indicateur si le rapport est automatique */
@Builder.Default private Boolean rapportAutomatique = false;
/** Fréquence de génération automatique (en heures) */
@DecimalMin(value = "1", message = "La fréquence minimum est 1 heure")
private Integer frequenceGenerationHeures;
/** Prochaine génération automatique */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime prochaineGeneration;
/** Liste des destinataires pour l'envoi automatique */
private List<String> destinatairesEmail;
/** Objet de l'email pour l'envoi automatique */
@Size(max = 200, message = "L'objet de l'email ne peut pas dépasser 200 caractères")
private String objetEmail;
/** Corps de l'email pour l'envoi automatique */
@Size(max = 2000, message = "Le corps de l'email ne peut pas dépasser 2000 caractères")
private String corpsEmail;
/** Paramètres de filtrage avancé */
private Map<String, Object> parametresFiltrage;
/** Tags pour catégoriser le rapport */
private List<String> tags;
/** Niveau de confidentialité (1=public, 5=confidentiel) */
@DecimalMin(value = "1", message = "Le niveau de confidentialité minimum est 1")
@DecimalMax(value = "5", message = "Le niveau de confidentialité maximum est 5")
@Builder.Default
private Integer niveauConfidentialite = 1;
/** Date de dernière génération */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereGeneration;
/** Nombre de générations effectuées */
@DecimalMin(value = "0", message = "Le nombre de générations doit être positif")
@Builder.Default
private Integer nombreGenerations = 0;
/** Taille moyenne des rapports générés (en KB) */
@DecimalMin(value = "0", message = "La taille moyenne doit être positive")
private Long tailleMoyenneKB;
/** Temps moyen de génération (en secondes) */
@DecimalMin(value = "0", message = "Le temps moyen de génération doit être positif")
private Integer tempsMoyenGenerationSecondes;
// === CLASSES INTERNES ===
/** Configuration d'une métrique dans le rapport */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MetriqueConfigDTO {
/** Type de métrique */
@NotNull(message = "Le type de métrique est obligatoire")
private TypeMetrique typeMetrique;
/** Libellé personnalisé */
@Size(max = 200, message = "Le libellé personnalisé ne peut pas dépasser 200 caractères")
private String libellePersonnalise;
/** Position dans le rapport (ordre d'affichage) */
@DecimalMin(value = "1", message = "La position minimum est 1")
private Integer position;
/** Taille d'affichage (1=petit, 2=moyen, 3=grand) */
@DecimalMin(value = "1", message = "La taille minimum est 1")
@DecimalMax(value = "3", message = "La taille maximum est 3")
@Builder.Default
private Integer tailleAffichage = 2;
/** Couleur personnalisée */
@Size(max = 7, message = "La couleur doit être au format #RRGGBB")
private String couleurPersonnalisee;
/** Indicateur si la métrique inclut un graphique */
@Builder.Default private Boolean inclureGraphique = true;
/** Type de graphique (line, bar, pie, area) */
@Size(max = 20, message = "Le type de graphique ne peut pas dépasser 20 caractères")
@Builder.Default
private String typeGraphique = "line";
/** Indicateur si la métrique inclut la tendance */
@Builder.Default private Boolean inclureTendance = true;
/** Indicateur si la métrique inclut la comparaison */
@Builder.Default private Boolean inclureComparaison = true;
/** Seuils d'alerte personnalisés */
private Map<String, Object> seuilsAlerte;
/** Filtres spécifiques à cette métrique */
private Map<String, Object> filtresSpecifiques;
}
/** Configuration d'une section du rapport */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SectionRapportDTO {
/** Nom de la section */
@NotBlank(message = "Le nom de la section est obligatoire")
@Size(max = 200, message = "Le nom de la section ne peut pas dépasser 200 caractères")
private String nom;
/** Description de la section */
@Size(max = 500, message = "La description de la section ne peut pas dépasser 500 caractères")
private String description;
/** Position de la section dans le rapport */
@DecimalMin(value = "1", message = "La position minimum est 1")
private Integer position;
/** Type de section (resume, metriques, graphiques, tableaux, analyse) */
@NotBlank(message = "Le type de section est obligatoire")
@Size(max = 50, message = "Le type de section ne peut pas dépasser 50 caractères")
private String typeSection;
/** Métriques incluses dans cette section */
private List<TypeMetrique> metriquesIncluses;
/** Configuration spécifique de la section */
private Map<String, Object> configurationSection;
/** Indicateur si la section est visible */
@Builder.Default private Boolean visible = true;
/** Indicateur si la section peut être réduite */
@Builder.Default private Boolean pliable = false;
}
// === MÉTHODES UTILITAIRES ===
/**
* Retourne le nombre de métriques configurées
*
* @return Le nombre de métriques
*/
public int getNombreMetriques() {
return metriques != null ? metriques.size() : 0;
}
/**
* Retourne le nombre de sections configurées
*
* @return Le nombre de sections
*/
public int getNombreSections() {
return sections != null ? sections.size() : 0;
}
/**
* Vérifie si le rapport utilise une période personnalisée
*
* @return true si la période est personnalisée
*/
public boolean isPeriodePersonnalisee() {
return periodeAnalyse == PeriodeAnalyse.PERIODE_PERSONNALISEE;
}
/**
* Vérifie si le rapport est confidentiel (niveau >= 4)
*
* @return true si le rapport est confidentiel
*/
public boolean isConfidentiel() {
return niveauConfidentialite != null && niveauConfidentialite >= 4;
}
/**
* Vérifie si le rapport nécessite une génération
*
* @return true si la prochaine génération est due
*/
public boolean necessiteGeneration() {
return rapportAutomatique
&& prochaineGeneration != null
&& prochaineGeneration.isBefore(LocalDateTime.now());
}
/**
* Retourne la fréquence de génération en texte
*
* @return La fréquence sous forme de texte
*/
public String getFrequenceTexte() {
if (frequenceGenerationHeures == null) return "Manuelle";
return switch (frequenceGenerationHeures) {
case 1 -> "Toutes les heures";
case 24 -> "Quotidienne";
case 168 -> "Hebdomadaire"; // 24 * 7
case 720 -> "Mensuelle"; // 24 * 30
default -> "Toutes les " + frequenceGenerationHeures + " heures";
};
}
}

View File

@@ -0,0 +1,161 @@
package dev.lions.unionflow.server.api.dto.base;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* Classe de base pour tous les DTOs UnionFlow Fournit les propriétés communes d'audit et de gestion
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public abstract class BaseDTO implements Serializable {
@Serial private static final long serialVersionUID = 1L;
/** Identifiant unique UUID */
private UUID id;
/** Date de création de l'enregistrement */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
public LocalDateTime dateCreation;
/** Date de dernière modification */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
public LocalDateTime dateModification;
/** Utilisateur qui a créé l'enregistrement */
private String creePar;
/** Utilisateur qui a modifié l'enregistrement en dernier */
private String modifiePar;
/** Version pour gestion de la concurrence optimiste */
private Long version;
/** Indicateur si l'enregistrement est actif */
private Boolean actif;
// Constructeur par défaut
public BaseDTO() {
this.id = UUID.randomUUID();
this.dateCreation = LocalDateTime.now();
this.actif = true;
this.version = 0L;
}
// Getters et Setters générés automatiquement par Lombok @Getter/@Setter
// Méthodes utilitaires
/**
* Marque l'entité comme nouvellement créée
*
* @param utilisateur L'utilisateur qui crée l'entité
*/
public void marquerCommeNouveau(String utilisateur) {
LocalDateTime maintenant = LocalDateTime.now();
this.dateCreation = maintenant;
this.dateModification = maintenant;
this.creePar = utilisateur;
this.modifiePar = utilisateur;
this.version = 0L;
this.actif = true;
}
/**
* Marque l'entité comme modifiée
*
* @param utilisateur L'utilisateur qui modifie l'entité
*/
public void marquerCommeModifie(String utilisateur) {
this.dateModification = LocalDateTime.now();
this.modifiePar = utilisateur;
if (this.version != null) {
this.version++;
}
}
/**
* Désactive l'entité (soft delete)
*
* @param utilisateur L'utilisateur qui désactive l'entité
*/
public void desactiver(String utilisateur) {
this.actif = false;
marquerCommeModifie(utilisateur);
}
/**
* Réactive l'entité
*
* @param utilisateur L'utilisateur qui réactive l'entité
*/
public void reactiver(String utilisateur) {
this.actif = true;
marquerCommeModifie(utilisateur);
}
/**
* Vérifie si l'entité est nouvelle (pas encore persistée)
*
* @return true si l'entité est nouvelle
*/
public boolean isNouveau() {
return id == null;
}
/**
* Vérifie si l'entité est active
*
* @return true si l'entité est active
*/
public boolean isActif() {
return Boolean.TRUE.equals(actif);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
BaseDTO baseDTO = (BaseDTO) obj;
return id != null && id.equals(baseDTO.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ "{"
+ "id="
+ id
+ ", dateCreation="
+ dateCreation
+ ", dateModification="
+ dateModification
+ ", creePar='"
+ creePar
+ '\''
+ ", modifiePar='"
+ modifiePar
+ '\''
+ ", version="
+ version
+ ", actif="
+ actif
+ '}';
}
}

View File

@@ -0,0 +1,57 @@
package dev.lions.unionflow.server.api.dto.comptabilite;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des comptes comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class CompteComptableDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de compte */
@NotBlank(message = "Le numéro de compte est obligatoire")
private String numeroCompte;
/** Libellé du compte */
@NotBlank(message = "Le libellé est obligatoire")
private String libelle;
/** Type de compte */
@NotNull(message = "Le type de compte est obligatoire")
private TypeCompteComptable typeCompte;
/** Classe comptable (1-7) */
@NotNull(message = "La classe comptable est obligatoire")
@Min(value = 1, message = "La classe comptable doit être entre 1 et 7")
@Max(value = 7, message = "La classe comptable doit être entre 1 et 7")
private Integer classeComptable;
/** Solde initial */
private BigDecimal soldeInitial;
/** Solde actuel */
private BigDecimal soldeActuel;
/** Compte collectif */
private Boolean compteCollectif;
/** Compte analytique */
private Boolean compteAnalytique;
/** Description */
private String description;
}

View File

@@ -0,0 +1,72 @@
package dev.lions.unionflow.server.api.dto.comptabilite;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des écritures comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class EcritureComptableDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de pièce */
@NotBlank(message = "Le numéro de pièce est obligatoire")
private String numeroPiece;
/** Date de l'écriture */
@NotNull(message = "La date de l'écriture est obligatoire")
private LocalDate dateEcriture;
/** Libellé de l'écriture */
@NotBlank(message = "Le libellé est obligatoire")
private String libelle;
/** Référence externe */
private String reference;
/** Lettrage */
private String lettrage;
/** Pointage */
private Boolean pointe;
/** Montant total débit */
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
private BigDecimal montantDebit;
/** Montant total crédit */
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
private BigDecimal montantCredit;
/** Commentaires */
private String commentaire;
/** ID du journal */
@NotNull(message = "Le journal est obligatoire")
private UUID journalId;
/** ID de l'organisation */
private UUID organisationId;
/** ID du paiement */
private UUID paiementId;
/** Lignes d'écriture */
private List<LigneEcritureDTO> lignes;
}

View File

@@ -0,0 +1,48 @@
package dev.lions.unionflow.server.api.dto.comptabilite;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des journaux comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class JournalComptableDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Code unique du journal */
@NotBlank(message = "Le code du journal est obligatoire")
private String code;
/** Libellé du journal */
@NotBlank(message = "Le libellé est obligatoire")
private String libelle;
/** Type de journal */
@NotNull(message = "Le type de journal est obligatoire")
private TypeJournalComptable typeJournal;
/** Date de début de la période */
private LocalDate dateDebut;
/** Date de fin de la période */
private LocalDate dateFin;
/** Statut du journal */
private String statut;
/** Description */
private String description;
}

View File

@@ -0,0 +1,52 @@
package dev.lions.unionflow.server.api.dto.comptabilite;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des lignes d'écriture
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class LigneEcritureDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de ligne */
@NotNull(message = "Le numéro de ligne est obligatoire")
@Min(value = 1, message = "Le numéro de ligne doit être positif")
private Integer numeroLigne;
/** Montant débit */
@DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
@Digits(integer = 12, fraction = 2)
private BigDecimal montantDebit;
/** Montant crédit */
@DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
@Digits(integer = 12, fraction = 2)
private BigDecimal montantCredit;
/** Libellé de la ligne */
private String libelle;
/** Référence */
private String reference;
/** ID de l'écriture */
@NotNull(message = "L'écriture est obligatoire")
private UUID ecritureId;
/** ID du compte comptable */
@NotNull(message = "Le compte comptable est obligatoire")
private UUID compteComptableId;
}

View File

@@ -0,0 +1,122 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* DTO principal pour toutes les données du dashboard
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardDataDTO {
@JsonProperty("stats")
private DashboardStatsDTO stats;
@JsonProperty("recentActivities")
private List<RecentActivityDTO> recentActivities;
@JsonProperty("upcomingEvents")
private List<UpcomingEventDTO> upcomingEvents;
@JsonProperty("userPreferences")
private Map<String, Object> userPreferences;
@JsonProperty("organizationId")
private String organizationId;
@JsonProperty("userId")
private String userId;
// Méthodes utilitaires
public Integer getTodayEventsCount() {
if (upcomingEvents == null) {
return 0;
}
return (int) upcomingEvents.stream()
.filter(event -> event.getIsToday() != null && event.getIsToday())
.count();
}
public Integer getTomorrowEventsCount() {
if (upcomingEvents == null) {
return 0;
}
return (int) upcomingEvents.stream()
.filter(event -> event.getIsTomorrow() != null && event.getIsTomorrow())
.count();
}
public Integer getRecentActivitiesCount() {
if (recentActivities == null) {
return 0;
}
return (int) recentActivities.stream()
.filter(activity -> activity.getIsRecent() != null && activity.getIsRecent())
.count();
}
public Integer getTodayActivitiesCount() {
if (recentActivities == null) {
return 0;
}
return (int) recentActivities.stream()
.filter(activity -> activity.getIsToday() != null && activity.getIsToday())
.count();
}
public Boolean getHasUpcomingEvents() {
return upcomingEvents != null && !upcomingEvents.isEmpty();
}
public Boolean getHasRecentActivities() {
return recentActivities != null && !recentActivities.isEmpty();
}
public String getThemePreference() {
if (userPreferences == null) {
return "royal_teal";
}
return (String) userPreferences.getOrDefault("theme", "royal_teal");
}
public String getLanguagePreference() {
if (userPreferences == null) {
return "fr";
}
return (String) userPreferences.getOrDefault("language", "fr");
}
public Boolean getNotificationsEnabled() {
if (userPreferences == null) {
return true;
}
return (Boolean) userPreferences.getOrDefault("notifications", true);
}
public Boolean getAutoRefreshEnabled() {
if (userPreferences == null) {
return true;
}
return (Boolean) userPreferences.getOrDefault("autoRefresh", true);
}
public Integer getRefreshInterval() {
if (userPreferences == null) {
return 300; // 5 minutes par défaut
}
return (Integer) userPreferences.getOrDefault("refreshInterval", 300);
}
}

View File

@@ -0,0 +1,98 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* DTO pour les statistiques du dashboard
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardStatsDTO {
@JsonProperty("totalMembers")
private Integer totalMembers;
@JsonProperty("activeMembers")
private Integer activeMembers;
@JsonProperty("totalEvents")
private Integer totalEvents;
@JsonProperty("upcomingEvents")
private Integer upcomingEvents;
@JsonProperty("totalContributions")
private Integer totalContributions;
@JsonProperty("totalContributionAmount")
private Double totalContributionAmount;
@JsonProperty("pendingRequests")
private Integer pendingRequests;
@JsonProperty("completedProjects")
private Integer completedProjects;
@JsonProperty("monthlyGrowth")
private Double monthlyGrowth;
@JsonProperty("engagementRate")
private Double engagementRate;
@JsonProperty("lastUpdated")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime lastUpdated;
// Méthodes utilitaires
public String getFormattedContributionAmount() {
if (totalContributionAmount == null) {
return "0";
}
if (totalContributionAmount >= 1_000_000) {
return String.format("%.1fM", totalContributionAmount / 1_000_000);
} else if (totalContributionAmount >= 1_000) {
return String.format("%.0fK", totalContributionAmount / 1_000);
} else {
return String.format("%.0f", totalContributionAmount);
}
}
public Boolean getHasGrowth() {
return monthlyGrowth != null && monthlyGrowth > 0;
}
public Boolean getIsHighEngagement() {
return engagementRate != null && engagementRate > 0.7;
}
public Double getInactiveMembers() {
if (totalMembers == null || activeMembers == null) {
return 0.0;
}
return (double) (totalMembers - activeMembers);
}
public Double getActiveMemberPercentage() {
if (totalMembers == null || activeMembers == null || totalMembers == 0) {
return 0.0;
}
return (double) activeMembers / totalMembers * 100;
}
public Double getEngagementPercentage() {
if (engagementRate == null) {
return 0.0;
}
return engagementRate * 100;
}
}

View File

@@ -0,0 +1,130 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
/**
* DTO pour les activités récentes du dashboard
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecentActivityDTO {
@JsonProperty("id")
private String id;
@JsonProperty("type")
private String type;
@JsonProperty("title")
private String title;
@JsonProperty("description")
private String description;
@JsonProperty("userName")
private String userName;
@JsonProperty("timestamp")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime timestamp;
@JsonProperty("userAvatar")
private String userAvatar;
@JsonProperty("actionUrl")
private String actionUrl;
// Méthodes utilitaires
public String getTimeAgo() {
if (timestamp == null) {
return "";
}
LocalDateTime now = LocalDateTime.now();
long minutes = ChronoUnit.MINUTES.between(timestamp, now);
long hours = ChronoUnit.HOURS.between(timestamp, now);
long days = ChronoUnit.DAYS.between(timestamp, now);
if (minutes < 60) {
return minutes + "min";
} else if (hours < 24) {
return hours + "h";
} else if (days < 7) {
return days + "j";
} else {
long weeks = days / 7;
return weeks + "sem";
}
}
public String getActivityIcon() {
if (type == null) {
return "help_outline";
}
switch (type.toLowerCase()) {
case "member":
return "person";
case "event":
return "event";
case "contribution":
return "payment";
case "organization":
return "business";
case "system":
return "settings";
default:
return "info";
}
}
public String getActivityColor() {
if (type == null) {
return "#6B7280"; // grey
}
switch (type.toLowerCase()) {
case "member":
return "#10B981"; // success
case "event":
return "#3B82F6"; // info
case "contribution":
return "#008B8B"; // teal blue
case "organization":
return "#4169E1"; // royal blue
case "system":
return "#6B7280"; // grey
default:
return "#6B7280"; // grey
}
}
public Boolean getIsRecent() {
if (timestamp == null) {
return false;
}
LocalDateTime now = LocalDateTime.now();
long hours = ChronoUnit.HOURS.between(timestamp, now);
return hours < 24;
}
public Boolean getIsToday() {
if (timestamp == null) {
return false;
}
LocalDateTime now = LocalDateTime.now();
return timestamp.toLocalDate().equals(now.toLocalDate());
}
}

View File

@@ -0,0 +1,174 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
/**
* DTO pour les événements à venir du dashboard
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UpcomingEventDTO {
@JsonProperty("id")
private String id;
@JsonProperty("title")
private String title;
@JsonProperty("description")
private String description;
@JsonProperty("startDate")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime startDate;
@JsonProperty("endDate")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime endDate;
@JsonProperty("location")
private String location;
@JsonProperty("maxParticipants")
private Integer maxParticipants;
@JsonProperty("currentParticipants")
private Integer currentParticipants;
@JsonProperty("status")
private String status;
@JsonProperty("imageUrl")
private String imageUrl;
@JsonProperty("tags")
private List<String> tags;
// Méthodes utilitaires
public String getDaysUntilEvent() {
if (startDate == null) {
return "";
}
LocalDateTime now = LocalDateTime.now();
long days = ChronoUnit.DAYS.between(now, startDate);
long hours = ChronoUnit.HOURS.between(now, startDate);
if (days == 0) {
if (hours < 0) {
return "En cours";
} else if (hours < 2) {
return "Bientôt";
} else {
return "Aujourd'hui";
}
} else if (days == 1) {
return "Demain";
} else if (days < 7) {
return "Dans " + days + " jours";
} else {
long weeks = days / 7;
return "Dans " + weeks + " semaine" + (weeks > 1 ? "s" : "");
}
}
public Double getFillPercentage() {
if (maxParticipants == null || currentParticipants == null || maxParticipants == 0) {
return 0.0;
}
return (double) currentParticipants / maxParticipants * 100;
}
public Boolean getIsFull() {
if (maxParticipants == null || currentParticipants == null) {
return false;
}
return currentParticipants >= maxParticipants;
}
public Boolean getIsAlmostFull() {
Double fillPercentage = getFillPercentage();
return fillPercentage >= 80.0 && fillPercentage < 100.0;
}
public Boolean getIsToday() {
if (startDate == null) {
return false;
}
LocalDateTime now = LocalDateTime.now();
return startDate.toLocalDate().equals(now.toLocalDate());
}
public Boolean getIsTomorrow() {
if (startDate == null) {
return false;
}
LocalDateTime now = LocalDateTime.now();
return startDate.toLocalDate().equals(now.toLocalDate().plusDays(1));
}
public String getStatusColor() {
if (status == null) {
return "#6B7280"; // grey
}
switch (status.toLowerCase()) {
case "confirmed":
return "#10B981"; // success
case "open":
return "#3B82F6"; // info
case "cancelled":
return "#EF4444"; // error
case "postponed":
return "#F59E0B"; // warning
default:
return "#6B7280"; // grey
}
}
public String getStatusLabel() {
if (status == null) {
return "Inconnu";
}
switch (status.toLowerCase()) {
case "confirmed":
return "Confirmé";
case "open":
return "Ouvert";
case "cancelled":
return "Annulé";
case "postponed":
return "Reporté";
default:
return status;
}
}
public Integer getAvailableSpots() {
if (maxParticipants == null || currentParticipants == null) {
return 0;
}
return Math.max(0, maxParticipants - currentParticipants);
}
public String getParticipationSummary() {
if (maxParticipants == null || currentParticipants == null) {
return "0/0 participants";
}
return currentParticipants + "/" + maxParticipants + " participants";
}
}

View File

@@ -0,0 +1,59 @@
package dev.lions.unionflow.server.api.dto.document;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.document.TypeDocument;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des documents
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class DocumentDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Nom du fichier */
@NotBlank(message = "Le nom du fichier est obligatoire")
private String nomFichier;
/** Nom original */
private String nomOriginal;
/** Chemin de stockage */
@NotBlank(message = "Le chemin de stockage est obligatoire")
private String cheminStockage;
/** Type MIME */
private String typeMime;
/** Taille en octets */
@NotNull(message = "La taille est obligatoire")
@Min(value = 0, message = "La taille doit être positive")
private Long tailleOctets;
/** Type de document */
private TypeDocument typeDocument;
/** Hash MD5 */
private String hashMd5;
/** Hash SHA256 */
private String hashSha256;
/** Description */
private String description;
/** Nombre de téléchargements */
private Integer nombreTelechargements;
/** Taille formatée (calculée) */
private String tailleFormatee;
}

View File

@@ -0,0 +1,55 @@
package dev.lions.unionflow.server.api.dto.document;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.*;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des pièces jointes
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class PieceJointeDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Ordre d'affichage */
@NotNull(message = "L'ordre est obligatoire")
@Min(value = 1, message = "L'ordre doit être positif")
private Integer ordre;
/** Libellé */
private String libelle;
/** Commentaire */
private String commentaire;
/** ID du document */
@NotNull(message = "Le document est obligatoire")
private UUID documentId;
/** ID du membre (optionnel) */
private UUID membreId;
/** ID de l'organisation (optionnel) */
private UUID organisationId;
/** ID de la cotisation (optionnel) */
private UUID cotisationId;
/** ID de l'adhésion (optionnel) */
private UUID adhesionId;
/** ID de la demande d'aide (optionnel) */
private UUID demandeAideId;
/** ID de la transaction Wave (optionnel) */
private UUID transactionWaveId;
}

View File

@@ -0,0 +1,793 @@
package dev.lions.unionflow.server.api.dto.evenement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.evenement.PrioriteEvenement;
import dev.lions.unionflow.server.api.enums.evenement.StatutEvenement;
import dev.lions.unionflow.server.api.enums.evenement.TypeEvenementMetier;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
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.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des événements dans l'API UnionFlow Représente un événement organisé par une
* association
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class EvenementDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Titre de l'événement */
@NotBlank(message = "Le titre" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.TITRE_MIN_LENGTH,
max = ValidationConstants.TITRE_MAX_LENGTH,
message = ValidationConstants.TITRE_SIZE_MESSAGE)
private String titre;
/** Description détaillée de l'événement */
@Size(
max = ValidationConstants.DESCRIPTION_COURTE_MAX_LENGTH,
message = ValidationConstants.DESCRIPTION_COURTE_SIZE_MESSAGE)
private String description;
/** Type d'événement */
@NotNull(message = "Le type d'événement est obligatoire")
private TypeEvenementMetier typeEvenement;
/** Statut de l'événement */
@NotNull(message = "Le statut est obligatoire")
private StatutEvenement statut;
/** Priorité de l'événement */
private PrioriteEvenement priorite;
/** Date de début de l'événement */
@JsonFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "La date de début est obligatoire")
@Future(message = "La date de début doit être dans le futur")
private LocalDate dateDebut;
/** Date de fin de l'événement */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFin;
/** Heure de début */
@JsonFormat(pattern = "HH:mm")
private LocalTime heureDebut;
/** Heure de fin */
@JsonFormat(pattern = "HH:mm")
private LocalTime heureFin;
/** Lieu de l'événement */
@NotBlank(message = "Le lieu est obligatoire")
@Size(max = 100, message = "Le lieu ne peut pas dépasser 100 caractères")
private String lieu;
/** Adresse complète du lieu */
@Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
private String adresse;
/** Ville */
@Size(max = 50, message = "La ville ne peut pas dépasser 50 caractères")
private String ville;
/** Région */
@Size(max = 50, message = "La région ne peut pas dépasser 50 caractères")
private String region;
/** Coordonnées GPS (latitude) */
@DecimalMin(value = "-90.0", message = "La latitude doit être entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être entre -90 et 90")
private BigDecimal latitude;
/** Coordonnées GPS (longitude) */
@DecimalMin(value = "-180.0", message = "La longitude doit être entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être entre -180 et 180")
private BigDecimal longitude;
/** Identifiant de l'association organisatrice */
@NotNull(message = "L'association organisatrice est obligatoire")
private UUID associationId;
/** Nom de l'association organisatrice (lecture seule) */
private String nomAssociation;
/** Nom de l'organisateur principal */
@Size(max = 100, message = "Le nom de l'organisateur ne peut pas dépasser 100 caractères")
private String organisateur;
/** Email de l'organisateur */
@Email(message = "Format d'email invalide")
@Size(max = 100, message = "L'email ne peut pas dépasser 100 caractères")
private String emailOrganisateur;
/** Téléphone de l'organisateur */
@Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$", message = "Format de téléphone invalide")
private String telephoneOrganisateur;
/** Capacité maximale de participants */
@Min(value = 1, message = "La capacité doit être d'au moins 1 personne")
@Max(value = 10000, message = "La capacité ne peut pas dépasser 10000 personnes")
private Integer capaciteMax;
/** Nombre de participants inscrits */
@Min(value = 0, message = "Le nombre de participants ne peut pas être négatif")
private Integer participantsInscrits;
/** Nombre de participants présents */
@Min(value = 0, message = "Le nombre de participants présents ne peut pas être négatif")
private Integer participantsPresents;
/** Budget prévu pour l'événement */
@DecimalMin(
value = ValidationConstants.MONTANT_MIN_VALUE,
message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal budget;
/** Coût réel de l'événement */
@DecimalMin(
value = ValidationConstants.MONTANT_MIN_VALUE,
message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal coutReel;
/** Code de la devise */
@Pattern(
regexp = ValidationConstants.DEVISE_PATTERN,
message = ValidationConstants.DEVISE_MESSAGE)
private String codeDevise;
/** Indique si l'inscription est obligatoire */
private Boolean inscriptionObligatoire = false;
/** Date limite d'inscription */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateLimiteInscription;
/** Indique si l'événement est public */
private Boolean evenementPublic = true;
/** Indique si l'événement est récurrent */
private Boolean recurrent = false;
/** Fréquence de récurrence (si récurrent) */
@Pattern(
regexp = "^(HEBDOMADAIRE|MENSUELLE|TRIMESTRIELLE|ANNUELLE)$",
message = "Fréquence de récurrence invalide")
private String frequenceRecurrence;
/** Instructions spéciales pour les participants */
@Size(max = 500, message = "Les instructions ne peuvent pas dépasser 500 caractères")
private String instructions;
/** Matériel nécessaire */
@Size(max = 500, message = "La liste du matériel ne peut pas dépasser 500 caractères")
private String materielNecessaire;
/** Conditions météorologiques requises */
@Size(max = 100, message = "Les conditions météo ne peuvent pas dépasser 100 caractères")
private String conditionsMeteo;
/** URL de l'image de l'événement */
@Size(max = 255, message = "L'URL de l'image ne peut pas dépasser 255 caractères")
private String imageUrl;
/** Couleur thème de l'événement */
@Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "Format de couleur invalide (format: #RRGGBB)")
private String couleurTheme;
/** Date d'annulation (si annulé) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateAnnulation;
/** Raison de l'annulation */
@Size(max = 500, message = "La raison d'annulation ne peut pas dépasser 500 caractères")
private String raisonAnnulation;
/** Identifiant de l'utilisateur qui a annulé */
private Long annulePar;
/** Nom de l'utilisateur qui a annulé */
private String nomAnnulateur;
// Constructeurs
public EvenementDTO() {
super();
this.statut = StatutEvenement.PLANIFIE;
this.priorite = PrioriteEvenement.NORMALE;
this.participantsInscrits = 0;
this.participantsPresents = 0;
this.inscriptionObligatoire = false;
this.evenementPublic = true;
this.recurrent = false;
this.codeDevise = "XOF"; // Franc CFA par défaut
}
public EvenementDTO(
String titre, TypeEvenementMetier typeEvenement, LocalDate dateDebut, String lieu) {
this();
this.titre = titre;
this.typeEvenement = typeEvenement;
this.dateDebut = dateDebut;
this.lieu = lieu;
}
// Getters et Setters
public String getTitre() {
return titre;
}
public void setTitre(String titre) {
this.titre = titre;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public TypeEvenementMetier getTypeEvenement() {
return typeEvenement;
}
public void setTypeEvenement(TypeEvenementMetier typeEvenement) {
this.typeEvenement = typeEvenement;
}
public StatutEvenement getStatut() {
return statut;
}
public void setStatut(StatutEvenement statut) {
this.statut = statut;
}
public PrioriteEvenement getPriorite() {
return priorite;
}
public void setPriorite(PrioriteEvenement priorite) {
this.priorite = priorite;
}
public LocalDate getDateDebut() {
return dateDebut;
}
public void setDateDebut(LocalDate dateDebut) {
this.dateDebut = dateDebut;
}
public LocalDate getDateFin() {
return dateFin;
}
public void setDateFin(LocalDate dateFin) {
this.dateFin = dateFin;
}
public LocalTime getHeureDebut() {
return heureDebut;
}
public void setHeureDebut(LocalTime heureDebut) {
this.heureDebut = heureDebut;
}
public LocalTime getHeureFin() {
return heureFin;
}
public void setHeureFin(LocalTime heureFin) {
this.heureFin = heureFin;
}
public String getLieu() {
return lieu;
}
public void setLieu(String lieu) {
this.lieu = lieu;
}
public String getAdresse() {
return adresse;
}
public void setAdresse(String adresse) {
this.adresse = adresse;
}
public String getVille() {
return ville;
}
public void setVille(String ville) {
this.ville = ville;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public BigDecimal getLatitude() {
return latitude;
}
public void setLatitude(BigDecimal latitude) {
this.latitude = latitude;
}
public BigDecimal getLongitude() {
return longitude;
}
public void setLongitude(BigDecimal longitude) {
this.longitude = longitude;
}
public UUID getAssociationId() {
return associationId;
}
public void setAssociationId(UUID associationId) {
this.associationId = associationId;
}
public String getNomAssociation() {
return nomAssociation;
}
public void setNomAssociation(String nomAssociation) {
this.nomAssociation = nomAssociation;
}
public String getOrganisateur() {
return organisateur;
}
public void setOrganisateur(String organisateur) {
this.organisateur = organisateur;
}
public String getEmailOrganisateur() {
return emailOrganisateur;
}
public void setEmailOrganisateur(String emailOrganisateur) {
this.emailOrganisateur = emailOrganisateur;
}
public String getTelephoneOrganisateur() {
return telephoneOrganisateur;
}
public void setTelephoneOrganisateur(String telephoneOrganisateur) {
this.telephoneOrganisateur = telephoneOrganisateur;
}
public Integer getCapaciteMax() {
return capaciteMax;
}
public void setCapaciteMax(Integer capaciteMax) {
this.capaciteMax = capaciteMax;
}
public Integer getParticipantsInscrits() {
return participantsInscrits;
}
public void setParticipantsInscrits(Integer participantsInscrits) {
this.participantsInscrits = participantsInscrits;
}
public Integer getParticipantsPresents() {
return participantsPresents;
}
public void setParticipantsPresents(Integer participantsPresents) {
this.participantsPresents = participantsPresents;
}
public BigDecimal getBudget() {
return budget;
}
public void setBudget(BigDecimal budget) {
this.budget = budget;
}
public BigDecimal getCoutReel() {
return coutReel;
}
public void setCoutReel(BigDecimal coutReel) {
this.coutReel = coutReel;
}
public String getCodeDevise() {
return codeDevise;
}
public void setCodeDevise(String codeDevise) {
this.codeDevise = codeDevise;
}
public Boolean getInscriptionObligatoire() {
return inscriptionObligatoire;
}
public void setInscriptionObligatoire(Boolean inscriptionObligatoire) {
this.inscriptionObligatoire = inscriptionObligatoire;
}
public LocalDate getDateLimiteInscription() {
return dateLimiteInscription;
}
public void setDateLimiteInscription(LocalDate dateLimiteInscription) {
this.dateLimiteInscription = dateLimiteInscription;
}
public Boolean getEvenementPublic() {
return evenementPublic;
}
public void setEvenementPublic(Boolean evenementPublic) {
this.evenementPublic = evenementPublic;
}
public Boolean getRecurrent() {
return recurrent;
}
public void setRecurrent(Boolean recurrent) {
this.recurrent = recurrent;
}
public String getFrequenceRecurrence() {
return frequenceRecurrence;
}
public void setFrequenceRecurrence(String frequenceRecurrence) {
this.frequenceRecurrence = frequenceRecurrence;
}
public String getInstructions() {
return instructions;
}
public void setInstructions(String instructions) {
this.instructions = instructions;
}
public String getMaterielNecessaire() {
return materielNecessaire;
}
public void setMaterielNecessaire(String materielNecessaire) {
this.materielNecessaire = materielNecessaire;
}
public String getConditionsMeteo() {
return conditionsMeteo;
}
public void setConditionsMeteo(String conditionsMeteo) {
this.conditionsMeteo = conditionsMeteo;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getCouleurTheme() {
return couleurTheme;
}
public void setCouleurTheme(String couleurTheme) {
this.couleurTheme = couleurTheme;
}
public LocalDateTime getDateAnnulation() {
return dateAnnulation;
}
public void setDateAnnulation(LocalDateTime dateAnnulation) {
this.dateAnnulation = dateAnnulation;
}
public String getRaisonAnnulation() {
return raisonAnnulation;
}
public void setRaisonAnnulation(String raisonAnnulation) {
this.raisonAnnulation = raisonAnnulation;
}
public Long getAnnulePar() {
return annulePar;
}
public void setAnnulePar(Long annulePar) {
this.annulePar = annulePar;
}
public String getNomAnnulateur() {
return nomAnnulateur;
}
public void setNomAnnulateur(String nomAnnulateur) {
this.nomAnnulateur = nomAnnulateur;
}
// Méthodes utilitaires
/**
* Vérifie si l'événement est en cours
*
* @return true si l'événement est actuellement en cours
*/
public boolean estEnCours() {
return StatutEvenement.EN_COURS.equals(statut);
}
/**
* Vérifie si l'événement est terminé
*
* @return true si l'événement est terminé
*/
public boolean estTermine() {
return StatutEvenement.TERMINE.equals(statut);
}
/**
* Vérifie si l'événement est annulé
*
* @return true si l'événement est annulé
*/
public boolean estAnnule() {
return StatutEvenement.ANNULE.equals(statut);
}
/**
* Vérifie si l'événement est complet (capacité atteinte)
*
* @return true si le nombre d'inscrits atteint la capacité maximale
*/
public boolean estComplet() {
return capaciteMax != null
&& participantsInscrits != null
&& participantsInscrits >= capaciteMax;
}
/**
* Calcule le nombre de places disponibles
*
* @return Le nombre de places restantes
*/
public int getPlacesDisponibles() {
if (capaciteMax == null || participantsInscrits == null) {
return 0;
}
return Math.max(0, capaciteMax - participantsInscrits);
}
/**
* Calcule le taux de remplissage en pourcentage
*
* @return Le pourcentage de remplissage (0-100)
*/
public int getTauxRemplissage() {
if (capaciteMax == null || capaciteMax == 0 || participantsInscrits == null) {
return 0;
}
return (participantsInscrits * 100) / capaciteMax;
}
/**
* Calcule le taux de présence en pourcentage
*
* @return Le pourcentage de présence (0-100)
*/
public int getTauxPresence() {
if (participantsInscrits == null || participantsInscrits == 0 || participantsPresents == null) {
return 0;
}
return (participantsPresents * 100) / participantsInscrits;
}
/**
* Vérifie si les inscriptions sont encore ouvertes
*
* @return true si les inscriptions sont ouvertes
*/
public boolean sontInscriptionsOuvertes() {
if (estAnnule() || estTermine()) {
return false;
}
if (dateLimiteInscription != null && LocalDate.now().isAfter(dateLimiteInscription)) {
return false;
}
return !estComplet();
}
/**
* Calcule la durée de l'événement en heures
*
* @return La durée en heures, ou 0 si non calculable
*/
public long getDureeEnHeures() {
if (heureDebut == null || heureFin == null) {
return 0;
}
return heureDebut.until(heureFin, java.time.temporal.ChronoUnit.HOURS);
}
/**
* Vérifie si l'événement dure plusieurs jours
*
* @return true si l'événement s'étend sur plusieurs jours
*/
public boolean estEvenementMultiJours() {
return dateFin != null && !dateDebut.equals(dateFin);
}
/**
* Retourne le libellé du type d'événement
*
* @return Le libellé du type
*/
public String getTypeEvenementLibelle() {
return typeEvenement != null ? typeEvenement.getLibelle() : "Non défini";
}
/**
* Retourne le libellé du statut
*
* @return Le libellé du statut
*/
public String getStatutLibelle() {
return statut != null ? statut.getLibelle() : "Non défini";
}
/**
* Retourne le libellé de la priorité
*
* @return Le libellé de la priorité
*/
public String getPrioriteLibelle() {
return priorite != null ? priorite.getLibelle() : "Normale";
}
/**
* Retourne l'adresse complète du lieu
*
* @return L'adresse complète formatée
*/
public String getAdresseComplete() {
StringBuilder adresseComplete = new StringBuilder();
if (lieu != null && !lieu.trim().isEmpty()) {
adresseComplete.append(lieu);
}
if (adresse != null && !adresse.trim().isEmpty()) {
if (adresseComplete.length() > 0) adresseComplete.append(", ");
adresseComplete.append(adresse);
}
if (ville != null && !ville.trim().isEmpty()) {
if (adresseComplete.length() > 0) adresseComplete.append(", ");
adresseComplete.append(ville);
}
if (region != null && !region.trim().isEmpty()) {
if (adresseComplete.length() > 0) adresseComplete.append(", ");
adresseComplete.append(region);
}
return adresseComplete.toString();
}
/**
* Vérifie si l'événement a des coordonnées GPS
*
* @return true si latitude et longitude sont définies
*/
public boolean hasCoordonnees() {
return latitude != null && longitude != null;
}
/**
* Calcule l'écart budgétaire
*
* @return La différence entre budget et coût réel (positif = économie, négatif = dépassement)
*/
public BigDecimal getEcartBudgetaire() {
if (budget == null || coutReel == null) {
return BigDecimal.ZERO;
}
return budget.subtract(coutReel);
}
/**
* Vérifie si le budget a été dépassé
*
* @return true si le coût réel dépasse le budget
*/
public boolean estBudgetDepasse() {
return getEcartBudgetaire().compareTo(BigDecimal.ZERO) < 0;
}
@Override
public String toString() {
return "EvenementDTO{"
+ "titre='"
+ titre
+ '\''
+ ", typeEvenement='"
+ typeEvenement
+ '\''
+ ", statut='"
+ statut
+ '\''
+ ", dateDebut="
+ dateDebut
+ ", lieu='"
+ lieu
+ '\''
+ ", participantsInscrits="
+ participantsInscrits
+ ", capaciteMax="
+ capaciteMax
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,250 @@
package dev.lions.unionflow.server.api.dto.finance;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des adhésions dans l'API UnionFlow
* Représente une demande d'adhésion d'un membre à une organisation
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-17
*/
@Getter
@Setter
public class AdhesionDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de référence unique de l'adhésion */
@NotBlank(message = "Le numéro de référence est obligatoire")
@Size(max = 50, message = "Le numéro de référence ne peut pas dépasser 50 caractères")
private String numeroReference;
/** Identifiant du membre */
@NotNull(message = "L'identifiant du membre est obligatoire")
private UUID membreId;
/** Numéro du membre (lecture seule) */
private String numeroMembre;
/** Nom complet du membre (lecture seule) */
private String nomMembre;
/** Email du membre (lecture seule) */
private String emailMembre;
/** Identifiant de l'organisation */
@NotNull(message = "L'identifiant de l'organisation est obligatoire")
private UUID organisationId;
/** Nom de l'organisation (lecture seule) */
private String nomOrganisation;
/** Date de la demande d'adhésion */
@NotNull(message = "La date de demande est obligatoire")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateDemande;
/** Frais d'adhésion */
@NotNull(message = "Les frais d'adhésion sont obligatoires")
@DecimalMin(value = "0.0", inclusive = false, message = "Les frais d'adhésion doivent être positifs")
@Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
private BigDecimal fraisAdhesion;
/** Montant payé */
@DecimalMin(value = "0.0", message = "Le montant payé ne peut pas être négatif")
@Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
private BigDecimal montantPaye;
/** Code devise (ISO 4217) */
@NotBlank(message = "Le code devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
private String codeDevise;
/** Statut de l'adhésion */
@NotBlank(message = "Le statut est obligatoire")
@Pattern(
regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$",
message = "Statut invalide")
private String statut;
/** Date d'approbation */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateApprobation;
/** Date de paiement */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime datePaiement;
/** Méthode de paiement */
@Pattern(
regexp = "^(ESPECES|VIREMENT|CHEQUE|WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$",
message = "Méthode de paiement invalide")
private String methodePaiement;
/** Référence de paiement */
@Size(max = 100, message = "La référence de paiement ne peut pas dépasser 100 caractères")
private String referencePaiement;
/** Motif de rejet */
@Size(max = 1000, message = "Le motif de rejet ne peut pas dépasser 1000 caractères")
private String motifRejet;
/** Observations */
@Size(max = 1000, message = "Les observations ne peuvent pas dépasser 1000 caractères")
private String observations;
/** Utilisateur ayant approuvé l'adhésion */
@Size(max = 255, message = "Le nom de l'approbateur ne peut pas dépasser 255 caractères")
private String approuvePar;
/** Date de validation */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateValidation;
// Méthodes utilitaires pour l'affichage
/** Vérifie si l'adhésion est payée intégralement */
public boolean isPayeeIntegralement() {
return montantPaye != null
&& fraisAdhesion != null
&& montantPaye.compareTo(fraisAdhesion) >= 0;
}
/** Vérifie si l'adhésion est en attente de paiement */
public boolean isEnAttentePaiement() {
return "APPROUVEE".equals(statut) && !isPayeeIntegralement();
}
/** Calcule le montant restant à payer */
public BigDecimal getMontantRestant() {
if (fraisAdhesion == null) return BigDecimal.ZERO;
if (montantPaye == null) return fraisAdhesion;
BigDecimal restant = fraisAdhesion.subtract(montantPaye);
return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
}
/** Calcule le pourcentage de paiement */
public int getPourcentagePaiement() {
if (fraisAdhesion == null || fraisAdhesion.compareTo(BigDecimal.ZERO) == 0) return 0;
if (montantPaye == null) return 0;
return montantPaye
.multiply(BigDecimal.valueOf(100))
.divide(fraisAdhesion, 0, java.math.RoundingMode.HALF_UP)
.intValue();
}
/** Calcule le nombre de jours depuis la demande */
public long getJoursDepuisDemande() {
if (dateDemande == null) return 0;
return ChronoUnit.DAYS.between(dateDemande, LocalDate.now());
}
/** Retourne le libellé du statut */
public String getStatutLibelle() {
if (statut == null) return "Non défini";
return switch (statut) {
case "EN_ATTENTE" -> "En attente";
case "APPROUVEE" -> "Approuvée";
case "REJETEE" -> "Rejetée";
case "ANNULEE" -> "Annulée";
case "EN_PAIEMENT" -> "En paiement";
case "PAYEE" -> "Payée";
default -> statut;
};
}
/** Retourne la sévérité du statut pour PrimeFaces */
public String getStatutSeverity() {
if (statut == null) return "secondary";
return switch (statut) {
case "APPROUVEE", "PAYEE" -> "success";
case "EN_ATTENTE", "EN_PAIEMENT" -> "warning";
case "REJETEE" -> "danger";
case "ANNULEE" -> "secondary";
default -> "secondary";
};
}
/** Retourne l'icône du statut pour PrimeFaces */
public String getStatutIcon() {
if (statut == null) return "pi-circle";
return switch (statut) {
case "APPROUVEE", "PAYEE" -> "pi-check";
case "EN_ATTENTE" -> "pi-clock";
case "EN_PAIEMENT" -> "pi-credit-card";
case "REJETEE" -> "pi-times";
case "ANNULEE" -> "pi-ban";
default -> "pi-circle";
};
}
/** Retourne le libellé de la méthode de paiement */
public String getMethodePaiementLibelle() {
if (methodePaiement == null) return "Non défini";
return switch (methodePaiement) {
case "ESPECES" -> "Espèces";
case "VIREMENT" -> "Virement bancaire";
case "CHEQUE" -> "Chèque";
case "WAVE_MONEY" -> "Wave Money";
case "ORANGE_MONEY" -> "Orange Money";
case "FREE_MONEY" -> "Free Money";
case "CARTE_BANCAIRE" -> "Carte bancaire";
default -> methodePaiement;
};
}
/** Formate la date de demande */
public String getDateDemandeFormatee() {
if (dateDemande == null) return "";
return dateDemande.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
}
/** Formate la date d'approbation */
public String getDateApprobationFormatee() {
if (dateApprobation == null) return "";
return dateApprobation.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
}
/** Formate la date de paiement */
public String getDatePaiementFormatee() {
if (datePaiement == null) return "";
return datePaiement.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"));
}
/** Formate les frais d'adhésion */
public String getFraisAdhesionFormatte() {
if (fraisAdhesion == null) return "0 FCFA";
return String.format("%,.0f FCFA", fraisAdhesion.doubleValue());
}
/** Formate le montant payé */
public String getMontantPayeFormatte() {
if (montantPaye == null) return "0 FCFA";
return String.format("%,.0f FCFA", montantPaye.doubleValue());
}
/** Formate le montant restant */
public String getMontantRestantFormatte() {
return String.format("%,.0f FCFA", getMontantRestant().doubleValue());
}
}

View File

@@ -0,0 +1,574 @@
package dev.lions.unionflow.server.api.dto.finance;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
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.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des cotisations dans l'API UnionFlow Représente une cotisation d'un membre à
* son organisation
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class CotisationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de référence unique de la cotisation */
@NotBlank(message = "Le numéro de référence est obligatoire")
@Size(max = 50, message = "Le numéro de référence ne peut pas dépasser 50 caractères")
private String numeroReference;
/** Identifiant du membre */
@NotNull(message = "L'identifiant du membre est obligatoire")
private UUID membreId;
/** Numéro du membre (lecture seule) */
private String numeroMembre;
/** Nom complet du membre (lecture seule) */
private String nomMembre;
/** Identifiant de l'association */
@NotNull(message = "L'identifiant de l'association est obligatoire")
private UUID associationId;
/** Nom de l'association (lecture seule) */
private String nomAssociation;
/** Type de cotisation */
@NotNull(message = "Le type de cotisation est obligatoire")
@Pattern(
regexp = "^(MENSUELLE|TRIMESTRIELLE|SEMESTRIELLE|ANNUELLE|EXCEPTIONNELLE|ADHESION)$",
message = "Type de cotisation invalide")
private String typeCotisation;
/** Libellé de la cotisation */
@NotBlank(message = "Le libellé est obligatoire")
@Size(max = 100, message = "Le libellé ne peut pas dépasser 100 caractères")
private String libelle;
/** Description détaillée */
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
/** Montant dû */
@NotNull(message = "Le montant dû est obligatoire")
@DecimalMin(value = "0.0", inclusive = false, message = "Le montant dû doit être positif")
@Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
private BigDecimal montantDu;
/** Montant payé */
@DecimalMin(value = "0.0", message = "Le montant payé ne peut pas être négatif")
@Digits(integer = 10, fraction = 2, message = "Format de montant invalide")
private BigDecimal montantPaye;
/** Code de la devise */
@NotBlank(message = "Le code devise est obligatoire")
@Size(min = 3, max = 3, message = "Le code devise doit faire exactement 3 caractères")
private String codeDevise;
/** Statut de la cotisation */
@NotNull(message = "Le statut est obligatoire")
@Pattern(
regexp = "^(EN_ATTENTE|PAYEE|PARTIELLEMENT_PAYEE|EN_RETARD|ANNULEE|REMBOURSEE)$",
message = "Statut invalide")
private String statut;
/** Date d'échéance */
@JsonFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "La date d'échéance est obligatoire")
private LocalDate dateEcheance;
/** Date de paiement */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime datePaiement;
/** Méthode de paiement */
@Pattern(
regexp = "^(ESPECES|VIREMENT|CHEQUE|WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$",
message = "Méthode de paiement invalide")
private String methodePaiement;
/** Référence du paiement (numéro de transaction, chèque, etc.) */
@Size(max = 100, message = "La référence de paiement ne peut pas dépasser 100 caractères")
private String referencePaiement;
/** Période concernée (ex: "Janvier 2025", "Q1 2025") */
@Size(max = 50, message = "La période ne peut pas dépasser 50 caractères")
private String periode;
/** Année de la cotisation */
@Min(value = 2020, message = "L'année doit être supérieure à 2020")
@Max(value = 2050, message = "L'année doit être inférieure à 2050")
private Integer annee;
/** Mois de la cotisation (1-12) */
@Min(value = 1, message = "Le mois doit être entre 1 et 12")
@Max(value = 12, message = "Le mois doit être entre 1 et 12")
private Integer mois;
/** Observations ou commentaires */
@Size(max = 500, message = "Les observations ne peuvent pas dépasser 500 caractères")
private String observations;
/** Indique si la cotisation est récurrente */
private Boolean recurrente = false;
/** Nombre de rappels envoyés */
@Min(value = 0, message = "Le nombre de rappels ne peut pas être négatif")
private Integer nombreRappels = 0;
/** Date du dernier rappel */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDernierRappel;
/** Identifiant de l'utilisateur qui a validé le paiement */
private UUID validePar;
/** Nom de l'utilisateur qui a validé le paiement */
private String nomValidateur;
// Constructeurs
public CotisationDTO() {
super();
this.montantPaye = BigDecimal.ZERO;
this.codeDevise = "XOF"; // Franc CFA par défaut
this.statut = "EN_ATTENTE";
this.recurrente = false;
this.nombreRappels = 0;
this.annee = LocalDate.now().getYear();
}
public CotisationDTO(
UUID membreId, String typeCotisation, BigDecimal montantDu, LocalDate dateEcheance) {
this();
this.membreId = membreId;
this.typeCotisation = typeCotisation;
this.montantDu = montantDu;
this.dateEcheance = dateEcheance;
this.numeroReference = genererNumeroReference();
}
// Getters et Setters
public String getNumeroReference() {
return numeroReference;
}
public void setNumeroReference(String numeroReference) {
this.numeroReference = numeroReference;
}
public UUID getMembreId() {
return membreId;
}
public void setMembreId(UUID membreId) {
this.membreId = membreId;
}
public String getNumeroMembre() {
return numeroMembre;
}
public void setNumeroMembre(String numeroMembre) {
this.numeroMembre = numeroMembre;
}
public String getNomMembre() {
return nomMembre;
}
public void setNomMembre(String nomMembre) {
this.nomMembre = nomMembre;
}
public UUID getAssociationId() {
return associationId;
}
public void setAssociationId(UUID associationId) {
this.associationId = associationId;
}
public String getNomAssociation() {
return nomAssociation;
}
public void setNomAssociation(String nomAssociation) {
this.nomAssociation = nomAssociation;
}
public String getTypeCotisation() {
return typeCotisation;
}
public void setTypeCotisation(String typeCotisation) {
this.typeCotisation = typeCotisation;
}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public BigDecimal getMontantDu() {
return montantDu;
}
public void setMontantDu(BigDecimal montantDu) {
this.montantDu = montantDu;
}
public BigDecimal getMontantPaye() {
return montantPaye;
}
public void setMontantPaye(BigDecimal montantPaye) {
this.montantPaye = montantPaye;
}
public String getCodeDevise() {
return codeDevise;
}
public void setCodeDevise(String codeDevise) {
this.codeDevise = codeDevise;
}
public String getStatut() {
return statut;
}
public void setStatut(String statut) {
this.statut = statut;
}
public LocalDate getDateEcheance() {
return dateEcheance;
}
public void setDateEcheance(LocalDate dateEcheance) {
this.dateEcheance = dateEcheance;
}
public LocalDateTime getDatePaiement() {
return datePaiement;
}
public void setDatePaiement(LocalDateTime datePaiement) {
this.datePaiement = datePaiement;
}
public String getMethodePaiement() {
return methodePaiement;
}
public void setMethodePaiement(String methodePaiement) {
this.methodePaiement = methodePaiement;
}
public String getReferencePaiement() {
return referencePaiement;
}
public void setReferencePaiement(String referencePaiement) {
this.referencePaiement = referencePaiement;
}
public String getPeriode() {
return periode;
}
public void setPeriode(String periode) {
this.periode = periode;
}
public Integer getAnnee() {
return annee;
}
public void setAnnee(Integer annee) {
this.annee = annee;
}
public Integer getMois() {
return mois;
}
public void setMois(Integer mois) {
this.mois = mois;
}
public String getObservations() {
return observations;
}
public void setObservations(String observations) {
this.observations = observations;
}
public Boolean getRecurrente() {
return recurrente;
}
public void setRecurrente(Boolean recurrente) {
this.recurrente = recurrente;
}
public Integer getNombreRappels() {
return nombreRappels;
}
public void setNombreRappels(Integer nombreRappels) {
this.nombreRappels = nombreRappels;
}
public LocalDateTime getDateDernierRappel() {
return dateDernierRappel;
}
public void setDateDernierRappel(LocalDateTime dateDernierRappel) {
this.dateDernierRappel = dateDernierRappel;
}
public UUID getValidePar() {
return validePar;
}
public void setValidePar(UUID validePar) {
this.validePar = validePar;
}
public String getNomValidateur() {
return nomValidateur;
}
public void setNomValidateur(String nomValidateur) {
this.nomValidateur = nomValidateur;
}
// Méthodes utilitaires
/**
* Génère un numéro de référence unique pour la cotisation
*
* @return Le numéro de référence généré
*/
private String genererNumeroReference() {
return "COT-"
+ LocalDate.now().getYear()
+ "-"
+ String.format("%06d", System.currentTimeMillis() % 1000000);
}
/**
* Vérifie si la cotisation est payée intégralement
*
* @return true si le montant payé égale le montant dû
*/
public boolean isPayeeIntegralement() {
return montantPaye != null && montantDu != null && montantPaye.compareTo(montantDu) >= 0;
}
/**
* Vérifie si la cotisation est en retard
*
* @return true si la date d'échéance est dépassée et non payée
*/
public boolean isEnRetard() {
return dateEcheance != null && LocalDate.now().isAfter(dateEcheance) && !isPayeeIntegralement();
}
/**
* Calcule le montant restant à payer
*
* @return Le montant restant
*/
public BigDecimal getMontantRestant() {
if (montantDu == null) return BigDecimal.ZERO;
if (montantPaye == null) return montantDu;
BigDecimal restant = montantDu.subtract(montantPaye);
return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
}
/**
* Calcule le pourcentage de paiement
*
* @return Le pourcentage payé (0-100)
*/
public int getPourcentagePaiement() {
if (montantDu == null || montantDu.compareTo(BigDecimal.ZERO) == 0) {
return 0;
}
if (montantPaye == null) {
return 0;
}
return montantPaye
.multiply(BigDecimal.valueOf(100))
.divide(montantDu, 0, java.math.RoundingMode.HALF_UP)
.intValue();
}
/**
* Calcule le nombre de jours de retard
*
* @return Le nombre de jours de retard, 0 si pas en retard
*/
public long getJoursRetard() {
if (dateEcheance == null || !isEnRetard()) {
return 0;
}
return ChronoUnit.DAYS.between(dateEcheance, LocalDate.now());
}
/**
* Retourne le libellé du type de cotisation
*
* @return Le libellé du type
*/
public String getTypeCotisationLibelle() {
if (typeCotisation == null) return "Non défini";
return switch (typeCotisation) {
case "MENSUELLE" -> "Mensuelle";
case "TRIMESTRIELLE" -> "Trimestrielle";
case "SEMESTRIELLE" -> "Semestrielle";
case "ANNUELLE" -> "Annuelle";
case "EXCEPTIONNELLE" -> "Exceptionnelle";
case "ADHESION" -> "Adhésion";
default -> typeCotisation;
};
}
/**
* Retourne le libellé du statut
*
* @return Le libellé du statut
*/
public String getStatutLibelle() {
if (statut == null) return "Non défini";
return switch (statut) {
case "EN_ATTENTE" -> "En attente";
case "PAYEE" -> "Payée";
case "PARTIELLEMENT_PAYEE" -> "Partiellement payée";
case "EN_RETARD" -> "En retard";
case "ANNULEE" -> "Annulée";
case "REMBOURSEE" -> "Remboursée";
default -> statut;
};
}
/**
* Retourne le libellé de la méthode de paiement
*
* @return Le libellé de la méthode
*/
public String getMethodePaiementLibelle() {
if (methodePaiement == null) return "Non défini";
return switch (methodePaiement) {
case "ESPECES" -> "Espèces";
case "VIREMENT" -> "Virement bancaire";
case "CHEQUE" -> "Chèque";
case "WAVE_MONEY" -> "Wave Money";
case "ORANGE_MONEY" -> "Orange Money";
case "FREE_MONEY" -> "Free Money";
case "CARTE_BANCAIRE" -> "Carte bancaire";
default -> methodePaiement;
};
}
/** Met à jour le statut en fonction du montant payé */
public void mettreAJourStatut() {
if (montantPaye == null || montantPaye.compareTo(BigDecimal.ZERO) == 0) {
if (isEnRetard()) {
this.statut = "EN_RETARD";
} else {
this.statut = "EN_ATTENTE";
}
} else if (isPayeeIntegralement()) {
this.statut = "PAYEE";
if (this.datePaiement == null) {
this.datePaiement = LocalDateTime.now();
}
} else {
this.statut = "PARTIELLEMENT_PAYEE";
}
}
/**
* Marque la cotisation comme payée
*
* @param montant Le montant payé
* @param methode La méthode de paiement
* @param reference La référence du paiement
*/
public void marquerCommePaye(BigDecimal montant, String methode, String reference) {
this.montantPaye = montant;
this.methodePaiement = methode;
this.referencePaiement = reference;
this.datePaiement = LocalDateTime.now();
mettreAJourStatut();
}
@Override
public String toString() {
return "CotisationDTO{"
+ "numeroReference='"
+ numeroReference
+ '\''
+ ", nomMembre='"
+ nomMembre
+ '\''
+ ", typeCotisation='"
+ typeCotisation
+ '\''
+ ", montantDu="
+ montantDu
+ ", montantPaye="
+ montantPaye
+ ", statut='"
+ statut
+ '\''
+ ", dateEcheance="
+ dateEcheance
+ ", periode='"
+ periode
+ '\''
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,704 @@
package dev.lions.unionflow.server.api.dto.formuleabonnement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
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 lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des formules d'abonnement UnionFlow Représente les différents plans/formules
* d'abonnement disponibles dans le catalogue
*
* <p>Distinction importante : - FormuleAbonnementDTO = Plans disponibles (BASIC, PREMIUM, etc.) -
* CATALOGUE - AbonnementDTO = Souscription effective d'une organisation - INSTANCE
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class FormuleAbonnementDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Types de formules disponibles */
public enum TypeFormule {
BASIC("Formule Basique"),
STANDARD("Formule Standard"),
PREMIUM("Formule Premium"),
ENTERPRISE("Formule Entreprise");
private final String libelle;
TypeFormule(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Statuts des formules */
public enum StatutFormule {
ACTIVE("Active"),
INACTIVE("Inactive"),
ARCHIVEE("Archivée"),
BIENTOT_DISPONIBLE("Bientôt Disponible");
private final String libelle;
StatutFormule(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Nom de la formule */
@NotBlank(message = "Le nom de la formule est obligatoire")
@Size(
min = 2,
max = 100,
message = "Le nom de la formule doit contenir entre 2 et 100 caractères")
private String nom;
/** Code unique de la formule */
@NotBlank(message = "Le code de la formule est obligatoire")
@Pattern(
regexp = "^[A-Z_]{2,20}$",
message = "Le code doit contenir uniquement des lettres majuscules et underscores")
private String code;
/** Description détaillée de la formule */
@Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères")
private String description;
/** Type de formule (enum) */
@NotNull(message = "Le type de formule est obligatoire")
private TypeFormule type;
/** Statut de la formule (enum) */
@NotNull(message = "Le statut est obligatoire")
private StatutFormule statut;
/** Prix mensuel de la formule */
@NotNull(message = "Le prix mensuel est obligatoire")
@DecimalMin(value = "0.0", inclusive = false, message = "Le prix mensuel doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le prix mensuel ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal prixMensuel;
/** Prix annuel de la formule (avec remise éventuelle) */
@DecimalMin(value = "0.0", inclusive = false, message = "Le prix annuel doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le prix annuel ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal prixAnnuel;
/** Devise utilisée */
@NotBlank(message = "La devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String devise = "XOF";
/** Nombre maximum de membres autorisés */
@NotNull(message = "Le nombre maximum de membres est obligatoire")
private Integer maxMembres;
/** Nombre maximum d'administrateurs autorisés */
@NotNull(message = "Le nombre maximum d'administrateurs est obligatoire")
private Integer maxAdministrateurs;
/** Espace de stockage alloué en GB */
@NotNull(message = "L'espace de stockage est obligatoire")
@DecimalMin(value = "0.1", message = "L'espace de stockage doit être d'au moins 0.1 GB")
private BigDecimal espaceStockageGB;
/** Support technique inclus */
@NotNull(message = "Le support technique doit être spécifié")
private Boolean supportTechnique;
/** Niveau de support */
@Pattern(
regexp = "^(EMAIL|CHAT|TELEPHONE|PREMIUM)$",
message = "Le niveau de support doit être EMAIL, CHAT, TELEPHONE ou PREMIUM")
private String niveauSupport;
/** Fonctionnalités avancées incluses */
private Boolean fonctionnalitesAvancees;
/** Accès API autorisé */
private Boolean apiAccess;
/** Rapports personnalisés autorisés */
private Boolean rapportsPersonnalises;
/** Intégrations tierces autorisées */
private Boolean integrationsTierces;
/** Sauvegarde automatique incluse */
private Boolean sauvegardeAutomatique;
/** Support multi-langues */
private Boolean multiLangues;
/** Personnalisation de l'interface */
private Boolean personnalisationInterface;
/** Formation incluse */
private Boolean formationIncluse;
/** Nombre d'heures de formation incluses */
private Integer heuresFormation;
/** Formule populaire (mise en avant) */
private Boolean populaire;
/** Formule recommandée */
private Boolean recommandee;
/** Période d'essai gratuite en jours */
private Integer periodeEssaiJours;
/** Date de début de validité de la formule */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateDebutValidite;
/** Date de fin de validité de la formule */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFinValidite;
/** Ordre d'affichage dans le catalogue */
private Integer ordreAffichage;
/** Couleur associée à la formule (code hexadécimal) */
@Pattern(
regexp = "^#[0-9A-Fa-f]{6}$",
message = "La couleur doit être un code hexadécimal valide")
private String couleur;
/** Icône associée à la formule */
@Size(max = 50, message = "Le nom de l'icône ne peut pas dépasser 50 caractères")
private String icone;
/** Notes administratives */
@Size(max = 500, message = "Les notes ne peuvent pas dépasser 500 caractères")
private String notes;
// Constructeurs
public FormuleAbonnementDTO() {
super();
this.devise = "XOF";
this.statut = StatutFormule.ACTIVE;
this.type = TypeFormule.BASIC;
this.supportTechnique = true;
this.fonctionnalitesAvancees = false;
this.apiAccess = false;
this.rapportsPersonnalises = false;
this.integrationsTierces = false;
this.sauvegardeAutomatique = true;
this.multiLangues = false;
this.personnalisationInterface = false;
this.formationIncluse = false;
this.populaire = false;
this.recommandee = false;
this.periodeEssaiJours = 0;
this.heuresFormation = 0;
this.ordreAffichage = 1;
}
public FormuleAbonnementDTO(String nom, String code, TypeFormule type, BigDecimal prixMensuel) {
this();
this.nom = nom;
this.code = code;
this.type = type;
this.prixMensuel = prixMensuel;
}
// Getters et Setters
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public TypeFormule getType() {
return type;
}
public void setType(TypeFormule type) {
this.type = type;
}
public StatutFormule getStatut() {
return statut;
}
public void setStatut(StatutFormule statut) {
this.statut = statut;
}
public BigDecimal getPrixMensuel() {
return prixMensuel;
}
public void setPrixMensuel(BigDecimal prixMensuel) {
this.prixMensuel = prixMensuel;
}
public BigDecimal getPrixAnnuel() {
return prixAnnuel;
}
public void setPrixAnnuel(BigDecimal prixAnnuel) {
this.prixAnnuel = prixAnnuel;
}
public String getDevise() {
return devise;
}
public void setDevise(String devise) {
this.devise = devise;
}
public Integer getMaxMembres() {
return maxMembres;
}
public void setMaxMembres(Integer maxMembres) {
this.maxMembres = maxMembres;
}
public Integer getMaxAdministrateurs() {
return maxAdministrateurs;
}
public void setMaxAdministrateurs(Integer maxAdministrateurs) {
this.maxAdministrateurs = maxAdministrateurs;
}
public BigDecimal getEspaceStockageGB() {
return espaceStockageGB;
}
public void setEspaceStockageGB(BigDecimal espaceStockageGB) {
this.espaceStockageGB = espaceStockageGB;
}
public Boolean getSupportTechnique() {
return supportTechnique;
}
public void setSupportTechnique(Boolean supportTechnique) {
this.supportTechnique = supportTechnique;
}
public String getNiveauSupport() {
return niveauSupport;
}
public void setNiveauSupport(String niveauSupport) {
this.niveauSupport = niveauSupport;
}
public Boolean getFonctionnalitesAvancees() {
return fonctionnalitesAvancees;
}
public void setFonctionnalitesAvancees(Boolean fonctionnalitesAvancees) {
this.fonctionnalitesAvancees = fonctionnalitesAvancees;
}
public Boolean getApiAccess() {
return apiAccess;
}
public void setApiAccess(Boolean apiAccess) {
this.apiAccess = apiAccess;
}
public Boolean getRapportsPersonnalises() {
return rapportsPersonnalises;
}
public void setRapportsPersonnalises(Boolean rapportsPersonnalises) {
this.rapportsPersonnalises = rapportsPersonnalises;
}
public Boolean getIntegrationsTierces() {
return integrationsTierces;
}
public void setIntegrationsTierces(Boolean integrationsTierces) {
this.integrationsTierces = integrationsTierces;
}
public Boolean getSauvegardeAutomatique() {
return sauvegardeAutomatique;
}
public void setSauvegardeAutomatique(Boolean sauvegardeAutomatique) {
this.sauvegardeAutomatique = sauvegardeAutomatique;
}
public Boolean getMultiLangues() {
return multiLangues;
}
public void setMultiLangues(Boolean multiLangues) {
this.multiLangues = multiLangues;
}
public Boolean getPersonnalisationInterface() {
return personnalisationInterface;
}
public void setPersonnalisationInterface(Boolean personnalisationInterface) {
this.personnalisationInterface = personnalisationInterface;
}
public Boolean getFormationIncluse() {
return formationIncluse;
}
public void setFormationIncluse(Boolean formationIncluse) {
this.formationIncluse = formationIncluse;
}
public Integer getHeuresFormation() {
return heuresFormation;
}
public void setHeuresFormation(Integer heuresFormation) {
this.heuresFormation = heuresFormation;
}
public Boolean getPopulaire() {
return populaire;
}
public void setPopulaire(Boolean populaire) {
this.populaire = populaire;
}
public Boolean getRecommandee() {
return recommandee;
}
public void setRecommandee(Boolean recommandee) {
this.recommandee = recommandee;
}
public Integer getPeriodeEssaiJours() {
return periodeEssaiJours;
}
public void setPeriodeEssaiJours(Integer periodeEssaiJours) {
this.periodeEssaiJours = periodeEssaiJours;
}
public LocalDate getDateDebutValidite() {
return dateDebutValidite;
}
public void setDateDebutValidite(LocalDate dateDebutValidite) {
this.dateDebutValidite = dateDebutValidite;
}
public LocalDate getDateFinValidite() {
return dateFinValidite;
}
public void setDateFinValidite(LocalDate dateFinValidite) {
this.dateFinValidite = dateFinValidite;
}
public Integer getOrdreAffichage() {
return ordreAffichage;
}
public void setOrdreAffichage(Integer ordreAffichage) {
this.ordreAffichage = ordreAffichage;
}
public String getCouleur() {
return couleur;
}
public void setCouleur(String couleur) {
this.couleur = couleur;
}
public String getIcone() {
return icone;
}
public void setIcone(String icone) {
this.icone = icone;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
// Méthodes utilitaires
/**
* Vérifie si la formule est active
*
* @return true si la formule est active
*/
public boolean isActive() {
return StatutFormule.ACTIVE.equals(statut);
}
/**
* Vérifie si la formule est inactive
*
* @return true si la formule est inactive
*/
public boolean isInactive() {
return StatutFormule.INACTIVE.equals(statut);
}
/**
* Vérifie si la formule est archivée
*
* @return true si la formule est archivée
*/
public boolean isArchivee() {
return StatutFormule.ARCHIVEE.equals(statut);
}
/**
* Vérifie si la formule est actuellement valide
*
* @return true si la formule est dans sa période de validité
*/
public boolean isValide() {
if (!isActive()) return false;
LocalDate aujourd = LocalDate.now();
if (dateDebutValidite != null && aujourd.isBefore(dateDebutValidite)) {
return false;
}
if (dateFinValidite != null && aujourd.isAfter(dateFinValidite)) {
return false;
}
return true;
}
/**
* Calcule l'économie annuelle par rapport au paiement mensuel
*
* @return L'économie réalisée en payant annuellement
*/
public BigDecimal getEconomieAnnuelle() {
if (prixMensuel == null || prixAnnuel == null) {
return BigDecimal.ZERO;
}
BigDecimal coutMensuelAnnuel = prixMensuel.multiply(BigDecimal.valueOf(12));
return coutMensuelAnnuel.subtract(prixAnnuel);
}
/**
* Calcule le pourcentage d'économie annuelle
*
* @return Le pourcentage d'économie (0-100)
*/
public int getPourcentageEconomieAnnuelle() {
if (prixMensuel == null || prixAnnuel == null) return 0;
BigDecimal coutMensuelAnnuel = prixMensuel.multiply(BigDecimal.valueOf(12));
if (coutMensuelAnnuel.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal economie = getEconomieAnnuelle();
return economie
.multiply(BigDecimal.valueOf(100))
.divide(coutMensuelAnnuel, 0, java.math.RoundingMode.HALF_UP)
.intValue();
}
return 0;
}
/**
* Vérifie si la formule a une période d'essai
*
* @return true si une période d'essai est disponible
*/
public boolean hasPeriodeEssai() {
return periodeEssaiJours != null && periodeEssaiJours > 0;
}
/**
* Vérifie si la formule inclut la formation
*
* @return true si la formation est incluse
*/
public boolean hasFormation() {
return Boolean.TRUE.equals(formationIncluse) && heuresFormation != null && heuresFormation > 0;
}
/**
* Vérifie si la formule est recommandée ou populaire
*
* @return true si la formule est mise en avant
*/
public boolean isMiseEnAvant() {
return Boolean.TRUE.equals(populaire) || Boolean.TRUE.equals(recommandee);
}
/**
* Retourne le badge à afficher pour la formule
*
* @return Le texte du badge ou null
*/
public String getBadge() {
if (Boolean.TRUE.equals(populaire)) return "POPULAIRE";
if (Boolean.TRUE.equals(recommandee)) return "RECOMMANDÉE";
if (hasPeriodeEssai()) return "ESSAI GRATUIT";
return null;
}
/**
* Calcule le score de fonctionnalités (0-100)
*
* @return Le score basé sur les fonctionnalités incluses
*/
public int getScoreFonctionnalites() {
int score = 0;
int total = 0;
// Fonctionnalités de base (poids 1)
if (Boolean.TRUE.equals(supportTechnique)) score += 10;
total += 10;
if (Boolean.TRUE.equals(sauvegardeAutomatique)) score += 10;
total += 10;
// Fonctionnalités avancées (poids 2)
if (Boolean.TRUE.equals(fonctionnalitesAvancees)) score += 15;
total += 15;
if (Boolean.TRUE.equals(apiAccess)) score += 15;
total += 15;
if (Boolean.TRUE.equals(rapportsPersonnalises)) score += 15;
total += 15;
if (Boolean.TRUE.equals(integrationsTierces)) score += 15;
total += 15;
// Fonctionnalités premium (poids 3)
if (Boolean.TRUE.equals(multiLangues)) score += 10;
total += 10;
if (Boolean.TRUE.equals(personnalisationInterface)) score += 10;
total += 10;
// total est toujours > 0 car il est toujours incrémenté (minimum 100)
return (score * 100) / total;
}
/**
* Détermine la classe CSS pour la couleur de la formule
*
* @return La classe CSS appropriée
*/
public String getCssClass() {
if (type == null) return "formule-default";
return switch (type) {
case BASIC -> "formule-basic";
case STANDARD -> "formule-standard";
case PREMIUM -> "formule-premium";
case ENTERPRISE -> "formule-enterprise";
};
}
/** Active la formule */
public void activer() {
this.statut = StatutFormule.ACTIVE;
marquerCommeModifie("SYSTEM");
}
/** Désactive la formule */
public void desactiver() {
this.statut = StatutFormule.INACTIVE;
marquerCommeModifie("SYSTEM");
}
/** Archive la formule */
public void archiver() {
this.statut = StatutFormule.ARCHIVEE;
marquerCommeModifie("SYSTEM");
}
@Override
public String toString() {
return "FormuleAbonnementDTO{"
+ "nom='"
+ nom
+ '\''
+ ", code='"
+ code
+ '\''
+ ", type="
+ type
+ ", prixMensuel="
+ prixMensuel
+ ", prixAnnuel="
+ prixAnnuel
+ ", devise='"
+ devise
+ '\''
+ ", statut="
+ statut
+ ", populaire="
+ populaire
+ ", recommandee="
+ recommandee
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,462 @@
package dev.lions.unionflow.server.api.dto.membre;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des membres dans l'API UnionFlow Contient toutes les informations relatives à
* un membre d'une organisation
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class MembreDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/**
* Numéro unique du membre (format: UF-YYYY-XXXXXXXX)
* AUTO-GÉNÉRÉ si non fourni - Ne pas afficher dans le formulaire de création
*/
@Size(max = 50, message = "Le numéro de membre ne peut pas dépasser 50 caractères")
private String numeroMembre;
/** Nom de famille du membre - OBLIGATOIRE */
@NotBlank(message = "Le nom" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.NOM_PRENOM_MIN_LENGTH,
max = ValidationConstants.NOM_PRENOM_MAX_LENGTH,
message = ValidationConstants.NOM_SIZE_MESSAGE)
@Pattern(
regexp = "^[a-zA-ZÀ-ÿ\\s\\-']+$",
message = "Le nom ne peut contenir que des lettres, espaces, tirets et apostrophes")
private String nom;
/** Prénom du membre - OBLIGATOIRE */
@NotBlank(message = "Le prénom" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.NOM_PRENOM_MIN_LENGTH,
max = ValidationConstants.NOM_PRENOM_MAX_LENGTH,
message = ValidationConstants.PRENOM_SIZE_MESSAGE)
@Pattern(
regexp = "^[a-zA-ZÀ-ÿ\\s\\-']+$",
message = "Le prénom ne peut contenir que des lettres, espaces, tirets et apostrophes")
private String prenom;
/** Adresse email du membre - OBLIGATOIRE */
@NotBlank(message = "L'email" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Email(message = ValidationConstants.EMAIL_FORMAT_MESSAGE)
@Size(
max = ValidationConstants.EMAIL_MAX_LENGTH,
message = ValidationConstants.EMAIL_SIZE_MESSAGE)
private String email;
/** Numéro de téléphone du membre - Optionnel, format flexible */
@Size(max = 20, message = "Le téléphone ne peut pas dépasser 20 caractères")
private String telephone;
/**
* Date de naissance du membre - OPTIONNELLE
* AUTO-GÉNÉRÉE à il y a 18 ans si non fournie - Peut être laissée vide dans le formulaire
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@Past(message = "La date de naissance doit être dans le passé")
private LocalDate dateNaissance;
/** Adresse physique du membre */
@Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
private String adresse;
/** Profession du membre */
@Size(max = 100, message = "La profession ne peut pas dépasser 100 caractères")
private String profession;
/** Statut matrimonial du membre */
@Size(max = 20, message = "Le statut matrimonial ne peut pas dépasser 20 caractères")
private String statutMatrimonial;
/** Nationalité du membre */
@Size(max = 50, message = "La nationalité ne peut pas dépasser 50 caractères")
private String nationalite;
/** Numéro de pièce d'identité */
@Size(max = 50, message = "Le numéro d'identité ne peut pas dépasser 50 caractères")
private String numeroIdentite;
/** Type de pièce d'identité (CNI, Passeport, etc.) */
@Size(max = 20, message = "Le type d'identité ne peut pas dépasser 20 caractères")
private String typeIdentite;
/** Statut du membre - OBLIGATOIRE */
@NotNull(message = "Le statut est obligatoire")
private StatutMembre statut;
/** Identifiant de l'association à laquelle appartient le membre - OBLIGATOIRE */
@NotNull(message = "L'association est obligatoire")
private UUID associationId;
/** Nom de l'association (lecture seule) */
private String associationNom;
/**
* Date d'adhésion du membre - OPTIONNELLE
* AUTO-GÉNÉRÉE à LocalDate.now() si non fournie - Ne pas afficher dans le formulaire de création
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateAdhesion;
/** Région géographique */
@Size(max = 50, message = "La région ne peut pas dépasser 50 caractères")
private String region;
/** Ville de résidence */
@Size(max = 50, message = "La ville ne peut pas dépasser 50 caractères")
private String ville;
/** Quartier de résidence */
@Size(max = 50, message = "Le quartier ne peut pas dépasser 50 caractères")
private String quartier;
/** Rôle du membre dans l'organisation */
@Size(max = 50, message = "Le rôle ne peut pas dépasser 50 caractères")
private String role;
/** Indique si le membre fait partie du bureau */
private Boolean membreBureau = false;
/** Indique si le membre est responsable */
private Boolean responsable = false;
/** Photo de profil (URL ou chemin) */
@Size(max = 255, message = "L'URL de la photo ne peut pas dépasser 255 caractères")
private String photoUrl;
// Constructeurs
public MembreDTO() {
super();
this.statut = StatutMembre.ACTIF;
this.dateAdhesion = LocalDate.now();
this.membreBureau = false;
this.responsable = false;
}
public MembreDTO(String numeroMembre, String nom, String prenom, String email) {
this();
this.numeroMembre = numeroMembre;
this.nom = nom;
this.prenom = prenom;
this.email = email;
}
// Getters et Setters
public String getNumeroMembre() {
return numeroMembre;
}
public void setNumeroMembre(String numeroMembre) {
this.numeroMembre = numeroMembre;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public LocalDate getDateNaissance() {
return dateNaissance;
}
public void setDateNaissance(LocalDate dateNaissance) {
this.dateNaissance = dateNaissance;
}
public String getAdresse() {
return adresse;
}
public void setAdresse(String adresse) {
this.adresse = adresse;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
public String getStatutMatrimonial() {
return statutMatrimonial;
}
public void setStatutMatrimonial(String statutMatrimonial) {
this.statutMatrimonial = statutMatrimonial;
}
public String getNationalite() {
return nationalite;
}
public void setNationalite(String nationalite) {
this.nationalite = nationalite;
}
public String getNumeroIdentite() {
return numeroIdentite;
}
public void setNumeroIdentite(String numeroIdentite) {
this.numeroIdentite = numeroIdentite;
}
public String getTypeIdentite() {
return typeIdentite;
}
public void setTypeIdentite(String typeIdentite) {
this.typeIdentite = typeIdentite;
}
public StatutMembre getStatut() {
return statut;
}
public void setStatut(StatutMembre statut) {
this.statut = statut;
}
public UUID getAssociationId() {
return associationId;
}
public void setAssociationId(UUID associationId) {
this.associationId = associationId;
}
public String getAssociationNom() {
return associationNom;
}
public void setAssociationNom(String associationNom) {
this.associationNom = associationNom;
}
public LocalDate getDateAdhesion() {
return dateAdhesion;
}
public void setDateAdhesion(LocalDate dateAdhesion) {
this.dateAdhesion = dateAdhesion;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getVille() {
return ville;
}
public void setVille(String ville) {
this.ville = ville;
}
public String getQuartier() {
return quartier;
}
public void setQuartier(String quartier) {
this.quartier = quartier;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Boolean getMembreBureau() {
return membreBureau;
}
public void setMembreBureau(Boolean membreBureau) {
this.membreBureau = membreBureau;
}
public Boolean getResponsable() {
return responsable;
}
public void setResponsable(Boolean responsable) {
this.responsable = responsable;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
// Méthodes utilitaires
/**
* Retourne le nom complet du membre (prénom + nom)
*
* @return Le nom complet
*/
public String getNomComplet() {
if (prenom != null && nom != null) {
return prenom + " " + nom;
} else if (nom != null) {
return nom;
} else if (prenom != null) {
return prenom;
}
return "";
}
/**
* Vérifie si le membre est majeur (18 ans ou plus)
*
* @return true si le membre est majeur, false sinon
*/
public boolean estMajeur() {
if (dateNaissance == null) {
return false;
}
return LocalDate.now().minusYears(18).isAfter(dateNaissance)
|| LocalDate.now().minusYears(18).isEqual(dateNaissance);
}
/**
* Calcule l'âge du membre
*
* @return L'âge en années, ou -1 si la date de naissance n'est pas définie
*/
public int getAge() {
if (dateNaissance == null) {
return -1;
}
return LocalDate.now().getYear() - dateNaissance.getYear();
}
/**
* Vérifie si le membre est actif
*
* @return true si le statut est ACTIF
*/
public boolean estActif() {
return StatutMembre.ACTIF.equals(statut);
}
/**
* Vérifie si le membre a un rôle de direction
*
* @return true si le membre fait partie du bureau ou est responsable
*/
public boolean hasRoleDirection() {
return Boolean.TRUE.equals(membreBureau) || Boolean.TRUE.equals(responsable);
}
/**
* Retourne une représentation textuelle du statut
*
* @return Le libellé du statut
*/
public String getStatutLibelle() {
return statut != null ? statut.getLibelle() : "Non défini";
}
/**
* Valide les données essentielles du membre
*
* @return true si les données sont valides
*/
public boolean sontDonneesValides() {
return numeroMembre != null
&& !numeroMembre.trim().isEmpty()
&& nom != null
&& !nom.trim().isEmpty()
&& prenom != null
&& !prenom.trim().isEmpty()
&& statut != null
&& associationId != null;
}
@Override
public String toString() {
return "MembreDTO{"
+ "numeroMembre='"
+ numeroMembre
+ '\''
+ ", nom='"
+ nom
+ '\''
+ ", prenom='"
+ prenom
+ '\''
+ ", email='"
+ email
+ '\''
+ ", statut='"
+ statut
+ '\''
+ ", associationId="
+ associationId
+ ", dateAdhesion="
+ dateAdhesion
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,226 @@
package dev.lions.unionflow.server.api.dto.membre;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
/**
* DTO pour les critères de recherche avancée des membres Permet de filtrer les membres selon de
* multiples critères
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Critères de recherche avancée pour les membres")
public class MembreSearchCriteria {
/** Terme de recherche général (nom, prénom, email) */
@Schema(description = "Terme de recherche général dans nom, prénom ou email", example = "marie")
@Size(max = 100, message = "Le terme de recherche ne peut pas dépasser 100 caractères")
private String query;
/** Recherche par nom exact ou partiel */
@Schema(description = "Filtre par nom (recherche partielle)", example = "Dupont")
@Size(max = 50, message = "Le nom ne peut pas dépasser 50 caractères")
private String nom;
/** Recherche par prénom exact ou partiel */
@Schema(description = "Filtre par prénom (recherche partielle)", example = "Marie")
@Size(max = 50, message = "Le prénom ne peut pas dépasser 50 caractères")
private String prenom;
/** Recherche par email exact ou partiel */
@Schema(description = "Filtre par email (recherche partielle)", example = "@unionflow.com")
@Size(max = 100, message = "L'email ne peut pas dépasser 100 caractères")
private String email;
/** Filtre par numéro de téléphone */
@Schema(description = "Filtre par numéro de téléphone", example = "+221")
@Size(max = 20, message = "Le téléphone ne peut pas dépasser 20 caractères")
private String telephone;
/** Liste des IDs d'organisations */
@Schema(description = "Liste des IDs d'organisations à inclure")
private List<UUID> organisationIds;
/** Liste des rôles à rechercher */
@Schema(description = "Liste des rôles à rechercher", example = "[\"PRESIDENT\", \"SECRETAIRE\"]")
private List<String> roles;
/** Filtre par statut d'activité */
@Schema(description = "Filtre par statut d'activité", example = "ACTIF")
@Pattern(regexp = "^(ACTIF|INACTIF|SUSPENDU|RADIE)$", message = "Statut invalide")
private String statut;
/** Date d'adhésion minimum */
@Schema(description = "Date d'adhésion minimum", example = "2020-01-01")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateAdhesionMin;
/** Date d'adhésion maximum */
@Schema(description = "Date d'adhésion maximum", example = "2025-12-31")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateAdhesionMax;
/** Âge minimum */
@Schema(description = "Âge minimum", example = "18")
@Min(value = 0, message = "L'âge minimum doit être positif")
@Max(value = 120, message = "L'âge minimum ne peut pas dépasser 120 ans")
private Integer ageMin;
/** Âge maximum */
@Schema(description = "Âge maximum", example = "65")
@Min(value = 0, message = "L'âge maximum doit être positif")
@Max(value = 120, message = "L'âge maximum ne peut pas dépasser 120 ans")
private Integer ageMax;
/** Filtre par région */
@Schema(description = "Filtre par région", example = "Dakar")
@Size(max = 50, message = "La région ne peut pas dépasser 50 caractères")
private String region;
/** Filtre par ville */
@Schema(description = "Filtre par ville", example = "Dakar")
@Size(max = 50, message = "La ville ne peut pas dépasser 50 caractères")
private String ville;
/** Filtre par profession */
@Schema(description = "Filtre par profession", example = "Ingénieur")
@Size(max = 100, message = "La profession ne peut pas dépasser 100 caractères")
private String profession;
/** Filtre par nationalité */
@Schema(description = "Filtre par nationalité", example = "Sénégalaise")
@Size(max = 50, message = "La nationalité ne peut pas dépasser 50 caractères")
private String nationalite;
/** Filtre membres du bureau uniquement */
@Schema(description = "Filtre pour les membres du bureau uniquement")
private Boolean membreBureau;
/** Filtre responsables uniquement */
@Schema(description = "Filtre pour les responsables uniquement")
private Boolean responsable;
/** Inclure les membres inactifs dans la recherche */
@Schema(description = "Inclure les membres inactifs", defaultValue = "false")
@Builder.Default
private Boolean includeInactifs = false;
/**
* Vérifie si au moins un critère de recherche est défini
*
* @return true si au moins un critère est défini
*/
public boolean hasAnyCriteria() {
return query != null && !query.trim().isEmpty()
|| nom != null && !nom.trim().isEmpty()
|| prenom != null && !prenom.trim().isEmpty()
|| email != null && !email.trim().isEmpty()
|| telephone != null && !telephone.trim().isEmpty()
|| organisationIds != null && !organisationIds.isEmpty()
|| roles != null && !roles.isEmpty()
|| statut != null && !statut.trim().isEmpty()
|| dateAdhesionMin != null
|| dateAdhesionMax != null
|| ageMin != null
|| ageMax != null
|| region != null && !region.trim().isEmpty()
|| ville != null && !ville.trim().isEmpty()
|| profession != null && !profession.trim().isEmpty()
|| nationalite != null && !nationalite.trim().isEmpty()
|| membreBureau != null
|| responsable != null;
}
/**
* Valide la cohérence des critères de recherche
*
* @return true si les critères sont cohérents
*/
public boolean isValid() {
// Validation des dates
if (dateAdhesionMin != null && dateAdhesionMax != null) {
if (dateAdhesionMin.isAfter(dateAdhesionMax)) {
return false;
}
}
// Validation des âges
if (ageMin != null && ageMax != null) {
if (ageMin > ageMax) {
return false;
}
}
return true;
}
/** Nettoie les chaînes de caractères (trim et null si vide) */
public void sanitize() {
query = sanitizeString(query);
nom = sanitizeString(nom);
prenom = sanitizeString(prenom);
email = sanitizeString(email);
telephone = sanitizeString(telephone);
statut = sanitizeString(statut);
region = sanitizeString(region);
ville = sanitizeString(ville);
profession = sanitizeString(profession);
nationalite = sanitizeString(nationalite);
}
private String sanitizeString(String str) {
if (str == null) return null;
str = str.trim();
return str.isEmpty() ? null : str;
}
/**
* Retourne une description textuelle des critères actifs
*
* @return Description des critères
*/
public String getDescription() {
StringBuilder sb = new StringBuilder();
if (query != null) sb.append("Recherche: '").append(query).append("' ");
if (nom != null) sb.append("Nom: '").append(nom).append("' ");
if (prenom != null) sb.append("Prénom: '").append(prenom).append("' ");
if (email != null) sb.append("Email: '").append(email).append("' ");
if (statut != null) sb.append("Statut: ").append(statut).append(" ");
if (organisationIds != null && !organisationIds.isEmpty()) {
sb.append("Organisations: ").append(organisationIds.size()).append(" ");
}
if (roles != null && !roles.isEmpty()) {
sb.append("Rôles: ").append(String.join(", ", roles)).append(" ");
}
if (dateAdhesionMin != null) sb.append("Adhésion >= ").append(dateAdhesionMin).append(" ");
if (dateAdhesionMax != null) sb.append("Adhésion <= ").append(dateAdhesionMax).append(" ");
if (ageMin != null) sb.append("Âge >= ").append(ageMin).append(" ");
if (ageMax != null) sb.append("Âge <= ").append(ageMax).append(" ");
if (region != null) sb.append("Région: '").append(region).append("' ");
if (ville != null) sb.append("Ville: '").append(ville).append("' ");
if (profession != null) sb.append("Profession: '").append(profession).append("' ");
if (nationalite != null) sb.append("Nationalité: '").append(nationalite).append("' ");
if (Boolean.TRUE.equals(membreBureau)) sb.append("Membre bureau ");
if (Boolean.TRUE.equals(responsable)) sb.append("Responsable ");
return sb.toString().trim();
}
}

View File

@@ -0,0 +1,199 @@
package dev.lions.unionflow.server.api.dto.membre;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
/**
* DTO pour les résultats de recherche avancée des membres Contient les résultats paginés et les
* métadonnées de recherche
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Résultats de recherche avancée des membres avec pagination")
public class MembreSearchResultDTO {
/** Liste des membres trouvés */
@Schema(description = "Liste des membres correspondant aux critères")
private List<MembreDTO> membres;
/** Nombre total de résultats (toutes pages confondues) */
@Schema(description = "Nombre total de résultats trouvés", example = "247")
private long totalElements;
/** Nombre total de pages */
@Schema(description = "Nombre total de pages", example = "13")
private int totalPages;
/** Numéro de la page actuelle (0-based) */
@Schema(description = "Numéro de la page actuelle", example = "0")
private int currentPage;
/** Taille de la page */
@Schema(description = "Nombre d'éléments par page", example = "20")
private int pageSize;
/** Nombre d'éléments sur la page actuelle */
@Schema(description = "Nombre d'éléments sur cette page", example = "20")
private int numberOfElements;
/** Indique s'il y a une page suivante */
@Schema(description = "Indique s'il y a une page suivante")
private boolean hasNext;
/** Indique s'il y a une page précédente */
@Schema(description = "Indique s'il y a une page précédente")
private boolean hasPrevious;
/** Indique si c'est la première page */
@Schema(description = "Indique si c'est la première page")
private boolean isFirst;
/** Indique si c'est la dernière page */
@Schema(description = "Indique si c'est la dernière page")
private boolean isLast;
/** Critères de recherche utilisés */
@Schema(description = "Critères de recherche qui ont été appliqués")
private MembreSearchCriteria criteria;
/** Temps d'exécution de la recherche en millisecondes */
@Schema(description = "Temps d'exécution de la recherche en ms", example = "45")
private long executionTimeMs;
/** Statistiques de recherche */
@Schema(description = "Statistiques sur les résultats de recherche")
private SearchStatistics statistics;
/** Statistiques sur les résultats de recherche */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Statistiques sur les résultats de recherche")
public static class SearchStatistics {
/** Répartition par statut */
@Schema(description = "Nombre de membres actifs dans les résultats")
private long membresActifs;
@Schema(description = "Nombre de membres inactifs dans les résultats")
private long membresInactifs;
/** Répartition par âge */
@Schema(description = "Âge moyen des membres trouvés")
private double ageMoyen;
@Schema(description = "Âge minimum des membres trouvés")
private int ageMin;
@Schema(description = "Âge maximum des membres trouvés")
private int ageMax;
/** Répartition par organisation */
@Schema(description = "Nombre d'organisations représentées")
private long nombreOrganisations;
/** Répartition par région */
@Schema(description = "Nombre de régions représentées")
private long nombreRegions;
/** Ancienneté moyenne */
@Schema(description = "Ancienneté moyenne en années")
private double ancienneteMoyenne;
}
/** Calcule et met à jour les indicateurs de pagination */
public void calculatePaginationFlags() {
this.isFirst = currentPage == 0;
this.isLast = currentPage >= totalPages - 1;
this.hasPrevious = currentPage > 0;
this.hasNext = currentPage < totalPages - 1;
this.numberOfElements = membres != null ? membres.size() : 0;
}
/**
* Vérifie si les résultats sont vides
*
* @return true si aucun résultat
*/
public boolean isEmpty() {
return membres == null || membres.isEmpty();
}
/**
* Retourne le numéro de la page suivante (1-based pour affichage)
*
* @return Numéro de page suivante ou -1 si pas de page suivante
*/
public int getNextPageNumber() {
return hasNext ? currentPage + 2 : -1;
}
/**
* Retourne le numéro de la page précédente (1-based pour affichage)
*
* @return Numéro de page précédente ou -1 si pas de page précédente
*/
public int getPreviousPageNumber() {
return hasPrevious ? currentPage : -1;
}
/**
* Retourne une description textuelle des résultats
*
* @return Description des résultats
*/
public String getResultDescription() {
if (isEmpty()) {
return "Aucun membre trouvé";
}
if (totalElements == 1) {
return "1 membre trouvé";
}
if (totalPages == 1) {
return String.format("%d membres trouvés", totalElements);
}
int startElement = currentPage * pageSize + 1;
int endElement = Math.min(startElement + numberOfElements - 1, (int) totalElements);
return String.format(
"Membres %d-%d sur %d (page %d/%d)",
startElement, endElement, totalElements, currentPage + 1, totalPages);
}
/**
* Factory method pour créer un résultat vide
*
* @param criteria Critères de recherche
* @return Résultat vide
*/
public static MembreSearchResultDTO empty(MembreSearchCriteria criteria) {
MembreSearchResultDTO result = new MembreSearchResultDTO();
result.setMembres(List.of());
result.setTotalElements(0L);
result.setTotalPages(0);
result.setCurrentPage(0);
result.setPageSize(20);
result.setNumberOfElements(0);
result.setHasNext(false);
result.setHasPrevious(false);
result.setFirst(true);
result.setLast(true);
result.setCriteria(criteria);
result.setExecutionTimeMs(0L);
return result;
}
}

View File

@@ -0,0 +1,477 @@
package dev.lions.unionflow.server.api.dto.notification;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.*;
import java.util.Map;
/**
* DTO pour les actions rapides des notifications UnionFlow
*
* <p>Ce DTO représente une action que l'utilisateur peut exécuter directement depuis la
* notification sans ouvrir l'application.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ActionNotificationDTO {
/** Identifiant unique de l'action */
@NotBlank(message = "L'identifiant de l'action est obligatoire")
private String id;
/** Libellé affiché sur le bouton d'action */
@NotBlank(message = "Le libellé de l'action est obligatoire")
@Size(max = 30, message = "Le libellé ne peut pas dépasser 30 caractères")
private String libelle;
/** Description de l'action (tooltip) */
@Size(max = 100, message = "La description ne peut pas dépasser 100 caractères")
private String description;
/** Type d'action à exécuter */
@NotBlank(message = "Le type d'action est obligatoire")
private String typeAction;
/** Icône de l'action (Material Design) */
private String icone;
/** Couleur de l'action (hexadécimal) */
private String couleur;
/** URL à ouvrir (pour les actions de type "url") */
private String url;
/** Route de l'application à ouvrir (pour les actions de type "route") */
private String route;
/** Paramètres de l'action */
private Map<String, String> parametres;
/** Indique si l'action ferme la notification */
private Boolean fermeNotification;
/** Indique si l'action nécessite une confirmation */
private Boolean necessiteConfirmation;
/** Message de confirmation à afficher */
private String messageConfirmation;
/** Indique si l'action est destructive (suppression, etc.) */
private Boolean estDestructive;
/** Ordre d'affichage de l'action */
private Integer ordre;
/** Indique si l'action est activée */
private Boolean estActivee;
/** Condition d'affichage de l'action (expression) */
private String conditionAffichage;
/** Rôles autorisés à exécuter cette action */
private String[] rolesAutorises;
/** Permissions requises pour exécuter cette action */
private String[] permissionsRequises;
/** Délai d'expiration de l'action en minutes */
private Integer delaiExpirationMinutes;
/** Nombre maximum d'exécutions autorisées */
private Integer maxExecutions;
/** Nombre d'exécutions actuelles */
private Integer nombreExecutions;
/** Indique si l'action peut être exécutée plusieurs fois */
private Boolean peutEtreRepetee;
/** Style du bouton (primary, secondary, outline, text) */
private String styleBouton;
/** Taille du bouton (small, medium, large) */
private String tailleBouton;
/** Position du bouton (left, center, right) */
private String positionBouton;
/** Données personnalisées de l'action */
private Map<String, Object> donneesPersonnalisees;
// === CONSTRUCTEURS ===
/** Constructeur par défaut */
public ActionNotificationDTO() {
this.fermeNotification = true;
this.necessiteConfirmation = false;
this.estDestructive = false;
this.ordre = 0;
this.estActivee = true;
this.maxExecutions = 1;
this.nombreExecutions = 0;
this.peutEtreRepetee = false;
this.styleBouton = "primary";
this.tailleBouton = "medium";
this.positionBouton = "right";
}
/** Constructeur avec paramètres essentiels */
public ActionNotificationDTO(String id, String libelle, String typeAction) {
this();
this.id = id;
this.libelle = libelle;
this.typeAction = typeAction;
}
/** Constructeur pour action URL */
public ActionNotificationDTO(String id, String libelle, String url, String icone) {
this(id, libelle, "url");
this.url = url;
this.icone = icone;
}
/** Constructeur pour action de route */
public ActionNotificationDTO(
String id, String libelle, String route, String icone, Map<String, String> parametres) {
this(id, libelle, "route");
this.route = route;
this.icone = icone;
this.parametres = parametres;
}
// === GETTERS ET SETTERS ===
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTypeAction() {
return typeAction;
}
public void setTypeAction(String typeAction) {
this.typeAction = typeAction;
}
public String getIcone() {
return icone;
}
public void setIcone(String icone) {
this.icone = icone;
}
public String getCouleur() {
return couleur;
}
public void setCouleur(String couleur) {
this.couleur = couleur;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRoute() {
return route;
}
public void setRoute(String route) {
this.route = route;
}
public Map<String, String> getParametres() {
return parametres;
}
public void setParametres(Map<String, String> parametres) {
this.parametres = parametres;
}
public Boolean getFermeNotification() {
return fermeNotification;
}
public void setFermeNotification(Boolean fermeNotification) {
this.fermeNotification = fermeNotification;
}
public Boolean getNecessiteConfirmation() {
return necessiteConfirmation;
}
public void setNecessiteConfirmation(Boolean necessiteConfirmation) {
this.necessiteConfirmation = necessiteConfirmation;
}
public String getMessageConfirmation() {
return messageConfirmation;
}
public void setMessageConfirmation(String messageConfirmation) {
this.messageConfirmation = messageConfirmation;
}
public Boolean getEstDestructive() {
return estDestructive;
}
public void setEstDestructive(Boolean estDestructive) {
this.estDestructive = estDestructive;
}
public Integer getOrdre() {
return ordre;
}
public void setOrdre(Integer ordre) {
this.ordre = ordre;
}
public Boolean getEstActivee() {
return estActivee;
}
public void setEstActivee(Boolean estActivee) {
this.estActivee = estActivee;
}
public String getConditionAffichage() {
return conditionAffichage;
}
public void setConditionAffichage(String conditionAffichage) {
this.conditionAffichage = conditionAffichage;
}
public String[] getRolesAutorises() {
return rolesAutorises;
}
public void setRolesAutorises(String[] rolesAutorises) {
this.rolesAutorises = rolesAutorises;
}
public String[] getPermissionsRequises() {
return permissionsRequises;
}
public void setPermissionsRequises(String[] permissionsRequises) {
this.permissionsRequises = permissionsRequises;
}
public Integer getDelaiExpirationMinutes() {
return delaiExpirationMinutes;
}
public void setDelaiExpirationMinutes(Integer delaiExpirationMinutes) {
this.delaiExpirationMinutes = delaiExpirationMinutes;
}
public Integer getMaxExecutions() {
return maxExecutions;
}
public void setMaxExecutions(Integer maxExecutions) {
this.maxExecutions = maxExecutions;
}
public Integer getNombreExecutions() {
return nombreExecutions;
}
public void setNombreExecutions(Integer nombreExecutions) {
this.nombreExecutions = nombreExecutions;
}
public Boolean getPeutEtreRepetee() {
return peutEtreRepetee;
}
public void setPeutEtreRepetee(Boolean peutEtreRepetee) {
this.peutEtreRepetee = peutEtreRepetee;
}
public String getStyleBouton() {
return styleBouton;
}
public void setStyleBouton(String styleBouton) {
this.styleBouton = styleBouton;
}
public String getTailleBouton() {
return tailleBouton;
}
public void setTailleBouton(String tailleBouton) {
this.tailleBouton = tailleBouton;
}
public String getPositionBouton() {
return positionBouton;
}
public void setPositionBouton(String positionBouton) {
this.positionBouton = positionBouton;
}
public Map<String, Object> getDonneesPersonnalisees() {
return donneesPersonnalisees;
}
public void setDonneesPersonnalisees(Map<String, Object> donneesPersonnalisees) {
this.donneesPersonnalisees = donneesPersonnalisees;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si l'action peut être exécutée */
public boolean peutEtreExecutee() {
return estActivee && (nombreExecutions < maxExecutions || peutEtreRepetee);
}
/** Vérifie si l'action est expirée */
public boolean isExpiree() {
// Implémentation basée sur delaiExpirationMinutes et date de création de la notification
return false; // À implémenter selon la logique métier
}
/** Incrémente le nombre d'exécutions */
public void incrementerExecutions() {
if (nombreExecutions == null) {
nombreExecutions = 0;
}
nombreExecutions++;
}
/** Vérifie si l'utilisateur a les permissions requises */
public boolean utilisateurAutorise(String[] rolesUtilisateur, String[] permissionsUtilisateur) {
// Vérification des rôles
if (rolesAutorises != null && rolesAutorises.length > 0) {
boolean roleAutorise = false;
for (String roleRequis : rolesAutorises) {
for (String roleUtilisateur : rolesUtilisateur) {
if (roleRequis.equals(roleUtilisateur)) {
roleAutorise = true;
break;
}
}
if (roleAutorise) break;
}
if (!roleAutorise) return false;
}
// Vérification des permissions
if (permissionsRequises != null && permissionsRequises.length > 0) {
boolean permissionAutorisee = false;
for (String permissionRequise : permissionsRequises) {
for (String permissionUtilisateur : permissionsUtilisateur) {
if (permissionRequise.equals(permissionUtilisateur)) {
permissionAutorisee = true;
break;
}
}
if (permissionAutorisee) break;
}
if (!permissionAutorisee) return false;
}
return true;
}
/** Retourne la couleur par défaut selon le type d'action */
public String getCouleurParDefaut() {
if (couleur != null) return couleur;
return switch (typeAction) {
case "confirm" -> "#4CAF50"; // Vert pour confirmation
case "cancel" -> "#F44336"; // Rouge pour annulation
case "info" -> "#2196F3"; // Bleu pour information
case "warning" -> "#FF9800"; // Orange pour avertissement
case "url", "route" -> "#2196F3"; // Bleu pour navigation
default -> "#9E9E9E"; // Gris par défaut
};
}
/** Retourne l'icône par défaut selon le type d'action */
public String getIconeParDefaut() {
if (icone != null) return icone;
return switch (typeAction) {
case "confirm" -> "check";
case "cancel" -> "close";
case "info" -> "info";
case "warning" -> "warning";
case "url" -> "open_in_new";
case "route" -> "arrow_forward";
case "call" -> "phone";
case "message" -> "message";
case "email" -> "email";
case "share" -> "share";
default -> "touch_app";
};
}
/** Crée une action de confirmation */
public static ActionNotificationDTO creerActionConfirmation(String id, String libelle) {
ActionNotificationDTO action = new ActionNotificationDTO(id, libelle, "confirm");
action.setCouleur("#4CAF50");
action.setIcone("check");
action.setStyleBouton("primary");
return action;
}
/** Crée une action d'annulation */
public static ActionNotificationDTO creerActionAnnulation(String id, String libelle) {
ActionNotificationDTO action = new ActionNotificationDTO(id, libelle, "cancel");
action.setCouleur("#F44336");
action.setIcone("close");
action.setStyleBouton("outline");
action.setEstDestructive(true);
return action;
}
/** Crée une action de navigation */
public static ActionNotificationDTO creerActionNavigation(
String id, String libelle, String route) {
ActionNotificationDTO action = new ActionNotificationDTO(id, libelle, "route");
action.setRoute(route);
action.setCouleur("#2196F3");
action.setIcone("arrow_forward");
return action;
}
@Override
public String toString() {
return String.format(
"ActionNotificationDTO{id='%s', libelle='%s', type='%s'}", id, libelle, typeAction);
}
}

View File

@@ -0,0 +1,68 @@
package dev.lions.unionflow.server.api.dto.notification;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification;
import dev.lions.unionflow.server.api.enums.notification.StatutNotification;
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class NotificationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Type de notification */
@NotNull(message = "Le type de notification est obligatoire")
private TypeNotification typeNotification;
/** Priorité */
private PrioriteNotification priorite;
/** Statut */
private StatutNotification statut;
/** Sujet */
private String sujet;
/** Corps du message */
private String corps;
/** Date d'envoi prévue */
private LocalDateTime dateEnvoiPrevue;
/** Date d'envoi réelle */
private LocalDateTime dateEnvoi;
/** Date de lecture */
private LocalDateTime dateLecture;
/** Nombre de tentatives */
private Integer nombreTentatives;
/** Message d'erreur */
private String messageErreur;
/** Données additionnelles (JSON) */
private String donneesAdditionnelles;
/** ID du membre */
private UUID membreId;
/** ID de l'organisation */
private UUID organisationId;
/** ID du template */
private UUID templateId;
}

View File

@@ -0,0 +1,119 @@
package dev.lions.unionflow.server.api.dto.notification;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.*;
/**
* DTO pour les préférences spécifiques à un canal de notification
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PreferenceCanalNotificationDTO {
/** Indique si ce canal est activé */
private Boolean active;
/** Niveau d'importance personnalisé (1-5) */
@Min(value = 1, message = "L'importance doit être comprise entre 1 et 5")
@Max(value = 5, message = "L'importance doit être comprise entre 1 et 5")
private Integer importance;
/** Son personnalisé pour ce canal */
private String sonPersonnalise;
/** Pattern de vibration personnalisé */
private long[] patternVibration;
/** Couleur LED personnalisée */
private String couleurLED;
/** Indique si le son est activé pour ce canal */
private Boolean sonActive;
/** Indique si la vibration est activée pour ce canal */
private Boolean vibrationActive;
/** Indique si la LED est activée pour ce canal */
private Boolean ledActive;
/** Indique si ce canal peut être désactivé par l'utilisateur */
private Boolean peutEtreDesactive;
// Constructeurs, getters et setters
public PreferenceCanalNotificationDTO() {}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public Integer getImportance() {
return importance;
}
public void setImportance(Integer importance) {
this.importance = importance;
}
public String getSonPersonnalise() {
return sonPersonnalise;
}
public void setSonPersonnalise(String sonPersonnalise) {
this.sonPersonnalise = sonPersonnalise;
}
public long[] getPatternVibration() {
return patternVibration;
}
public void setPatternVibration(long[] patternVibration) {
this.patternVibration = patternVibration;
}
public String getCouleurLED() {
return couleurLED;
}
public void setCouleurLED(String couleurLED) {
this.couleurLED = couleurLED;
}
public Boolean getSonActive() {
return sonActive;
}
public void setSonActive(Boolean sonActive) {
this.sonActive = sonActive;
}
public Boolean getVibrationActive() {
return vibrationActive;
}
public void setVibrationActive(Boolean vibrationActive) {
this.vibrationActive = vibrationActive;
}
public Boolean getLedActive() {
return ledActive;
}
public void setLedActive(Boolean ledActive) {
this.ledActive = ledActive;
}
public Boolean getPeutEtreDesactive() {
return peutEtreDesactive;
}
public void setPeutEtreDesactive(Boolean peutEtreDesactive) {
this.peutEtreDesactive = peutEtreDesactive;
}
}

View File

@@ -0,0 +1,132 @@
package dev.lions.unionflow.server.api.dto.notification;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.*;
/**
* DTO pour les préférences spécifiques à un type de notification
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PreferenceTypeNotificationDTO {
/** Indique si ce type de notification est activé */
private Boolean active;
/** Priorité personnalisée (1-5) */
@Min(value = 1, message = "La priorité doit être comprise entre 1 et 5")
@Max(value = 5, message = "La priorité doit être comprise entre 1 et 5")
private Integer priorite;
/** Son personnalisé pour ce type */
private String sonPersonnalise;
/** Pattern de vibration personnalisé */
private long[] patternVibration;
/** Couleur LED personnalisée */
private String couleurLED;
/** Durée d'affichage personnalisée (secondes) */
@Min(value = 1, message = "La durée d'affichage doit être au moins 1 seconde")
@Max(value = 300, message = "La durée d'affichage ne peut pas dépasser 5 minutes")
private Integer dureeAffichageSecondes;
/** Indique si les notifications de ce type doivent vibrer */
private Boolean doitVibrer;
/** Indique si les notifications de ce type doivent émettre un son */
private Boolean doitEmettreSon;
/** Indique si les notifications de ce type doivent allumer la LED */
private Boolean doitAllumerLED;
/** Indique si ce type ignore le mode silencieux */
private Boolean ignoreModesilencieux;
// Constructeurs, getters et setters
public PreferenceTypeNotificationDTO() {}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public Integer getPriorite() {
return priorite;
}
public void setPriorite(Integer priorite) {
this.priorite = priorite;
}
public String getSonPersonnalise() {
return sonPersonnalise;
}
public void setSonPersonnalise(String sonPersonnalise) {
this.sonPersonnalise = sonPersonnalise;
}
public long[] getPatternVibration() {
return patternVibration;
}
public void setPatternVibration(long[] patternVibration) {
this.patternVibration = patternVibration;
}
public String getCouleurLED() {
return couleurLED;
}
public void setCouleurLED(String couleurLED) {
this.couleurLED = couleurLED;
}
public Integer getDureeAffichageSecondes() {
return dureeAffichageSecondes;
}
public void setDureeAffichageSecondes(Integer dureeAffichageSecondes) {
this.dureeAffichageSecondes = dureeAffichageSecondes;
}
public Boolean getDoitVibrer() {
return doitVibrer;
}
public void setDoitVibrer(Boolean doitVibrer) {
this.doitVibrer = doitVibrer;
}
public Boolean getDoitEmettreSon() {
return doitEmettreSon;
}
public void setDoitEmettreSon(Boolean doitEmettreSon) {
this.doitEmettreSon = doitEmettreSon;
}
public Boolean getDoitAllumerLED() {
return doitAllumerLED;
}
public void setDoitAllumerLED(Boolean doitAllumerLED) {
this.doitAllumerLED = doitAllumerLED;
}
public Boolean getIgnoreModeSilencieux() {
return ignoreModesilencieux;
}
public void setIgnoreModeSilencieux(Boolean ignoreModesilencieux) {
this.ignoreModesilencieux = ignoreModesilencieux;
}
}

View File

@@ -0,0 +1,636 @@
package dev.lions.unionflow.server.api.dto.notification;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.lions.unionflow.server.api.enums.notification.CanalNotification;
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
import jakarta.validation.constraints.*;
import java.time.LocalTime;
import java.util.Map;
import java.util.Set;
/**
* DTO pour les préférences de notification d'un utilisateur
*
* <p>Ce DTO représente les préférences personnalisées d'un utilisateur concernant la réception et
* l'affichage des notifications.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PreferencesNotificationDTO {
/** Identifiant unique des préférences */
private String id;
/** Identifiant de l'utilisateur */
@NotBlank(message = "L'identifiant utilisateur est obligatoire")
private String utilisateurId;
/** Identifiant de l'organisation */
private String organisationId;
/** Indique si les notifications sont activées globalement */
@NotNull(message = "L'activation globale des notifications est obligatoire")
private Boolean notificationsActivees;
/** Indique si les notifications push sont activées */
private Boolean pushActivees;
/** Indique si les notifications par email sont activées */
private Boolean emailActivees;
/** Indique si les notifications SMS sont activées */
private Boolean smsActivees;
/** Indique si les notifications in-app sont activées */
private Boolean inAppActivees;
/** Types de notifications activés */
private Set<TypeNotification> typesActives;
/** Types de notifications désactivés */
private Set<TypeNotification> typesDesactivees;
/** Canaux de notification activés */
private Set<CanalNotification> canauxActifs;
/** Canaux de notification désactivés */
private Set<CanalNotification> canauxDesactives;
/** Mode Ne Pas Déranger activé */
private Boolean modeSilencieux;
/** Heure de début du mode silencieux */
@JsonFormat(pattern = "HH:mm")
private LocalTime heureDebutSilencieux;
/** Heure de fin du mode silencieux */
@JsonFormat(pattern = "HH:mm")
private LocalTime heureFinSilencieux;
/** Jours de la semaine pour le mode silencieux (1=Lundi, 7=Dimanche) */
private Set<Integer> joursSilencieux;
/** Indique si les notifications urgentes passent outre le mode silencieux */
private Boolean urgentesIgnorentSilencieux;
/** Fréquence de regroupement des notifications (minutes) */
@Min(value = 0, message = "La fréquence de regroupement doit être positive")
@Max(value = 1440, message = "La fréquence de regroupement ne peut pas dépasser 24h")
private Integer frequenceRegroupementMinutes;
/** Nombre maximum de notifications affichées simultanément */
@Min(value = 1, message = "Le nombre maximum de notifications doit être au moins 1")
@Max(value = 50, message = "Le nombre maximum de notifications ne peut pas dépasser 50")
private Integer maxNotificationsSimultanees;
/** Durée d'affichage par défaut des notifications (secondes) */
@Min(value = 1, message = "La durée d'affichage doit être au moins 1 seconde")
@Max(value = 300, message = "La durée d'affichage ne peut pas dépasser 5 minutes")
private Integer dureeAffichageSecondes;
/** Indique si les notifications doivent vibrer */
private Boolean vibrationActivee;
/** Indique si les notifications doivent émettre un son */
private Boolean sonActive;
/** Indique si la LED doit s'allumer */
private Boolean ledActivee;
/** Son personnalisé pour les notifications */
private String sonPersonnalise;
/** Pattern de vibration personnalisé */
private long[] patternVibrationPersonnalise;
/** Couleur de LED personnalisée */
private String couleurLEDPersonnalisee;
/** Indique si les aperçus de contenu sont affichés sur l'écran de verrouillage */
private Boolean apercuEcranVerrouillage;
/** Indique si les notifications sont affichées dans l'historique */
private Boolean affichageHistorique;
/** Durée de conservation dans l'historique (jours) */
@Min(value = 1, message = "La durée de conservation doit être au moins 1 jour")
@Max(value = 365, message = "La durée de conservation ne peut pas dépasser 1 an")
private Integer dureeConservationJours;
/** Indique si les notifications sont automatiquement marquées comme lues */
private Boolean marquageLectureAutomatique;
/** Délai avant marquage automatique comme lu (secondes) */
private Integer delaiMarquageLectureSecondes;
/** Indique si les notifications sont automatiquement archivées */
private Boolean archivageAutomatique;
/** Délai avant archivage automatique (heures) */
private Integer delaiArchivageHeures;
/** Préférences par type de notification */
private Map<TypeNotification, PreferenceTypeNotificationDTO> preferencesParType;
/** Préférences par canal de notification */
private Map<CanalNotification, PreferenceCanalNotificationDTO> preferencesParCanal;
/** Mots-clés pour filtrage automatique */
private Set<String> motsClesFiltre;
/** Expéditeurs bloqués */
private Set<String> expediteursBloqués;
/** Expéditeurs prioritaires */
private Set<String> expediteursPrioritaires;
/** Indique si les notifications de test sont activées */
private Boolean notificationsTestActivees;
/** Niveau de log pour les notifications (DEBUG, INFO, WARN, ERROR) */
private String niveauLog;
/** Token FCM pour les notifications push */
private String tokenFCM;
/** Plateforme de l'appareil (android, ios, web) */
private String plateforme;
/** Version de l'application */
private String versionApp;
/** Langue préférée pour les notifications */
private String langue;
/** Fuseau horaire de l'utilisateur */
private String fuseauHoraire;
/** Métadonnées personnalisées */
private Map<String, Object> metadonnees;
// === CONSTRUCTEURS ===
/** Constructeur par défaut avec valeurs par défaut */
public PreferencesNotificationDTO() {
this.notificationsActivees = true;
this.pushActivees = true;
this.emailActivees = true;
this.smsActivees = false;
this.inAppActivees = true;
this.modeSilencieux = false;
this.urgentesIgnorentSilencieux = true;
this.frequenceRegroupementMinutes = 5;
this.maxNotificationsSimultanees = 10;
this.dureeAffichageSecondes = 10;
this.vibrationActivee = true;
this.sonActive = true;
this.ledActivee = true;
this.apercuEcranVerrouillage = true;
this.affichageHistorique = true;
this.dureeConservationJours = 30;
this.marquageLectureAutomatique = false;
this.archivageAutomatique = true;
this.delaiArchivageHeures = 168; // 1 semaine
this.notificationsTestActivees = false;
this.niveauLog = "INFO";
this.langue = "fr";
}
/** Constructeur avec utilisateur */
public PreferencesNotificationDTO(String utilisateurId) {
this();
this.utilisateurId = utilisateurId;
}
// === GETTERS ET SETTERS ===
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUtilisateurId() {
return utilisateurId;
}
public void setUtilisateurId(String utilisateurId) {
this.utilisateurId = utilisateurId;
}
public String getOrganisationId() {
return organisationId;
}
public void setOrganisationId(String organisationId) {
this.organisationId = organisationId;
}
public Boolean getNotificationsActivees() {
return notificationsActivees;
}
public void setNotificationsActivees(Boolean notificationsActivees) {
this.notificationsActivees = notificationsActivees;
}
public Boolean getPushActivees() {
return pushActivees;
}
public void setPushActivees(Boolean pushActivees) {
this.pushActivees = pushActivees;
}
public Boolean getEmailActivees() {
return emailActivees;
}
public void setEmailActivees(Boolean emailActivees) {
this.emailActivees = emailActivees;
}
public Boolean getSmsActivees() {
return smsActivees;
}
public void setSmsActivees(Boolean smsActivees) {
this.smsActivees = smsActivees;
}
public Boolean getInAppActivees() {
return inAppActivees;
}
public void setInAppActivees(Boolean inAppActivees) {
this.inAppActivees = inAppActivees;
}
public Set<TypeNotification> getTypesActives() {
return typesActives;
}
public void setTypesActives(Set<TypeNotification> typesActives) {
this.typesActives = typesActives;
}
public Set<TypeNotification> getTypesDesactivees() {
return typesDesactivees;
}
public void setTypesDesactivees(Set<TypeNotification> typesDesactivees) {
this.typesDesactivees = typesDesactivees;
}
public Set<CanalNotification> getCanauxActifs() {
return canauxActifs;
}
public void setCanauxActifs(Set<CanalNotification> canauxActifs) {
this.canauxActifs = canauxActifs;
}
public Set<CanalNotification> getCanauxDesactives() {
return canauxDesactives;
}
public void setCanauxDesactives(Set<CanalNotification> canauxDesactives) {
this.canauxDesactives = canauxDesactives;
}
public Boolean getModeSilencieux() {
return modeSilencieux;
}
public void setModeSilencieux(Boolean modeSilencieux) {
this.modeSilencieux = modeSilencieux;
}
public LocalTime getHeureDebutSilencieux() {
return heureDebutSilencieux;
}
public void setHeureDebutSilencieux(LocalTime heureDebutSilencieux) {
this.heureDebutSilencieux = heureDebutSilencieux;
}
public LocalTime getHeureFinSilencieux() {
return heureFinSilencieux;
}
public void setHeureFinSilencieux(LocalTime heureFinSilencieux) {
this.heureFinSilencieux = heureFinSilencieux;
}
public Set<Integer> getJoursSilencieux() {
return joursSilencieux;
}
public void setJoursSilencieux(Set<Integer> joursSilencieux) {
this.joursSilencieux = joursSilencieux;
}
public Boolean getUrgentesIgnorentSilencieux() {
return urgentesIgnorentSilencieux;
}
public void setUrgentesIgnorentSilencieux(Boolean urgentesIgnorentSilencieux) {
this.urgentesIgnorentSilencieux = urgentesIgnorentSilencieux;
}
public Integer getFrequenceRegroupementMinutes() {
return frequenceRegroupementMinutes;
}
public void setFrequenceRegroupementMinutes(Integer frequenceRegroupementMinutes) {
this.frequenceRegroupementMinutes = frequenceRegroupementMinutes;
}
public Integer getMaxNotificationsSimultanees() {
return maxNotificationsSimultanees;
}
public void setMaxNotificationsSimultanees(Integer maxNotificationsSimultanees) {
this.maxNotificationsSimultanees = maxNotificationsSimultanees;
}
public Integer getDureeAffichageSecondes() {
return dureeAffichageSecondes;
}
public void setDureeAffichageSecondes(Integer dureeAffichageSecondes) {
this.dureeAffichageSecondes = dureeAffichageSecondes;
}
public Boolean getVibrationActivee() {
return vibrationActivee;
}
public void setVibrationActivee(Boolean vibrationActivee) {
this.vibrationActivee = vibrationActivee;
}
public Boolean getSonActive() {
return sonActive;
}
public void setSonActive(Boolean sonActive) {
this.sonActive = sonActive;
}
public Boolean getLedActivee() {
return ledActivee;
}
public void setLedActivee(Boolean ledActivee) {
this.ledActivee = ledActivee;
}
public String getSonPersonnalise() {
return sonPersonnalise;
}
public void setSonPersonnalise(String sonPersonnalise) {
this.sonPersonnalise = sonPersonnalise;
}
public long[] getPatternVibrationPersonnalise() {
return patternVibrationPersonnalise;
}
public void setPatternVibrationPersonnalise(long[] patternVibrationPersonnalise) {
this.patternVibrationPersonnalise = patternVibrationPersonnalise;
}
public String getCouleurLEDPersonnalisee() {
return couleurLEDPersonnalisee;
}
public void setCouleurLEDPersonnalisee(String couleurLEDPersonnalisee) {
this.couleurLEDPersonnalisee = couleurLEDPersonnalisee;
}
public Boolean getApercuEcranVerrouillage() {
return apercuEcranVerrouillage;
}
public void setApercuEcranVerrouillage(Boolean apercuEcranVerrouillage) {
this.apercuEcranVerrouillage = apercuEcranVerrouillage;
}
public Boolean getAffichageHistorique() {
return affichageHistorique;
}
public void setAffichageHistorique(Boolean affichageHistorique) {
this.affichageHistorique = affichageHistorique;
}
public Integer getDureeConservationJours() {
return dureeConservationJours;
}
public void setDureeConservationJours(Integer dureeConservationJours) {
this.dureeConservationJours = dureeConservationJours;
}
public Boolean getMarquageLectureAutomatique() {
return marquageLectureAutomatique;
}
public void setMarquageLectureAutomatique(Boolean marquageLectureAutomatique) {
this.marquageLectureAutomatique = marquageLectureAutomatique;
}
public Integer getDelaiMarquageLectureSecondes() {
return delaiMarquageLectureSecondes;
}
public void setDelaiMarquageLectureSecondes(Integer delaiMarquageLectureSecondes) {
this.delaiMarquageLectureSecondes = delaiMarquageLectureSecondes;
}
public Boolean getArchivageAutomatique() {
return archivageAutomatique;
}
public void setArchivageAutomatique(Boolean archivageAutomatique) {
this.archivageAutomatique = archivageAutomatique;
}
public Integer getDelaiArchivageHeures() {
return delaiArchivageHeures;
}
public void setDelaiArchivageHeures(Integer delaiArchivageHeures) {
this.delaiArchivageHeures = delaiArchivageHeures;
}
public Map<TypeNotification, PreferenceTypeNotificationDTO> getPreferencesParType() {
return preferencesParType;
}
public void setPreferencesParType(
Map<TypeNotification, PreferenceTypeNotificationDTO> preferencesParType) {
this.preferencesParType = preferencesParType;
}
public Map<CanalNotification, PreferenceCanalNotificationDTO> getPreferencesParCanal() {
return preferencesParCanal;
}
public void setPreferencesParCanal(
Map<CanalNotification, PreferenceCanalNotificationDTO> preferencesParCanal) {
this.preferencesParCanal = preferencesParCanal;
}
public Set<String> getMotsClesFiltre() {
return motsClesFiltre;
}
public void setMotsClesFiltre(Set<String> motsClesFiltre) {
this.motsClesFiltre = motsClesFiltre;
}
public Set<String> getExpediteursBloques() {
return expediteursBloqués;
}
public void setExpediteursBloques(Set<String> expediteursBloqués) {
this.expediteursBloqués = expediteursBloqués;
}
public Set<String> getExpediteursPrioritaires() {
return expediteursPrioritaires;
}
public void setExpediteursPrioritaires(Set<String> expediteursPrioritaires) {
this.expediteursPrioritaires = expediteursPrioritaires;
}
public Boolean getNotificationsTestActivees() {
return notificationsTestActivees;
}
public void setNotificationsTestActivees(Boolean notificationsTestActivees) {
this.notificationsTestActivees = notificationsTestActivees;
}
public String getNiveauLog() {
return niveauLog;
}
public void setNiveauLog(String niveauLog) {
this.niveauLog = niveauLog;
}
public String getTokenFCM() {
return tokenFCM;
}
public void setTokenFCM(String tokenFCM) {
this.tokenFCM = tokenFCM;
}
public String getPlateforme() {
return plateforme;
}
public void setPlateforme(String plateforme) {
this.plateforme = plateforme;
}
public String getVersionApp() {
return versionApp;
}
public void setVersionApp(String versionApp) {
this.versionApp = versionApp;
}
public String getLangue() {
return langue;
}
public void setLangue(String langue) {
this.langue = langue;
}
public String getFuseauHoraire() {
return fuseauHoraire;
}
public void setFuseauHoraire(String fuseauHoraire) {
this.fuseauHoraire = fuseauHoraire;
}
public Map<String, Object> getMetadonnees() {
return metadonnees;
}
public void setMetadonnees(Map<String, Object> metadonnees) {
this.metadonnees = metadonnees;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si un type de notification est activé */
public boolean isTypeActive(TypeNotification type) {
if (!notificationsActivees) return false;
if (typesDesactivees != null && typesDesactivees.contains(type)) return false;
if (typesActives != null) return typesActives.contains(type);
return type.isActiveeParDefaut();
}
/** Vérifie si un canal de notification est activé */
public boolean isCanalActif(CanalNotification canal) {
if (!notificationsActivees) return false;
if (canauxDesactives != null && canauxDesactives.contains(canal)) return false;
if (canauxActifs != null) return canauxActifs.contains(canal);
return true;
}
/**
* Version de test de isEnModeSilencieux avec une heure fixe
* Permet de tester toutes les branches sans dépendre de l'heure actuelle
*/
boolean isEnModeSilencieux(LocalTime heureActuelle) {
if (!modeSilencieux) return false;
if (heureDebutSilencieux == null || heureFinSilencieux == null) return false;
// Gestion du cas où la période traverse minuit
if (heureDebutSilencieux.isAfter(heureFinSilencieux)) {
return heureActuelle.isAfter(heureDebutSilencieux) || heureActuelle.isBefore(heureFinSilencieux);
} else {
return heureActuelle.isAfter(heureDebutSilencieux) && heureActuelle.isBefore(heureFinSilencieux);
}
}
/** Vérifie si on est en mode silencieux actuellement */
public boolean isEnModeSilencieux() {
return isEnModeSilencieux(LocalTime.now());
}
/** Vérifie si un expéditeur est bloqué */
public boolean isExpediteurBloque(String expediteurId) {
return expediteursBloqués != null && expediteursBloqués.contains(expediteurId);
}
/** Vérifie si un expéditeur est prioritaire */
public boolean isExpediteurPrioritaire(String expediteurId) {
return expediteursPrioritaires != null && expediteursPrioritaires.contains(expediteurId);
}
@Override
public String toString() {
return String.format(
"PreferencesNotificationDTO{utilisateurId='%s', notificationsActivees=%s}",
utilisateurId, notificationsActivees);
}
}

View File

@@ -0,0 +1,46 @@
package dev.lions.unionflow.server.api.dto.notification;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des templates de notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class TemplateNotificationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Code unique du template */
@NotBlank(message = "Le code est obligatoire")
private String code;
/** Sujet du template */
private String sujet;
/** Corps du template (texte) */
private String corpsTexte;
/** Corps du template (HTML) */
private String corpsHtml;
/** Variables disponibles (JSON) */
private String variablesDisponibles;
/** Canaux supportés (JSON array) */
private String canauxSupportes;
/** Langue du template */
private String langue;
/** Description */
private String description;
}

View File

@@ -0,0 +1,518 @@
package dev.lions.unionflow.server.api.dto.organisation;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation;
import dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Email;
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.time.Period;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des organisations (Lions Club, Associations, Coopératives, etc.) Représente
* une organisation avec ses informations complètes et sa hiérarchie
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class OrganisationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Nom de l'organisation */
@NotBlank(message = "Le nom de l'organisation" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.NOM_ORGANISATION_MIN_LENGTH,
max = ValidationConstants.NOM_ORGANISATION_MAX_LENGTH,
message = ValidationConstants.NOM_ORGANISATION_SIZE_MESSAGE)
private String nom;
/** Nom court ou sigle */
@Size(
max = ValidationConstants.NOM_COURT_MAX_LENGTH,
message = ValidationConstants.NOM_COURT_SIZE_MESSAGE)
private String nomCourt;
/** Type d'organisation */
@NotNull(message = "Le type d'organisation est obligatoire")
private TypeOrganisation typeOrganisation;
/** Statut de l'organisation */
@NotNull(message = "Le statut de l'organisation est obligatoire")
private StatutOrganisation statut;
/** Description de l'organisation */
@Size(
max = ValidationConstants.DESCRIPTION_MAX_LENGTH,
message = ValidationConstants.DESCRIPTION_SIZE_MESSAGE)
private String description;
/** Date de fondation */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFondation;
/** Numéro d'enregistrement officiel */
@Size(max = 100, message = "Le numéro d'enregistrement ne peut pas dépasser 100 caractères")
private String numeroEnregistrement;
/** Adresse complète */
@Size(max = 500, message = "L'adresse ne peut pas dépasser 500 caractères")
private String adresse;
/** Ville */
@Size(max = 100, message = "La ville ne peut pas dépasser 100 caractères")
private String ville;
/** Région/Province */
@Size(max = 100, message = "La région ne peut pas dépasser 100 caractères")
private String region;
/** Pays */
@Size(max = 100, message = "Le pays ne peut pas dépasser 100 caractères")
private String pays;
/** Code postal */
@Pattern(regexp = "^[0-9A-Z\\-\\s]{3,10}$", message = "Format de code postal invalide")
private String codePostal;
/** Latitude pour géolocalisation */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(
integer = 2,
fraction = 8,
message = "La latitude ne peut avoir plus de 2 chiffres entiers et 8 décimales")
private BigDecimal latitude;
/** Longitude pour géolocalisation */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(
integer = 3,
fraction = 8,
message = "La longitude ne peut avoir plus de 3 chiffres entiers et 8 décimales")
private BigDecimal longitude;
/** Téléphone principal */
@Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$", message = "Format de téléphone invalide")
private String telephone;
/** Téléphone secondaire */
@Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$", message = "Format de téléphone invalide")
private String telephoneSecondaire;
/** Email principal */
@Email(message = "Format d'email invalide")
@Size(max = 200, message = "L'email ne peut pas dépasser 200 caractères")
private String email;
/** Email secondaire */
@Email(message = "Format d'email invalide")
@Size(max = 200, message = "L'email ne peut pas dépasser 200 caractères")
private String emailSecondaire;
/** Site web */
@Pattern(
regexp =
"^https?://[\\w\\-]+(\\.[\\w\\-]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?$",
message = "Format d'URL invalide")
@Size(max = 500, message = "L'URL ne peut pas dépasser 500 caractères")
private String siteWeb;
/** Logo de l'organisation (URL ou nom de fichier) */
@Size(max = 500, message = "Le logo ne peut pas dépasser 500 caractères")
private String logo;
/** Organisation parente (pour hiérarchie) */
private UUID organisationParenteId;
/** Nom de l'organisation parente */
private String nomOrganisationParente;
/** Niveau hiérarchique (0 = racine) */
private Integer niveauHierarchique;
/** Nombre de membres actifs */
private Integer nombreMembres;
/** Nombre d'administrateurs */
private Integer nombreAdministrateurs;
/** Budget annuel */
@DecimalMin(value = "0.0", message = "Le budget doit être positif")
@Digits(
integer = 12,
fraction = 2,
message = "Le budget ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal budgetAnnuel;
/** Devise du budget */
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String devise;
/** Objectifs de l'organisation */
@Size(max = 2000, message = "Les objectifs ne peuvent pas dépasser 2000 caractères")
private String objectifs;
/** Activités principales */
@Size(max = 2000, message = "Les activités ne peuvent pas dépasser 2000 caractères")
private String activitesPrincipales;
/** Réseaux sociaux (JSON) */
@Size(max = 1000, message = "Les réseaux sociaux ne peuvent pas dépasser 1000 caractères")
private String reseauxSociaux;
/** Certifications ou labels */
@Size(max = 500, message = "Les certifications ne peuvent pas dépasser 500 caractères")
private String certifications;
/** Partenaires principaux */
@Size(max = 1000, message = "Les partenaires ne peuvent pas dépasser 1000 caractères")
private String partenaires;
/** Notes administratives */
@Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères")
private String notes;
/** Organisation publique (visible dans l'annuaire) */
private Boolean organisationPublique;
/** Accepte nouveaux membres */
private Boolean accepteNouveauxMembres;
/** Cotisation obligatoire */
private Boolean cotisationObligatoire;
/** Montant cotisation annuelle */
@DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal montantCotisationAnnuelle;
// Constructeurs
public OrganisationDTO() {
super();
this.statut = StatutOrganisation.ACTIVE;
this.typeOrganisation = TypeOrganisation.ASSOCIATION;
this.devise = "XOF";
this.niveauHierarchique = 0;
this.nombreMembres = 0;
this.nombreAdministrateurs = 0;
this.organisationPublique = true;
this.accepteNouveauxMembres = true;
this.cotisationObligatoire = false;
}
public OrganisationDTO(String nom, TypeOrganisation type) {
this();
this.nom = nom;
this.typeOrganisation = type;
}
// Méthodes utilitaires
/**
* Vérifie si l'organisation est active
*
* @return true si l'organisation est active
*/
public boolean estActive() {
return StatutOrganisation.ACTIVE.equals(statut);
}
/**
* Vérifie si l'organisation est inactive
*
* @return true si l'organisation est inactive
*/
public boolean estInactive() {
return StatutOrganisation.INACTIVE.equals(statut);
}
/**
* Vérifie si l'organisation est suspendue
*
* @return true si l'organisation est suspendue
*/
public boolean estSuspendue() {
return StatutOrganisation.SUSPENDUE.equals(statut);
}
/**
* Vérifie si l'organisation est en cours de création
*
* @return true si l'organisation est en création
*/
public boolean estEnCreation() {
return StatutOrganisation.EN_CREATION.equals(statut);
}
/**
* Vérifie si l'organisation est dissoute
*
* @return true si l'organisation est dissoute
*/
public boolean estDissoute() {
return StatutOrganisation.DISSOUTE.equals(statut);
}
/**
* Calcule l'ancienneté de l'organisation en années
*
* @return L'ancienneté en années
*/
public int getAncienneteAnnees() {
if (dateFondation == null) return 0;
return Period.between(dateFondation, LocalDate.now()).getYears();
}
/**
* Calcule l'ancienneté de l'organisation en mois
*
* @return L'ancienneté en mois
*/
public int getAncienneteMois() {
if (dateFondation == null) return 0;
Period periode = Period.between(dateFondation, LocalDate.now());
return periode.getYears() * 12 + periode.getMonths();
}
/**
* Vérifie si l'organisation a une géolocalisation
*
* @return true si latitude et longitude sont définies
*/
public boolean possedGeolocalisation() {
return latitude != null && longitude != null;
}
/**
* Vérifie si l'organisation est une organisation racine (sans parent)
*
* @return true si l'organisation n'a pas de parent
*/
public boolean estOrganisationRacine() {
return organisationParenteId == null;
}
/**
* Vérifie si l'organisation a des sous-organisations
*
* @return true si le niveau hiérarchique est supérieur à 0
*/
public boolean possedeSousOrganisations() {
return niveauHierarchique != null && niveauHierarchique > 0;
}
/**
* Retourne le nom d'affichage (nom court si disponible, sinon nom complet)
*
* @return Le nom d'affichage
*/
public String getNomAffichage() {
return (nomCourt != null && !nomCourt.trim().isEmpty()) ? nomCourt : nom;
}
/**
* Retourne l'adresse complète formatée
*
* @return L'adresse complète
*/
public String getAdresseComplete() {
StringBuilder sb = new StringBuilder();
if (adresse != null && !adresse.trim().isEmpty()) {
sb.append(adresse);
}
if (ville != null && !ville.trim().isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(ville);
}
if (codePostal != null && !codePostal.trim().isEmpty()) {
if (sb.length() > 0) sb.append(" ");
sb.append(codePostal);
}
if (region != null && !region.trim().isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(region);
}
if (pays != null && !pays.trim().isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(pays);
}
return sb.toString();
}
/**
* Calcule le ratio administrateurs/membres
*
* @return Le pourcentage d'administrateurs
*/
public double getRatioAdministrateurs() {
if (nombreMembres == null || nombreMembres == 0) return 0.0;
if (nombreAdministrateurs == null) return 0.0;
return (nombreAdministrateurs.doubleValue() / nombreMembres.doubleValue()) * 100.0;
}
/**
* Vérifie si l'organisation a un budget défini
*
* @return true si un budget annuel est défini
*/
public boolean hasBudget() {
return budgetAnnuel != null && budgetAnnuel.compareTo(BigDecimal.ZERO) > 0;
}
/**
* Active l'organisation
*
* @param utilisateur L'utilisateur qui active l'organisation
*/
public void activer(String utilisateur) {
this.statut = StatutOrganisation.ACTIVE;
marquerCommeModifie(utilisateur);
}
/**
* Suspend l'organisation
*
* @param utilisateur L'utilisateur qui suspend l'organisation
*/
public void suspendre(String utilisateur) {
this.statut = StatutOrganisation.SUSPENDUE;
marquerCommeModifie(utilisateur);
}
/**
* Dissout l'organisation
*
* @param utilisateur L'utilisateur qui dissout l'organisation
*/
public void dissoudre(String utilisateur) {
this.statut = StatutOrganisation.DISSOUTE;
this.accepteNouveauxMembres = false;
marquerCommeModifie(utilisateur);
}
/**
* Désactive l'organisation
*
* @param utilisateur L'utilisateur qui désactive l'organisation
*/
public void desactiver(String utilisateur) {
this.statut = StatutOrganisation.INACTIVE;
this.accepteNouveauxMembres = false;
marquerCommeModifie(utilisateur);
}
/**
* Met à jour le nombre de membres
*
* @param nouveauNombre Le nouveau nombre de membres
* @param utilisateur L'utilisateur qui fait la mise à jour
*/
public void mettreAJourNombreMembres(int nouveauNombre, String utilisateur) {
this.nombreMembres = nouveauNombre;
marquerCommeModifie(utilisateur);
}
/**
* Ajoute un membre
*
* @param utilisateur L'utilisateur qui ajoute le membre
*/
public void ajouterMembre(String utilisateur) {
if (nombreMembres == null) nombreMembres = 0;
nombreMembres++;
marquerCommeModifie(utilisateur);
}
/**
* Retire un membre
*
* @param utilisateur L'utilisateur qui retire le membre
*/
public void retirerMembre(String utilisateur) {
if (nombreMembres != null && nombreMembres > 0) {
nombreMembres--;
marquerCommeModifie(utilisateur);
}
}
@Override
public String toString() {
return "OrganisationDTO{"
+ "nom='"
+ nom
+ '\''
+ ", nomCourt='"
+ nomCourt
+ '\''
+ ", typeOrganisation="
+ typeOrganisation
+ ", statut="
+ statut
+ ", ville='"
+ ville
+ '\''
+ ", pays='"
+ pays
+ '\''
+ ", nombreMembres="
+ nombreMembres
+ ", anciennete="
+ getAncienneteAnnees()
+ " ans"
+ "} "
+ super.toString();
}
// === MÉTHODES UTILITAIRES ===
/** Retourne le libellé du statut */
public String getStatutLibelle() {
return statut != null ? statut.getLibelle() : "Non défini";
}
/** Retourne le libellé du type d'organisation */
public String getTypeLibelle() {
return typeOrganisation != null ? typeOrganisation.getLibelle() : "Non défini";
}
/** Ajoute un administrateur */
public void ajouterAdministrateur(String utilisateur) {
if (nombreAdministrateurs == null) nombreAdministrateurs = 0;
nombreAdministrateurs++;
marquerCommeModifie(utilisateur);
}
/** Retire un administrateur */
public void retirerAdministrateur(String utilisateur) {
if (nombreAdministrateurs != null && nombreAdministrateurs > 0) {
nombreAdministrateurs--;
marquerCommeModifie(utilisateur);
}
}
}

View File

@@ -0,0 +1,46 @@
package dev.lions.unionflow.server.api.dto.organisation;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour le catalogue des types d'organisation.
*
* <p>Permet de gérer dynamiquement la liste des types utilisables par les organisations
* (codes, libellés, ordre d'affichage, activation/désactivation dans l'UI).
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
@Getter
@Setter
public class TypeOrganisationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Code fonctionnel unique du type (ex: LIONS_CLUB, ASSOCIATION, COOPERATIVE, ...) */
@NotBlank(message = "Le code du type d'organisation est obligatoire")
@Size(max = 50, message = "Le code du type d'organisation ne peut pas dépasser 50 caractères")
private String code;
/** Libellé affiché dans l'interface utilisateur */
@NotBlank(message = "Le libellé du type d'organisation est obligatoire")
@Size(max = 150, message = "Le libellé du type d'organisation ne peut pas dépasser 150 caractères")
private String libelle;
/** Description fonctionnelle (optionnelle) */
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
/** Ordre d'affichage dans les listes déroulantes */
private Integer ordreAffichage;
/** Indique si le type est actif dans l'application */
private Boolean actif = true;
}

View File

@@ -0,0 +1,80 @@
package dev.lions.unionflow.server.api.dto.paiement;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des paiements
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class PaiementDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de référence unique */
@NotBlank(message = "Le numéro de référence est obligatoire")
private String numeroReference;
/** Montant du paiement */
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2)
private BigDecimal montant;
/** Code devise (ISO 3 lettres) */
@NotBlank(message = "Le code devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
private String codeDevise;
/** Méthode de paiement */
@NotNull(message = "La méthode de paiement est obligatoire")
private MethodePaiement methodePaiement;
/** Statut du paiement */
@NotNull(message = "Le statut du paiement est obligatoire")
private StatutPaiement statutPaiement;
/** Date de paiement */
private LocalDateTime datePaiement;
/** Date de validation */
private LocalDateTime dateValidation;
/** Validateur (email de l'administrateur) */
private String validateur;
/** Référence externe */
private String referenceExterne;
/** URL de preuve de paiement */
private String urlPreuve;
/** Commentaires et notes */
private String commentaire;
/** Adresse IP de l'initiateur */
private String ipAddress;
/** User-Agent de l'initiateur */
private String userAgent;
/** ID du membre payeur */
@NotNull(message = "Le membre payeur est obligatoire")
private UUID membreId;
/** ID de la transaction Wave (si applicable) */
private UUID transactionWaveId;
}

View File

@@ -0,0 +1,363 @@
package dev.lions.unionflow.server.api.dto.paiement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la consultation du solde Wave Money (Balance API) Représente le solde d'un wallet Wave
* Business
*
* <p>Basé sur l'API officielle Wave : https://docs.wave.com/business#balance-api
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class WaveBalanceDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Solde disponible */
@NotNull(message = "Le solde disponible est obligatoire")
@DecimalMin(value = "0.0", message = "Le solde doit être positif ou nul")
@Digits(
integer = 12,
fraction = 2,
message = "Le solde ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal soldeDisponible;
/** Solde en attente (transactions en cours) */
@DecimalMin(value = "0.0", message = "Le solde en attente doit être positif ou nul")
@Digits(
integer = 12,
fraction = 2,
message = "Le solde ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal soldeEnAttente;
/** Solde total (disponible + en attente) */
@DecimalMin(value = "0.0", message = "Le solde total doit être positif ou nul")
@Digits(
integer = 12,
fraction = 2,
message = "Le solde ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal soldeTotal;
/** Devise du wallet */
@NotBlank(message = "La devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String devise = "XOF";
/** Numéro du wallet Wave */
@NotBlank(message = "Le numéro de wallet est obligatoire")
private String numeroWallet;
/** Nom du business associé au wallet */
private String nomBusiness;
/** Date de dernière mise à jour du solde */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereMiseAJour;
/** Date de dernière synchronisation avec Wave */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateDerniereSynchronisation;
/** Statut du wallet */
@Pattern(
regexp = "^(ACTIVE|INACTIVE|SUSPENDED|BLOCKED)$",
message = "Le statut doit être ACTIVE, INACTIVE, SUSPENDED ou BLOCKED")
private String statutWallet;
/** Limite de transaction quotidienne */
@DecimalMin(value = "0.0", message = "La limite doit être positive ou nulle")
@Digits(
integer = 12,
fraction = 2,
message = "La limite ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal limiteQuotidienne;
/** Montant déjà utilisé aujourd'hui */
@DecimalMin(value = "0.0", message = "Le montant utilisé doit être positif ou nul")
@Digits(
integer = 12,
fraction = 2,
message = "Le montant ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal montantUtiliseAujourdhui;
/** Limite mensuelle */
@DecimalMin(value = "0.0", message = "La limite doit être positive ou nulle")
@Digits(
integer = 12,
fraction = 2,
message = "La limite ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal limiteMensuelle;
/** Montant utilisé ce mois */
@DecimalMin(value = "0.0", message = "Le montant utilisé doit être positif ou nul")
@Digits(
integer = 12,
fraction = 2,
message = "Le montant ne peut avoir plus de 12 chiffres entiers et 2 décimales")
private BigDecimal montantUtiliseCeMois;
/** Nombre de transactions aujourd'hui */
private Integer nombreTransactionsAujourdhui;
/** Nombre de transactions ce mois */
private Integer nombreTransactionsCeMois;
/** Dernière erreur de synchronisation */
private String derniereErreur;
/** Code de la dernière erreur */
private String codeDerniereErreur;
// Constructeurs
public WaveBalanceDTO() {
super();
this.devise = "XOF";
this.statutWallet = "ACTIVE";
this.soldeEnAttente = BigDecimal.ZERO;
this.montantUtiliseAujourdhui = BigDecimal.ZERO;
this.montantUtiliseCeMois = BigDecimal.ZERO;
this.nombreTransactionsAujourdhui = 0;
this.nombreTransactionsCeMois = 0;
}
public WaveBalanceDTO(String numeroWallet, BigDecimal soldeDisponible) {
this();
this.numeroWallet = numeroWallet;
this.soldeDisponible = soldeDisponible;
this.soldeTotal = soldeDisponible;
}
// Getters et Setters
public BigDecimal getSoldeDisponible() {
return soldeDisponible;
}
public void setSoldeDisponible(BigDecimal soldeDisponible) {
this.soldeDisponible = soldeDisponible;
calculerSoldeTotal();
}
public BigDecimal getSoldeEnAttente() {
return soldeEnAttente;
}
public void setSoldeEnAttente(BigDecimal soldeEnAttente) {
this.soldeEnAttente = soldeEnAttente;
calculerSoldeTotal();
}
public BigDecimal getSoldeTotal() {
return soldeTotal;
}
public void setSoldeTotal(BigDecimal soldeTotal) {
this.soldeTotal = soldeTotal;
}
public String getDevise() {
return devise;
}
public void setDevise(String devise) {
this.devise = devise;
}
public String getNumeroWallet() {
return numeroWallet;
}
public void setNumeroWallet(String numeroWallet) {
this.numeroWallet = numeroWallet;
}
public String getNomBusiness() {
return nomBusiness;
}
public void setNomBusiness(String nomBusiness) {
this.nomBusiness = nomBusiness;
}
public LocalDateTime getDateDerniereMiseAJour() {
return dateDerniereMiseAJour;
}
public void setDateDerniereMiseAJour(LocalDateTime dateDerniereMiseAJour) {
this.dateDerniereMiseAJour = dateDerniereMiseAJour;
}
public LocalDateTime getDateDerniereSynchronisation() {
return dateDerniereSynchronisation;
}
public void setDateDerniereSynchronisation(LocalDateTime dateDerniereSynchronisation) {
this.dateDerniereSynchronisation = dateDerniereSynchronisation;
}
public String getStatutWallet() {
return statutWallet;
}
public void setStatutWallet(String statutWallet) {
this.statutWallet = statutWallet;
}
public BigDecimal getLimiteQuotidienne() {
return limiteQuotidienne;
}
public void setLimiteQuotidienne(BigDecimal limiteQuotidienne) {
this.limiteQuotidienne = limiteQuotidienne;
}
public BigDecimal getMontantUtiliseAujourdhui() {
return montantUtiliseAujourdhui;
}
public void setMontantUtiliseAujourdhui(BigDecimal montantUtiliseAujourdhui) {
this.montantUtiliseAujourdhui = montantUtiliseAujourdhui;
}
public BigDecimal getLimiteMensuelle() {
return limiteMensuelle;
}
public void setLimiteMensuelle(BigDecimal limiteMensuelle) {
this.limiteMensuelle = limiteMensuelle;
}
public BigDecimal getMontantUtiliseCeMois() {
return montantUtiliseCeMois;
}
public void setMontantUtiliseCeMois(BigDecimal montantUtiliseCeMois) {
this.montantUtiliseCeMois = montantUtiliseCeMois;
}
public Integer getNombreTransactionsAujourdhui() {
return nombreTransactionsAujourdhui;
}
public void setNombreTransactionsAujourdhui(Integer nombreTransactionsAujourdhui) {
this.nombreTransactionsAujourdhui = nombreTransactionsAujourdhui;
}
public Integer getNombreTransactionsCeMois() {
return nombreTransactionsCeMois;
}
public void setNombreTransactionsCeMois(Integer nombreTransactionsCeMois) {
this.nombreTransactionsCeMois = nombreTransactionsCeMois;
}
public String getDerniereErreur() {
return derniereErreur;
}
public void setDerniereErreur(String derniereErreur) {
this.derniereErreur = derniereErreur;
}
public String getCodeDerniereErreur() {
return codeDerniereErreur;
}
public void setCodeDerniereErreur(String codeDerniereErreur) {
this.codeDerniereErreur = codeDerniereErreur;
}
// Méthodes utilitaires
/** Calcule le solde total */
private void calculerSoldeTotal() {
if (soldeDisponible != null && soldeEnAttente != null) {
this.soldeTotal = soldeDisponible.add(soldeEnAttente);
}
}
/**
* Vérifie si le wallet est actif
*
* @return true si le wallet est actif
*/
public boolean isWalletActif() {
return "ACTIVE".equals(statutWallet);
}
/**
* Vérifie si le solde est suffisant pour un montant donné
*
* @param montant Le montant à vérifier
* @return true si le solde est suffisant
*/
public boolean isSoldeSuffisant(BigDecimal montant) {
return soldeDisponible != null && soldeDisponible.compareTo(montant) >= 0;
}
/**
* Calcule le solde disponible restant pour aujourd'hui
*
* @return Le montant encore disponible aujourd'hui
*/
public BigDecimal getSoldeDisponibleAujourdhui() {
if (soldeDisponible == null || limiteQuotidienne == null || montantUtiliseAujourdhui == null) {
return soldeDisponible;
}
BigDecimal limiteRestante = limiteQuotidienne.subtract(montantUtiliseAujourdhui);
return soldeDisponible.min(limiteRestante);
}
/**
* Met à jour les statistiques après une transaction
*
* @param montant Le montant de la transaction
*/
public void mettreAJourApresTransaction(BigDecimal montant) {
if (montantUtiliseAujourdhui == null) montantUtiliseAujourdhui = BigDecimal.ZERO;
if (montantUtiliseCeMois == null) montantUtiliseCeMois = BigDecimal.ZERO;
if (nombreTransactionsAujourdhui == null) nombreTransactionsAujourdhui = 0;
if (nombreTransactionsCeMois == null) nombreTransactionsCeMois = 0;
this.montantUtiliseAujourdhui = montantUtiliseAujourdhui.add(montant);
this.montantUtiliseCeMois = montantUtiliseCeMois.add(montant);
this.nombreTransactionsAujourdhui++;
this.nombreTransactionsCeMois++;
this.dateDerniereMiseAJour = LocalDateTime.now();
}
@Override
public String toString() {
return "WaveBalanceDTO{"
+ "numeroWallet='"
+ numeroWallet
+ '\''
+ ", soldeDisponible="
+ soldeDisponible
+ ", soldeTotal="
+ soldeTotal
+ ", devise='"
+ devise
+ '\''
+ ", statutWallet='"
+ statutWallet
+ '\''
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,168 @@
package dev.lions.unionflow.server.api.dto.paiement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.paiement.StatutSession;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
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.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour les sessions de paiement Wave Money (Checkout API) Représente une session de paiement
* créée via l'API Wave Checkout
*
* <p>Basé sur l'API officielle Wave : https://docs.wave.com/business#checkout-api
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class WaveCheckoutSessionDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** ID de la session Wave (retourné par l'API) */
@NotBlank(message = "L'ID de session Wave est obligatoire")
private String waveSessionId;
/** URL de la session de paiement Wave (générée par l'API Wave) */
@Size(max = 500, message = "L'URL ne peut pas dépasser 500 caractères")
private String waveUrl;
/** Montant du paiement */
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.01", message = "Le montant doit être positif")
@Digits(
integer = 10,
fraction = 2,
message = "Le montant ne peut avoir plus de 10 chiffres entiers et 2 décimales")
private BigDecimal montant;
/** Devise (XOF pour le Sénégal) */
@NotBlank(message = "La devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String devise = "XOF";
/** URL de succès (redirection après paiement réussi) */
@NotBlank(message = "L'URL de succès est obligatoire")
@Size(max = 500, message = "L'URL de succès ne peut pas dépasser 500 caractères")
private String successUrl;
/** URL d'erreur (redirection après échec) */
@NotBlank(message = "L'URL d'erreur est obligatoire")
@Size(max = 500, message = "L'URL d'erreur ne peut pas dépasser 500 caractères")
private String errorUrl;
/** Statut de la session */
@NotNull(message = "Le statut est obligatoire")
private StatutSession statut;
/** ID de l'organisation qui effectue le paiement */
private UUID organisationId;
/** Nom de l'organisation */
private String nomOrganisation;
/** ID du membre qui effectue le paiement */
private UUID membreId;
/** Nom du membre */
private String nomMembre;
/** Type de paiement (COTISATION, ABONNEMENT, DON, AUTRE) */
@Pattern(
regexp = "^(COTISATION|ABONNEMENT|DON|EVENEMENT|FORMATION|AUTRE)$",
message = "Type de paiement invalide")
private String typePaiement;
/** Référence du paiement dans UnionFlow */
@Size(max = 100, message = "La référence ne peut pas dépasser 100 caractères")
private String referenceUnionFlow;
/** Description du paiement */
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
/** Nom du business affiché (override_business_name) */
@Size(max = 100, message = "Le nom du business ne peut pas dépasser 100 caractères")
private String nomBusinessAffiche;
/** ID du marchand agrégé (si applicable) */
private String aggregatedMerchantId;
/** Date d'expiration de la session */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateExpiration;
/** Date de completion du paiement */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateCompletion;
/** Numéro de téléphone du payeur (si fourni) */
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Format de numéro de téléphone invalide")
private String telephonePayeur;
/** Email du payeur (si fourni) */
@Pattern(regexp = "^[A-Za-z0-9+_.-]+@(.+)$", message = "Format d'email invalide")
private String emailPayeur;
/** Adresse IP du client */
private String adresseIpClient;
/** User Agent du navigateur */
@Size(max = 500, message = "Le User Agent ne peut pas dépasser 500 caractères")
private String userAgent;
/** Données de callback reçues de Wave */
@Size(max = 2000, message = "Les données callback ne peuvent pas dépasser 2000 caractères")
private String callbackData;
/** Code d'erreur Wave (si échec) */
private String codeErreurWave;
/** Message d'erreur Wave (si échec) */
@Size(max = 500, message = "Le message d'erreur ne peut pas dépasser 500 caractères")
private String messageErreurWave;
/** Nombre de tentatives de paiement */
private Integer nombreTentatives;
/** Session liée à un webhook */
private Boolean webhookRecu;
/** Date de réception du webhook */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateWebhook;
/** Données du webhook reçu */
@Size(max = 2000, message = "Les données webhook ne peuvent pas dépasser 2000 caractères")
private String donneesWebhook;
// Constructeurs
public WaveCheckoutSessionDTO() {
super();
this.devise = "XOF";
this.statut = StatutSession.PENDING;
this.nombreTentatives = 0;
this.webhookRecu = false;
}
public WaveCheckoutSessionDTO(BigDecimal montant, String successUrl, String errorUrl) {
this();
this.montant = montant;
this.successUrl = successUrl;
this.errorUrl = errorUrl;
}
// Getters et Setters générés automatiquement par Lombok @Getter/@Setter
}

View File

@@ -0,0 +1,464 @@
package dev.lions.unionflow.server.api.dto.paiement;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.paiement.StatutTraitement;
import dev.lions.unionflow.server.api.enums.paiement.TypeEvenement;
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.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour les webhooks Wave Money Représente les notifications reçues de Wave lors d'événements
*
* <p>Basé sur l'API officielle Wave : https://docs.wave.com/business#webhooks
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@Getter
@Setter
public class WaveWebhookDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** ID unique du webhook Wave */
@NotBlank(message = "L'ID du webhook est obligatoire")
private String webhookId;
/** Type d'événement */
@NotNull(message = "Le type d'événement est obligatoire")
private TypeEvenement typeEvenement;
/** Code de l'événement tel que reçu de Wave */
@NotBlank(message = "Le code événement est obligatoire")
private String codeEvenement;
/** Statut de traitement du webhook */
@NotNull(message = "Le statut de traitement est obligatoire")
private StatutTraitement statutTraitement;
/** Payload JSON complet reçu de Wave */
@NotBlank(message = "Le payload est obligatoire")
@Size(max = 5000, message = "Le payload ne peut pas dépasser 5000 caractères")
private String payloadJson;
/** Headers HTTP reçus */
@Size(max = 2000, message = "Les headers ne peuvent pas dépasser 2000 caractères")
private String headersHttp;
/** Signature Wave pour vérification */
private String signatureWave;
/** Date de réception du webhook */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateReception;
/** Date de traitement du webhook */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTraitement;
/** ID de la session checkout concernée (si applicable) */
private String sessionCheckoutId;
/** ID de la transaction Wave concernée */
private String transactionWaveId;
/** Montant de la transaction (si applicable) */
private BigDecimal montantTransaction;
/** Devise de la transaction */
@Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres")
private String deviseTransaction;
/** Statut de la transaction Wave */
private String statutTransactionWave;
/** ID de l'organisation UnionFlow concernée */
private UUID organisationId;
/** ID du membre UnionFlow concerné */
private UUID membreId;
/** Référence UnionFlow liée */
private String referenceUnionFlow;
/** Type de paiement UnionFlow */
@Pattern(
regexp = "^(COTISATION|ABONNEMENT|DON|EVENEMENT|FORMATION|AUTRE)$",
message = "Type de paiement invalide")
private String typePaiementUnionFlow;
/** Adresse IP source du webhook */
private String adresseIpSource;
/** User Agent du webhook */
@Size(max = 500, message = "Le User Agent ne peut pas dépasser 500 caractères")
private String userAgentSource;
/** Nombre de tentatives de traitement */
private Integer nombreTentativesTraitement;
/** Message d'erreur de traitement (si échec) */
@Size(max = 1000, message = "Le message d'erreur ne peut pas dépasser 1000 caractères")
private String messageErreurTraitement;
/** Code d'erreur de traitement (si échec) */
private String codeErreurTraitement;
/** Stack trace de l'erreur (si échec) */
@Size(max = 3000, message = "La stack trace ne peut pas dépasser 3000 caractères")
private String stackTraceErreur;
/** Webhook traité automatiquement */
private Boolean traitementAutomatique;
/** Webhook nécessitant une intervention manuelle */
private Boolean interventionManuelleRequise;
/** Notes de traitement manuel */
@Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères")
private String notesTraitementManuel;
/** Utilisateur ayant traité manuellement */
private String utilisateurTraitementManuel;
/** Date du traitement manuel */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTraitementManuel;
// Constructeurs
public WaveWebhookDTO() {
super();
this.statutTraitement = StatutTraitement.RECU;
this.dateReception = LocalDateTime.now();
this.nombreTentativesTraitement = 0;
this.traitementAutomatique = true;
this.interventionManuelleRequise = false;
}
public WaveWebhookDTO(String webhookId, TypeEvenement typeEvenement, String payloadJson) {
this();
this.webhookId = webhookId;
this.typeEvenement = typeEvenement;
this.codeEvenement = typeEvenement.getCodeWave();
this.payloadJson = payloadJson;
}
// Getters et Setters
public String getWebhookId() {
return webhookId;
}
public void setWebhookId(String webhookId) {
this.webhookId = webhookId;
}
public TypeEvenement getTypeEvenement() {
return typeEvenement;
}
public void setTypeEvenement(TypeEvenement typeEvenement) {
this.typeEvenement = typeEvenement;
if (typeEvenement != null) {
this.codeEvenement = typeEvenement.getCodeWave();
}
}
public String getCodeEvenement() {
return codeEvenement;
}
public void setCodeEvenement(String codeEvenement) {
this.codeEvenement = codeEvenement;
this.typeEvenement = TypeEvenement.fromCode(codeEvenement);
}
public StatutTraitement getStatutTraitement() {
return statutTraitement;
}
public void setStatutTraitement(StatutTraitement statutTraitement) {
this.statutTraitement = statutTraitement;
}
public String getPayloadJson() {
return payloadJson;
}
public void setPayloadJson(String payloadJson) {
this.payloadJson = payloadJson;
}
public String getHeadersHttp() {
return headersHttp;
}
public void setHeadersHttp(String headersHttp) {
this.headersHttp = headersHttp;
}
public String getSignatureWave() {
return signatureWave;
}
public void setSignatureWave(String signatureWave) {
this.signatureWave = signatureWave;
}
public LocalDateTime getDateReception() {
return dateReception;
}
public void setDateReception(LocalDateTime dateReception) {
this.dateReception = dateReception;
}
public LocalDateTime getDateTraitement() {
return dateTraitement;
}
public void setDateTraitement(LocalDateTime dateTraitement) {
this.dateTraitement = dateTraitement;
}
public String getSessionCheckoutId() {
return sessionCheckoutId;
}
public void setSessionCheckoutId(String sessionCheckoutId) {
this.sessionCheckoutId = sessionCheckoutId;
}
public String getTransactionWaveId() {
return transactionWaveId;
}
public void setTransactionWaveId(String transactionWaveId) {
this.transactionWaveId = transactionWaveId;
}
public BigDecimal getMontantTransaction() {
return montantTransaction;
}
public void setMontantTransaction(BigDecimal montantTransaction) {
this.montantTransaction = montantTransaction;
}
public String getDeviseTransaction() {
return deviseTransaction;
}
public void setDeviseTransaction(String deviseTransaction) {
this.deviseTransaction = deviseTransaction;
}
public String getStatutTransactionWave() {
return statutTransactionWave;
}
public void setStatutTransactionWave(String statutTransactionWave) {
this.statutTransactionWave = statutTransactionWave;
}
public UUID getOrganisationId() {
return organisationId;
}
public void setOrganisationId(UUID organisationId) {
this.organisationId = organisationId;
}
public UUID getMembreId() {
return membreId;
}
public void setMembreId(UUID membreId) {
this.membreId = membreId;
}
public String getReferenceUnionFlow() {
return referenceUnionFlow;
}
public void setReferenceUnionFlow(String referenceUnionFlow) {
this.referenceUnionFlow = referenceUnionFlow;
}
public String getTypePaiementUnionFlow() {
return typePaiementUnionFlow;
}
public void setTypePaiementUnionFlow(String typePaiementUnionFlow) {
this.typePaiementUnionFlow = typePaiementUnionFlow;
}
public String getAdresseIpSource() {
return adresseIpSource;
}
public void setAdresseIpSource(String adresseIpSource) {
this.adresseIpSource = adresseIpSource;
}
public String getUserAgentSource() {
return userAgentSource;
}
public void setUserAgentSource(String userAgentSource) {
this.userAgentSource = userAgentSource;
}
public Integer getNombreTentativesTraitement() {
return nombreTentativesTraitement;
}
public void setNombreTentativesTraitement(Integer nombreTentativesTraitement) {
this.nombreTentativesTraitement = nombreTentativesTraitement;
}
public String getMessageErreurTraitement() {
return messageErreurTraitement;
}
public void setMessageErreurTraitement(String messageErreurTraitement) {
this.messageErreurTraitement = messageErreurTraitement;
}
public String getCodeErreurTraitement() {
return codeErreurTraitement;
}
public void setCodeErreurTraitement(String codeErreurTraitement) {
this.codeErreurTraitement = codeErreurTraitement;
}
public String getStackTraceErreur() {
return stackTraceErreur;
}
public void setStackTraceErreur(String stackTraceErreur) {
this.stackTraceErreur = stackTraceErreur;
}
public Boolean getTraitementAutomatique() {
return traitementAutomatique;
}
public void setTraitementAutomatique(Boolean traitementAutomatique) {
this.traitementAutomatique = traitementAutomatique;
}
public Boolean getInterventionManuelleRequise() {
return interventionManuelleRequise;
}
public void setInterventionManuelleRequise(Boolean interventionManuelleRequise) {
this.interventionManuelleRequise = interventionManuelleRequise;
}
public String getNotesTraitementManuel() {
return notesTraitementManuel;
}
public void setNotesTraitementManuel(String notesTraitementManuel) {
this.notesTraitementManuel = notesTraitementManuel;
}
public String getUtilisateurTraitementManuel() {
return utilisateurTraitementManuel;
}
public void setUtilisateurTraitementManuel(String utilisateurTraitementManuel) {
this.utilisateurTraitementManuel = utilisateurTraitementManuel;
}
public LocalDateTime getDateTraitementManuel() {
return dateTraitementManuel;
}
public void setDateTraitementManuel(LocalDateTime dateTraitementManuel) {
this.dateTraitementManuel = dateTraitementManuel;
}
// Méthodes utilitaires
/**
* Vérifie si le webhook concerne un checkout
*
* @return true si c'est un événement de checkout
*/
public boolean isEvenementCheckout() {
return typeEvenement == TypeEvenement.CHECKOUT_COMPLETE
|| typeEvenement == TypeEvenement.CHECKOUT_CANCELLED
|| typeEvenement == TypeEvenement.CHECKOUT_EXPIRED;
}
/**
* Vérifie si le webhook concerne un payout
*
* @return true si c'est un événement de payout
*/
public boolean isEvenementPayout() {
return typeEvenement == TypeEvenement.PAYOUT_COMPLETE
|| typeEvenement == TypeEvenement.PAYOUT_FAILED;
}
/** Marque le webhook comme traité avec succès */
public void marquerCommeTraite() {
this.statutTraitement = StatutTraitement.TRAITE;
this.dateTraitement = LocalDateTime.now();
marquerCommeModifie("SYSTEM");
}
/**
* Marque le webhook comme échoué
*
* @param messageErreur Le message d'erreur
* @param codeErreur Le code d'erreur
*/
public void marquerCommeEchec(String messageErreur, String codeErreur) {
this.statutTraitement = StatutTraitement.ECHEC;
this.messageErreurTraitement = messageErreur;
this.codeErreurTraitement = codeErreur;
this.nombreTentativesTraitement++;
this.dateTraitement = LocalDateTime.now();
marquerCommeModifie("SYSTEM");
}
/** Démarre le traitement du webhook */
public void demarrerTraitement() {
this.statutTraitement = StatutTraitement.EN_COURS;
this.nombreTentativesTraitement++;
marquerCommeModifie("SYSTEM");
}
@Override
public String toString() {
return "WaveWebhookDTO{"
+ "webhookId='"
+ webhookId
+ '\''
+ ", typeEvenement="
+ typeEvenement
+ ", statutTraitement="
+ statutTraitement
+ ", dateReception="
+ dateReception
+ ", sessionCheckoutId='"
+ sessionCheckoutId
+ '\''
+ ", montantTransaction="
+ montantTransaction
+ "} "
+ super.toString();
}
}

View File

@@ -0,0 +1,71 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les bénéficiaires d'une aide
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BeneficiaireAideDTO {
/** Identifiant unique du bénéficiaire */
private String id;
/** Nom complet du bénéficiaire */
@NotBlank(message = "Le nom du bénéficiaire est obligatoire")
@Size(max = 100, message = "Le nom ne peut pas dépasser 100 caractères")
private String nomComplet;
/** Relation avec le demandeur */
@NotBlank(message = "La relation avec le demandeur est obligatoire")
private String relationDemandeur;
/** Date de naissance */
private LocalDate dateNaissance;
/** Âge calculé */
private Integer age;
/** Genre */
private String genre;
/** Numéro de téléphone */
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Le numéro de téléphone n'est pas valide")
private String telephone;
/** Adresse email */
@Email(message = "L'adresse email n'est pas valide")
private String email;
/** Adresse physique */
@Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
private String adresse;
/** Situation particulière (handicap, maladie, etc.) */
@Size(max = 500, message = "La situation particulière ne peut pas dépasser 500 caractères")
private String situationParticuliere;
/** Indique si le bénéficiaire est le demandeur principal */
@Builder.Default private Boolean estDemandeurPrincipal = false;
/** Pourcentage de l'aide destiné à ce bénéficiaire */
@DecimalMin(value = "0.0", message = "Le pourcentage doit être positif")
@DecimalMax(value = "100.0", message = "Le pourcentage ne peut pas dépasser 100%")
private Double pourcentageAide;
/** Montant spécifique pour ce bénéficiaire */
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
private Double montantSpecifique;
}

View File

@@ -0,0 +1,86 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les commentaires sur une demande d'aide
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CommentaireAideDTO {
/** Identifiant unique du commentaire */
private String id;
/** Contenu du commentaire */
@NotBlank(message = "Le contenu du commentaire est obligatoire")
@Size(min = 5, max = 2000, message = "Le commentaire doit contenir entre 5 et 2000 caractères")
private String contenu;
/** Type de commentaire */
@NotBlank(message = "Le type de commentaire est obligatoire")
private String typeCommentaire;
/** Date de création du commentaire */
@NotNull(message = "La date de création est obligatoire")
@Builder.Default
private LocalDateTime dateCreation = LocalDateTime.now();
/** Date de dernière modification */
private LocalDateTime dateModification;
/** Identifiant de l'auteur du commentaire */
@NotBlank(message = "L'identifiant de l'auteur est obligatoire")
private String auteurId;
/** Nom de l'auteur du commentaire */
private String auteurNom;
/** Rôle de l'auteur */
private String auteurRole;
/** Indique si le commentaire est privé (visible seulement aux évaluateurs) */
@Builder.Default private Boolean estPrive = false;
/** Indique si le commentaire est important */
@Builder.Default private Boolean estImportant = false;
/** Identifiant du commentaire parent (pour les réponses) */
private String commentaireParentId;
/** Réponses à ce commentaire */
private List<CommentaireAideDTO> reponses;
/** Pièces jointes au commentaire */
private List<PieceJustificativeDTO> piecesJointes;
/** Mentions d'utilisateurs dans le commentaire */
private List<String> mentionsUtilisateurs;
/** Indique si le commentaire a été modifié */
@Builder.Default private Boolean estModifie = false;
/** Nombre de likes/réactions */
@Builder.Default private Integer nombreReactions = 0;
/** Indique si le commentaire est résolu (pour les questions) */
@Builder.Default private Boolean estResolu = false;
/** Date de résolution */
private LocalDateTime dateResolution;
/** Identifiant de la personne qui a marqué comme résolu */
private String resoluteurId;
}

View File

@@ -0,0 +1,77 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les informations de contact du proposant d'aide
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ContactProposantDTO {
/** Numéro de téléphone principal */
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Le numéro de téléphone n'est pas valide")
private String telephonePrincipal;
/** Numéro de téléphone secondaire */
@Pattern(
regexp = "^\\+?[0-9]{8,15}$",
message = "Le numéro de téléphone secondaire n'est pas valide")
private String telephoneSecondaire;
/** Adresse email */
@Email(message = "L'adresse email n'est pas valide")
private String email;
/** Adresse email secondaire */
@Email(message = "L'adresse email secondaire n'est pas valide")
private String emailSecondaire;
/** Identifiant WhatsApp */
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Le numéro WhatsApp n'est pas valide")
private String whatsapp;
/** Identifiant Telegram */
@Size(max = 50, message = "L'identifiant Telegram ne peut pas dépasser 50 caractères")
private String telegram;
/** Autres moyens de contact (réseaux sociaux, etc.) */
private java.util.Map<String, String> autresContacts;
/** Adresse physique pour rencontres */
@Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
private String adressePhysique;
/** Indique si les rencontres physiques sont possibles */
@Builder.Default private Boolean rencontresPhysiquesPossibles = false;
/** Indique si les appels téléphoniques sont acceptés */
@Builder.Default private Boolean appelsAcceptes = true;
/** Indique si les SMS sont acceptés */
@Builder.Default private Boolean smsAcceptes = true;
/** Indique si les emails sont acceptés */
@Builder.Default private Boolean emailsAcceptes = true;
/** Horaires de disponibilité pour contact */
@Size(max = 200, message = "Les horaires ne peuvent pas dépasser 200 caractères")
private String horairesDisponibilite;
/** Langue(s) de communication préférée(s) */
private java.util.List<String> languesPreferees;
/** Instructions spéciales pour le contact */
@Size(max = 300, message = "Les instructions ne peuvent pas dépasser 300 caractères")
private String instructionsSpeciales;
}

View File

@@ -0,0 +1,64 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les informations de contact d'urgence
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ContactUrgenceDTO {
/** Nom complet du contact d'urgence */
@NotBlank(message = "Le nom du contact d'urgence est obligatoire")
@Size(max = 100, message = "Le nom ne peut pas dépasser 100 caractères")
private String nomComplet;
/** Relation avec le demandeur */
@NotBlank(message = "La relation avec le demandeur est obligatoire")
@Size(max = 50, message = "La relation ne peut pas dépasser 50 caractères")
private String relation;
/** Numéro de téléphone principal */
@NotBlank(message = "Le numéro de téléphone est obligatoire")
@Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Le numéro de téléphone n'est pas valide")
private String telephonePrincipal;
/** Numéro de téléphone secondaire */
@Pattern(
regexp = "^\\+?[0-9]{8,15}$",
message = "Le numéro de téléphone secondaire n'est pas valide")
private String telephoneSecondaire;
/** Adresse email */
@Email(message = "L'adresse email n'est pas valide")
private String email;
/** Adresse physique */
@Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
private String adresse;
/** Disponibilité (horaires) */
@Size(max = 100, message = "La disponibilité ne peut pas dépasser 100 caractères")
private String disponibilite;
/** Indique si ce contact peut prendre des décisions pour le demandeur */
@Builder.Default private Boolean peutPrendreDecisions = false;
/** Indique si ce contact doit être notifié automatiquement */
@Builder.Default private Boolean notificationAutomatique = true;
/** Commentaires additionnels */
@Size(max = 300, message = "Les commentaires ne peuvent pas dépasser 300 caractères")
private String commentaires;
}

View File

@@ -0,0 +1,137 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les créneaux de disponibilité du proposant
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CreneauDisponibiliteDTO {
/** Identifiant unique du créneau */
private String id;
/** Jour de la semaine (pour créneaux récurrents) */
private DayOfWeek jourSemaine;
/** Date spécifique (pour créneaux ponctuels) */
private LocalDate dateSpecifique;
/** Heure de début */
@NotNull(message = "L'heure de début est obligatoire")
private LocalTime heureDebut;
/** Heure de fin */
@NotNull(message = "L'heure de fin est obligatoire")
private LocalTime heureFin;
/** Type de créneau */
@NotNull(message = "Le type de créneau est obligatoire")
@Builder.Default
private TypeCreneau type = TypeCreneau.RECURRENT;
/** Indique si le créneau est actif */
@Builder.Default private Boolean estActif = true;
/** Fuseau horaire */
@Builder.Default private String fuseauHoraire = "Africa/Abidjan";
/** Commentaires sur le créneau */
@Size(max = 200, message = "Les commentaires ne peuvent pas dépasser 200 caractères")
private String commentaires;
/** Priorité du créneau (1 = haute, 5 = basse) */
@Min(value = 1, message = "La priorité doit être au moins 1")
@Max(value = 5, message = "La priorité ne peut pas dépasser 5")
@Builder.Default
private Integer priorite = 3;
/** Durée maximale d'intervention en minutes */
@Min(value = 15, message = "La durée doit être au moins 15 minutes")
@Max(value = 480, message = "La durée ne peut pas dépasser 8 heures")
private Integer dureeMaxMinutes;
/** Indique si des pauses sont nécessaires */
@Builder.Default private Boolean pausesNecessaires = false;
/** Durée des pauses en minutes */
@Min(value = 5, message = "La durée de pause doit être au moins 5 minutes")
private Integer dureePauseMinutes;
/** Énumération des types de créneaux */
public enum TypeCreneau {
RECURRENT("Récurrent"),
PONCTUEL("Ponctuel"),
URGENCE("Urgence"),
FLEXIBLE("Flexible");
private final String libelle;
TypeCreneau(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si le créneau est valide (heure fin > heure début) */
public boolean isValide() {
return heureDebut != null && heureFin != null && heureFin.isAfter(heureDebut);
}
/** Calcule la durée du créneau en minutes */
public long getDureeMinutes() {
if (!isValide()) return 0;
return java.time.Duration.between(heureDebut, heureFin).toMinutes();
}
/** Vérifie si le créneau est disponible à une date donnée */
public boolean isDisponibleLe(LocalDate date) {
if (!estActif) return false;
return switch (type) {
case PONCTUEL -> dateSpecifique != null && dateSpecifique.equals(date);
case RECURRENT -> jourSemaine != null && date.getDayOfWeek() == jourSemaine;
case URGENCE, FLEXIBLE -> true;
};
}
/** Vérifie si une heure est dans le créneau */
public boolean contientHeure(LocalTime heure) {
if (!isValide()) return false;
return !heure.isBefore(heureDebut) && !heure.isAfter(heureFin);
}
/** Retourne le libellé du créneau */
public String getLibelle() {
StringBuilder sb = new StringBuilder();
if (type == TypeCreneau.RECURRENT && jourSemaine != null) {
sb.append(jourSemaine.name()).append(" ");
} else if (type == TypeCreneau.PONCTUEL && dateSpecifique != null) {
sb.append(dateSpecifique.toString()).append(" ");
}
sb.append(heureDebut.toString()).append(" - ").append(heureFin.toString());
return sb.toString();
}
}

View File

@@ -0,0 +1,54 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les critères de sélection des bénéficiaires
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CritereSelectionDTO {
/** Nom du critère */
@NotBlank(message = "Le nom du critère est obligatoire")
@Size(max = 100, message = "Le nom ne peut pas dépasser 100 caractères")
private String nom;
/** Type de critère (age, situation, localisation, etc.) */
@NotBlank(message = "Le type de critère est obligatoire")
private String type;
/** Opérateur de comparaison (equals, greater_than, less_than, contains, etc.) */
@NotBlank(message = "L'opérateur est obligatoire")
private String operateur;
/** Valeur de référence pour la comparaison */
@NotBlank(message = "La valeur est obligatoire")
private String valeur;
/** Valeur maximale (pour les plages) */
private String valeurMax;
/** Indique si le critère est obligatoire */
@Builder.Default private Boolean estObligatoire = false;
/** Poids du critère dans la sélection (1-10) */
@Min(value = 1, message = "Le poids doit être au moins 1")
@Max(value = 10, message = "Le poids ne peut pas dépasser 10")
@Builder.Default
private Integer poids = 5;
/** Description du critère */
@Size(max = 200, message = "La description ne peut pas dépasser 200 caractères")
private String description;
}

View File

@@ -0,0 +1,468 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
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.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
/**
* DTO unifié pour les demandes d'aide dans le système de solidarité
*
* <p>Ce DTO représente une demande d'aide complète avec toutes les informations nécessaires pour le
* traitement, l'évaluation et le suivi. Remplace l'ancien AideDTO pour une approche unifiée.
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
public class DemandeAideDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
// === IDENTIFICATION ===
// ID hérité de BaseDTO
/** Numéro de référence de la demande (généré automatiquement) */
@Pattern(
regexp = ValidationConstants.REFERENCE_AIDE_PATTERN,
message = ValidationConstants.REFERENCE_AIDE_MESSAGE)
private String numeroReference;
// === INFORMATIONS DE BASE ===
/** Type d'aide demandée */
@NotNull(message = "Le type d'aide est obligatoire.")
private TypeAide typeAide;
/** Titre court de la demande */
@NotBlank(message = "Le titre" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.TITRE_MIN_LENGTH,
max = ValidationConstants.TITRE_MAX_LENGTH,
message = ValidationConstants.TITRE_SIZE_MESSAGE)
private String titre;
/** Description détaillée de la demande */
@NotBlank(message = "La description" + ValidationConstants.OBLIGATOIRE_MESSAGE)
@Size(
min = ValidationConstants.DESCRIPTION_MIN_LENGTH,
max = ValidationConstants.DESCRIPTION_MAX_LENGTH,
message = ValidationConstants.DESCRIPTION_SIZE_MESSAGE)
private String description;
/** Justification de la demande */
@Size(
max = ValidationConstants.JUSTIFICATION_MAX_LENGTH,
message = ValidationConstants.JUSTIFICATION_SIZE_MESSAGE)
private String justification;
// === MONTANT ET FINANCES ===
/** Montant demandé (si applicable) */
@DecimalMin(
value = ValidationConstants.MONTANT_MIN_VALUE,
inclusive = false,
message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal montantDemande;
/** Montant approuvé (si différent du montant demandé) */
@DecimalMin(
value = ValidationConstants.MONTANT_MIN_VALUE,
inclusive = false,
message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal montantApprouve;
/** Montant versé effectivement */
@DecimalMin(
value = ValidationConstants.MONTANT_MIN_VALUE,
inclusive = false,
message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal montantVerse;
/** Devise du montant (code ISO 3 lettres) */
@Pattern(
regexp = ValidationConstants.DEVISE_PATTERN,
message = ValidationConstants.DEVISE_MESSAGE)
private String devise = "XOF";
// === ACTEURS ===
/** Identifiant du demandeur (UUID) */
@NotNull(message = "L'identifiant du demandeur est obligatoire")
private UUID membreDemandeurId;
/** Nom complet du demandeur */
private String nomDemandeur;
/** Numéro de membre du demandeur */
private String numeroMembreDemandeur;
/** Identifiant de l'évaluateur assigné */
private String evaluateurId;
/** Nom de l'évaluateur */
private String evaluateurNom;
/** Identifiant de l'approbateur */
private String approvateurId;
/** Nom de l'approbateur */
private String approvateurNom;
/** Identifiant de l'organisation (UUID) */
@NotNull(message = "L'identifiant de l'organisation est obligatoire")
private UUID associationId;
/** Nom de l'association */
private String nomAssociation;
// === STATUT ET PRIORITÉ ===
/** Statut actuel de la demande */
@NotNull(message = "Le statut est obligatoire")
private StatutAide statut = StatutAide.BROUILLON;
/** Priorité de la demande */
@NotNull(message = "La priorité est obligatoire")
private PrioriteAide priorite = PrioriteAide.NORMALE;
/** Motif de rejet (si applicable) */
@Size(max = 500, message = "Le motif de rejet ne peut pas dépasser 500 caractères")
private String motifRejet;
/** Commentaires de l'évaluateur */
@Size(max = 1000, message = "Les commentaires ne peuvent pas dépasser 1000 caractères")
private String commentairesEvaluateur;
// === DATES ===
// Date de création héritée de BaseDTO
/** Date de soumission de la demande */
private LocalDateTime dateSoumission;
/** Date limite de traitement */
private LocalDateTime dateLimiteTraitement;
/** Date d'évaluation */
private LocalDateTime dateEvaluation;
/** Date d'approbation */
private LocalDateTime dateApprobation;
/** Date de versement */
private LocalDateTime dateVersement;
/** Date de clôture */
private LocalDateTime dateCloture;
// Date de modification héritée de BaseDTO
// === INFORMATIONS COMPLÉMENTAIRES ===
/** Pièces justificatives attachées */
private List<PieceJustificativeDTO> piecesJustificatives;
/** Bénéficiaires de l'aide (si différents du demandeur) */
private List<BeneficiaireAideDTO> beneficiaires;
/** Historique des changements de statut */
private List<HistoriqueStatutDTO> historiqueStatuts;
/** Commentaires et échanges */
private List<CommentaireAideDTO> commentaires;
/** Données personnalisées spécifiques au type d'aide */
private Map<String, Object> donneesPersonnalisees;
/** Tags pour catégorisation */
private List<String> tags;
// === MÉTADONNÉES ===
/** Indique si la demande est confidentielle */
private Boolean estConfidentielle = false;
/** Indique si la demande nécessite un suivi */
private Boolean necessiteSuivi = false;
/** Score de priorité calculé automatiquement */
private Double scorePriorite;
/** Nombre de vues de la demande */
private Integer nombreVues = 0;
// Version héritée de BaseDTO
/** Informations de géolocalisation (si pertinent) */
private LocalisationDTO localisation;
/** Informations de contact d'urgence */
private ContactUrgenceDTO contactUrgence;
// === CHAMPS ADDITIONNELS D'AIDE ===
/** Date limite pour l'aide */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateLimite;
/** Justificatifs fournis */
private Boolean justificatifsFournis = false;
/** Liste des documents joints (noms de fichiers) */
@Size(max = 1000, message = "La liste des documents ne peut pas dépasser 1000 caractères")
private String documentsJoints;
/** Date de début de l'aide */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateDebutAide;
/** Date de fin de l'aide */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateFinAide;
/** Identifiant du membre aidant */
private UUID membreAidantId;
/** Nom du membre aidant */
private String nomAidant;
/** Mode de versement */
@Size(max = 50, message = "Le mode de versement ne peut pas dépasser 50 caractères")
private String modeVersement;
/** Numéro de transaction */
@Size(max = 100, message = "Le numéro de transaction ne peut pas dépasser 100 caractères")
private String numeroTransaction;
/** Identifiant de celui qui a rejeté */
private UUID rejeteParId;
/** Nom de celui qui a rejeté */
private String rejetePar;
/** Date de rejet */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateRejet;
/** Raison du rejet (si applicable) */
@Size(max = 500, message = "La raison du rejet ne peut pas dépasser 500 caractères")
private String raisonRejet;
// === CONSTRUCTEURS ===
/** Constructeur par défaut */
public DemandeAideDTO() {
super(); // Appelle le constructeur de BaseDTO qui génère l'UUID
this.statut = StatutAide.EN_ATTENTE;
this.priorite = PrioriteAide.NORMALE;
this.devise = "XOF";
this.nombreVues = 0;
this.numeroReference = genererNumeroReference();
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si la demande est modifiable */
public boolean estModifiable() {
return statut != null && statut.permetModification();
}
/** Vérifie si la demande peut être annulée */
public boolean peutEtreAnnulee() {
return statut != null && statut.permetAnnulation();
}
/** Vérifie si la demande est urgente */
public boolean estUrgente() {
return priorite != null && priorite.isUrgente();
}
/** Vérifie si la demande est terminée */
public boolean estTerminee() {
return statut != null && statut.isEstFinal();
}
/** Vérifie si la demande est en succès */
public boolean estEnSucces() {
return statut != null && statut.isSucces();
}
/** Calcule le pourcentage d'avancement */
public double getPourcentageAvancement() {
if (statut == null) {
return 0.0;
}
return switch (statut) {
case BROUILLON -> 5.0;
case SOUMISE -> 10.0;
case EN_ATTENTE -> 20.0;
case EN_COURS_EVALUATION -> 40.0;
case INFORMATIONS_REQUISES -> 35.0;
case APPROUVEE, APPROUVEE_PARTIELLEMENT -> 60.0;
case EN_COURS_TRAITEMENT -> 70.0;
case EN_COURS_VERSEMENT -> 85.0;
case VERSEE, LIVREE, TERMINEE -> 100.0;
case REJETEE, ANNULEE, EXPIREE -> 100.0;
case SUSPENDUE -> 50.0;
case EN_SUIVI -> 95.0;
case CLOTUREE -> 100.0;
};
}
/** Retourne le délai restant en heures */
public long getDelaiRestantHeures() {
if (dateLimiteTraitement == null) {
return -1;
}
LocalDateTime maintenant = LocalDateTime.now();
if (maintenant.isAfter(dateLimiteTraitement)) {
return 0;
}
return java.time.Duration.between(maintenant, dateLimiteTraitement).toHours();
}
/** Vérifie si le délai est dépassé */
public boolean estDelaiDepasse() {
return getDelaiRestantHeures() == 0;
}
/** Retourne la durée de traitement en jours */
public long getDureeTraitementJours() {
if (dateCreation == null) {
return 0;
}
LocalDateTime dateFin = dateCloture != null ? dateCloture : LocalDateTime.now();
return java.time.Duration.between(dateCreation, dateFin).toDays();
}
// === MÉTHODES MÉTIER D'AIDE ===
/** Retourne le libellé du statut */
public String getStatutLibelle() {
return statut != null ? statut.getLibelle() : "Non défini";
}
/** Retourne le libellé de la priorité */
public String getPrioriteLibelle() {
return priorite != null ? priorite.getLibelle() : "Normale";
}
/** Approuve la demande d'aide */
public void approuver(
UUID evaluateurId, String nomEvaluateur, BigDecimal montantApprouve, String commentaires) {
this.statut = StatutAide.APPROUVEE;
this.evaluateurId = evaluateurId.toString();
this.evaluateurNom = nomEvaluateur;
this.montantApprouve = montantApprouve;
this.commentairesEvaluateur = commentaires;
this.dateEvaluation = LocalDateTime.now();
this.dateApprobation = LocalDateTime.now();
marquerCommeModifie(nomEvaluateur);
}
/** Rejette la demande d'aide */
public void rejeter(UUID evaluateurId, String nomEvaluateur, String raison) {
this.statut = StatutAide.REJETEE;
this.rejeteParId = evaluateurId;
this.rejetePar = nomEvaluateur;
this.raisonRejet = raison;
this.dateRejet = LocalDateTime.now();
this.dateEvaluation = LocalDateTime.now();
marquerCommeModifie(nomEvaluateur);
}
/** Démarre l'aide */
public void demarrerAide(UUID aidantId, String nomAidant) {
this.statut = StatutAide.EN_COURS_TRAITEMENT;
this.membreAidantId = aidantId;
this.nomAidant = nomAidant;
this.dateDebutAide = LocalDate.now();
marquerCommeModifie(nomAidant);
}
/** Termine l'aide avec versement */
public void terminerAvecVersement(
BigDecimal montantVerse, String modeVersement, String numeroTransaction) {
this.statut = StatutAide.TERMINEE;
this.montantVerse = montantVerse;
this.modeVersement = modeVersement;
this.numeroTransaction = numeroTransaction;
this.dateVersement = LocalDateTime.now();
this.dateFinAide = LocalDate.now();
marquerCommeModifie("SYSTEM");
}
/** Incrémente le nombre de vues */
public void incrementerVues() {
if (nombreVues == null) {
nombreVues = 1;
} else {
nombreVues++;
}
}
/** Génère un numéro de référence unique */
public static String genererNumeroReference() {
return "DA-"
+ LocalDate.now().getYear()
+ "-"
+ String.format("%06d", (int) (Math.random() * 1000000));
}
// === GETTERS EXPLICITES POUR COMPATIBILITÉ ===
/** Retourne le type d'aide demandée */
public TypeAide getTypeAide() {
return typeAide;
}
/** Retourne le montant demandé */
public BigDecimal getMontantDemande() {
return montantDemande;
}
/** Marque comme modifié */
public void marquerCommeModifie(String utilisateur) {
LocalDateTime maintenant = LocalDateTime.now();
this.dateModification = maintenant;
super.marquerCommeModifie(utilisateur);
}
}

View File

@@ -0,0 +1,256 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour l'évaluation d'une aide reçue ou fournie
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EvaluationAideDTO {
/** Identifiant unique de l'évaluation */
private String id;
/** Identifiant de la demande d'aide évaluée */
@NotBlank(message = "L'identifiant de la demande d'aide est obligatoire")
private String demandeAideId;
/** Identifiant de la proposition d'aide évaluée (si applicable) */
private String propositionAideId;
/** Identifiant de l'évaluateur */
@NotBlank(message = "L'identifiant de l'évaluateur est obligatoire")
private String evaluateurId;
/** Nom de l'évaluateur */
private String evaluateurNom;
/** Rôle de l'évaluateur (beneficiaire, proposant, evaluateur_externe) */
@NotBlank(message = "Le rôle de l'évaluateur est obligatoire")
private String roleEvaluateur;
/** Type d'évaluation */
@NotNull(message = "Le type d'évaluation est obligatoire")
@Builder.Default
private TypeEvaluation typeEvaluation = TypeEvaluation.SATISFACTION_BENEFICIAIRE;
/** Note globale (1-5) */
@NotNull(message = "La note globale est obligatoire")
@DecimalMin(value = "1.0", message = "La note doit être au moins 1")
@DecimalMax(value = "5.0", message = "La note ne peut pas dépasser 5")
private Double noteGlobale;
/** Notes détaillées par critère */
private Map<String, Double> notesDetaillees;
/** Commentaire principal */
@Size(min = 10, max = 1000, message = "Le commentaire doit contenir entre 10 et 1000 caractères")
private String commentairePrincipal;
/** Points positifs */
@Size(max = 500, message = "Les points positifs ne peuvent pas dépasser 500 caractères")
private String pointsPositifs;
/** Points d'amélioration */
@Size(max = 500, message = "Les points d'amélioration ne peuvent pas dépasser 500 caractères")
private String pointsAmelioration;
/** Recommandations */
@Size(max = 500, message = "Les recommandations ne peuvent pas dépasser 500 caractères")
private String recommandations;
/** Indique si l'évaluateur recommande cette aide/proposant */
@Builder.Default private Boolean recommande = true;
/** Indique si l'aide a été utile */
@Builder.Default private Boolean aideUtile = true;
/** Indique si l'aide a résolu le problème */
@Builder.Default private Boolean problemeResolu = true;
/** Délai de réponse perçu (1=très lent, 5=très rapide) */
@DecimalMin(value = "1.0", message = "La note délai doit être au moins 1")
@DecimalMax(value = "5.0", message = "La note délai ne peut pas dépasser 5")
private Double noteDelaiReponse;
/** Qualité de la communication (1=très mauvaise, 5=excellente) */
@DecimalMin(value = "1.0", message = "La note communication doit être au moins 1")
@DecimalMax(value = "5.0", message = "La note communication ne peut pas dépasser 5")
private Double noteCommunication;
/** Professionnalisme (1=très mauvais, 5=excellent) */
@DecimalMin(value = "1.0", message = "La note professionnalisme doit être au moins 1")
@DecimalMax(value = "5.0", message = "La note professionnalisme ne peut pas dépasser 5")
private Double noteProfessionnalisme;
/** Respect des engagements (1=très mauvais, 5=excellent) */
@DecimalMin(value = "1.0", message = "La note engagement doit être au moins 1")
@DecimalMax(value = "5.0", message = "La note engagement ne peut pas dépasser 5")
private Double noteRespectEngagements;
/** Date de création de l'évaluation */
@NotNull(message = "La date de création est obligatoire")
@Builder.Default
private LocalDateTime dateCreation = LocalDateTime.now();
/** Date de dernière modification */
@Builder.Default private LocalDateTime dateModification = LocalDateTime.now();
/** Indique si l'évaluation est publique */
@Builder.Default private Boolean estPublique = true;
/** Indique si l'évaluation est anonyme */
@Builder.Default private Boolean estAnonyme = false;
/** Indique si l'évaluation a été vérifiée */
@Builder.Default private Boolean estVerifiee = false;
/** Date de vérification */
private LocalDateTime dateVerification;
/** Identifiant du vérificateur */
private String verificateurId;
/** Pièces jointes à l'évaluation (photos, documents) */
private List<PieceJustificativeDTO> piecesJointes;
/** Tags associés à l'évaluation */
private List<String> tags;
/** Données additionnelles */
private Map<String, Object> donneesAdditionnelles;
/** Nombre de personnes qui ont trouvé cette évaluation utile */
@Builder.Default private Integer nombreUtile = 0;
/** Nombre de signalements de cette évaluation */
@Builder.Default private Integer nombreSignalements = 0;
/** Statut de l'évaluation */
@NotNull(message = "Le statut est obligatoire")
@Builder.Default
private StatutEvaluation statut = StatutEvaluation.ACTIVE;
/** Énumération des types d'évaluation */
public enum TypeEvaluation {
SATISFACTION_BENEFICIAIRE("Satisfaction du bénéficiaire"),
EVALUATION_PROPOSANT("Évaluation du proposant"),
EVALUATION_PROCESSUS("Évaluation du processus"),
SUIVI_POST_AIDE("Suivi post-aide"),
EVALUATION_IMPACT("Évaluation d'impact"),
RETOUR_EXPERIENCE("Retour d'expérience");
private final String libelle;
TypeEvaluation(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Énumération des statuts d'évaluation */
public enum StatutEvaluation {
BROUILLON("Brouillon"),
ACTIVE("Active"),
MASQUEE("Masquée"),
SIGNALEE("Signalée"),
SUPPRIMEE("Supprimée");
private final String libelle;
StatutEvaluation(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
// === MÉTHODES UTILITAIRES ===
/** Calcule la note moyenne des critères détaillés */
public Double getNoteMoyenneDetaillees() {
if (notesDetaillees == null || notesDetaillees.isEmpty()) {
return noteGlobale;
}
return notesDetaillees.values().stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(noteGlobale);
}
/** Vérifie si l'évaluation est positive (note >= 4) */
public boolean isPositive() {
return noteGlobale != null && noteGlobale >= 4.0;
}
/** Vérifie si l'évaluation est négative (note <= 2) */
public boolean isNegative() {
return noteGlobale != null && noteGlobale <= 2.0;
}
/** Calcule un score de qualité global */
public double getScoreQualite() {
double score = noteGlobale != null ? noteGlobale : 0.0;
// Bonus pour les notes détaillées
if (noteDelaiReponse != null) score += noteDelaiReponse * 0.1;
if (noteCommunication != null) score += noteCommunication * 0.1;
if (noteProfessionnalisme != null) score += noteProfessionnalisme * 0.1;
if (noteRespectEngagements != null) score += noteRespectEngagements * 0.1;
// Bonus pour recommandation
if (recommande != null && recommande) score += 0.2;
// Bonus pour résolution du problème
if (problemeResolu != null && problemeResolu) score += 0.3;
// Malus pour signalements
if (nombreSignalements > 0) score -= nombreSignalements * 0.1;
return Math.min(5.0, Math.max(0.0, score));
}
/** Vérifie si l'évaluation est complète */
public boolean isComplete() {
return noteGlobale != null
&& commentairePrincipal != null
&& !commentairePrincipal.trim().isEmpty()
&& recommande != null
&& aideUtile != null
&& problemeResolu != null;
}
/** Retourne le niveau de satisfaction */
public String getNiveauSatisfaction() {
if (noteGlobale == null) return "Non évalué";
return switch (noteGlobale.intValue()) {
case 5 -> "Excellent";
case 4 -> "Très bien";
case 3 -> "Bien";
case 2 -> "Passable";
case 1 -> "Insuffisant";
default -> "Non évalué";
};
}
}

View File

@@ -0,0 +1,62 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour l'historique des changements de statut d'une demande d'aide
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class HistoriqueStatutDTO {
/** Identifiant unique de l'entrée d'historique */
private String id;
/** Ancien statut */
private StatutAide ancienStatut;
/** Nouveau statut */
@NotNull(message = "Le nouveau statut est obligatoire")
private StatutAide nouveauStatut;
/** Date du changement de statut */
@NotNull(message = "La date de changement est obligatoire")
@Builder.Default
private LocalDateTime dateChangement = LocalDateTime.now();
/** Identifiant de la personne qui a effectué le changement */
@NotBlank(message = "L'identifiant de l'auteur est obligatoire")
private String auteurId;
/** Nom de la personne qui a effectué le changement */
private String auteurNom;
/** Motif du changement de statut */
@Size(max = 500, message = "Le motif ne peut pas dépasser 500 caractères")
private String motif;
/** Commentaires additionnels */
@Size(max = 1000, message = "Les commentaires ne peuvent pas dépasser 1000 caractères")
private String commentaires;
/** Indique si le changement est automatique (système) */
@Builder.Default private Boolean estAutomatique = false;
/** Durée en minutes depuis le statut précédent */
private Long dureeDepuisPrecedent;
/** Données additionnelles liées au changement */
private java.util.Map<String, Object> donneesAdditionnelles;
}

View File

@@ -0,0 +1,58 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les informations de géolocalisation
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LocalisationDTO {
/** Latitude */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
private Double latitude;
/** Longitude */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
private Double longitude;
/** Adresse complète */
@Size(max = 300, message = "L'adresse ne peut pas dépasser 300 caractères")
private String adresseComplete;
/** Ville */
@Size(max = 100, message = "La ville ne peut pas dépasser 100 caractères")
private String ville;
/** Région/Province */
@Size(max = 100, message = "La région ne peut pas dépasser 100 caractères")
private String region;
/** Pays */
@Size(max = 100, message = "Le pays ne peut pas dépasser 100 caractères")
private String pays;
/** Code postal */
@Size(max = 20, message = "Le code postal ne peut pas dépasser 20 caractères")
private String codePostal;
/** Précision de la localisation en mètres */
@Min(value = 0, message = "La précision doit être positive")
private Double precision;
/** Indique si la localisation est approximative */
@Builder.Default private Boolean estApproximative = false;
}

View File

@@ -0,0 +1,68 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import jakarta.validation.constraints.*;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les pièces justificatives d'une demande d'aide
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PieceJustificativeDTO {
/** Identifiant unique de la pièce justificative */
private String id;
/** Nom du fichier */
@NotBlank(message = "Le nom du fichier est obligatoire")
@Size(max = 255, message = "Le nom du fichier ne peut pas dépasser 255 caractères")
private String nomFichier;
/** Type de pièce justificative */
@NotBlank(message = "Le type de pièce est obligatoire")
private String typePiece;
/** Description de la pièce */
@Size(max = 500, message = "La description ne peut pas dépasser 500 caractères")
private String description;
/** URL ou chemin d'accès au fichier */
@NotBlank(message = "L'URL du fichier est obligatoire")
private String urlFichier;
/** Type MIME du fichier */
private String typeMime;
/** Taille du fichier en octets */
@Min(value = 1, message = "La taille du fichier doit être positive")
private Long tailleFichier;
/** Indique si la pièce est obligatoire */
@Builder.Default private Boolean estObligatoire = false;
/** Indique si la pièce a été vérifiée */
@Builder.Default private Boolean estVerifiee = false;
/** Date d'ajout de la pièce */
@Builder.Default private LocalDateTime dateAjout = LocalDateTime.now();
/** Date de vérification */
private LocalDateTime dateVerification;
/** Identifiant de la personne qui a vérifié */
private String verificateurId;
/** Commentaires sur la vérification */
@Size(max = 500, message = "Les commentaires ne peuvent pas dépasser 500 caractères")
private String commentairesVerification;
}

View File

@@ -0,0 +1,290 @@
package dev.lions.unionflow.server.api.dto.solidarite;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* DTO pour les propositions d'aide dans le système de solidarité
*
* <p>Ce DTO représente une proposition d'aide faite par un membre pour aider soit une demande
* spécifique, soit de manière générale.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PropositionAideDTO {
// === IDENTIFICATION ===
/** Identifiant unique de la proposition d'aide */
private String id;
/** Numéro de référence de la proposition (généré automatiquement) */
@Pattern(
regexp = "^PA-\\d{4}-\\d{6}$",
message = "Le numéro de référence doit suivre le format PA-YYYY-NNNNNN")
private String numeroReference;
// === INFORMATIONS DE BASE ===
/** Type d'aide proposée */
@NotNull(message = "Le type d'aide proposée est obligatoire")
private TypeAide typeAide;
/** Titre de la proposition */
@NotBlank(message = "Le titre est obligatoire")
@Size(min = 10, max = 100, message = "Le titre doit contenir entre 10 et 100 caractères")
private String titre;
/** Description détaillée de l'aide proposée */
@NotBlank(message = "La description est obligatoire")
@Size(min = 20, max = 1000, message = "La description doit contenir entre 20 et 1000 caractères")
private String description;
/** Conditions ou critères pour bénéficier de l'aide */
@Size(max = 500, message = "Les conditions ne peuvent pas dépasser 500 caractères")
private String conditions;
// === MONTANT ET CAPACITÉ ===
/** Montant maximum que le proposant peut offrir (si applicable) */
@DecimalMin(value = "0.0", inclusive = false, message = "Le montant doit être positif")
@DecimalMax(value = "1000000.0", message = "Le montant ne peut pas dépasser 1 000 000 FCFA")
@Digits(
integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
private BigDecimal montantMaximum;
/** Nombre maximum de bénéficiaires */
@Min(value = 1, message = "Le nombre de bénéficiaires doit être au moins 1")
@Max(value = 100, message = "Le nombre de bénéficiaires ne peut pas dépasser 100")
@Builder.Default
private Integer nombreMaxBeneficiaires = 1;
/** Devise du montant */
@Builder.Default private String devise = "FCFA";
// === ACTEURS ===
/** Identifiant du proposant */
@NotBlank(message = "L'identifiant du proposant est obligatoire")
private String proposantId;
/** Nom complet du proposant */
private String proposantNom;
/** Identifiant de l'organisation du proposant */
@NotBlank(message = "L'identifiant de l'organisation est obligatoire")
private String organisationId;
/** Identifiant de la demande d'aide liée (si proposition spécifique) */
private String demandeAideId;
// === STATUT ET DISPONIBILITÉ ===
/** Statut de la proposition */
@NotNull(message = "Le statut est obligatoire")
@Builder.Default
private StatutProposition statut = StatutProposition.ACTIVE;
/** Indique si la proposition est disponible */
@Builder.Default private Boolean estDisponible = true;
/** Indique si la proposition est récurrente */
@Builder.Default private Boolean estRecurrente = false;
/** Fréquence de récurrence (si applicable) */
private String frequenceRecurrence;
// === DATES ET DÉLAIS ===
/** Date de création de la proposition */
@NotNull(message = "La date de création est obligatoire")
@Builder.Default
private LocalDateTime dateCreation = LocalDateTime.now();
/** Date d'expiration de la proposition */
private LocalDateTime dateExpiration;
/** Date de dernière modification */
@Builder.Default private LocalDateTime dateModification = LocalDateTime.now();
/** Délai de réponse souhaité en heures */
@Min(value = 1, message = "Le délai de réponse doit être au moins 1 heure")
@Max(value = 8760, message = "Le délai de réponse ne peut pas dépasser 1 an")
@Builder.Default
private Integer delaiReponseHeures = 72;
// === CRITÈRES ET PRÉFÉRENCES ===
/** Critères de sélection des bénéficiaires */
private List<CritereSelectionDTO> criteresSelection;
/** Zones géographiques couvertes */
private List<String> zonesGeographiques;
/** Groupes cibles (âge, situation, etc.) */
private List<String> groupesCibles;
/** Compétences ou ressources disponibles */
private List<String> competencesRessources;
// === CONTACT ET DISPONIBILITÉ ===
/** Informations de contact préférées */
private ContactProposantDTO contactProposant;
/** Créneaux de disponibilité */
private List<CreneauDisponibiliteDTO> creneauxDisponibilite;
/** Mode de contact préféré */
private String modeContactPrefere;
// === HISTORIQUE ET SUIVI ===
/** Nombre de demandes traitées avec cette proposition */
@Builder.Default private Integer nombreDemandesTraitees = 0;
/** Nombre de bénéficiaires aidés */
@Builder.Default private Integer nombreBeneficiairesAides = 0;
/** Montant total versé */
@Builder.Default private Double montantTotalVerse = 0.0;
/** Note moyenne des bénéficiaires */
@DecimalMin(value = "0.0", message = "La note doit être positive")
@DecimalMax(value = "5.0", message = "La note ne peut pas dépasser 5")
private Double noteMoyenne;
/** Nombre d'évaluations reçues */
@Builder.Default private Integer nombreEvaluations = 0;
// === MÉTADONNÉES ===
/** Tags pour catégorisation */
private List<String> tags;
/** Données personnalisées */
private Map<String, Object> donneesPersonnalisees;
/** Indique si la proposition est mise en avant */
@Builder.Default private Boolean estMiseEnAvant = false;
/** Score de pertinence calculé automatiquement */
private Double scorePertinence;
/** Nombre de vues de la proposition */
@Builder.Default private Integer nombreVues = 0;
/** Nombre de candidatures reçues */
@Builder.Default private Integer nombreCandidatures = 0;
// === MÉTHODES UTILITAIRES ===
/** Vérifie si la proposition est active et disponible */
public boolean isActiveEtDisponible() {
return statut == StatutProposition.ACTIVE && estDisponible && !isExpiree();
}
/** Vérifie si la proposition est expirée */
public boolean isExpiree() {
return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration);
}
/** Vérifie si la proposition peut encore accepter des bénéficiaires */
public boolean peutAccepterBeneficiaires() {
return isActiveEtDisponible() && nombreBeneficiairesAides < nombreMaxBeneficiaires;
}
/** Calcule le pourcentage de capacité utilisée */
public double getPourcentageCapaciteUtilisee() {
if (nombreMaxBeneficiaires == 0) return 100.0;
return (nombreBeneficiairesAides * 100.0) / nombreMaxBeneficiaires;
}
/** Retourne le nombre de places restantes */
public int getPlacesRestantes() {
return Math.max(0, nombreMaxBeneficiaires - nombreBeneficiairesAides);
}
/** Vérifie si la proposition correspond à un type d'aide */
public boolean correspondAuType(TypeAide type) {
return typeAide == type
|| (typeAide.getCategorie().equals(type.getCategorie()) && typeAide != TypeAide.AUTRE);
}
/** Calcule le score de compatibilité avec une demande */
public double getScoreCompatibilite(DemandeAideDTO demande) {
double score = 0.0;
// Correspondance exacte du type
if (typeAide == demande.getTypeAide()) {
score += 50.0;
} else if (typeAide.getCategorie().equals(demande.getTypeAide().getCategorie())) {
score += 30.0;
}
// Montant compatible
if (montantMaximum != null && demande.getMontantDemande() != null) {
if (demande.getMontantDemande().compareTo(montantMaximum) <= 0) {
score += 20.0;
} else {
score -= 10.0;
}
}
// Disponibilité
if (peutAccepterBeneficiaires()) {
score += 15.0;
}
// Réputation
if (noteMoyenne != null && noteMoyenne >= 4.0) {
score += 10.0;
}
// Récence
long joursDepuisCreation =
java.time.Duration.between(dateCreation, LocalDateTime.now()).toDays();
if (joursDepuisCreation <= 30) {
score += 5.0;
}
return Math.min(100.0, Math.max(0.0, score));
}
/** Énumération des statuts de proposition */
public enum StatutProposition {
BROUILLON("Brouillon"),
ACTIVE("Active"),
SUSPENDUE("Suspendue"),
EXPIREE("Expirée"),
TERMINEE("Terminée"),
ANNULEE("Annulée");
private final String libelle;
StatutProposition(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
}

View File

@@ -0,0 +1,53 @@
package dev.lions.unionflow.server.api.dto.wave;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des comptes Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class CompteWaveDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Numéro de téléphone Wave */
@NotBlank(message = "Le numéro de téléphone est obligatoire")
@Pattern(
regexp = "^\\+225[0-9]{8}$",
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
private String numeroTelephone;
/** Statut du compte */
private StatutCompteWave statutCompte;
/** Identifiant Wave API (encrypté) */
private String waveAccountId;
/** Environnement (SANDBOX ou PRODUCTION) */
private String environnement;
/** Date de dernière vérification */
private LocalDateTime dateDerniereVerification;
/** Commentaires */
private String commentaire;
/** ID de l'organisation (si compte d'organisation) */
private UUID organisationId;
/** ID du membre (si compte de membre) */
private UUID membreId;
}

View File

@@ -0,0 +1,87 @@
package dev.lions.unionflow.server.api.dto.wave;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des transactions Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class TransactionWaveDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Identifiant Wave de la transaction */
@NotBlank(message = "L'identifiant Wave est obligatoire")
private String waveTransactionId;
/** Identifiant de requête Wave */
private String waveRequestId;
/** Référence Wave */
private String waveReference;
/** Type de transaction */
@NotNull(message = "Le type de transaction est obligatoire")
private TypeTransactionWave typeTransaction;
/** Statut de la transaction */
@NotNull(message = "Le statut de la transaction est obligatoire")
private StatutTransactionWave statutTransaction;
/** Montant de la transaction */
@NotNull(message = "Le montant est obligatoire")
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2)
private BigDecimal montant;
/** Frais de transaction */
@DecimalMin(value = "0.0")
@Digits(integer = 10, fraction = 2)
private BigDecimal frais;
/** Montant net */
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
private BigDecimal montantNet;
/** Code devise */
@NotBlank(message = "Le code devise est obligatoire")
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
private String codeDevise;
/** Numéro téléphone payeur */
private String telephonePayeur;
/** Numéro téléphone bénéficiaire */
private String telephoneBeneficiaire;
/** Métadonnées JSON */
private String metadonnees;
/** Nombre de tentatives */
private Integer nombreTentatives;
/** Date de dernière tentative */
private LocalDateTime dateDerniereTentative;
/** Message d'erreur */
private String messageErreur;
/** ID du compte Wave */
@NotNull(message = "Le compte Wave est obligatoire")
private UUID compteWaveId;
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.abonnement;
/**
* Énumération des statuts d'abonnements UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutAbonnement {
ACTIF("Actif"),
SUSPENDU("Suspendu"),
EXPIRE("Expiré"),
ANNULE("Annulé"),
EN_ATTENTE_PAIEMENT("En attente de paiement");
private final String libelle;
StatutAbonnement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,25 @@
package dev.lions.unionflow.server.api.enums.abonnement;
/**
* Énumération des statuts de formules d'abonnement UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutFormule {
ACTIVE("Active"),
INACTIVE("Inactive"),
ARCHIVEE("Archivée"),
BIENTOT_DISPONIBLE("Bientôt Disponible");
private final String libelle;
StatutFormule(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,25 @@
package dev.lions.unionflow.server.api.enums.abonnement;
/**
* Énumération des types de formules d'abonnement UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum TypeFormule {
BASIC("Formule Basique"),
STANDARD("Formule Standard"),
PREMIUM("Formule Premium"),
ENTERPRISE("Formule Entreprise");
private final String libelle;
TypeFormule(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.adresse;
/**
* Énumération des types d'adresse dans UnionFlow
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeAdresse {
SIEGE_SOCIAL("Siège Social"),
BUREAU("Bureau"),
DOMICILE("Domicile"),
AUTRE("Autre");
private final String libelle;
TypeAdresse(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,257 @@
package dev.lions.unionflow.server.api.enums.analytics;
/**
* Énumération des formats d'export disponibles pour les rapports et données analytics
*
* <p>Cette énumération définit les différents formats dans lesquels les données peuvent être
* exportées depuis l'application UnionFlow.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum FormatExport {
// === FORMATS DOCUMENTS ===
PDF("PDF", "pdf", "application/pdf", "Portable Document Format", true, true),
WORD(
"Word",
"docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"Microsoft Word",
true,
false),
// === FORMATS TABLEURS ===
EXCEL(
"Excel",
"xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Microsoft Excel",
true,
true),
CSV("CSV", "csv", "text/csv", "Comma Separated Values", false, true),
// === FORMATS DONNÉES ===
JSON("JSON", "json", "application/json", "JavaScript Object Notation", false, true),
XML("XML", "xml", "application/xml", "eXtensible Markup Language", false, false),
// === FORMATS IMAGES ===
PNG("PNG", "png", "image/png", "Portable Network Graphics", true, false),
JPEG("JPEG", "jpg", "image/jpeg", "Joint Photographic Experts Group", true, false),
SVG("SVG", "svg", "image/svg+xml", "Scalable Vector Graphics", true, false),
// === FORMATS SPÉCIALISÉS ===
POWERPOINT(
"PowerPoint",
"pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"Microsoft PowerPoint",
true,
false),
HTML("HTML", "html", "text/html", "HyperText Markup Language", true, false);
private final String libelle;
private final String extension;
private final String mimeType;
private final String description;
private final boolean supporteGraphiques;
private final boolean supporteGrandesQuantitesDonnees;
/**
* Constructeur de l'énumération FormatExport
*
* @param libelle Le libellé affiché à l'utilisateur
* @param extension L'extension de fichier
* @param mimeType Le type MIME du format
* @param description La description du format
* @param supporteGraphiques true si le format supporte les graphiques
* @param supporteGrandesQuantitesDonnees true si le format supporte de grandes quantités de
* données
*/
FormatExport(
String libelle,
String extension,
String mimeType,
String description,
boolean supporteGraphiques,
boolean supporteGrandesQuantitesDonnees) {
this.libelle = libelle;
this.extension = extension;
this.mimeType = mimeType;
this.description = description;
this.supporteGraphiques = supporteGraphiques;
this.supporteGrandesQuantitesDonnees = supporteGrandesQuantitesDonnees;
}
/**
* Retourne le libellé du format
*
* @return Le libellé affiché à l'utilisateur
*/
public String getLibelle() {
return libelle;
}
/**
* Retourne l'extension de fichier
*
* @return L'extension sans le point (ex: "pdf", "xlsx")
*/
public String getExtension() {
return extension;
}
/**
* Retourne le type MIME du format
*
* @return Le type MIME complet
*/
public String getMimeType() {
return mimeType;
}
/**
* Retourne la description du format
*
* @return La description complète du format
*/
public String getDescription() {
return description;
}
/**
* Vérifie si le format supporte les graphiques
*
* @return true si le format peut inclure des graphiques
*/
public boolean supporteGraphiques() {
return supporteGraphiques;
}
/**
* Vérifie si le format supporte de grandes quantités de données
*
* @return true si le format peut gérer de gros volumes de données
*/
public boolean supporteGrandesQuantitesDonnees() {
return supporteGrandesQuantitesDonnees;
}
/**
* Vérifie si le format est adapté aux rapports exécutifs
*
* @return true si le format convient aux rapports de direction
*/
public boolean isFormatExecutif() {
return this == PDF || this == POWERPOINT || this == WORD;
}
/**
* Vérifie si le format est adapté à l'analyse de données
*
* @return true si le format convient à l'analyse de données
*/
public boolean isFormatAnalyse() {
return this == EXCEL || this == CSV || this == JSON;
}
/**
* Vérifie si le format est adapté au partage web
*
* @return true si le format convient au partage sur le web
*/
public boolean isFormatWeb() {
return this == HTML || this == PNG || this == SVG || this == JSON;
}
/**
* Retourne l'icône appropriée pour le format
*
* @return L'icône Material Design
*/
public String getIcone() {
return switch (this) {
case PDF -> "picture_as_pdf";
case WORD -> "description";
case EXCEL -> "table_chart";
case CSV -> "grid_on";
case JSON -> "code";
case XML -> "code";
case PNG, JPEG -> "image";
case SVG -> "vector_image";
case POWERPOINT -> "slideshow";
case HTML -> "web";
};
}
/**
* Retourne la couleur appropriée pour le format
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return switch (this) {
case PDF -> "#FF5722"; // Rouge-orange
case WORD -> "#2196F3"; // Bleu
case EXCEL -> "#4CAF50"; // Vert
case CSV -> "#607D8B"; // Bleu gris
case JSON -> "#FF9800"; // Orange
case XML -> "#795548"; // Marron
case PNG, JPEG -> "#E91E63"; // Rose
case SVG -> "#9C27B0"; // Violet
case POWERPOINT -> "#FF5722"; // Rouge-orange
case HTML -> "#00BCD4"; // Cyan
};
}
/**
* Génère un nom de fichier avec l'extension appropriée
*
* @param nomBase Le nom de base du fichier
* @return Le nom de fichier complet avec extension
*/
public String genererNomFichier(String nomBase) {
return nomBase + "." + extension;
}
/**
* Retourne la taille maximale recommandée pour ce format (en MB)
*
* @return La taille maximale en mégaoctets
*/
public int getTailleMaximaleRecommandee() {
return switch (this) {
case PDF, WORD, POWERPOINT -> 50; // 50 MB pour les documents
case EXCEL -> 100; // 100 MB pour Excel
case CSV, JSON, XML -> 200; // 200 MB pour les données
case PNG, JPEG -> 10; // 10 MB pour les images
case SVG, HTML -> 5; // 5 MB pour les formats légers
};
}
/**
* Vérifie si le format nécessite un traitement spécial
*
* @return true si le format nécessite un traitement particulier
*/
public boolean necessiteTraitementSpecial() {
return this == PDF || this == EXCEL || this == POWERPOINT || this == WORD;
}
/**
* Retourne les formats recommandés pour un type de rapport donné
*
* @param typeRapport Le type de rapport (executif, analytique, technique)
* @return Un tableau des formats recommandés
*/
public static FormatExport[] getFormatsRecommandes(String typeRapport) {
return switch (typeRapport.toLowerCase()) {
case "executif" -> new FormatExport[] {PDF, POWERPOINT, WORD};
case "analytique" -> new FormatExport[] {EXCEL, CSV, JSON, PDF};
case "technique" -> new FormatExport[] {JSON, XML, CSV, HTML};
case "partage" -> new FormatExport[] {PDF, PNG, HTML};
default -> new FormatExport[] {PDF, EXCEL, CSV};
};
}
}

View File

@@ -0,0 +1,226 @@
package dev.lions.unionflow.server.api.enums.analytics;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
/**
* Énumération des périodes d'analyse disponibles pour les métriques et rapports
*
* <p>Cette énumération définit les différentes périodes temporelles qui peuvent être utilisées pour
* analyser les données et générer des rapports.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum PeriodeAnalyse {
// === PÉRIODES COURTES ===
AUJOURD_HUI("Aujourd'hui", "today", 1, ChronoUnit.DAYS),
HIER("Hier", "yesterday", 1, ChronoUnit.DAYS),
CETTE_SEMAINE("Cette semaine", "this_week", 7, ChronoUnit.DAYS),
SEMAINE_DERNIERE("Semaine dernière", "last_week", 7, ChronoUnit.DAYS),
// === PÉRIODES MENSUELLES ===
CE_MOIS("Ce mois", "this_month", 1, ChronoUnit.MONTHS),
MOIS_DERNIER("Mois dernier", "last_month", 1, ChronoUnit.MONTHS),
TROIS_DERNIERS_MOIS("3 derniers mois", "last_3_months", 3, ChronoUnit.MONTHS),
SIX_DERNIERS_MOIS("6 derniers mois", "last_6_months", 6, ChronoUnit.MONTHS),
// === PÉRIODES ANNUELLES ===
CETTE_ANNEE("Cette année", "this_year", 1, ChronoUnit.YEARS),
ANNEE_DERNIERE("Année dernière", "last_year", 1, ChronoUnit.YEARS),
DEUX_DERNIERES_ANNEES("2 dernières années", "last_2_years", 2, ChronoUnit.YEARS),
// === PÉRIODES PERSONNALISÉES ===
SEPT_DERNIERS_JOURS("7 derniers jours", "last_7_days", 7, ChronoUnit.DAYS),
TRENTE_DERNIERS_JOURS("30 derniers jours", "last_30_days", 30, ChronoUnit.DAYS),
QUATRE_VINGT_DIX_DERNIERS_JOURS("90 derniers jours", "last_90_days", 90, ChronoUnit.DAYS),
// === PÉRIODES SPÉCIALES ===
DEPUIS_CREATION("Depuis la création", "since_creation", 0, ChronoUnit.FOREVER),
PERIODE_PERSONNALISEE("Période personnalisée", "custom", 0, ChronoUnit.DAYS);
private final String libelle;
private final String code;
private final int duree;
private final ChronoUnit unite;
/**
* Constructeur de l'énumération PeriodeAnalyse
*
* @param libelle Le libellé affiché à l'utilisateur
* @param code Le code technique de la période
* @param duree La durée de la période
* @param unite L'unité de temps (jours, mois, années)
*/
PeriodeAnalyse(String libelle, String code, int duree, ChronoUnit unite) {
this.libelle = libelle;
this.code = code;
this.duree = duree;
this.unite = unite;
}
/**
* Retourne le libellé de la période
*
* @return Le libellé affiché à l'utilisateur
*/
public String getLibelle() {
return libelle;
}
/**
* Retourne le code technique de la période
*
* @return Le code technique
*/
public String getCode() {
return code;
}
/**
* Retourne la durée de la période
*
* @return La durée numérique
*/
public int getDuree() {
return duree;
}
/**
* Retourne l'unité de temps de la période
*
* @return L'unité de temps (ChronoUnit)
*/
public ChronoUnit getUnite() {
return unite;
}
/**
* Calcule la date de début pour cette période
*
* @return La date de début de la période
*/
public LocalDateTime getDateDebut() {
LocalDateTime maintenant = LocalDateTime.now();
return switch (this) {
case AUJOURD_HUI -> maintenant.toLocalDate().atStartOfDay();
case HIER -> maintenant.minusDays(1).toLocalDate().atStartOfDay();
case CETTE_SEMAINE ->
maintenant
.minusDays(maintenant.getDayOfWeek().getValue() - 1)
.toLocalDate()
.atStartOfDay();
case SEMAINE_DERNIERE ->
maintenant
.minusDays(maintenant.getDayOfWeek().getValue() + 6)
.toLocalDate()
.atStartOfDay();
case CE_MOIS -> maintenant.withDayOfMonth(1).toLocalDate().atStartOfDay();
case MOIS_DERNIER -> maintenant.minusMonths(1).withDayOfMonth(1).toLocalDate().atStartOfDay();
case CETTE_ANNEE -> maintenant.withDayOfYear(1).toLocalDate().atStartOfDay();
case ANNEE_DERNIERE -> maintenant.minusYears(1).withDayOfYear(1).toLocalDate().atStartOfDay();
case DEPUIS_CREATION -> LocalDateTime.of(2020, 1, 1, 0, 0); // Date de création d'UnionFlow
case PERIODE_PERSONNALISEE -> maintenant; // À définir par l'utilisateur
default -> maintenant.minus(duree, unite).toLocalDate().atStartOfDay();
};
}
/**
* Calcule la date de fin pour cette période
*
* @return La date de fin de la période
*/
public LocalDateTime getDateFin() {
LocalDateTime maintenant = LocalDateTime.now();
return switch (this) {
case AUJOURD_HUI -> maintenant.toLocalDate().atTime(23, 59, 59);
case HIER -> maintenant.minusDays(1).toLocalDate().atTime(23, 59, 59);
case CETTE_SEMAINE -> maintenant.toLocalDate().atTime(23, 59, 59);
case SEMAINE_DERNIERE ->
maintenant
.minusDays(maintenant.getDayOfWeek().getValue())
.toLocalDate()
.atTime(23, 59, 59);
case CE_MOIS -> maintenant.toLocalDate().atTime(23, 59, 59);
case MOIS_DERNIER ->
maintenant.withDayOfMonth(1).minusDays(1).toLocalDate().atTime(23, 59, 59);
case CETTE_ANNEE -> maintenant.toLocalDate().atTime(23, 59, 59);
case ANNEE_DERNIERE ->
maintenant.withDayOfYear(1).minusDays(1).toLocalDate().atTime(23, 59, 59);
case DEPUIS_CREATION, PERIODE_PERSONNALISEE -> maintenant;
default -> maintenant.toLocalDate().atTime(23, 59, 59);
};
}
/**
* Vérifie si la période est une période courte (moins d'un mois)
*
* @return true si la période est courte
*/
public boolean isPeriodeCourte() {
return this == AUJOURD_HUI
|| this == HIER
|| this == CETTE_SEMAINE
|| this == SEMAINE_DERNIERE
|| this == SEPT_DERNIERS_JOURS;
}
/**
* Vérifie si la période est une période longue (plus d'un an)
*
* @return true si la période est longue
*/
public boolean isPeriodeLongue() {
return this == CETTE_ANNEE
|| this == ANNEE_DERNIERE
|| this == DEUX_DERNIERES_ANNEES
|| this == DEPUIS_CREATION;
}
/**
* Vérifie si la période est personnalisable
*
* @return true si la période peut être personnalisée
*/
public boolean isPersonnalisable() {
return this == PERIODE_PERSONNALISEE;
}
/**
* Retourne l'intervalle de regroupement recommandé pour cette période
*
* @return L'intervalle de regroupement (jour, semaine, mois)
*/
public String getIntervalleRegroupement() {
return switch (this) {
case AUJOURD_HUI, HIER -> "heure";
case CETTE_SEMAINE, SEMAINE_DERNIERE, SEPT_DERNIERS_JOURS -> "jour";
case CE_MOIS, MOIS_DERNIER, TRENTE_DERNIERS_JOURS -> "jour";
case TROIS_DERNIERS_MOIS, SIX_DERNIERS_MOIS, QUATRE_VINGT_DIX_DERNIERS_JOURS -> "semaine";
case CETTE_ANNEE, ANNEE_DERNIERE, DEUX_DERNIERES_ANNEES -> "mois";
case DEPUIS_CREATION -> "annee";
default -> "jour";
};
}
/**
* Retourne le format de date approprié pour cette période
*
* @return Le format de date (dd/MM, MM/yyyy, etc.)
*/
public String getFormatDate() {
return switch (this) {
case AUJOURD_HUI, HIER -> "HH:mm";
case CETTE_SEMAINE, SEMAINE_DERNIERE, SEPT_DERNIERS_JOURS -> "dd/MM";
case CE_MOIS, MOIS_DERNIER, TRENTE_DERNIERS_JOURS -> "dd/MM";
case TROIS_DERNIERS_MOIS, SIX_DERNIERS_MOIS, QUATRE_VINGT_DIX_DERNIERS_JOURS -> "dd/MM";
case CETTE_ANNEE, ANNEE_DERNIERE -> "MM/yyyy";
case DEUX_DERNIERES_ANNEES, DEPUIS_CREATION -> "yyyy";
default -> "dd/MM/yyyy";
};
}
}

View File

@@ -0,0 +1,187 @@
package dev.lions.unionflow.server.api.enums.analytics;
/**
* Énumération des types de métriques disponibles dans le système analytics UnionFlow
*
* <p>Cette énumération définit les différents types de métriques qui peuvent être calculées et
* affichées dans les tableaux de bord et rapports.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum TypeMetrique {
// === MÉTRIQUES MEMBRES ===
NOMBRE_MEMBRES_ACTIFS("Nombre de membres actifs", "membres", "count"),
NOMBRE_MEMBRES_INACTIFS("Nombre de membres inactifs", "membres", "count"),
TAUX_CROISSANCE_MEMBRES("Taux de croissance des membres", "membres", "percentage"),
MOYENNE_AGE_MEMBRES("Âge moyen des membres", "membres", "average"),
REPARTITION_GENRE_MEMBRES("Répartition par genre", "membres", "distribution"),
// === MÉTRIQUES FINANCIÈRES ===
TOTAL_COTISATIONS_COLLECTEES("Total des cotisations collectées", "finance", "amount"),
COTISATIONS_EN_ATTENTE("Cotisations en attente", "finance", "amount"),
TAUX_RECOUVREMENT_COTISATIONS("Taux de recouvrement", "finance", "percentage"),
MOYENNE_COTISATION_MEMBRE("Cotisation moyenne par membre", "finance", "average"),
EVOLUTION_REVENUS_MENSUELLE("Évolution des revenus mensuels", "finance", "trend"),
// === MÉTRIQUES ÉVÉNEMENTS ===
NOMBRE_EVENEMENTS_ORGANISES("Nombre d'événements organisés", "evenements", "count"),
TAUX_PARTICIPATION_EVENEMENTS("Taux de participation aux événements", "evenements", "percentage"),
MOYENNE_PARTICIPANTS_EVENEMENT("Moyenne de participants par événement", "evenements", "average"),
EVENEMENTS_ANNULES("Événements annulés", "evenements", "count"),
SATISFACTION_EVENEMENTS("Satisfaction des événements", "evenements", "rating"),
// === MÉTRIQUES SOLIDARITÉ ===
NOMBRE_DEMANDES_AIDE("Nombre de demandes d'aide", "solidarite", "count"),
MONTANT_AIDES_ACCORDEES("Montant des aides accordées", "solidarite", "amount"),
TAUX_APPROBATION_AIDES("Taux d'approbation des aides", "solidarite", "percentage"),
DELAI_TRAITEMENT_DEMANDES("Délai moyen de traitement", "solidarite", "duration"),
IMPACT_SOCIAL_MESURE("Impact social mesuré", "solidarite", "score"),
// === MÉTRIQUES ENGAGEMENT ===
TAUX_CONNEXION_MOBILE("Taux de connexion mobile", "engagement", "percentage"),
FREQUENCE_UTILISATION_APP("Fréquence d'utilisation de l'app", "engagement", "frequency"),
ACTIONS_UTILISATEUR_JOUR("Actions utilisateur par jour", "engagement", "count"),
RETENTION_UTILISATEURS("Rétention des utilisateurs", "engagement", "percentage"),
NPS_SATISFACTION("Net Promoter Score", "engagement", "score"),
// === MÉTRIQUES ORGANISATIONNELLES ===
NOMBRE_ORGANISATIONS_ACTIVES("Organisations actives", "organisation", "count"),
TAUX_CROISSANCE_ORGANISATIONS("Croissance des organisations", "organisation", "percentage"),
MOYENNE_MEMBRES_PAR_ORGANISATION("Membres moyens par organisation", "organisation", "average"),
ORGANISATIONS_PREMIUM("Organisations premium", "organisation", "count"),
CHURN_RATE_ORGANISATIONS("Taux de désabonnement", "organisation", "percentage"),
// === MÉTRIQUES TECHNIQUES ===
TEMPS_REPONSE_API("Temps de réponse API", "technique", "duration"),
TAUX_DISPONIBILITE_SYSTEME("Taux de disponibilité", "technique", "percentage"),
NOMBRE_ERREURS_SYSTEME("Nombre d'erreurs système", "technique", "count"),
UTILISATION_STOCKAGE("Utilisation du stockage", "technique", "size"),
PERFORMANCE_MOBILE("Performance mobile", "technique", "score");
private final String libelle;
private final String categorie;
private final String typeValeur;
/**
* Constructeur de l'énumération TypeMetrique
*
* @param libelle Le libellé affiché à l'utilisateur
* @param categorie La catégorie de la métrique
* @param typeValeur Le type de valeur (count, percentage, amount, etc.)
*/
TypeMetrique(String libelle, String categorie, String typeValeur) {
this.libelle = libelle;
this.categorie = categorie;
this.typeValeur = typeValeur;
}
/**
* Retourne le libellé de la métrique
*
* @return Le libellé affiché à l'utilisateur
*/
public String getLibelle() {
return libelle;
}
/**
* Retourne la catégorie de la métrique
*
* @return La catégorie (membres, finance, evenements, etc.)
*/
public String getCategorie() {
return categorie;
}
/**
* Retourne le type de valeur de la métrique
*
* @return Le type de valeur (count, percentage, amount, etc.)
*/
public String getTypeValeur() {
return typeValeur;
}
/**
* Vérifie si la métrique est de type financier
*
* @return true si la métrique concerne les finances
*/
public boolean isFinanciere() {
return "finance".equals(this.categorie);
}
/**
* Vérifie si la métrique est de type pourcentage
*
* @return true si la métrique est un pourcentage
*/
public boolean isPourcentage() {
return "percentage".equals(this.typeValeur);
}
/**
* Vérifie si la métrique est de type compteur
*
* @return true si la métrique est un compteur
*/
public boolean isCompteur() {
return "count".equals(this.typeValeur);
}
/**
* Retourne l'unité de mesure appropriée pour la métrique
*
* @return L'unité de mesure (%, XOF, jours, etc.)
*/
public String getUnite() {
return switch (this.typeValeur) {
case "percentage" -> "%";
case "amount" -> "XOF";
case "duration" -> "jours";
case "size" -> "MB";
case "frequency" -> "/jour";
case "rating", "score" -> "/10";
default -> "";
};
}
/**
* Retourne l'icône appropriée pour la métrique
*
* @return L'icône Material Design
*/
public String getIcone() {
return switch (this.categorie) {
case "membres" -> "people";
case "finance" -> "attach_money";
case "evenements" -> "event";
case "solidarite" -> "favorite";
case "engagement" -> "trending_up";
case "organisation" -> "business";
case "technique" -> "settings";
default -> "analytics";
};
}
/**
* Retourne la couleur appropriée pour la métrique
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return switch (this.categorie) {
case "membres" -> "#2196F3"; // Bleu
case "finance" -> "#4CAF50"; // Vert
case "evenements" -> "#FF9800"; // Orange
case "solidarite" -> "#E91E63"; // Rose
case "engagement" -> "#9C27B0"; // Violet
case "organisation" -> "#607D8B"; // Bleu gris
case "technique" -> "#795548"; // Marron
default -> "#757575"; // Gris
};
}
}

View File

@@ -0,0 +1,28 @@
package dev.lions.unionflow.server.api.enums.comptabilite;
/**
* Énumération des types de comptes comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeCompteComptable {
ACTIF("Actif"),
PASSIF("Passif"),
CHARGES("Charges"),
PRODUITS("Produits"),
TRESORERIE("Trésorerie"),
AUTRE("Autre");
private final String libelle;
TypeCompteComptable(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,27 @@
package dev.lions.unionflow.server.api.enums.comptabilite;
/**
* Énumération des types de journaux comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeJournalComptable {
ACHATS("Achats"),
VENTES("Ventes"),
BANQUE("Banque"),
CAISSE("Caisse"),
OD("Opérations Diverses");
private final String libelle;
TypeJournalComptable(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,30 @@
package dev.lions.unionflow.server.api.enums.document;
/**
* Énumération des types de documents
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeDocument {
IDENTITE("Pièce d'Identité"),
JUSTIFICATIF_DOMICILE("Justificatif de Domicile"),
PHOTO("Photo"),
CONTRAT("Contrat"),
FACTURE("Facture"),
RECU("Reçu"),
RAPPORT("Rapport"),
AUTRE("Autre");
private final String libelle;
TypeDocument(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,159 @@
package dev.lions.unionflow.server.api.enums.evenement;
/**
* Énumération des priorités d'événements dans UnionFlow
*
* <p>Cette énumération définit les niveaux de priorité pour les événements, permettant de prioriser
* l'affichage et les notifications selon l'importance.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-09-21
*/
public enum PrioriteEvenement {
CRITIQUE(
"Critique",
"critical",
1,
"Événement critique nécessitant une attention immédiate",
"#F44336",
"priority_high",
true,
true),
HAUTE(
"Haute",
"high",
2,
"Événement de haute priorité",
"#FF9800",
"keyboard_arrow_up",
true,
false),
NORMALE(
"Normale", "normal", 3, "Événement de priorité normale", "#2196F3", "remove", false, false),
BASSE(
"Basse",
"low",
4,
"Événement de priorité basse",
"#4CAF50",
"keyboard_arrow_down",
false,
false);
private final String libelle;
private final String code;
private final int niveau;
private final String description;
private final String couleur;
private final String icone;
private final boolean notificationImmediate;
private final boolean escaladeAutomatique;
PrioriteEvenement(
String libelle,
String code,
int niveau,
String description,
String couleur,
String icone,
boolean notificationImmediate,
boolean escaladeAutomatique) {
this.libelle = libelle;
this.code = code;
this.niveau = niveau;
this.description = description;
this.couleur = couleur;
this.icone = icone;
this.notificationImmediate = notificationImmediate;
this.escaladeAutomatique = escaladeAutomatique;
}
// === GETTERS ===
public String getLibelle() {
return libelle;
}
public String getCode() {
return code;
}
public int getNiveau() {
return niveau;
}
public String getDescription() {
return description;
}
public String getCouleur() {
return couleur;
}
public String getIcone() {
return icone;
}
public boolean isNotificationImmediate() {
return notificationImmediate;
}
public boolean isEscaladeAutomatique() {
return escaladeAutomatique;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si la priorité est élevée (critique ou haute) */
public boolean isElevee() {
return this == CRITIQUE || this == HAUTE;
}
/** Vérifie si la priorité nécessite une attention immédiate */
public boolean isUrgente() {
return this == CRITIQUE || this == HAUTE;
}
/** Compare deux priorités */
public boolean isSuperieurA(PrioriteEvenement autre) {
return this.niveau < autre.niveau; // Plus le niveau est bas, plus la priorité est haute
}
/** Retourne les priorités élevées */
public static java.util.List<PrioriteEvenement> getPrioritesElevees() {
return java.util.Arrays.stream(values())
.filter(PrioriteEvenement::isElevee)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les priorités urgentes */
public static java.util.List<PrioriteEvenement> getPrioritesUrgentes() {
return java.util.Arrays.stream(values())
.filter(PrioriteEvenement::isUrgente)
.collect(java.util.stream.Collectors.toList());
}
/** Détermine la priorité basée sur le type d'événement */
public static PrioriteEvenement determinerPriorite(TypeEvenementMetier typeEvenement) {
return switch (typeEvenement) {
case ASSEMBLEE_GENERALE -> HAUTE;
case REUNION_BUREAU -> HAUTE;
case ACTION_CARITATIVE -> NORMALE;
case FORMATION -> NORMALE;
case CONFERENCE -> NORMALE;
case ACTIVITE_SOCIALE -> BASSE;
case ATELIER -> BASSE;
case CEREMONIE -> NORMALE;
case AUTRE -> NORMALE;
};
}
/** Retourne la priorité par défaut */
public static PrioriteEvenement getDefaut() {
return NORMALE;
}
}

View File

@@ -0,0 +1,233 @@
package dev.lions.unionflow.server.api.enums.evenement;
/**
* Énumération des statuts d'événements dans UnionFlow
*
* <p>Cette énumération définit les différents états qu'un événement peut avoir tout au long de son
* cycle de vie, de la planification à la clôture.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-09-21
*/
public enum StatutEvenement {
// === STATUTS DE PLANIFICATION ===
PLANIFIE(
"Planifié",
"planned",
"L'événement est planifié et en préparation",
"#2196F3",
"event",
false,
false),
CONFIRME(
"Confirmé",
"confirmed",
"L'événement est confirmé et les inscriptions sont ouvertes",
"#4CAF50",
"event_available",
false,
false),
// === STATUTS D'EXÉCUTION ===
EN_COURS(
"En cours",
"ongoing",
"L'événement est actuellement en cours",
"#FF9800",
"play_circle",
false,
false),
// === STATUTS FINAUX ===
TERMINE(
"Terminé",
"completed",
"L'événement s'est terminé avec succès",
"#4CAF50",
"check_circle",
true,
false),
ANNULE("Annulé", "cancelled", "L'événement a été annulé", "#F44336", "cancel", true, true),
REPORTE(
"Reporté",
"postponed",
"L'événement a été reporté à une date ultérieure",
"#FF5722",
"schedule",
false,
false);
private final String libelle;
private final String code;
private final String description;
private final String couleur;
private final String icone;
private final boolean estFinal;
private final boolean estEchec;
StatutEvenement(
String libelle,
String code,
String description,
String couleur,
String icone,
boolean estFinal,
boolean estEchec) {
this.libelle = libelle;
this.code = code;
this.description = description;
this.couleur = couleur;
this.icone = icone;
this.estFinal = estFinal;
this.estEchec = estEchec;
}
// === GETTERS ===
public String getLibelle() {
return libelle;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public String getCouleur() {
return couleur;
}
public String getIcone() {
return icone;
}
public boolean isEstFinal() {
return estFinal;
}
public boolean isEstEchec() {
return estEchec;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si l'événement peut être modifié */
public boolean permetModification() {
return switch (this) {
case PLANIFIE, CONFIRME, REPORTE -> true;
case EN_COURS, TERMINE, ANNULE -> false;
};
}
/** Vérifie si l'événement peut être annulé */
public boolean permetAnnulation() {
return switch (this) {
case PLANIFIE, CONFIRME, EN_COURS, REPORTE -> true;
case TERMINE, ANNULE -> false;
};
}
/** Vérifie si l'événement est en cours d'exécution */
public boolean isEnCours() {
return this == EN_COURS;
}
/** Vérifie si l'événement est terminé avec succès */
public boolean isSucces() {
return this == TERMINE;
}
/** Retourne les statuts finaux */
public static java.util.List<StatutEvenement> getStatutsFinaux() {
return java.util.Arrays.stream(values())
.filter(StatutEvenement::isEstFinal)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les statuts d'échec */
public static java.util.List<StatutEvenement> getStatutsEchec() {
return java.util.Arrays.stream(values())
.filter(StatutEvenement::isEstEchec)
.collect(java.util.stream.Collectors.toList());
}
/** Vérifie si la transition vers un autre statut est valide */
public boolean peutTransitionnerVers(StatutEvenement nouveauStatut) {
if (this == nouveauStatut) return false;
if (estFinal && nouveauStatut != REPORTE) return false;
return switch (this) {
case PLANIFIE ->
nouveauStatut == CONFIRME || nouveauStatut == ANNULE || nouveauStatut == REPORTE;
case CONFIRME ->
nouveauStatut == EN_COURS || nouveauStatut == ANNULE || nouveauStatut == REPORTE;
case EN_COURS -> nouveauStatut == TERMINE || nouveauStatut == ANNULE;
case REPORTE -> nouveauStatut == PLANIFIE || nouveauStatut == ANNULE;
default -> false;
};
}
/** Retourne le niveau de priorité pour l'affichage */
public int getNiveauPriorite() {
return switch (this) {
case EN_COURS -> 1;
case CONFIRME -> 2;
case PLANIFIE -> 3;
case REPORTE -> 4;
case TERMINE -> 5;
case ANNULE -> 6;
};
}
// === MÉTHODES STATIQUES ===
/** Retourne les statuts actifs (non finaux) */
public static StatutEvenement[] getStatutsActifs() {
return new StatutEvenement[] {PLANIFIE, CONFIRME, EN_COURS, REPORTE};
}
/** Trouve un statut par son code */
public static StatutEvenement fromCode(String code) {
if (code == null || code.trim().isEmpty()) {
return null;
}
for (StatutEvenement statut : values()) {
if (statut.code.equals(code)) {
return statut;
}
}
return null;
}
/** Trouve un statut par son libellé */
public static StatutEvenement fromLibelle(String libelle) {
if (libelle == null || libelle.trim().isEmpty()) {
return null;
}
for (StatutEvenement statut : values()) {
if (statut.libelle.equals(libelle)) {
return statut;
}
}
return null;
}
/** Retourne les transitions possibles depuis ce statut */
public StatutEvenement[] getTransitionsPossibles() {
return switch (this) {
case PLANIFIE -> new StatutEvenement[] {CONFIRME, ANNULE, REPORTE};
case CONFIRME -> new StatutEvenement[] {EN_COURS, ANNULE, REPORTE};
case EN_COURS -> new StatutEvenement[] {TERMINE, ANNULE};
case REPORTE -> new StatutEvenement[] {PLANIFIE, CONFIRME, ANNULE};
case TERMINE, ANNULE -> new StatutEvenement[] {}; // Aucune transition possible
};
}
}

View File

@@ -0,0 +1,30 @@
package dev.lions.unionflow.server.api.enums.evenement;
/**
* Énumération des types d'événements métier dans UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum TypeEvenementMetier {
ASSEMBLEE_GENERALE("Assemblée Générale"),
FORMATION("Formation"),
ACTIVITE_SOCIALE("Activité Sociale"),
ACTION_CARITATIVE("Action Caritative"),
REUNION_BUREAU("Réunion de Bureau"),
CONFERENCE("Conférence"),
ATELIER("Atelier"),
CEREMONIE("Cérémonie"),
AUTRE("Autre");
private final String libelle;
TypeEvenementMetier(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,27 @@
package dev.lions.unionflow.server.api.enums.finance;
/**
* Énumération des statuts de cotisations dans UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutCotisation {
EN_ATTENTE("En attente"),
PAYEE("Payée"),
PARTIELLEMENT_PAYEE("Partiellement payée"),
EN_RETARD("En retard"),
ANNULEE("Annulée"),
REMBOURSEE("Remboursée");
private final String libelle;
StatutCotisation(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,25 @@
package dev.lions.unionflow.server.api.enums.membre;
/**
* Énumération des statuts de membres dans UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutMembre {
ACTIF("Actif"),
INACTIF("Inactif"),
SUSPENDU("Suspendu"),
RADIE("Radié");
private final String libelle;
StatutMembre(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,465 @@
package dev.lions.unionflow.server.api.enums.notification;
/**
* Énumération des canaux de notification pour Android et iOS
*
* <p>Cette énumération définit les différents canaux de notification utilisés pour organiser et
* prioriser les notifications push dans UnionFlow.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum CanalNotification {
// === CANAUX PAR PRIORITÉ ===
URGENT_CHANNEL(
"urgent",
"Notifications urgentes",
"Alertes critiques nécessitant une action immédiate",
5,
true,
true,
true,
"urgent",
"#F44336"),
ERROR_CHANNEL(
"error",
"Erreurs système",
"Notifications d'erreurs et de problèmes techniques",
4,
true,
true,
false,
"error",
"#F44336"),
WARNING_CHANNEL(
"warning",
"Avertissements",
"Notifications d'avertissement et d'attention",
4,
true,
true,
false,
"warning",
"#FF9800"),
IMPORTANT_CHANNEL(
"important",
"Notifications importantes",
"Informations importantes à ne pas manquer",
4,
true,
true,
false,
"important",
"#FF5722"),
REMINDER_CHANNEL(
"reminder",
"Rappels",
"Rappels d'événements, cotisations et échéances",
3,
true,
true,
false,
"reminder",
"#2196F3"),
SUCCESS_CHANNEL(
"success",
"Confirmations",
"Notifications de succès et confirmations",
2,
false,
false,
false,
"success",
"#4CAF50"),
CELEBRATION_CHANNEL(
"celebration",
"Célébrations",
"Anniversaires, félicitations et événements joyeux",
2,
false,
false,
false,
"celebration",
"#FF9800"),
DEFAULT_CHANNEL(
"default",
"Notifications générales",
"Notifications d'information générale",
2,
false,
false,
false,
"info",
"#2196F3"),
// === CANAUX PAR CATÉGORIE ===
EVENTS_CHANNEL(
"events",
"Événements",
"Notifications liées aux événements et activités",
3,
true,
false,
false,
"event",
"#2196F3"),
PAYMENTS_CHANNEL(
"payments",
"Paiements",
"Notifications de cotisations et paiements",
4,
true,
true,
false,
"payment",
"#4CAF50"),
SOLIDARITY_CHANNEL(
"solidarity",
"Solidarité",
"Notifications d'aide et de solidarité",
3,
true,
false,
false,
"help",
"#E91E63"),
MEMBERS_CHANNEL(
"members",
"Membres",
"Notifications concernant les membres",
2,
false,
false,
false,
"people",
"#2196F3"),
ORGANIZATION_CHANNEL(
"organization",
"Organisation",
"Annonces et informations organisationnelles",
3,
true,
false,
false,
"business",
"#2196F3"),
SYSTEM_CHANNEL(
"system",
"Système",
"Notifications système et maintenance",
2,
false,
false,
false,
"settings",
"#607D8B"),
MESSAGES_CHANNEL(
"messages",
"Messages",
"Messages privés et communications",
3,
true,
false,
false,
"message",
"#2196F3"),
LOCATION_CHANNEL(
"location",
"Géolocalisation",
"Notifications basées sur la localisation",
2,
false,
false,
false,
"location_on",
"#4CAF50");
private final String id;
private final String nom;
private final String description;
private final int importance;
private final boolean sonActive;
private final boolean vibrationActive;
private final boolean lumiereLED;
private final String typeDefaut;
private final String couleur;
/**
* Constructeur de l'énumération CanalNotification
*
* @param id L'identifiant unique du canal
* @param nom Le nom affiché du canal
* @param description La description du canal
* @param importance Le niveau d'importance (1=Min, 2=Low, 3=Default, 4=High, 5=Max)
* @param sonActive true si le son est activé par défaut
* @param vibrationActive true si la vibration est activée par défaut
* @param lumiereLED true si la lumière LED est activée par défaut
* @param typeDefaut Le type de notification par défaut pour ce canal
* @param couleur La couleur hexadécimale du canal
*/
CanalNotification(
String id,
String nom,
String description,
int importance,
boolean sonActive,
boolean vibrationActive,
boolean lumiereLED,
String typeDefaut,
String couleur) {
this.id = id;
this.nom = nom;
this.description = description;
this.importance = importance;
this.sonActive = sonActive;
this.vibrationActive = vibrationActive;
this.lumiereLED = lumiereLED;
this.typeDefaut = typeDefaut;
this.couleur = couleur;
}
/**
* Retourne l'identifiant du canal
*
* @return L'ID unique du canal
*/
public String getId() {
return id;
}
/**
* Retourne le nom du canal
*
* @return Le nom affiché du canal
*/
public String getNom() {
return nom;
}
/**
* Retourne la description du canal
*
* @return La description détaillée du canal
*/
public String getDescription() {
return description;
}
/**
* Retourne le niveau d'importance
*
* @return Le niveau d'importance (1-5)
*/
public int getImportance() {
return importance;
}
/**
* Vérifie si le son est activé par défaut
*
* @return true si le son est activé
*/
public boolean isSonActive() {
return sonActive;
}
/**
* Vérifie si la vibration est activée par défaut
*
* @return true si la vibration est activée
*/
public boolean isVibrationActive() {
return vibrationActive;
}
/**
* Vérifie si la lumière LED est activée par défaut
*
* @return true si la lumière LED est activée
*/
public boolean isLumiereLED() {
return lumiereLED;
}
/**
* Retourne le type de notification par défaut
*
* @return Le type par défaut pour ce canal
*/
public String getTypeDefaut() {
return typeDefaut;
}
/**
* Retourne la couleur du canal
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return couleur;
}
/**
* Vérifie si le canal est critique
*
* @return true si le canal a une importance élevée (4-5)
*/
public boolean isCritique() {
return importance >= 4;
}
/**
* Vérifie si le canal est silencieux par défaut
*
* @return true si le canal n'émet ni son ni vibration
*/
public boolean isSilencieux() {
return !sonActive && !vibrationActive;
}
/**
* Retourne le niveau d'importance Android
*
* @return Le niveau d'importance pour Android (IMPORTANCE_MIN à IMPORTANCE_MAX)
*/
public String getImportanceAndroid() {
return switch (importance) {
case 1 -> "IMPORTANCE_MIN";
case 2 -> "IMPORTANCE_LOW";
case 3 -> "IMPORTANCE_DEFAULT";
case 4 -> "IMPORTANCE_HIGH";
case 5 -> "IMPORTANCE_MAX";
default -> "IMPORTANCE_DEFAULT";
};
}
/**
* Retourne la priorité iOS
*
* @return La priorité pour iOS (low ou high)
*/
public String getPrioriteIOS() {
return importance >= 4 ? "high" : "low";
}
/**
* Retourne le son par défaut pour le canal
*
* @return Le nom du fichier son ou "default"
*/
public String getSonDefaut() {
return switch (this) {
case URGENT_CHANNEL -> "urgent_sound.mp3";
case ERROR_CHANNEL -> "error_sound.mp3";
case WARNING_CHANNEL -> "warning_sound.mp3";
case IMPORTANT_CHANNEL -> "important_sound.mp3";
case REMINDER_CHANNEL -> "reminder_sound.mp3";
case SUCCESS_CHANNEL -> "success_sound.mp3";
case CELEBRATION_CHANNEL -> "celebration_sound.mp3";
default -> "default";
};
}
/**
* Retourne le pattern de vibration
*
* @return Le pattern de vibration en millisecondes
*/
public long[] getPatternVibration() {
return switch (this) {
case URGENT_CHANNEL -> new long[] {0, 500, 200, 500, 200, 500}; // Triple vibration
case ERROR_CHANNEL -> new long[] {0, 1000, 500, 1000}; // Double vibration longue
case WARNING_CHANNEL -> new long[] {0, 300, 200, 300}; // Double vibration courte
case IMPORTANT_CHANNEL -> new long[] {0, 500, 100, 200}; // Vibration distinctive
case REMINDER_CHANNEL -> new long[] {0, 200, 100, 200}; // Vibration douce
default -> new long[] {0, 250}; // Vibration simple
};
}
/**
* Vérifie si le canal peut être désactivé par l'utilisateur
*
* @return true si l'utilisateur peut désactiver ce canal
*/
public boolean peutEtreDesactive() {
return this != URGENT_CHANNEL && this != ERROR_CHANNEL;
}
/**
* Retourne la durée de vie par défaut des notifications de ce canal
*
* @return La durée de vie en millisecondes
*/
public long getDureeVieMs() {
return switch (this) {
case URGENT_CHANNEL -> 3600000L; // 1 heure
case ERROR_CHANNEL -> 86400000L; // 24 heures
case WARNING_CHANNEL -> 172800000L; // 48 heures
case IMPORTANT_CHANNEL -> 259200000L; // 72 heures
case REMINDER_CHANNEL -> 86400000L; // 24 heures
case SUCCESS_CHANNEL -> 172800000L; // 48 heures
case CELEBRATION_CHANNEL -> 259200000L; // 72 heures
default -> 604800000L; // 1 semaine
};
}
/**
* Trouve un canal par son ID
*
* @param id L'identifiant du canal
* @return Le canal correspondant ou DEFAULT_CHANNEL si non trouvé
*/
public static CanalNotification parId(String id) {
for (CanalNotification canal : values()) {
if (canal.getId().equals(id)) {
return canal;
}
}
return DEFAULT_CHANNEL;
}
/**
* Retourne tous les canaux critiques
*
* @return Un tableau des canaux critiques
*/
public static CanalNotification[] getCanauxCritiques() {
return new CanalNotification[] {
URGENT_CHANNEL, ERROR_CHANNEL, WARNING_CHANNEL, IMPORTANT_CHANNEL
};
}
/**
* Retourne tous les canaux par catégorie
*
* @return Un tableau des canaux catégoriels
*/
public static CanalNotification[] getCanauxCategories() {
return new CanalNotification[] {
EVENTS_CHANNEL,
PAYMENTS_CHANNEL,
SOLIDARITY_CHANNEL,
MEMBERS_CHANNEL,
ORGANIZATION_CHANNEL,
SYSTEM_CHANNEL,
MESSAGES_CHANNEL,
LOCATION_CHANNEL
};
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.notification;
/**
* Énumération des priorités de notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum PrioriteNotification {
CRITIQUE("Critique"),
HAUTE("Haute"),
NORMALE("Normale"),
BASSE("Basse");
private final String libelle;
PrioriteNotification(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,459 @@
package dev.lions.unionflow.server.api.enums.notification;
/**
* Énumération des statuts de notification dans UnionFlow
*
* <p>Cette énumération définit les différents états qu'une notification peut avoir tout au long de
* son cycle de vie, de la création à l'archivage.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum StatutNotification {
// === STATUTS DE CRÉATION ===
BROUILLON(
"Brouillon",
"draft",
"La notification est en cours de création",
"edit",
"#9E9E9E",
false,
false),
PROGRAMMEE(
"Programmée",
"scheduled",
"La notification est programmée pour envoi ultérieur",
"schedule",
"#FF9800",
false,
false),
EN_ATTENTE(
"En attente",
"pending",
"La notification est en attente d'envoi",
"hourglass_empty",
"#FF9800",
false,
false),
// === STATUTS D'ENVOI ===
EN_COURS_ENVOI(
"En cours d'envoi",
"sending",
"La notification est en cours d'envoi",
"send",
"#2196F3",
false,
false),
ENVOYEE(
"Envoyée",
"sent",
"La notification a été envoyée avec succès",
"check_circle",
"#4CAF50",
true,
false),
ECHEC_ENVOI(
"Échec d'envoi",
"failed",
"L'envoi de la notification a échoué",
"error",
"#F44336",
true,
true),
PARTIELLEMENT_ENVOYEE(
"Partiellement envoyée",
"partial",
"La notification a été envoyée à certains destinataires seulement",
"warning",
"#FF9800",
true,
true),
// === STATUTS DE RÉCEPTION ===
RECUE(
"Reçue",
"received",
"La notification a été reçue par l'appareil",
"download_done",
"#4CAF50",
true,
false),
AFFICHEE(
"Affichée",
"displayed",
"La notification a été affichée à l'utilisateur",
"visibility",
"#2196F3",
true,
false),
OUVERTE(
"Ouverte",
"opened",
"L'utilisateur a ouvert la notification",
"open_in_new",
"#4CAF50",
true,
false),
IGNOREE(
"Ignorée",
"ignored",
"La notification a été ignorée par l'utilisateur",
"visibility_off",
"#9E9E9E",
true,
false),
// === STATUTS D'INTERACTION ===
LUE(
"Lue",
"read",
"La notification a été lue par l'utilisateur",
"mark_email_read",
"#4CAF50",
true,
false),
NON_LUE(
"Non lue",
"unread",
"La notification n'a pas encore été lue",
"mark_email_unread",
"#FF9800",
true,
false),
MARQUEE_IMPORTANTE(
"Marquée importante",
"starred",
"L'utilisateur a marqué la notification comme importante",
"star",
"#FF9800",
true,
false),
ACTION_EXECUTEE(
"Action exécutée",
"action_done",
"L'utilisateur a exécuté l'action demandée",
"task_alt",
"#4CAF50",
true,
false),
// === STATUTS DE GESTION ===
SUPPRIMEE(
"Supprimée",
"deleted",
"La notification a été supprimée par l'utilisateur",
"delete",
"#F44336",
false,
false),
ARCHIVEE(
"Archivée", "archived", "La notification a été archivée", "archive", "#9E9E9E", false, false),
EXPIREE(
"Expirée",
"expired",
"La notification a dépassé sa durée de vie",
"schedule",
"#9E9E9E",
false,
false),
ANNULEE(
"Annulée",
"cancelled",
"L'envoi de la notification a été annulé",
"cancel",
"#F44336",
false,
true),
// === STATUTS D'ERREUR ===
ERREUR_TECHNIQUE(
"Erreur technique",
"error",
"Une erreur technique a empêché le traitement",
"bug_report",
"#F44336",
false,
true),
DESTINATAIRE_INVALIDE(
"Destinataire invalide",
"invalid_recipient",
"Le destinataire n'est pas valide",
"person_off",
"#F44336",
false,
true),
TOKEN_INVALIDE(
"Token invalide",
"invalid_token",
"Le token FCM du destinataire est invalide",
"key_off",
"#F44336",
false,
true),
QUOTA_DEPASSE(
"Quota dépassé",
"quota_exceeded",
"Le quota d'envoi a été dépassé",
"block",
"#F44336",
false,
true);
private final String libelle;
private final String code;
private final String description;
private final String icone;
private final String couleur;
private final boolean visibleUtilisateur;
private final boolean necessiteAttention;
/**
* Constructeur de l'énumération StatutNotification
*
* @param libelle Le libellé affiché à l'utilisateur
* @param code Le code technique du statut
* @param description La description détaillée du statut
* @param icone L'icône Material Design
* @param couleur La couleur hexadécimale
* @param visibleUtilisateur true si visible à l'utilisateur final
* @param necessiteAttention true si le statut nécessite une attention particulière
*/
StatutNotification(
String libelle,
String code,
String description,
String icone,
String couleur,
boolean visibleUtilisateur,
boolean necessiteAttention) {
this.libelle = libelle;
this.code = code;
this.description = description;
this.icone = icone;
this.couleur = couleur;
this.visibleUtilisateur = visibleUtilisateur;
this.necessiteAttention = necessiteAttention;
}
/**
* Retourne le libellé du statut
*
* @return Le libellé affiché à l'utilisateur
*/
public String getLibelle() {
return libelle;
}
/**
* Retourne le code technique du statut
*
* @return Le code technique
*/
public String getCode() {
return code;
}
/**
* Retourne la description du statut
*
* @return La description détaillée
*/
public String getDescription() {
return description;
}
/**
* Retourne l'icône du statut
*
* @return L'icône Material Design
*/
public String getIcone() {
return icone;
}
/**
* Retourne la couleur du statut
*
* @return Le code couleur hexadécimal
*/
public String getCouleur() {
return couleur;
}
/**
* Vérifie si le statut est visible à l'utilisateur final
*
* @return true si visible à l'utilisateur
*/
public boolean isVisibleUtilisateur() {
return visibleUtilisateur;
}
/**
* Vérifie si le statut nécessite une attention particulière
*
* @return true si le statut nécessite attention
*/
public boolean isNecessiteAttention() {
return necessiteAttention;
}
/**
* Vérifie si le statut indique un succès
*
* @return true si le statut indique un succès
*/
public boolean isSucces() {
return this == ENVOYEE
|| this == RECUE
|| this == AFFICHEE
|| this == OUVERTE
|| this == LUE
|| this == ACTION_EXECUTEE;
}
/**
* Vérifie si le statut indique une erreur
*
* @return true si le statut indique une erreur
*/
public boolean isErreur() {
return this == ECHEC_ENVOI
|| this == ERREUR_TECHNIQUE
|| this == DESTINATAIRE_INVALIDE
|| this == TOKEN_INVALIDE
|| this == QUOTA_DEPASSE;
}
/**
* Vérifie si le statut indique un état en cours
*
* @return true si le statut indique un traitement en cours
*/
public boolean isEnCours() {
return this == PROGRAMMEE || this == EN_ATTENTE || this == EN_COURS_ENVOI;
}
/**
* Vérifie si le statut indique un état final
*
* @return true si le statut est final (pas de transition possible)
*/
public boolean isFinal() {
return this == SUPPRIMEE
|| this == ARCHIVEE
|| this == EXPIREE
|| this == ANNULEE
|| isErreur();
}
/**
* Vérifie si le statut permet la modification
*
* @return true si la notification peut encore être modifiée
*/
public boolean permetModification() {
return this == BROUILLON || this == PROGRAMMEE;
}
/**
* Vérifie si le statut permet l'annulation
*
* @return true si la notification peut être annulée
*/
public boolean permetAnnulation() {
return this == PROGRAMMEE || this == EN_ATTENTE;
}
/**
* Retourne la priorité d'affichage du statut
*
* @return La priorité (1=haute, 5=basse)
*/
public int getPrioriteAffichage() {
if (isErreur()) return 1;
if (necessiteAttention) return 2;
if (isEnCours()) return 3;
if (isSucces()) return 4;
return 5;
}
/**
* Retourne les statuts suivants possibles
*
* @return Un tableau des statuts de transition possibles
*/
public StatutNotification[] getStatutsSuivantsPossibles() {
return switch (this) {
case BROUILLON -> new StatutNotification[] {PROGRAMMEE, EN_ATTENTE, ANNULEE};
case PROGRAMMEE -> new StatutNotification[] {EN_ATTENTE, EN_COURS_ENVOI, ANNULEE};
case EN_ATTENTE -> new StatutNotification[] {EN_COURS_ENVOI, ECHEC_ENVOI, ANNULEE};
case EN_COURS_ENVOI -> new StatutNotification[] {ENVOYEE, PARTIELLEMENT_ENVOYEE, ECHEC_ENVOI};
case ENVOYEE -> new StatutNotification[] {RECUE, ECHEC_ENVOI};
case RECUE -> new StatutNotification[] {AFFICHEE, IGNOREE};
case AFFICHEE -> new StatutNotification[] {OUVERTE, LUE, NON_LUE, IGNOREE};
case OUVERTE -> new StatutNotification[] {LUE, ACTION_EXECUTEE, MARQUEE_IMPORTANTE};
case NON_LUE -> new StatutNotification[] {LUE, OUVERTE, SUPPRIMEE, ARCHIVEE};
case LUE ->
new StatutNotification[] {ACTION_EXECUTEE, MARQUEE_IMPORTANTE, SUPPRIMEE, ARCHIVEE};
default -> new StatutNotification[] {};
};
}
/**
* Trouve un statut par son code
*
* @param code Le code du statut
* @return Le statut correspondant ou null si non trouvé
*/
public static StatutNotification parCode(String code) {
for (StatutNotification statut : values()) {
if (statut.getCode().equals(code)) {
return statut;
}
}
return null;
}
/**
* Retourne tous les statuts visibles à l'utilisateur
*
* @return Un tableau des statuts visibles
*/
public static StatutNotification[] getStatutsVisibles() {
return java.util.Arrays.stream(values())
.filter(StatutNotification::isVisibleUtilisateur)
.toArray(StatutNotification[]::new);
}
/**
* Retourne tous les statuts d'erreur
*
* @return Un tableau des statuts d'erreur
*/
public static StatutNotification[] getStatutsErreur() {
return java.util.Arrays.stream(values())
.filter(StatutNotification::isErreur)
.toArray(StatutNotification[]::new);
}
}

View File

@@ -0,0 +1,32 @@
package dev.lions.unionflow.server.api.enums.notification;
/**
* Énumération des types de notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeNotification {
EMAIL("Email"),
SMS("SMS"),
PUSH("Push Notification"),
IN_APP("Notification In-App"),
SYSTEME("Notification Système");
private final String libelle;
TypeNotification(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
/** Vérifie si ce type de notification est activé par défaut */
public boolean isActiveeParDefaut() {
// Par défaut, tous les types sont activés sauf SYSTEME
return this != SYSTEME;
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.organisation;
/**
* Énumération des statuts d'organisations dans UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutOrganisation {
ACTIVE("Active"),
INACTIVE("Inactive"),
SUSPENDUE("Suspendue"),
EN_CREATION("En Création"),
DISSOUTE("Dissoute");
private final String libelle;
StatutOrganisation(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,29 @@
package dev.lions.unionflow.server.api.enums.organisation;
/**
* Énumération des types d'organisations supportés par UnionFlow
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum TypeOrganisation {
LIONS_CLUB("Lions Club"),
ASSOCIATION("Association"),
FEDERATION("Fédération"),
COOPERATIVE("Coopérative"),
MUTUELLE("Mutuelle"),
SYNDICAT("Syndicat"),
FONDATION("Fondation"),
ONG("ONG");
private final String libelle;
TypeOrganisation(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,39 @@
package dev.lions.unionflow.server.api.enums.paiement;
/**
* Énumération des méthodes de paiement dans UnionFlow
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum MethodePaiement {
WAVE_MOBILE_MONEY("Wave Mobile Money"),
ORANGE_MONEY("Orange Money"),
MTN_MOBILE_MONEY("MTN Mobile Money"),
MOOV_MONEY("Moov Money"),
VIREMENT_BANCAIRE("Virement Bancaire"),
CARTE_BANCAIRE("Carte Bancaire"),
ESPECES("Espèces"),
CHEQUE("Chèque"),
AUTRE("Autre");
private final String libelle;
MethodePaiement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
/** Vérifie si c'est une méthode de mobile money */
public boolean isMobileMoney() {
return this == WAVE_MOBILE_MONEY
|| this == ORANGE_MONEY
|| this == MTN_MOBILE_MONEY
|| this == MOOV_MONEY;
}
}

View File

@@ -0,0 +1,38 @@
package dev.lions.unionflow.server.api.enums.paiement;
/**
* Énumération des statuts de paiement dans UnionFlow
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum StatutPaiement {
EN_ATTENTE("En Attente"),
EN_COURS("En Cours"),
VALIDE("Validé"),
ECHOUE("Échoué"),
ANNULE("Annulé"),
REMBOURSE("Remboursé");
private final String libelle;
StatutPaiement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
/** Vérifie si le paiement est finalisé (ne peut plus changer) */
public boolean isFinalise() {
return this == VALIDE || this == ECHOUE || this == ANNULE || this == REMBOURSE;
}
/** Vérifie si le paiement est en cours de traitement */
public boolean isEnTraitement() {
return this == EN_ATTENTE || this == EN_COURS;
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.paiement;
/**
* Énumération des statuts de sessions de paiement Wave Money
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutSession {
PENDING("En attente"),
COMPLETED("Complétée"),
CANCELLED("Annulée"),
EXPIRED("Expirée"),
FAILED("Échouée");
private final String libelle;
StatutSession(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.paiement;
/**
* Énumération des statuts de traitement des webhooks Wave Money
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum StatutTraitement {
RECU("Reçu"),
EN_COURS("En cours de traitement"),
TRAITE("Traité avec succès"),
ECHEC("Échec de traitement"),
IGNORE("Ignoré");
private final String libelle;
StatutTraitement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,44 @@
package dev.lions.unionflow.server.api.enums.paiement;
/**
* Énumération des types d'événements Wave Money pour les webhooks
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
public enum TypeEvenement {
CHECKOUT_COMPLETE("checkout.complete"),
CHECKOUT_CANCELLED("checkout.cancelled"),
CHECKOUT_EXPIRED("checkout.expired"),
PAYOUT_COMPLETE("payout.complete"),
PAYOUT_FAILED("payout.failed"),
BALANCE_UPDATED("balance.updated"),
TRANSACTION_CREATED("transaction.created"),
TRANSACTION_UPDATED("transaction.updated");
private final String codeWave;
TypeEvenement(String codeWave) {
this.codeWave = codeWave;
}
public String getCodeWave() {
return codeWave;
}
/**
* Trouve un type d'événement par son code Wave
*
* @param code Le code Wave
* @return Le type d'événement correspondant ou null si non trouvé
*/
public static TypeEvenement fromCode(String code) {
for (TypeEvenement type : values()) {
if (type.codeWave.equals(code)) {
return type;
}
}
return null;
}
}

View File

@@ -0,0 +1,268 @@
package dev.lions.unionflow.server.api.enums.solidarite;
/**
* Énumération des priorités d'aide dans le système de solidarité
*
* <p>Cette énumération définit les niveaux de priorité pour les demandes d'aide, permettant de
* prioriser le traitement selon l'urgence de la situation.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum PrioriteAide {
CRITIQUE(
"Critique",
"critical",
1,
"Situation critique nécessitant une intervention immédiate",
"#F44336",
"emergency",
24,
true,
true),
URGENTE(
"Urgente",
"urgent",
2,
"Situation urgente nécessitant une réponse rapide",
"#FF5722",
"priority_high",
72,
true,
false),
ELEVEE(
"Élevée",
"high",
3,
"Priorité élevée, traitement dans les meilleurs délais",
"#FF9800",
"keyboard_arrow_up",
168,
false,
false),
NORMALE(
"Normale",
"normal",
4,
"Priorité normale, traitement selon les délais standards",
"#2196F3",
"remove",
336,
false,
false),
FAIBLE(
"Faible",
"low",
5,
"Priorité faible, traitement quand les ressources le permettent",
"#4CAF50",
"keyboard_arrow_down",
720,
false,
false);
private final String libelle;
private final String code;
private final int niveau;
private final String description;
private final String couleur;
private final String icone;
private final int delaiTraitementHeures;
private final boolean notificationImmediate;
private final boolean escaladeAutomatique;
PrioriteAide(
String libelle,
String code,
int niveau,
String description,
String couleur,
String icone,
int delaiTraitementHeures,
boolean notificationImmediate,
boolean escaladeAutomatique) {
this.libelle = libelle;
this.code = code;
this.niveau = niveau;
this.description = description;
this.couleur = couleur;
this.icone = icone;
this.delaiTraitementHeures = delaiTraitementHeures;
this.notificationImmediate = notificationImmediate;
this.escaladeAutomatique = escaladeAutomatique;
}
// === GETTERS ===
public String getLibelle() {
return libelle;
}
public String getCode() {
return code;
}
public int getNiveau() {
return niveau;
}
public String getDescription() {
return description;
}
public String getCouleur() {
return couleur;
}
public String getIcone() {
return icone;
}
public int getDelaiTraitementHeures() {
return delaiTraitementHeures;
}
public boolean isNotificationImmediate() {
return notificationImmediate;
}
public boolean isEscaladeAutomatique() {
return escaladeAutomatique;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si la priorité est critique ou urgente */
public boolean isUrgente() {
return this == CRITIQUE || this == URGENTE;
}
/** Vérifie si la priorité nécessite un traitement immédiat */
public boolean necessiteTraitementImmediat() {
return niveau <= 2;
}
/** Retourne la date limite de traitement */
public java.time.LocalDateTime getDateLimiteTraitement() {
return java.time.LocalDateTime.now().plusHours(delaiTraitementHeures);
}
/** Retourne la priorité suivante (escalade) */
public PrioriteAide getPrioriteEscalade() {
return switch (this) {
case FAIBLE -> NORMALE;
case NORMALE -> ELEVEE;
case ELEVEE -> URGENTE;
case URGENTE -> CRITIQUE;
case CRITIQUE -> CRITIQUE; // Déjà au maximum
};
}
/** Détermine la priorité basée sur le type d'aide */
public static PrioriteAide determinerPriorite(TypeAide typeAide) {
if (typeAide.isUrgent()) {
return switch (typeAide) {
case AIDE_FINANCIERE_URGENTE, AIDE_FRAIS_MEDICAUX -> CRITIQUE;
case HEBERGEMENT_URGENCE, AIDE_ALIMENTAIRE -> URGENTE;
default -> ELEVEE;
};
}
if (typeAide.getPriorite().equals("important")) {
return ELEVEE;
}
return NORMALE;
}
/** Retourne les priorités urgentes */
public static java.util.List<PrioriteAide> getPrioritesUrgentes() {
return java.util.Arrays.stream(values())
.filter(PrioriteAide::isUrgente)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les priorités par niveau croissant */
public static java.util.List<PrioriteAide> getParNiveauCroissant() {
return java.util.Arrays.stream(values())
.sorted(java.util.Comparator.comparingInt(PrioriteAide::getNiveau))
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les priorités par niveau décroissant */
public static java.util.List<PrioriteAide> getParNiveauDecroissant() {
return java.util.Arrays.stream(values())
.sorted(java.util.Comparator.comparingInt(PrioriteAide::getNiveau).reversed())
.collect(java.util.stream.Collectors.toList());
}
/** Trouve la priorité par code */
public static PrioriteAide parCode(String code) {
return java.util.Arrays.stream(values())
.filter(p -> p.getCode().equals(code))
.findFirst()
.orElse(NORMALE);
}
/** Calcule le score de priorité (plus bas = plus prioritaire) */
public double getScorePriorite() {
double score = niveau;
// Bonus pour notification immédiate
if (notificationImmediate) score -= 0.5;
// Bonus pour escalade automatique
if (escaladeAutomatique) score -= 0.3;
// Malus pour délai long
if (delaiTraitementHeures > 168) score += 0.2;
return score;
}
/** Vérifie si le délai de traitement est dépassé */
public boolean isDelaiDepasse(java.time.LocalDateTime dateCreation) {
return isDelaiDepasse(dateCreation, java.time.LocalDateTime.now());
}
/** Vérifie si le délai de traitement est dépassé (version avec date de référence) */
public boolean isDelaiDepasse(java.time.LocalDateTime dateCreation, java.time.LocalDateTime maintenant) {
java.time.LocalDateTime dateLimite = dateCreation.plusHours(delaiTraitementHeures);
// Utilise isAfter strictement : le délai est dépassé seulement si on est APRÈS la limite
// Si on est exactement à la limite, le délai n'est pas encore dépassé
return maintenant.isAfter(dateLimite);
}
/** Calcule le pourcentage de temps écoulé */
public double getPourcentageTempsEcoule(java.time.LocalDateTime dateCreation) {
java.time.LocalDateTime maintenant = java.time.LocalDateTime.now();
java.time.LocalDateTime dateLimite = dateCreation.plusHours(delaiTraitementHeures);
long dureeTotal = java.time.Duration.between(dateCreation, dateLimite).toMinutes();
long dureeEcoulee = java.time.Duration.between(dateCreation, maintenant).toMinutes();
if (dureeTotal <= 0) return 100.0;
return Math.min(100.0, (dureeEcoulee * 100.0) / dureeTotal);
}
/** Retourne le message d'alerte selon le temps écoulé */
public String getMessageAlerte(java.time.LocalDateTime dateCreation) {
double pourcentage = getPourcentageTempsEcoule(dateCreation);
if (pourcentage >= 100) {
return "Délai de traitement dépassé !";
} else if (pourcentage >= 80) {
return "Délai de traitement bientôt dépassé";
} else if (pourcentage >= 60) {
return "Plus de la moitié du délai écoulé";
}
return null;
}
}

View File

@@ -0,0 +1,296 @@
package dev.lions.unionflow.server.api.enums.solidarite;
/**
* Énumération des statuts d'aide dans le système de solidarité
*
* <p>Cette énumération définit les différents statuts qu'une demande d'aide peut avoir tout au long
* de son cycle de vie.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum StatutAide {
// === STATUTS INITIAUX ===
BROUILLON(
"Brouillon",
"draft",
"La demande est en cours de rédaction",
"#9E9E9E",
"edit",
false,
false),
SOUMISE(
"Soumise",
"submitted",
"La demande a été soumise et attend validation",
"#FF9800",
"send",
false,
false),
// === STATUTS D'ÉVALUATION ===
EN_ATTENTE(
"En attente",
"pending",
"La demande est en attente d'évaluation",
"#2196F3",
"hourglass_empty",
false,
false),
EN_COURS_EVALUATION(
"En cours d'évaluation",
"under_review",
"La demande est en cours d'évaluation",
"#FF9800",
"rate_review",
false,
false),
INFORMATIONS_REQUISES(
"Informations requises",
"info_required",
"Des informations complémentaires sont requises",
"#FF5722",
"info",
false,
false),
// === STATUTS DE DÉCISION ===
APPROUVEE(
"Approuvée",
"approved",
"La demande a été approuvée",
"#4CAF50",
"check_circle",
true,
false),
APPROUVEE_PARTIELLEMENT(
"Approuvée partiellement",
"partially_approved",
"La demande a été approuvée partiellement",
"#8BC34A",
"check_circle_outline",
true,
false),
REJETEE("Rejetée", "rejected", "La demande a été rejetée", "#F44336", "cancel", true, true),
// === STATUTS DE TRAITEMENT ===
EN_COURS_TRAITEMENT(
"En cours de traitement",
"processing",
"La demande approuvée est en cours de traitement",
"#9C27B0",
"settings",
false,
false),
EN_COURS_VERSEMENT(
"En cours de versement",
"payment_processing",
"Le versement est en cours",
"#3F51B5",
"payment",
false,
false),
// === STATUTS FINAUX ===
VERSEE("Versée", "paid", "L'aide a été versée avec succès", "#4CAF50", "paid", true, false),
LIVREE(
"Livrée",
"delivered",
"L'aide matérielle a été livrée",
"#4CAF50",
"local_shipping",
true,
false),
TERMINEE(
"Terminée",
"completed",
"L'aide a été fournie avec succès",
"#4CAF50",
"done_all",
true,
false),
// === STATUTS D'EXCEPTION ===
ANNULEE("Annulée", "cancelled", "La demande a été annulée", "#9E9E9E", "cancel", true, true),
SUSPENDUE(
"Suspendue",
"suspended",
"La demande a été suspendue temporairement",
"#FF5722",
"pause_circle",
false,
false),
EXPIREE("Expirée", "expired", "La demande a expiré", "#795548", "schedule", true, true),
// === STATUTS DE SUIVI ===
EN_SUIVI(
"En suivi",
"follow_up",
"L'aide fait l'objet d'un suivi",
"#607D8B",
"track_changes",
false,
false),
CLOTUREE("Clôturée", "closed", "Le dossier d'aide est clôturé", "#9E9E9E", "folder", true, false);
private final String libelle;
private final String code;
private final String description;
private final String couleur;
private final String icone;
private final boolean estFinal;
private final boolean estEchec;
StatutAide(
String libelle,
String code,
String description,
String couleur,
String icone,
boolean estFinal,
boolean estEchec) {
this.libelle = libelle;
this.code = code;
this.description = description;
this.couleur = couleur;
this.icone = icone;
this.estFinal = estFinal;
this.estEchec = estEchec;
}
// === GETTERS ===
public String getLibelle() {
return libelle;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public String getCouleur() {
return couleur;
}
public String getIcone() {
return icone;
}
public boolean isEstFinal() {
return estFinal;
}
public boolean isEstEchec() {
return estEchec;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si le statut indique un succès */
public boolean isSucces() {
return this == VERSEE || this == LIVREE || this == TERMINEE;
}
/** Vérifie si le statut est en cours de traitement */
public boolean isEnCours() {
return this == EN_COURS_EVALUATION || this == EN_COURS_TRAITEMENT || this == EN_COURS_VERSEMENT;
}
/** Vérifie si le statut permet la modification */
public boolean permetModification() {
return this == BROUILLON || this == INFORMATIONS_REQUISES;
}
/** Vérifie si le statut permet l'annulation */
public boolean permetAnnulation() {
return !estFinal && this != ANNULEE;
}
/** Retourne les statuts finaux */
public static java.util.List<StatutAide> getStatutsFinaux() {
return java.util.Arrays.stream(values())
.filter(StatutAide::isEstFinal)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les statuts d'échec */
public static java.util.List<StatutAide> getStatutsEchec() {
return java.util.Arrays.stream(values())
.filter(StatutAide::isEstEchec)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les statuts de succès */
public static java.util.List<StatutAide> getStatutsSucces() {
return java.util.Arrays.stream(values())
.filter(StatutAide::isSucces)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les statuts en cours */
public static java.util.List<StatutAide> getStatutsEnCours() {
return java.util.Arrays.stream(values())
.filter(StatutAide::isEnCours)
.collect(java.util.stream.Collectors.toList());
}
/** Vérifie si la transition vers un autre statut est valide */
public boolean peutTransitionnerVers(StatutAide nouveauStatut) {
// Règles de transition simplifiées
if (this == nouveauStatut) return false;
// Les statuts finaux ne peuvent transitionner que vers EN_SUIVI
// Exception : APPROUVEE et APPROUVEE_PARTIELLEMENT peuvent transitionner vers EN_COURS_TRAITEMENT ou SUSPENDUE
// car ce sont des statuts de décision qui doivent permettre le démarrage du traitement
if (estFinal
&& this != APPROUVEE
&& this != APPROUVEE_PARTIELLEMENT
&& nouveauStatut != EN_SUIVI) {
return false;
}
return switch (this) {
case BROUILLON -> nouveauStatut == SOUMISE || nouveauStatut == ANNULEE;
case SOUMISE -> nouveauStatut == EN_ATTENTE || nouveauStatut == ANNULEE;
case EN_ATTENTE -> nouveauStatut == EN_COURS_EVALUATION || nouveauStatut == ANNULEE;
case EN_COURS_EVALUATION ->
nouveauStatut == APPROUVEE
|| nouveauStatut == APPROUVEE_PARTIELLEMENT
|| nouveauStatut == REJETEE
|| nouveauStatut == INFORMATIONS_REQUISES
|| nouveauStatut == SUSPENDUE;
case INFORMATIONS_REQUISES ->
nouveauStatut == EN_COURS_EVALUATION || nouveauStatut == ANNULEE;
case APPROUVEE, APPROUVEE_PARTIELLEMENT ->
nouveauStatut == EN_COURS_TRAITEMENT || nouveauStatut == SUSPENDUE;
case EN_COURS_TRAITEMENT ->
nouveauStatut == EN_COURS_VERSEMENT
|| nouveauStatut == LIVREE
|| nouveauStatut == TERMINEE
|| nouveauStatut == SUSPENDUE;
case EN_COURS_VERSEMENT -> nouveauStatut == VERSEE || nouveauStatut == SUSPENDUE;
case SUSPENDUE -> nouveauStatut == EN_COURS_EVALUATION || nouveauStatut == ANNULEE;
default -> false;
};
}
/** Retourne le niveau de priorité pour l'affichage */
public int getNiveauPriorite() {
return switch (this) {
case INFORMATIONS_REQUISES -> 1;
case EN_COURS_EVALUATION, EN_COURS_TRAITEMENT, EN_COURS_VERSEMENT -> 2;
case APPROUVEE, APPROUVEE_PARTIELLEMENT -> 3;
case EN_ATTENTE, SOUMISE -> 4;
case SUSPENDUE -> 5;
case BROUILLON -> 6;
case EN_SUIVI -> 7;
default -> 8; // Statuts finaux
};
}
}

View File

@@ -0,0 +1,515 @@
package dev.lions.unionflow.server.api.enums.solidarite;
/**
* Énumération des types d'aide disponibles dans le système de solidarité
*
* <p>Cette énumération définit les différents types d'aide que les membres peuvent demander ou
* proposer dans le cadre du système de solidarité.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
public enum TypeAide {
// === AIDE FINANCIÈRE ===
AIDE_FINANCIERE_URGENTE(
"Aide financière urgente",
"financiere",
"urgent",
"Aide financière pour situation d'urgence",
"emergency_fund",
"#F44336",
true,
true,
5000.0,
50000.0,
7),
PRET_SANS_INTERET(
"Prêt sans intérêt",
"financiere",
"important",
"Prêt sans intérêt entre membres",
"account_balance",
"#FF9800",
true,
true,
10000.0,
100000.0,
30),
AIDE_COTISATION(
"Aide pour cotisation",
"financiere",
"normal",
"Aide pour payer les cotisations",
"payment",
"#2196F3",
true,
false,
1000.0,
10000.0,
14),
AIDE_FRAIS_MEDICAUX(
"Aide frais médicaux",
"financiere",
"urgent",
"Aide pour frais médicaux et hospitaliers",
"medical_services",
"#E91E63",
true,
true,
5000.0,
200000.0,
7),
AIDE_FRAIS_SCOLARITE(
"Aide frais de scolarité",
"financiere",
"important",
"Aide pour frais de scolarité des enfants",
"school",
"#9C27B0",
true,
true,
10000.0,
100000.0,
21),
// === AIDE MATÉRIELLE ===
DON_MATERIEL(
"Don de matériel",
"materielle",
"normal",
"Don d'objets, équipements ou matériel",
"inventory",
"#4CAF50",
false,
false,
null,
null,
14),
PRET_MATERIEL(
"Prêt de matériel",
"materielle",
"normal",
"Prêt temporaire d'objets ou équipements",
"build",
"#607D8B",
false,
false,
null,
null,
30),
AIDE_DEMENAGEMENT(
"Aide déménagement",
"materielle",
"normal",
"Aide pour déménagement (transport, main d'œuvre)",
"local_shipping",
"#795548",
false,
false,
null,
null,
7),
AIDE_TRAVAUX(
"Aide travaux",
"materielle",
"normal",
"Aide pour travaux de rénovation ou construction",
"construction",
"#FF5722",
false,
false,
null,
null,
21),
// === AIDE PROFESSIONNELLE ===
AIDE_RECHERCHE_EMPLOI(
"Aide recherche d'emploi",
"professionnelle",
"important",
"Aide pour recherche d'emploi et CV",
"work",
"#3F51B5",
false,
false,
null,
null,
30),
FORMATION_PROFESSIONNELLE(
"Formation professionnelle",
"professionnelle",
"normal",
"Formation et développement des compétences",
"school",
"#009688",
false,
false,
null,
null,
60),
CONSEIL_JURIDIQUE(
"Conseil juridique",
"professionnelle",
"important",
"Conseil et assistance juridique",
"gavel",
"#8BC34A",
false,
false,
null,
null,
14),
AIDE_CREATION_ENTREPRISE(
"Aide création d'entreprise",
"professionnelle",
"normal",
"Accompagnement création d'entreprise",
"business",
"#CDDC39",
false,
false,
null,
null,
90),
// === AIDE SOCIALE ===
GARDE_ENFANTS(
"Garde d'enfants",
"sociale",
"normal",
"Garde d'enfants ponctuelle ou régulière",
"child_care",
"#FFC107",
false,
false,
null,
null,
7),
AIDE_PERSONNES_AGEES(
"Aide personnes âgées",
"sociale",
"important",
"Aide et accompagnement personnes âgées",
"elderly",
"#FF9800",
false,
false,
null,
null,
30),
TRANSPORT(
"Transport",
"sociale",
"normal",
"Aide au transport (covoiturage, accompagnement)",
"directions_car",
"#2196F3",
false,
false,
null,
null,
7),
AIDE_ADMINISTRATIVE(
"Aide administrative",
"sociale",
"normal",
"Aide pour démarches administratives",
"description",
"#9E9E9E",
false,
false,
null,
null,
14),
// === AIDE D'URGENCE ===
HEBERGEMENT_URGENCE(
"Hébergement d'urgence",
"urgence",
"urgent",
"Hébergement temporaire d'urgence",
"home",
"#F44336",
false,
true,
null,
null,
7),
AIDE_ALIMENTAIRE(
"Aide alimentaire",
"urgence",
"urgent",
"Aide alimentaire d'urgence",
"restaurant",
"#FF5722",
false,
true,
null,
null,
3),
AIDE_VESTIMENTAIRE(
"Aide vestimentaire",
"urgence",
"normal",
"Don de vêtements et accessoires",
"checkroom",
"#795548",
false,
false,
null,
null,
14),
// === AIDE SPÉCIALISÉE ===
SOUTIEN_PSYCHOLOGIQUE(
"Soutien psychologique",
"specialisee",
"important",
"Soutien et écoute psychologique",
"psychology",
"#E91E63",
false,
true,
null,
null,
30),
AIDE_NUMERIQUE(
"Aide numérique",
"specialisee",
"normal",
"Aide pour utilisation outils numériques",
"computer",
"#607D8B",
false,
false,
null,
null,
14),
TRADUCTION(
"Traduction",
"specialisee",
"normal",
"Services de traduction et interprétariat",
"translate",
"#9C27B0",
false,
false,
null,
null,
7),
AUTRE(
"Autre",
"autre",
"normal",
"Autre type d'aide non catégorisé",
"help",
"#9E9E9E",
false,
false,
null,
null,
14);
private final String libelle;
private final String categorie;
private final String priorite;
private final String description;
private final String icone;
private final String couleur;
private final boolean necessiteMontant;
private final boolean necessiteValidation;
private final Double montantMin;
private final Double montantMax;
private final int delaiReponseJours;
TypeAide(
String libelle,
String categorie,
String priorite,
String description,
String icone,
String couleur,
boolean necessiteMontant,
boolean necessiteValidation,
Double montantMin,
Double montantMax,
int delaiReponseJours) {
this.libelle = libelle;
this.categorie = categorie;
this.priorite = priorite;
this.description = description;
this.icone = icone;
this.couleur = couleur;
this.necessiteMontant = necessiteMontant;
this.necessiteValidation = necessiteValidation;
this.montantMin = montantMin;
this.montantMax = montantMax;
this.delaiReponseJours = delaiReponseJours;
}
// === GETTERS ===
public String getLibelle() {
return libelle;
}
public String getCategorie() {
return categorie;
}
public String getPriorite() {
return priorite;
}
public String getDescription() {
return description;
}
public String getIcone() {
return icone;
}
public String getCouleur() {
return couleur;
}
public boolean isNecessiteMontant() {
return necessiteMontant;
}
public boolean isNecessiteValidation() {
return necessiteValidation;
}
public Double getMontantMin() {
return montantMin;
}
public Double getMontantMax() {
return montantMax;
}
public int getDelaiReponseJours() {
return delaiReponseJours;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si le type d'aide est urgent */
public boolean isUrgent() {
return "urgent".equals(priorite);
}
/** Vérifie si le type d'aide est financier */
public boolean isFinancier() {
return "financiere".equals(categorie);
}
/** Vérifie si le type d'aide est matériel */
public boolean isMateriel() {
return "materielle".equals(categorie);
}
/** Vérifie si le montant est dans la fourchette autorisée */
public boolean isMontantValide(Double montant) {
if (!necessiteMontant || montant == null) return true;
if (montantMin != null && montant < montantMin) return false;
if (montantMax != null && montant > montantMax) return false;
return true;
}
/** Retourne le niveau de priorité numérique */
public int getNiveauPriorite() {
return switch (priorite) {
case "urgent" -> 1;
case "important" -> 2;
case "normal" -> 3;
default -> 3;
};
}
/** Retourne la date limite de réponse */
public java.time.LocalDateTime getDateLimiteReponse() {
return java.time.LocalDateTime.now().plusDays(delaiReponseJours);
}
/** Retourne les types d'aide par catégorie */
public static java.util.List<TypeAide> getParCategorie(String categorie) {
return java.util.Arrays.stream(values())
.filter(type -> type.getCategorie().equals(categorie))
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les types d'aide urgents */
public static java.util.List<TypeAide> getUrgents() {
return java.util.Arrays.stream(values())
.filter(TypeAide::isUrgent)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les types d'aide financiers */
public static java.util.List<TypeAide> getFinanciers() {
return java.util.Arrays.stream(values())
.filter(TypeAide::isFinancier)
.collect(java.util.stream.Collectors.toList());
}
/** Retourne les catégories disponibles */
public static java.util.Set<String> getCategories() {
return java.util.Arrays.stream(values())
.map(TypeAide::getCategorie)
.collect(java.util.stream.Collectors.toSet());
}
/** Retourne le libellé de la catégorie */
public String getLibelleCategorie() {
return switch (categorie) {
case "financiere" -> "Aide financière";
case "materielle" -> "Aide matérielle";
case "professionnelle" -> "Aide professionnelle";
case "sociale" -> "Aide sociale";
case "urgence" -> "Aide d'urgence";
case "specialisee" -> "Aide spécialisée";
case "autre" -> "Autre";
default -> categorie;
};
}
/** Retourne l'unité du montant si applicable */
public String getUniteMontant() {
return necessiteMontant ? "FCFA" : null;
}
/** Retourne le message de validation du montant */
public String getMessageValidationMontant(Double montant) {
if (!necessiteMontant) return null;
if (montant == null) return "Le montant est obligatoire";
if (montantMin != null && montant < montantMin) {
return String.format("Le montant minimum est de %.0f FCFA", montantMin);
}
if (montantMax != null && montant > montantMax) {
return String.format("Le montant maximum est de %.0f FCFA", montantMax);
}
return null;
}
}

View File

@@ -0,0 +1,26 @@
package dev.lions.unionflow.server.api.enums.wave;
/**
* Énumération des statuts de compte Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum StatutCompteWave {
NON_VERIFIE("Non Vérifié"),
VERIFIE("Vérifié"),
SUSPENDU("Suspendu"),
BLOQUE("Bloqué");
private final String libelle;
StatutCompteWave(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,34 @@
package dev.lions.unionflow.server.api.enums.wave;
/**
* Énumération des statuts de transaction Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum StatutTransactionWave {
INITIALISE("Initialisé"),
EN_ATTENTE("En Attente"),
EN_COURS("En Cours"),
REUSSIE("Réussie"),
ECHOUE("Échoué"),
ANNULEE("Annulée"),
EXPIRED("Expiré");
private final String libelle;
StatutTransactionWave(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
/** Vérifie si la transaction est finalisée */
public boolean isFinalise() {
return this == REUSSIE || this == ECHOUE || this == ANNULEE || this == EXPIRED;
}
}

View File

@@ -0,0 +1,27 @@
package dev.lions.unionflow.server.api.enums.wave;
/**
* Énumération des statuts de traitement de webhook Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum StatutWebhook {
EN_ATTENTE("En Attente"),
EN_TRAITEMENT("En Traitement"),
TRAITE("Traité"),
ECHOUE("Échoué"),
IGNORE("Ignoré");
private final String libelle;
StatutWebhook(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,29 @@
package dev.lions.unionflow.server.api.enums.wave;
/**
* Énumération des types d'événements webhook Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeEvenementWebhook {
TRANSACTION_CREATED("Transaction Créée"),
TRANSACTION_COMPLETED("Transaction Complétée"),
TRANSACTION_FAILED("Transaction Échouée"),
TRANSACTION_CANCELLED("Transaction Annulée"),
ACCOUNT_VERIFIED("Compte Vérifié"),
ACCOUNT_SUSPENDED("Compte Suspendu"),
OTHER("Autre");
private final String libelle;
TypeEvenementWebhook(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,27 @@
package dev.lions.unionflow.server.api.enums.wave;
/**
* Énumération des types de transaction Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
public enum TypeTransactionWave {
DEPOT("Dépôt"),
RETRAIT("Retrait"),
TRANSFERT("Transfert"),
PAIEMENT("Paiement"),
REMBOURSEMENT("Remboursement");
private final String libelle;
TypeTransactionWave(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}

View File

@@ -0,0 +1,59 @@
package dev.lions.unionflow.server.api.service.dashboard;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO;
import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO;
import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO;
import java.util.List;
/**
* Interface de service pour la gestion des données du dashboard
*
* <p>Cette interface définit le contrat pour récupérer les données du dashboard,
* incluant les statistiques, activités récentes et événements à venir.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
public interface DashboardService {
/**
* Récupère toutes les données du dashboard pour une organisation et un utilisateur
*
* @param organizationId L'identifiant de l'organisation
* @param userId L'identifiant de l'utilisateur
* @return Les données complètes du dashboard
*/
DashboardDataDTO getDashboardData(String organizationId, String userId);
/**
* Récupère uniquement les statistiques du dashboard
*
* @param organizationId L'identifiant de l'organisation
* @param userId L'identifiant de l'utilisateur
* @return Les statistiques du dashboard
*/
DashboardStatsDTO getDashboardStats(String organizationId, String userId);
/**
* Récupère les activités récentes
*
* @param organizationId L'identifiant de l'organisation
* @param userId L'identifiant de l'utilisateur
* @param limit Le nombre maximum d'activités à retourner
* @return La liste des activités récentes
*/
List<RecentActivityDTO> getRecentActivities(String organizationId, String userId, int limit);
/**
* Récupère les événements à venir
*
* @param organizationId L'identifiant de l'organisation
* @param userId L'identifiant de l'utilisateur
* @param limit Le nombre maximum d'événements à retourner
* @return La liste des événements à venir
*/
List<UpcomingEventDTO> getUpcomingEvents(String organizationId, String userId, int limit);
}

View File

@@ -0,0 +1,233 @@
package dev.lions.unionflow.server.api.validation;
/**
* Constantes pour la validation des DTOs
*
* <p>Cette classe centralise toutes les contraintes de validation pour assurer la cohérence entre
* les différents DTOs du système.
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
public final class ValidationConstants {
private ValidationConstants() {
// Classe utilitaire - constructeur privé
}
// === CONTRAINTES DE TAILLE POUR LES TEXTES ===
/** Titre court (événements, aides, etc.) */
public static final int TITRE_MIN_LENGTH = 5;
public static final int TITRE_MAX_LENGTH = 100;
public static final String TITRE_SIZE_MESSAGE =
"Le titre doit contenir entre "
+ TITRE_MIN_LENGTH
+ " et "
+ TITRE_MAX_LENGTH
+ " caractères";
/** Nom d'organisation */
public static final int NOM_ORGANISATION_MIN_LENGTH = 2;
public static final int NOM_ORGANISATION_MAX_LENGTH = 200;
public static final String NOM_ORGANISATION_SIZE_MESSAGE =
"Le nom doit contenir entre "
+ NOM_ORGANISATION_MIN_LENGTH
+ " et "
+ NOM_ORGANISATION_MAX_LENGTH
+ " caractères";
/** Description standard */
public static final int DESCRIPTION_MIN_LENGTH = 20;
public static final int DESCRIPTION_MAX_LENGTH = 2000;
public static final String DESCRIPTION_SIZE_MESSAGE =
"La description doit contenir entre "
+ DESCRIPTION_MIN_LENGTH
+ " et "
+ DESCRIPTION_MAX_LENGTH
+ " caractères";
/** Description courte (événements) */
public static final int DESCRIPTION_COURTE_MAX_LENGTH = 1000;
public static final String DESCRIPTION_COURTE_SIZE_MESSAGE =
"La description ne peut pas dépasser " + DESCRIPTION_COURTE_MAX_LENGTH + " caractères";
/** Justification */
public static final int JUSTIFICATION_MAX_LENGTH = 1000;
public static final String JUSTIFICATION_SIZE_MESSAGE =
"La justification ne peut pas dépasser " + JUSTIFICATION_MAX_LENGTH + " caractères";
/** Commentaires */
public static final int COMMENTAIRES_MAX_LENGTH = 1000;
public static final String COMMENTAIRES_SIZE_MESSAGE =
"Les commentaires ne peuvent pas dépasser " + COMMENTAIRES_MAX_LENGTH + " caractères";
/** Raison de rejet */
public static final int RAISON_REJET_MAX_LENGTH = 500;
public static final String RAISON_REJET_SIZE_MESSAGE =
"La raison du rejet ne peut pas dépasser " + RAISON_REJET_MAX_LENGTH + " caractères";
/** Adresse */
public static final int ADRESSE_MAX_LENGTH = 200;
public static final String ADRESSE_SIZE_MESSAGE =
"L'adresse ne peut pas dépasser " + ADRESSE_MAX_LENGTH + " caractères";
/** Ville, région, quartier */
public static final int LOCALISATION_MAX_LENGTH = 50;
public static final String VILLE_SIZE_MESSAGE =
"La ville ne peut pas dépasser " + LOCALISATION_MAX_LENGTH + " caractères";
public static final String REGION_SIZE_MESSAGE =
"La région ne peut pas dépasser " + LOCALISATION_MAX_LENGTH + " caractères";
public static final String QUARTIER_SIZE_MESSAGE =
"Le quartier ne peut pas dépasser " + LOCALISATION_MAX_LENGTH + " caractères";
/** Rôle */
public static final int ROLE_MAX_LENGTH = 50;
public static final String ROLE_SIZE_MESSAGE =
"Le rôle ne peut pas dépasser " + ROLE_MAX_LENGTH + " caractères";
/** URL */
public static final int URL_MAX_LENGTH = 255;
public static final String URL_SIZE_MESSAGE =
"L'URL ne peut pas dépasser " + URL_MAX_LENGTH + " caractères";
/** Email */
public static final int EMAIL_MAX_LENGTH = 100;
public static final String EMAIL_SIZE_MESSAGE =
"L'email ne peut pas dépasser " + EMAIL_MAX_LENGTH + " caractères";
/** Nom et prénom */
public static final int NOM_PRENOM_MIN_LENGTH = 2;
public static final int NOM_PRENOM_MAX_LENGTH = 50;
public static final String NOM_SIZE_MESSAGE =
"Le nom doit contenir entre "
+ NOM_PRENOM_MIN_LENGTH
+ " et "
+ NOM_PRENOM_MAX_LENGTH
+ " caractères";
public static final String PRENOM_SIZE_MESSAGE =
"Le prénom doit contenir entre "
+ NOM_PRENOM_MIN_LENGTH
+ " et "
+ NOM_PRENOM_MAX_LENGTH
+ " caractères";
// === PATTERNS DE VALIDATION ===
/** Numéro de téléphone international */
public static final String TELEPHONE_PATTERN = "^\\+?[0-9]{8,15}$";
public static final String TELEPHONE_MESSAGE =
"Le numéro de téléphone doit contenir entre 8 et 15 chiffres, avec un indicatif optionnel"
+ " (+)";
/** Code devise ISO */
public static final String DEVISE_PATTERN = "^[A-Z]{3}$";
public static final String DEVISE_MESSAGE =
"La devise doit être un code ISO à 3 lettres majuscules";
/** Numéro de référence aide */
public static final String REFERENCE_AIDE_PATTERN = "^DA-\\d{4}-\\d{6}$";
public static final String REFERENCE_AIDE_MESSAGE =
"Le numéro de référence doit suivre le format DA-YYYY-NNNNNN";
/** Numéro de membre */
public static final String NUMERO_MEMBRE_PATTERN = "^UF-\\d{4}-[A-Z0-9]{8}$";
public static final String NUMERO_MEMBRE_MESSAGE =
"Format de numéro de membre invalide (UF-YYYY-XXXXXXXX)";
/** Couleur hexadécimale */
public static final String COULEUR_HEX_PATTERN = "^#[0-9A-Fa-f]{6}$";
public static final String COULEUR_HEX_MESSAGE = "Format de couleur invalide (format: #RRGGBB)";
// === CONTRAINTES NUMÉRIQUES ===
/** Montant minimum */
public static final String MONTANT_MIN_VALUE = "0.0";
public static final String MONTANT_POSITIF_MESSAGE = "Le montant doit être positif";
/** Contraintes pour les montants */
public static final int MONTANT_INTEGER_DIGITS = 10;
public static final int MONTANT_FRACTION_DIGITS = 2;
public static final String MONTANT_DIGITS_MESSAGE =
"Le montant doit avoir au maximum "
+ MONTANT_INTEGER_DIGITS
+ " chiffres entiers et "
+ MONTANT_FRACTION_DIGITS
+ " décimales";
// === MESSAGES D'ERREUR STANDARD ===
public static final String OBLIGATOIRE_MESSAGE = " est obligatoire";
public static final String EMAIL_FORMAT_MESSAGE = "L'adresse email n'est pas valide";
public static final String DATE_PASSEE_MESSAGE = "La date doit être dans le passé";
public static final String DATE_FUTURE_MESSAGE = "La date doit être dans le futur";
// === CONTRAINTES SPÉCIFIQUES ===
/** Documents joints */
public static final int DOCUMENTS_JOINTS_MAX_LENGTH = 1000;
public static final String DOCUMENTS_JOINTS_SIZE_MESSAGE =
"La liste des documents ne peut pas dépasser " + DOCUMENTS_JOINTS_MAX_LENGTH + " caractères";
/** Mode de versement */
public static final int MODE_VERSEMENT_MAX_LENGTH = 50;
public static final String MODE_VERSEMENT_SIZE_MESSAGE =
"Le mode de versement ne peut pas dépasser " + MODE_VERSEMENT_MAX_LENGTH + " caractères";
/** Numéro de transaction */
public static final int NUMERO_TRANSACTION_MAX_LENGTH = 100;
public static final String NUMERO_TRANSACTION_SIZE_MESSAGE =
"Le numéro de transaction ne peut pas dépasser "
+ NUMERO_TRANSACTION_MAX_LENGTH
+ " caractères";
/** Numéro d'enregistrement */
public static final int NUMERO_ENREGISTREMENT_MAX_LENGTH = 100;
public static final String NUMERO_ENREGISTREMENT_SIZE_MESSAGE =
"Le numéro d'enregistrement ne peut pas dépasser "
+ NUMERO_ENREGISTREMENT_MAX_LENGTH
+ " caractères";
/** Nom court d'organisation */
public static final int NOM_COURT_MAX_LENGTH = 50;
public static final String NOM_COURT_SIZE_MESSAGE =
"Le nom court ne peut pas dépasser " + NOM_COURT_MAX_LENGTH + " caractères";
/** Instructions et matériel */
public static final int INSTRUCTIONS_MAX_LENGTH = 500;
public static final String INSTRUCTIONS_SIZE_MESSAGE =
"Les instructions ne peuvent pas dépasser " + INSTRUCTIONS_MAX_LENGTH + " caractères";
/** Conditions météo */
public static final int CONDITIONS_METEO_MAX_LENGTH = 100;
public static final String CONDITIONS_METEO_SIZE_MESSAGE =
"Les conditions météo ne peuvent pas dépasser " + CONDITIONS_METEO_MAX_LENGTH + " caractères";
}

View File

@@ -0,0 +1,144 @@
package dev.lions.unionflow.server.api;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.dto.evenement.EvenementDTO;
import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO;
import dev.lions.unionflow.server.api.enums.evenement.PrioriteEvenement;
import dev.lions.unionflow.server.api.enums.evenement.StatutEvenement;
import dev.lions.unionflow.server.api.enums.evenement.TypeEvenementMetier;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Test de compilation pour vérifier que tous les DTOs compilent correctement
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@DisplayName("Tests de Compilation")
class CompilationTest {
@Test
@DisplayName("Test compilation EvenementDTO")
void testCompilationEvenementDTO() {
EvenementDTO evenement = new EvenementDTO();
evenement.setTitre("Test Formation");
evenement.setStatut(StatutEvenement.PLANIFIE);
evenement.setPriorite(PrioriteEvenement.NORMALE);
evenement.setTypeEvenement(TypeEvenementMetier.FORMATION);
evenement.setDateDebut(LocalDate.now().plusDays(30));
// Test des méthodes métier
assertThat(evenement.estEnCours()).isFalse();
assertThat(evenement.getStatutLibelle()).isEqualTo("Planifié");
assertThat(evenement.getTypeEvenementLibelle()).isEqualTo("Formation");
// Test des setters
evenement.setStatut(StatutEvenement.CONFIRME);
assertThat(evenement.getStatut()).isEqualTo(StatutEvenement.CONFIRME);
}
@Test
@DisplayName("Test compilation DemandeAideDTO")
void testCompilationDemandeAideDTO() {
DemandeAideDTO demande = new DemandeAideDTO();
demande.setTitre("Test Demande");
demande.setMontantDemande(new BigDecimal("50000"));
demande.setDevise("XOF");
// Test des méthodes métier
assertThat(demande.getId()).isNotNull(); // BaseDTO génère automatiquement un UUID
assertThat(demande.getVersion()).isEqualTo(0L);
// Test de la méthode marquerCommeModifie
demande.marquerCommeModifie("testUser");
assertThat(demande.getModifiePar()).isEqualTo("testUser");
}
@Test
@DisplayName("Test compilation PropositionAideDTO")
void testCompilationPropositionAideDTO() {
PropositionAideDTO proposition = new PropositionAideDTO();
proposition.setTitre("Test Proposition");
proposition.setMontantMaximum(new BigDecimal("100000"));
// Vérifier que le type est correct
assertThat(proposition.getMontantMaximum()).isInstanceOf(BigDecimal.class);
}
@Test
@DisplayName("Test compilation ValidationConstants")
void testCompilationValidationConstants() {
// Test que les constantes sont accessibles
assertThat(ValidationConstants.TITRE_MIN_LENGTH).isEqualTo(5);
assertThat(ValidationConstants.TITRE_MAX_LENGTH).isEqualTo(100);
assertThat(ValidationConstants.TELEPHONE_PATTERN).isNotNull();
assertThat(ValidationConstants.DEVISE_PATTERN).isNotNull();
}
@Test
@DisplayName("Test compilation énumérations")
void testCompilationEnumerations() {
// Test StatutEvenement
StatutEvenement statut = StatutEvenement.PLANIFIE;
assertThat(statut.getLibelle()).isEqualTo("Planifié");
assertThat(statut.permetModification()).isTrue();
// Test PrioriteEvenement
PrioriteEvenement priorite = PrioriteEvenement.HAUTE;
assertThat(priorite.getLibelle()).isEqualTo("Haute");
assertThat(priorite.isUrgente()).isTrue(); // Amélioration TDD : HAUTE est maintenant urgente
// Test TypeEvenementMetier
TypeEvenementMetier type = TypeEvenementMetier.FORMATION;
assertThat(type.getLibelle()).isEqualTo("Formation");
}
@Test
@DisplayName("Test intégration complète")
void testIntegrationComplete() {
// Créer un événement complet
EvenementDTO evenement =
new EvenementDTO(
"Formation Leadership",
TypeEvenementMetier.FORMATION,
LocalDate.now().plusDays(30),
"Centre de Formation");
evenement.setStatut(StatutEvenement.PLANIFIE);
evenement.setPriorite(PrioriteEvenement.HAUTE);
evenement.setCapaciteMax(50);
evenement.setParticipantsInscrits(0);
evenement.setBudget(new BigDecimal("500000"));
evenement.setCodeDevise("XOF");
evenement.setAssociationId(UUID.randomUUID());
// Vérifier que tout fonctionne
assertThat(evenement.estEnCours()).isFalse();
assertThat(evenement.estComplet()).isFalse();
assertThat(evenement.sontInscriptionsOuvertes()).isTrue();
// Créer une demande d'aide complète
DemandeAideDTO demande = new DemandeAideDTO();
demande.setTitre("Aide Médicale Urgente");
demande.setDescription("Besoin d'aide pour frais médicaux");
demande.setMontantDemande(new BigDecimal("250000"));
demande.setDevise("XOF");
demande.setMembreDemandeurId(UUID.randomUUID());
demande.setAssociationId(UUID.randomUUID());
// Vérifier que tout fonctionne
assertThat(demande.getId()).isNotNull();
assertThat(demande.getVersion()).isEqualTo(0L);
assertThat(demande.getMontantDemande()).isEqualTo(new BigDecimal("250000"));
}
}

View File

@@ -0,0 +1,243 @@
package dev.lions.unionflow.server.api;
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO;
import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO;
import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO;
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
import dev.lions.unionflow.server.api.dto.notification.NotificationDTO;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Factory pour créer des objets de test réutilisables.
* Principe: Write Once, Use Everywhere (WOU) et DRY.
*/
public final class TestDataFactory {
private TestDataFactory() {
// Utility class
}
// ===== MEMBRE DTO =====
public static MembreDTO createMembreDTO() {
return createMembreDTO("UF-2025-000001", "Dupont", "Jean", "jean@example.com");
}
public static MembreDTO createMembreDTO(String numero, String nom, String prenom, String email) {
MembreDTO membre = new MembreDTO(numero, nom, prenom, email);
membre.setAssociationId(UUID.randomUUID());
membre.setStatut(StatutMembre.ACTIF);
return membre;
}
public static MembreDTO createMembreDTOWithAge(int age) {
MembreDTO membre = createMembreDTO();
membre.setDateNaissance(LocalDate.now().minusYears(age));
return membre;
}
// ===== MEMBRE SEARCH CRITERIA =====
public static MembreSearchCriteria createMembreSearchCriteria() {
return new MembreSearchCriteria();
}
public static MembreSearchCriteria createMembreSearchCriteria(String query) {
MembreSearchCriteria criteria = new MembreSearchCriteria();
criteria.setQuery(query);
return criteria;
}
// ===== MEMBRE SEARCH RESULT =====
public static MembreSearchResultDTO createMembreSearchResultDTO() {
MembreSearchResultDTO result = new MembreSearchResultDTO();
result.setMembres(List.of());
result.setTotalElements(0L);
result.setTotalPages(0);
result.setCurrentPage(0);
result.setPageSize(20);
return result;
}
public static MembreSearchResultDTO createMembreSearchResultDTO(
List<MembreDTO> membres, long totalElements, int totalPages, int currentPage) {
MembreSearchResultDTO result = new MembreSearchResultDTO();
result.setMembres(membres);
result.setTotalElements(totalElements);
result.setTotalPages(totalPages);
result.setCurrentPage(currentPage);
result.setPageSize(20);
result.setNumberOfElements(membres != null ? membres.size() : 0);
result.calculatePaginationFlags();
return result;
}
// ===== DASHBOARD DTOs =====
public static DashboardStatsDTO createDashboardStatsDTO() {
return DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(80)
.totalEvents(50)
.upcomingEvents(10)
.totalContributions(200)
.totalContributionAmount(50000.0)
.pendingRequests(5)
.completedProjects(15)
.monthlyGrowth(5.5)
.engagementRate(0.75)
.lastUpdated(LocalDateTime.now())
.build();
}
public static RecentActivityDTO createRecentActivityDTO() {
return createRecentActivityDTO("member", LocalDateTime.now().minusHours(1));
}
public static RecentActivityDTO createRecentActivityDTO(String type, LocalDateTime timestamp) {
return RecentActivityDTO.builder()
.id("act-" + UUID.randomUUID().toString().substring(0, 8))
.type(type)
.title("Test Activity")
.description("Test Description")
.userName("Test User")
.timestamp(timestamp)
.build();
}
public static UpcomingEventDTO createUpcomingEventDTO() {
return createUpcomingEventDTO(LocalDateTime.now().plusDays(1));
}
public static UpcomingEventDTO createUpcomingEventDTO(LocalDateTime startDate) {
return UpcomingEventDTO.builder()
.id("event-" + UUID.randomUUID().toString().substring(0, 8))
.title("Test Event")
.description("Test Description")
.startDate(startDate)
.endDate(startDate != null ? startDate.plusHours(2) : null)
.location("Test Location")
.maxParticipants(100)
.currentParticipants(50)
.status("open")
.build();
}
public static DashboardDataDTO createDashboardDataDTO() {
return DashboardDataDTO.builder()
.stats(createDashboardStatsDTO())
.recentActivities(List.of(createRecentActivityDTO()))
.upcomingEvents(List.of(createUpcomingEventDTO()))
.userPreferences(Map.of("theme", "royal_teal", "language", "fr"))
.organizationId("org-123")
.userId("user-456")
.build();
}
// ===== HELPERS POUR DATES =====
public static LocalDateTime now() {
return LocalDateTime.now();
}
public static LocalDateTime hoursAgo(int hours) {
return now().minusHours(hours);
}
public static LocalDateTime daysAgo(int days) {
return now().minusDays(days);
}
public static LocalDateTime daysFromNow(int days) {
return now().plusDays(days);
}
public static LocalDate date(int year, int month, int day) {
return LocalDate.of(year, month, day);
}
// ===== NOTIFICATION DTO =====
public static NotificationDTO createNotificationDTO() {
NotificationDTO notification = new NotificationDTO();
notification.setId(UUID.randomUUID());
notification.setDateCreation(now());
return notification;
}
// ===== ANALYTICS DATA DTO =====
public static AnalyticsDataDTO createAnalyticsDataDTO() {
return AnalyticsDataDTO.builder()
.dateCalcul(now())
.dateDebut(now().minusDays(30))
.dateFin(now())
.build();
}
// ===== SOLIDARITE DTOs =====
public static dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO createDemandeAideDTO() {
dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO dto =
new dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO();
dto.setNumeroReference("DA-2025-000001");
dto.setTypeAide(dev.lions.unionflow.server.api.enums.solidarite.TypeAide.AIDE_FINANCIERE_URGENTE);
dto.setTitre("Aide pour frais médicaux");
dto.setDescription("Demande d'aide pour couvrir les frais d'hospitalisation");
dto.setMembreDemandeurId(UUID.randomUUID());
dto.setNomDemandeur("Jean Dupont");
dto.setAssociationId(UUID.randomUUID());
dto.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE);
dto.setPriorite(dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide.NORMALE);
return dto;
}
public static dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO
createPropositionAideDTO() {
return dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO.builder()
.id("prop-" + UUID.randomUUID().toString())
.numeroReference("PA-2025-000001")
.typeAide(dev.lions.unionflow.server.api.enums.solidarite.TypeAide.AIDE_FINANCIERE_URGENTE)
.titre("Proposition d'aide financière")
.description("Je propose une aide financière pour les membres en difficulté")
.proposantId("membre-" + UUID.randomUUID().toString())
.proposantNom("Marie Martin")
.organisationId("org-" + UUID.randomUUID().toString())
.build();
}
public static dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO
createEvaluationAideDTO() {
return dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO.builder()
.id("eval-" + UUID.randomUUID().toString())
.demandeAideId("demande-" + UUID.randomUUID().toString())
.evaluateurId("eval-" + UUID.randomUUID().toString())
.evaluateurNom("Évaluateur Test")
.roleEvaluateur("beneficiaire")
.noteGlobale(4.5)
.commentairePrincipal("Très satisfait de l'aide reçue")
.build();
}
public static dev.lions.unionflow.server.api.dto.solidarite.CommentaireAideDTO
createCommentaireAideDTO() {
return dev.lions.unionflow.server.api.dto.solidarite.CommentaireAideDTO.builder()
.id("comment-" + UUID.randomUUID().toString())
.auteurId("auteur-" + UUID.randomUUID().toString())
.auteurNom("Auteur Test")
.contenu("Ceci est un commentaire de test")
.dateCreation(now())
.build();
}
}

View File

@@ -0,0 +1,267 @@
package dev.lions.unionflow.server.api.dto.abonnement;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests unitaires complets pour AbonnementDTO.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@DisplayName("Tests AbonnementDTO")
class AbonnementDTOBasicTest {
private AbonnementDTO abonnement;
@BeforeEach
void setUp() {
abonnement = new AbonnementDTO();
}
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
assertThat(abonnement).isNotNull();
}
@Nested
@DisplayName("Tests de Construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut - Initialisation correcte")
void testConstructeurParDefaut() {
AbonnementDTO newAbonnement = new AbonnementDTO();
assertThat(newAbonnement.getId()).isNotNull();
assertThat(newAbonnement.getDateCreation()).isNotNull();
assertThat(newAbonnement.isActif()).isTrue();
assertThat(newAbonnement.getVersion()).isEqualTo(0L);
assertThat(newAbonnement.getStatut()).isEqualTo("EN_ATTENTE_PAIEMENT");
assertThat(newAbonnement.getDevise()).isEqualTo("XOF");
assertThat(newAbonnement.getRenouvellementAutomatique()).isTrue();
assertThat(newAbonnement.getPeriodeEssaiUtilisee()).isFalse();
assertThat(newAbonnement.getSupportTechnique()).isTrue();
assertThat(newAbonnement.getFonctionnalitesAvancees()).isFalse();
assertThat(newAbonnement.getApiAccess()).isFalse();
assertThat(newAbonnement.getRapportsPersonnalises()).isFalse();
assertThat(newAbonnement.getIntegrationsTierces()).isFalse();
assertThat(newAbonnement.getConnexionsCeMois()).isEqualTo(0);
assertThat(newAbonnement.getAlertesActivees()).isTrue();
assertThat(newAbonnement.getNotificationsEmail()).isTrue();
assertThat(newAbonnement.getNotificationsSMS()).isFalse();
assertThat(newAbonnement.getNumeroReference()).isNotNull();
assertThat(newAbonnement.getNumeroReference()).matches("^ABO-\\d{4}-\\d{8}$");
}
@Test
@DisplayName("Constructeur avec paramètres - Initialisation correcte")
void testConstructeurAvecParametres() {
UUID organisationId = UUID.randomUUID();
String nomOrganisation = "Lions Club Dakar";
String typeFormule = "PREMIUM";
AbonnementDTO newAbonnement = new AbonnementDTO(organisationId, nomOrganisation, typeFormule);
assertThat(newAbonnement.getOrganisationId()).isEqualTo(organisationId);
assertThat(newAbonnement.getNomOrganisation()).isEqualTo(nomOrganisation);
assertThat(newAbonnement.getTypeFormule()).isEqualTo(typeFormule);
assertThat(newAbonnement.getStatut()).isEqualTo("EN_ATTENTE_PAIEMENT");
}
}
@Nested
@DisplayName("Tests Getters/Setters")
class GettersSettersTests {
@Test
@DisplayName("Test tous les getters/setters - Partie 1")
void testTousLesGettersSettersPart1() {
// Données de test
String numeroReference = "ABO-2025-12345678";
UUID organisationId = UUID.randomUUID();
String nomOrganisation = "Lions Club Test";
UUID formulaireId = UUID.randomUUID();
String codeFormule = "PREM001";
String nomFormule = "Formule Premium";
String typeFormule = "PREMIUM";
String statut = "ACTIF";
String typeAbonnement = "ANNUEL";
LocalDate dateDebut = LocalDate.now();
LocalDate dateFin = LocalDate.now().plusYears(1);
LocalDate dateProchainePeriode = LocalDate.now().plusMonths(1);
BigDecimal montant = new BigDecimal("500000.00");
String devise = "XOF";
BigDecimal remise = new BigDecimal("10.00");
BigDecimal montantFinal = new BigDecimal("450000.00");
// Test des setters
abonnement.setNumeroReference(numeroReference);
abonnement.setOrganisationId(organisationId);
abonnement.setNomOrganisation(nomOrganisation);
abonnement.setFormulaireId(formulaireId);
abonnement.setCodeFormule(codeFormule);
abonnement.setNomFormule(nomFormule);
abonnement.setTypeFormule(typeFormule);
abonnement.setStatut(statut);
abonnement.setTypeAbonnement(typeAbonnement);
abonnement.setDateDebut(dateDebut);
abonnement.setDateFin(dateFin);
abonnement.setDateProchainePeriode(dateProchainePeriode);
abonnement.setMontant(montant);
abonnement.setDevise(devise);
abonnement.setRemise(remise);
abonnement.setMontantFinal(montantFinal);
// Test des getters
assertThat(abonnement.getNumeroReference()).isEqualTo(numeroReference);
assertThat(abonnement.getOrganisationId()).isEqualTo(organisationId);
assertThat(abonnement.getNomOrganisation()).isEqualTo(nomOrganisation);
assertThat(abonnement.getFormulaireId()).isEqualTo(formulaireId);
assertThat(abonnement.getCodeFormule()).isEqualTo(codeFormule);
assertThat(abonnement.getNomFormule()).isEqualTo(nomFormule);
assertThat(abonnement.getTypeFormule()).isEqualTo(typeFormule);
assertThat(abonnement.getStatut()).isEqualTo(statut);
assertThat(abonnement.getTypeAbonnement()).isEqualTo(typeAbonnement);
assertThat(abonnement.getDateDebut()).isEqualTo(dateDebut);
assertThat(abonnement.getDateFin()).isEqualTo(dateFin);
assertThat(abonnement.getDateProchainePeriode()).isEqualTo(dateProchainePeriode);
assertThat(abonnement.getMontant()).isEqualTo(montant);
assertThat(abonnement.getDevise()).isEqualTo(devise);
assertThat(abonnement.getRemise()).isEqualTo(remise);
assertThat(abonnement.getMontantFinal()).isEqualTo(montantFinal);
}
@Test
@DisplayName("Test tous les getters/setters - Partie 2")
void testTousLesGettersSettersPart2() {
// Données de test
Boolean renouvellementAutomatique = false;
Boolean periodeEssaiUtilisee = true;
LocalDate dateFinEssai = LocalDate.now().plusDays(30);
Integer maxMembres = 100;
Integer nombreMembresActuels = 75;
BigDecimal espaceStockageGB = new BigDecimal("50.0");
BigDecimal espaceStockageUtilise = new BigDecimal("25.5");
Boolean supportTechnique = true;
String niveauSupport = "PREMIUM";
Boolean fonctionnalitesAvancees = true;
Boolean apiAccess = true;
Boolean rapportsPersonnalises = true;
Boolean integrationsTierces = true;
LocalDateTime dateDerniereUtilisation = LocalDateTime.now();
Integer connexionsCeMois = 150;
// Test des setters
abonnement.setRenouvellementAutomatique(renouvellementAutomatique);
abonnement.setPeriodeEssaiUtilisee(periodeEssaiUtilisee);
abonnement.setDateFinEssai(dateFinEssai);
abonnement.setMaxMembres(maxMembres);
abonnement.setNombreMembresActuels(nombreMembresActuels);
abonnement.setEspaceStockageGB(espaceStockageGB);
abonnement.setEspaceStockageUtilise(espaceStockageUtilise);
abonnement.setSupportTechnique(supportTechnique);
abonnement.setNiveauSupport(niveauSupport);
abonnement.setFonctionnalitesAvancees(fonctionnalitesAvancees);
abonnement.setApiAccess(apiAccess);
abonnement.setRapportsPersonnalises(rapportsPersonnalises);
abonnement.setIntegrationsTierces(integrationsTierces);
abonnement.setDateDerniereUtilisation(dateDerniereUtilisation);
abonnement.setConnexionsCeMois(connexionsCeMois);
// Test des getters
assertThat(abonnement.getRenouvellementAutomatique()).isEqualTo(renouvellementAutomatique);
assertThat(abonnement.getPeriodeEssaiUtilisee()).isEqualTo(periodeEssaiUtilisee);
assertThat(abonnement.getDateFinEssai()).isEqualTo(dateFinEssai);
assertThat(abonnement.getMaxMembres()).isEqualTo(maxMembres);
assertThat(abonnement.getNombreMembresActuels()).isEqualTo(nombreMembresActuels);
assertThat(abonnement.getEspaceStockageGB()).isEqualTo(espaceStockageGB);
assertThat(abonnement.getEspaceStockageUtilise()).isEqualTo(espaceStockageUtilise);
assertThat(abonnement.getSupportTechnique()).isEqualTo(supportTechnique);
assertThat(abonnement.getNiveauSupport()).isEqualTo(niveauSupport);
assertThat(abonnement.getFonctionnalitesAvancees()).isEqualTo(fonctionnalitesAvancees);
assertThat(abonnement.getApiAccess()).isEqualTo(apiAccess);
assertThat(abonnement.getRapportsPersonnalises()).isEqualTo(rapportsPersonnalises);
assertThat(abonnement.getIntegrationsTierces()).isEqualTo(integrationsTierces);
assertThat(abonnement.getDateDerniereUtilisation()).isEqualTo(dateDerniereUtilisation);
assertThat(abonnement.getConnexionsCeMois()).isEqualTo(connexionsCeMois);
}
@Test
@DisplayName("Test tous les getters/setters - Partie 3")
void testTousLesGettersSettersPart3() {
// Données de test
UUID responsableId = UUID.randomUUID();
String nomResponsable = "Jean Dupont";
String emailResponsable = "jean.dupont@example.com";
String telephoneResponsable = "+221701234567";
String modePaiementPrefere = "WAVE_MONEY";
String numeroPaiementMobile = "+221701234567";
String historiquePaiements = "{\"paiements\": []}";
String notes = "Notes sur l'abonnement";
Boolean alertesActivees = false;
Boolean notificationsEmail = false;
Boolean notificationsSMS = true;
LocalDateTime dateSuspension = LocalDateTime.now();
String raisonSuspension = "Non-paiement";
LocalDateTime dateAnnulation = LocalDateTime.now();
String raisonAnnulation = "Demande client";
// Test des setters
abonnement.setResponsableId(responsableId);
abonnement.setNomResponsable(nomResponsable);
abonnement.setEmailResponsable(emailResponsable);
abonnement.setTelephoneResponsable(telephoneResponsable);
abonnement.setModePaiementPrefere(modePaiementPrefere);
abonnement.setNumeroPaiementMobile(numeroPaiementMobile);
abonnement.setHistoriquePaiements(historiquePaiements);
abonnement.setNotes(notes);
abonnement.setAlertesActivees(alertesActivees);
abonnement.setNotificationsEmail(notificationsEmail);
abonnement.setNotificationsSMS(notificationsSMS);
abonnement.setDateSuspension(dateSuspension);
abonnement.setRaisonSuspension(raisonSuspension);
abonnement.setDateAnnulation(dateAnnulation);
abonnement.setRaisonAnnulation(raisonAnnulation);
// Test des getters
assertThat(abonnement.getResponsableId()).isEqualTo(responsableId);
assertThat(abonnement.getNomResponsable()).isEqualTo(nomResponsable);
assertThat(abonnement.getEmailResponsable()).isEqualTo(emailResponsable);
assertThat(abonnement.getTelephoneResponsable()).isEqualTo(telephoneResponsable);
assertThat(abonnement.getModePaiementPrefere()).isEqualTo(modePaiementPrefere);
assertThat(abonnement.getNumeroPaiementMobile()).isEqualTo(numeroPaiementMobile);
assertThat(abonnement.getHistoriquePaiements()).isEqualTo(historiquePaiements);
assertThat(abonnement.getNotes()).isEqualTo(notes);
assertThat(abonnement.getAlertesActivees()).isEqualTo(alertesActivees);
assertThat(abonnement.getNotificationsEmail()).isEqualTo(notificationsEmail);
assertThat(abonnement.getNotificationsSMS()).isEqualTo(notificationsSMS);
assertThat(abonnement.getDateSuspension()).isEqualTo(dateSuspension);
assertThat(abonnement.getRaisonSuspension()).isEqualTo(raisonSuspension);
assertThat(abonnement.getDateAnnulation()).isEqualTo(dateAnnulation);
assertThat(abonnement.getRaisonAnnulation()).isEqualTo(raisonAnnulation);
}
}
@Test
@DisplayName("Test toString")
void testToString() {
abonnement.setNumeroReference("ABO-2025-12345678");
abonnement.setNomOrganisation("Lions Club Test");
abonnement.setStatut("ACTIF");
String result = abonnement.toString();
assertThat(result).isNotNull();
assertThat(result).contains("AbonnementDTO");
}
}

View File

@@ -0,0 +1,570 @@
package dev.lions.unionflow.server.api.dto.analytics;
import static dev.lions.unionflow.server.api.TestDataFactory.createAnalyticsDataDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.now;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@DisplayName("Tests pour AnalyticsDataDTO")
class AnalyticsDataDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
assertThat(dto).isNotNull();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
LocalDateTime dateCalcul = now();
UUID orgId = UUID.randomUUID();
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.valeur(new BigDecimal("100.50"))
.valeurPrecedente(new BigDecimal("90.25"))
.pourcentageEvolution(new BigDecimal("11.36"))
.dateDebut(now().minusDays(30))
.dateFin(now())
.dateCalcul(dateCalcul)
.organisationId(orgId)
.nomOrganisation("Test Org")
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
assertThat(dto.getDateCalcul()).isEqualTo(dateCalcul);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
}
@Test
@DisplayName("Constructeur avec paramètres essentiels")
void testConstructeurAvecParametres() {
AnalyticsDataDTO dto = new AnalyticsDataDTO(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
PeriodeAnalyse.CE_MOIS,
new BigDecimal("100.0"));
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.0"));
assertThat(dto.getDateCalcul()).isNotNull();
assertThat(dto.getTempsReel()).isFalse();
assertThat(dto.getNecessiteMiseAJour()).isFalse();
assertThat(dto.getNiveauPriorite()).isEqualTo(3);
assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.0"));
}
}
@Nested
@DisplayName("Tests getLibelleAffichage")
class GetLibelleAffichageTests {
@Test
@DisplayName("getLibelleAffichage - avec libellé personnalisé")
void testGetLibelleAffichagePersonnalise() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise("Membres Actifs")
.build();
assertThat(dto.getLibelleAffichage()).isEqualTo("Membres Actifs");
}
@Test
@DisplayName("getLibelleAffichage - sans libellé personnalisé")
void testGetLibelleAffichageParDefaut() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.build();
assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
@Test
@DisplayName("getLibelleAffichage - libellé personnalisé vide")
void testGetLibelleAffichageVide() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise(" ")
.build();
assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
@Test
@DisplayName("getLibelleAffichage - typeMetrique null")
void testGetLibelleAffichageTypeMetriqueNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.libellePersonnalise("Libellé personnalisé")
.build();
// Si typeMetrique est null, getLibelle() lancerait NPE, mais testons le comportement
// En fait, si libellePersonnalise n'est pas null/vide, il devrait être retourné
assertThat(dto.getLibelleAffichage()).isEqualTo("Libellé personnalisé");
}
@Test
@DisplayName("getLibelleAffichage - typeMetrique null et libellé personnalisé null")
void testGetLibelleAffichageTypeMetriqueNullEtLibelleNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.libellePersonnalise(null)
.build();
// Si typeMetrique est null et libellePersonnalise est null, cela devrait lancer NPE
org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getLibelleAffichage())
.isInstanceOf(NullPointerException.class);
}
@Test
@DisplayName("getLibelleAffichage - typeMetrique null et libellePersonnalise vide")
void testGetLibelleAffichageTypeMetriqueNullEtLibellePersonnaliseVide() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.libellePersonnalise(" ")
.build();
// Si libellePersonnalise est vide (trim), on essaie d'appeler typeMetrique.getLibelle()
// ce qui devrait lancer NPE
org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getLibelleAffichage())
.isInstanceOf(NullPointerException.class);
}
@Test
@DisplayName("getLibelleAffichage - libellePersonnalise null et typeMetrique non null")
void testGetLibelleAffichageLibelleNullEtTypeMetriqueNonNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise(null)
.build();
// Si libellePersonnalise est null, on retourne typeMetrique.getLibelle()
assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
@Test
@DisplayName("getLibelleAffichage - libellePersonnalise vide et typeMetrique non null")
void testGetLibelleAffichageLibelleVideEtTypeMetriqueNonNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise("")
.build();
// Si libellePersonnalise est vide, on retourne typeMetrique.getLibelle()
assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
}
@Nested
@DisplayName("Tests méthodes utilitaires TypeMetrique")
class MethodesTypeMetriqueTests {
@Test
@DisplayName("getUnite - retourne l'unité du type métrique")
void testGetUnite() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.build();
assertThat(dto.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite());
}
@Test
@DisplayName("getUnite - typeMetrique null")
void testGetUniteTypeMetriqueNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.build();
org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getUnite())
.isInstanceOf(NullPointerException.class);
}
@Test
@DisplayName("getIcone - retourne l'icône du type métrique")
void testGetIcone() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.build();
assertThat(dto.getIcone()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
}
@Test
@DisplayName("getIcone - typeMetrique null")
void testGetIconeTypeMetriqueNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.build();
org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getIcone())
.isInstanceOf(NullPointerException.class);
}
@Test
@DisplayName("getCouleur - retourne la couleur du type métrique")
void testGetCouleur() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.build();
assertThat(dto.getCouleur()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
}
@Test
@DisplayName("getCouleur - typeMetrique null")
void testGetCouleurTypeMetriqueNull() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(null)
.build();
org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getCouleur())
.isInstanceOf(NullPointerException.class);
}
}
@Nested
@DisplayName("Tests hasEvolutionPositive")
class HasEvolutionPositiveTests {
@Test
@DisplayName("hasEvolutionPositive - évolution positive")
void testHasEvolutionPositive() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal("10.5"))
.build();
assertThat(dto.hasEvolutionPositive()).isTrue();
}
@Test
@DisplayName("hasEvolutionPositive - évolution négative")
void testHasEvolutionPositiveNegative() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal("-5.0"))
.build();
assertThat(dto.hasEvolutionPositive()).isFalse();
}
@Test
@DisplayName("hasEvolutionPositive - null")
void testHasEvolutionPositiveNull() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
assertThat(dto.hasEvolutionPositive()).isFalse();
}
}
@Nested
@DisplayName("Tests hasEvolutionNegative")
class HasEvolutionNegativeTests {
@Test
@DisplayName("hasEvolutionNegative - évolution négative")
void testHasEvolutionNegative() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal("-10.5"))
.build();
assertThat(dto.hasEvolutionNegative()).isTrue();
}
@Test
@DisplayName("hasEvolutionNegative - évolution positive")
void testHasEvolutionNegativePositive() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal("5.0"))
.build();
assertThat(dto.hasEvolutionNegative()).isFalse();
}
}
@Nested
@DisplayName("Tests isStable")
class IsStableTests {
@Test
@DisplayName("isStable - évolution nulle")
void testIsStable() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(BigDecimal.ZERO)
.build();
assertThat(dto.isStable()).isTrue();
}
@Test
@DisplayName("isStable - évolution non nulle")
void testIsStableNonNulle() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal("5.0"))
.build();
assertThat(dto.isStable()).isFalse();
}
}
@Nested
@DisplayName("Tests getTendance")
class GetTendanceTests {
@ParameterizedTest
@CsvSource({
"10.5, hausse",
"-5.0, baisse",
"0.0, stable"
})
@DisplayName("getTendance - toutes les tendances")
void testGetTendance(String evolution, String expected) {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.pourcentageEvolution(new BigDecimal(evolution))
.build();
assertThat(dto.getTendance()).isEqualTo(expected);
}
}
@Nested
@DisplayName("Tests isDonneesFiables")
class IsDonneesFiablesTests {
@ParameterizedTest
@CsvSource({
"80.0, true",
"95.0, true",
"79.9, false",
"50.0, false"
})
@DisplayName("isDonneesFiables - seuil 80%")
void testIsDonneesFiables(String fiabilite, Boolean expected) {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.indicateurFiabilite(new BigDecimal(fiabilite))
.build();
assertThat(dto.isDonneesFiables()).isEqualTo(expected);
}
@Test
@DisplayName("isDonneesFiables - null")
void testIsDonneesFiablesNull() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
assertThat(dto.isDonneesFiables()).isFalse();
}
}
@Nested
@DisplayName("Tests isCritique")
class IsCritiqueTests {
@ParameterizedTest
@CsvSource({
"4, true",
"5, true",
"3, false",
"1, false"
})
@DisplayName("isCritique - priorité >= 4")
void testIsCritique(Integer priorite, Boolean expected) {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.niveauPriorite(priorite)
.build();
assertThat(dto.isCritique()).isEqualTo(expected);
}
@Test
@DisplayName("isCritique - null")
void testIsCritiqueNull() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
assertThat(dto.isCritique()).isFalse();
}
}
@Nested
@DisplayName("Tests Builder complet - tous les champs")
class BuilderCompletTests {
@Test
@DisplayName("Builder - tous les champs")
void testBuilderTousChamps() {
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
Map<String, Object> metadonnees = new HashMap<>();
metadonnees.put("key", "value");
List<String> tags = Arrays.asList("tag1", "tag2");
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.valeur(new BigDecimal("100.50"))
.valeurPrecedente(new BigDecimal("90.25"))
.pourcentageEvolution(new BigDecimal("11.36"))
.dateDebut(now.minusDays(30))
.dateFin(now)
.dateCalcul(now)
.organisationId(orgId)
.nomOrganisation("Test Org")
.utilisateurId(userId)
.nomUtilisateur("Test User")
.libellePersonnalise("Libellé personnalisé")
.description("Description test")
.donneesDetaillees("{\"data\": \"test\"}")
.configurationGraphique("{\"config\": \"test\"}")
.metadonnees(metadonnees)
.indicateurFiabilite(new BigDecimal("95.5"))
.nombreElementsAnalyses(1000)
.tempsCalculMs(150L)
.tempsReel(true)
.necessiteMiseAJour(true)
.niveauPriorite(4)
.tags(tags)
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
assertThat(dto.getValeurPrecedente()).isEqualTo(new BigDecimal("90.25"));
assertThat(dto.getPourcentageEvolution()).isEqualTo(new BigDecimal("11.36"));
assertThat(dto.getDateDebut()).isEqualTo(now.minusDays(30));
assertThat(dto.getDateFin()).isEqualTo(now);
assertThat(dto.getDateCalcul()).isEqualTo(now);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Test Org");
assertThat(dto.getUtilisateurId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateur()).isEqualTo("Test User");
assertThat(dto.getLibellePersonnalise()).isEqualTo("Libellé personnalisé");
assertThat(dto.getDescription()).isEqualTo("Description test");
assertThat(dto.getDonneesDetaillees()).isEqualTo("{\"data\": \"test\"}");
assertThat(dto.getConfigurationGraphique()).isEqualTo("{\"config\": \"test\"}");
assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.5"));
assertThat(dto.getNombreElementsAnalyses()).isEqualTo(1000);
assertThat(dto.getTempsCalculMs()).isEqualTo(150L);
assertThat(dto.getTempsReel()).isTrue();
assertThat(dto.getNecessiteMiseAJour()).isTrue();
assertThat(dto.getNiveauPriorite()).isEqualTo(4);
assertThat(dto.getTags()).isEqualTo(tags);
}
@Test
@DisplayName("Builder - valeurs par défaut")
void testBuilderValeursParDefaut() {
AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.valeur(new BigDecimal("100.0"))
.dateDebut(LocalDateTime.now().minusDays(30))
.dateFin(LocalDateTime.now())
.dateCalcul(LocalDateTime.now())
.build();
assertThat(dto.getTempsReel()).isFalse();
assertThat(dto.getNecessiteMiseAJour()).isFalse();
}
}
@Nested
@DisplayName("Tests getters/setters complets")
class GettersSettersCompletsTests {
@Test
@DisplayName("Test tous les getters/setters")
void testTousLesGettersSetters() {
AnalyticsDataDTO dto = new AnalyticsDataDTO();
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
Map<String, Object> metadonnees = new HashMap<>();
metadonnees.put("key", "value");
List<String> tags = Arrays.asList("tag1", "tag2");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
dto.setValeur(new BigDecimal("100.50"));
dto.setValeurPrecedente(new BigDecimal("90.25"));
dto.setPourcentageEvolution(new BigDecimal("11.36"));
dto.setDateDebut(now.minusDays(30));
dto.setDateFin(now);
dto.setDateCalcul(now);
dto.setOrganisationId(orgId);
dto.setNomOrganisation("Test Org");
dto.setUtilisateurId(userId);
dto.setNomUtilisateur("Test User");
dto.setLibellePersonnalise("Libellé personnalisé");
dto.setDescription("Description test");
dto.setDonneesDetaillees("{\"data\": \"test\"}");
dto.setConfigurationGraphique("{\"config\": \"test\"}");
dto.setMetadonnees(metadonnees);
dto.setIndicateurFiabilite(new BigDecimal("95.5"));
dto.setNombreElementsAnalyses(1000);
dto.setTempsCalculMs(150L);
dto.setTempsReel(true);
dto.setNecessiteMiseAJour(true);
dto.setNiveauPriorite(4);
dto.setTags(tags);
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
assertThat(dto.getValeurPrecedente()).isEqualTo(new BigDecimal("90.25"));
assertThat(dto.getPourcentageEvolution()).isEqualTo(new BigDecimal("11.36"));
assertThat(dto.getDateDebut()).isEqualTo(now.minusDays(30));
assertThat(dto.getDateFin()).isEqualTo(now);
assertThat(dto.getDateCalcul()).isEqualTo(now);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Test Org");
assertThat(dto.getUtilisateurId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateur()).isEqualTo("Test User");
assertThat(dto.getLibellePersonnalise()).isEqualTo("Libellé personnalisé");
assertThat(dto.getDescription()).isEqualTo("Description test");
assertThat(dto.getDonneesDetaillees()).isEqualTo("{\"data\": \"test\"}");
assertThat(dto.getConfigurationGraphique()).isEqualTo("{\"config\": \"test\"}");
assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.5"));
assertThat(dto.getNombreElementsAnalyses()).isEqualTo(1000);
assertThat(dto.getTempsCalculMs()).isEqualTo(150L);
assertThat(dto.getTempsReel()).isTrue();
assertThat(dto.getNecessiteMiseAJour()).isTrue();
assertThat(dto.getNiveauPriorite()).isEqualTo(4);
assertThat(dto.getTags()).isEqualTo(tags);
}
}
}

View File

@@ -0,0 +1,634 @@
package dev.lions.unionflow.server.api.dto.analytics;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
@DisplayName("Tests pour DashboardWidgetDTO")
class DashboardWidgetDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
assertThat(dto).isNotNull();
assertThat(dto.getOrdreAffichage()).isEqualTo(0);
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getRedimensionnable()).isTrue();
assertThat(dto.getDeplacable()).isTrue();
assertThat(dto.getSupprimable()).isTrue();
assertThat(dto.getMiseAJourAutomatique()).isTrue();
assertThat(dto.getFrequenceMiseAJourSecondes()).isEqualTo(300);
assertThat(dto.getAlerteActive()).isFalse();
assertThat(dto.getNombreVues()).isEqualTo(0L);
assertThat(dto.getNombreInteractions()).isEqualTo(0L);
assertThat(dto.getTauxErreur()).isEqualTo(0.0);
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
UUID userId = UUID.randomUUID();
DashboardWidgetDTO dto = DashboardWidgetDTO.builder()
.titre("Widget Test")
.typeWidget("kpi")
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.utilisateurProprietaireId(userId)
.positionX(0)
.positionY(0)
.largeur(4)
.hauteur(2)
.build();
assertThat(dto.getTitre()).isEqualTo("Widget Test");
assertThat(dto.getTypeWidget()).isEqualTo("kpi");
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getUtilisateurProprietaireId()).isEqualTo(userId);
assertThat(dto.getPositionX()).isEqualTo(0);
assertThat(dto.getPositionY()).isEqualTo(0);
assertThat(dto.getLargeur()).isEqualTo(4);
assertThat(dto.getHauteur()).isEqualTo(2);
}
}
@Nested
@DisplayName("Tests méthodes utilitaires")
class MethodesUtilitairesTests {
@Test
@DisplayName("getLibelleMetrique - avec typeMetrique")
void testGetLibelleMetriqueAvecType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getLibelleMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
@Test
@DisplayName("getLibelleMetrique - sans typeMetrique")
void testGetLibelleMetriqueSansType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setTypeMetrique(null);
assertThat(dto.getLibelleMetrique()).isNull();
}
@Test
@DisplayName("getUnite - avec typeMetrique")
void testGetUniteAvecType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite());
}
@Test
@DisplayName("getUnite - sans typeMetrique")
void testGetUniteSansType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setTypeMetrique(null);
assertThat(dto.getUnite()).isEmpty();
}
@Test
@DisplayName("getIconeAffichage - avec icône personnalisée")
void testGetIconeAffichagePersonnalisee() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setIcone("custom_icon");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getIconeAffichage()).isEqualTo("custom_icon");
}
@Test
@DisplayName("getIconeAffichage - avec typeMetrique")
void testGetIconeAffichageAvecType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setIcone(null);
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getIconeAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
}
@Test
@DisplayName("getIconeAffichage - sans icône ni type")
void testGetIconeAffichageParDefaut() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setIcone(null);
dto.setTypeMetrique(null);
assertThat(dto.getIconeAffichage()).isEqualTo("dashboard");
}
@Test
@DisplayName("getIconeAffichage - icône vide (trim)")
void testGetIconeAffichageIconeVide() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setIcone(" ");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getIconeAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
}
@Test
@DisplayName("getCouleurAffichage - avec couleur personnalisée")
void testGetCouleurAffichagePersonnalisee() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setCouleurPrincipale("#FF0000");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getCouleurAffichage()).isEqualTo("#FF0000");
}
@Test
@DisplayName("getCouleurAffichage - avec typeMetrique")
void testGetCouleurAffichageAvecType() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setCouleurPrincipale(null);
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getCouleurAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
}
@Test
@DisplayName("getCouleurAffichage - sans couleur ni type")
void testGetCouleurAffichageParDefaut() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setCouleurPrincipale(null);
dto.setTypeMetrique(null);
assertThat(dto.getCouleurAffichage()).isEqualTo("#757575");
}
@Test
@DisplayName("getCouleurAffichage - couleur vide (trim)")
void testGetCouleurAffichageCouleurVide() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setCouleurPrincipale(" ");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getCouleurAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
}
@Test
@DisplayName("necessiteMiseAJour - mise à jour automatique et prochaine mise à jour passée")
void testNecessiteMiseAJourTrue() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setMiseAJourAutomatique(true);
dto.setProchaineMiseAJour(LocalDateTime.now().minusMinutes(1));
assertThat(dto.necessiteMiseAJour()).isTrue();
}
@Test
@DisplayName("necessiteMiseAJour - mise à jour automatique désactivée")
void testNecessiteMiseAJourFalseDesactivee() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setMiseAJourAutomatique(false);
dto.setProchaineMiseAJour(LocalDateTime.now().minusMinutes(1));
assertThat(dto.necessiteMiseAJour()).isFalse();
}
@Test
@DisplayName("necessiteMiseAJour - prochaine mise à jour future")
void testNecessiteMiseAJourFalseFuture() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setMiseAJourAutomatique(true);
dto.setProchaineMiseAJour(LocalDateTime.now().plusMinutes(1));
assertThat(dto.necessiteMiseAJour()).isFalse();
}
@Test
@DisplayName("necessiteMiseAJour - prochaine mise à jour null")
void testNecessiteMiseAJourFalseNull() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setMiseAJourAutomatique(true);
dto.setProchaineMiseAJour(null);
assertThat(dto.necessiteMiseAJour()).isFalse();
}
@ParameterizedTest
@CsvSource({
"chart, true",
"table, true",
"gauge, true",
"kpi, false",
"text, false",
"progress, false"
})
@DisplayName("isInteractif - tous les types")
void testIsInteractif(String typeWidget, boolean expected) {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setTypeWidget(typeWidget);
assertThat(dto.isInteractif()).isEqualTo(expected);
}
@Test
@DisplayName("isTempsReel - fréquence <= 60 secondes")
void testIsTempsReelTrue() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setFrequenceMiseAJourSecondes(30);
assertThat(dto.isTempsReel()).isTrue();
}
@Test
@DisplayName("isTempsReel - fréquence > 60 secondes")
void testIsTempsReelFalse() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setFrequenceMiseAJourSecondes(300);
assertThat(dto.isTempsReel()).isFalse();
}
@Test
@DisplayName("isTempsReel - fréquence null")
void testIsTempsReelNull() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setFrequenceMiseAJourSecondes(null);
assertThat(dto.isTempsReel()).isFalse();
}
@Test
@DisplayName("getTailleWidget - calcul correct")
void testGetTailleWidget() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setLargeur(4);
dto.setHauteur(3);
assertThat(dto.getTailleWidget()).isEqualTo(12);
}
@Test
@DisplayName("isWidgetGrand - taille > 6")
void testIsWidgetGrandTrue() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setLargeur(4);
dto.setHauteur(2);
assertThat(dto.isWidgetGrand()).isTrue();
}
@Test
@DisplayName("isWidgetGrand - taille <= 6")
void testIsWidgetGrandFalse() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setLargeur(2);
dto.setHauteur(2);
assertThat(dto.isWidgetGrand()).isFalse();
}
@Test
@DisplayName("hasErreursRecentes - erreur < 24h")
void testHasErreursRecentesTrue() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(12));
assertThat(dto.hasErreursRecentes()).isTrue();
}
@Test
@DisplayName("hasErreursRecentes - erreur > 24h")
void testHasErreursRecentesFalse() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25));
assertThat(dto.hasErreursRecentes()).isFalse();
}
@Test
@DisplayName("hasErreursRecentes - date null")
void testHasErreursRecentesNull() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(null);
assertThat(dto.hasErreursRecentes()).isFalse();
}
@ParameterizedTest
@CsvSource({
"true, true, 5.0, erreur",
"false, false, 5.0, inactif",
"false, true, 15.0, maintenance",
"false, true, 5.0, actif"
})
@DisplayName("getStatutWidget - tous les cas")
void testGetStatutWidget(boolean hasErreursRecentes, boolean visible, double tauxErreur,
String expected) {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
if (hasErreursRecentes) {
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(12));
} else {
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); // Plus de 24h pour être sûr
}
dto.setVisible(visible);
dto.setTauxErreur(tauxErreur);
assertThat(dto.getStatutWidget()).isEqualTo(expected);
}
@Test
@DisplayName("getStatutWidget - tauxErreur exactement 10.0")
void testGetStatutWidgetTauxErreurExactement10() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); // Pas d'erreurs récentes
dto.setVisible(true);
dto.setTauxErreur(10.0);
// tauxErreur > 10.0 est false, donc devrait retourner "actif"
assertThat(dto.getStatutWidget()).isEqualTo("actif");
}
@Test
@DisplayName("getStatutWidget - tauxErreur null (via setter)")
void testGetStatutWidgetTauxErreurNull() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); // Pas d'erreurs récentes
dto.setVisible(true);
dto.setTauxErreur(null);
// Si tauxErreur est null, la condition tauxErreur > 10.0 est fausse, donc devrait retourner "actif"
assertThat(dto.getStatutWidget()).isEqualTo("actif");
}
@Test
@DisplayName("getStatutWidget - tauxErreur exactement 10.0 (limite)")
void testGetStatutWidgetTauxErreurExactement10Limite() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); // Pas d'erreurs récentes
dto.setVisible(true);
dto.setTauxErreur(10.0);
// tauxErreur > 10.0 est false (10.0 n'est pas > 10.0), donc devrait retourner "actif"
assertThat(dto.getStatutWidget()).isEqualTo("actif");
}
@Test
@DisplayName("getStatutWidget - tauxErreur 10.1 (juste au-dessus)")
void testGetStatutWidgetTauxErreurJusteAuDessus() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); // Pas d'erreurs récentes
dto.setVisible(true);
dto.setTauxErreur(10.1);
// tauxErreur > 10.0 est true, donc devrait retourner "maintenance"
assertThat(dto.getStatutWidget()).isEqualTo("maintenance");
}
}
@Nested
@DisplayName("Tests getters/setters")
class GettersSettersTests {
@Test
@DisplayName("Test tous les getters/setters")
void testTousLesGettersSetters() {
DashboardWidgetDTO dto = new DashboardWidgetDTO();
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
Map<String, Object> filtres = new HashMap<>();
filtres.put("key", "value");
Map<String, Object> alertes = new HashMap<>();
alertes.put("alert", "config");
Map<String, Object> metadonnees = new HashMap<>();
metadonnees.put("meta", "data");
dto.setTitre("Titre Widget");
dto.setDescription("Description");
dto.setTypeWidget("kpi");
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
dto.setOrganisationId(orgId);
dto.setNomOrganisation("Org Test");
dto.setUtilisateurProprietaireId(userId);
dto.setNomUtilisateurProprietaire("User Test");
dto.setPositionX(1);
dto.setPositionY(2);
dto.setLargeur(4);
dto.setHauteur(3);
dto.setOrdreAffichage(1);
dto.setConfigurationVisuelle("config");
dto.setCouleurPrincipale("#FF0000");
dto.setCouleurSecondaire("#00FF00");
dto.setIcone("icon");
dto.setVisible(false);
dto.setRedimensionnable(false);
dto.setDeplacable(false);
dto.setSupprimable(false);
dto.setMiseAJourAutomatique(false);
dto.setFrequenceMiseAJourSecondes(60);
dto.setDateDerniereMiseAJour(LocalDateTime.now());
dto.setProchaineMiseAJour(LocalDateTime.now().plusHours(1));
dto.setDonneesWidget("data");
dto.setConfigurationFiltres(filtres);
dto.setConfigurationAlertes(alertes);
dto.setSeuilAlerteBas(10.0);
dto.setSeuilAlerteHaut(90.0);
dto.setAlerteActive(true);
dto.setMessageAlerte("Alerte");
dto.setTypeAlerte("warning");
dto.setPermissions("read");
dto.setRolesAutorises("ADMIN");
dto.setTemplatePersonnalise("template");
dto.setCssPersonnalise("css");
dto.setJavascriptPersonnalise("js");
dto.setMetadonnees(metadonnees);
dto.setNombreVues(100L);
dto.setNombreInteractions(50L);
dto.setTempsMoyenSecondes(30);
dto.setTauxErreur(5.0);
dto.setDateDerniereErreur(LocalDateTime.now().minusHours(1));
dto.setMessageDerniereErreur("Erreur");
assertThat(dto.getTitre()).isEqualTo("Titre Widget");
assertThat(dto.getDescription()).isEqualTo("Description");
assertThat(dto.getTypeWidget()).isEqualTo("kpi");
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getUtilisateurProprietaireId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateurProprietaire()).isEqualTo("User Test");
assertThat(dto.getPositionX()).isEqualTo(1);
assertThat(dto.getPositionY()).isEqualTo(2);
assertThat(dto.getLargeur()).isEqualTo(4);
assertThat(dto.getHauteur()).isEqualTo(3);
assertThat(dto.getOrdreAffichage()).isEqualTo(1);
assertThat(dto.getConfigurationVisuelle()).isEqualTo("config");
assertThat(dto.getCouleurPrincipale()).isEqualTo("#FF0000");
assertThat(dto.getCouleurSecondaire()).isEqualTo("#00FF00");
assertThat(dto.getIcone()).isEqualTo("icon");
assertThat(dto.getVisible()).isFalse();
assertThat(dto.getRedimensionnable()).isFalse();
assertThat(dto.getDeplacable()).isFalse();
assertThat(dto.getSupprimable()).isFalse();
assertThat(dto.getMiseAJourAutomatique()).isFalse();
assertThat(dto.getFrequenceMiseAJourSecondes()).isEqualTo(60);
assertThat(dto.getDateDerniereMiseAJour()).isNotNull();
assertThat(dto.getProchaineMiseAJour()).isNotNull();
assertThat(dto.getDonneesWidget()).isEqualTo("data");
assertThat(dto.getConfigurationFiltres()).isEqualTo(filtres);
assertThat(dto.getConfigurationAlertes()).isEqualTo(alertes);
assertThat(dto.getSeuilAlerteBas()).isEqualTo(10.0);
assertThat(dto.getSeuilAlerteHaut()).isEqualTo(90.0);
assertThat(dto.getAlerteActive()).isTrue();
assertThat(dto.getMessageAlerte()).isEqualTo("Alerte");
assertThat(dto.getTypeAlerte()).isEqualTo("warning");
assertThat(dto.getPermissions()).isEqualTo("read");
assertThat(dto.getRolesAutorises()).isEqualTo("ADMIN");
assertThat(dto.getTemplatePersonnalise()).isEqualTo("template");
assertThat(dto.getCssPersonnalise()).isEqualTo("css");
assertThat(dto.getJavascriptPersonnalise()).isEqualTo("js");
assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
assertThat(dto.getNombreVues()).isEqualTo(100L);
assertThat(dto.getNombreInteractions()).isEqualTo(50L);
assertThat(dto.getTempsMoyenSecondes()).isEqualTo(30);
assertThat(dto.getTauxErreur()).isEqualTo(5.0);
assertThat(dto.getDateDerniereErreur()).isNotNull();
assertThat(dto.getMessageDerniereErreur()).isEqualTo("Erreur");
}
}
@Nested
@DisplayName("Tests Builder complet - tous les champs")
class BuilderCompletTests {
@Test
@DisplayName("Builder - tous les champs")
void testBuilderTousChamps() {
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
Map<String, Object> filtres = new HashMap<>();
filtres.put("key", "value");
Map<String, Object> alertes = new HashMap<>();
alertes.put("alert", "config");
Map<String, Object> metadonnees = new HashMap<>();
metadonnees.put("meta", "data");
DashboardWidgetDTO dto = DashboardWidgetDTO.builder()
.titre("Widget Test Complet")
.description("Description complète")
.typeWidget("kpi")
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.organisationId(orgId)
.nomOrganisation("Org Test")
.utilisateurProprietaireId(userId)
.nomUtilisateurProprietaire("User Test")
.positionX(1)
.positionY(2)
.largeur(4)
.hauteur(3)
.ordreAffichage(1)
.configurationVisuelle("config visuelle")
.couleurPrincipale("#FF0000")
.couleurSecondaire("#00FF00")
.icone("icon")
.visible(true)
.redimensionnable(true)
.deplacable(true)
.supprimable(true)
.miseAJourAutomatique(true)
.frequenceMiseAJourSecondes(60)
.dateDerniereMiseAJour(now)
.prochaineMiseAJour(now.plusHours(1))
.donneesWidget("donnees")
.configurationFiltres(filtres)
.configurationAlertes(alertes)
.seuilAlerteBas(10.0)
.seuilAlerteHaut(90.0)
.alerteActive(true)
.messageAlerte("Alerte")
.typeAlerte("warning")
.permissions("read")
.rolesAutorises("ADMIN")
.templatePersonnalise("template")
.cssPersonnalise("css")
.javascriptPersonnalise("js")
.metadonnees(metadonnees)
.nombreVues(100L)
.nombreInteractions(50L)
.tempsMoyenSecondes(30)
.tauxErreur(5.0)
.dateDerniereErreur(now.minusHours(1))
.messageDerniereErreur("Erreur")
.build();
assertThat(dto.getTitre()).isEqualTo("Widget Test Complet");
assertThat(dto.getDescription()).isEqualTo("Description complète");
assertThat(dto.getTypeWidget()).isEqualTo("kpi");
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getUtilisateurProprietaireId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateurProprietaire()).isEqualTo("User Test");
assertThat(dto.getPositionX()).isEqualTo(1);
assertThat(dto.getPositionY()).isEqualTo(2);
assertThat(dto.getLargeur()).isEqualTo(4);
assertThat(dto.getHauteur()).isEqualTo(3);
assertThat(dto.getOrdreAffichage()).isEqualTo(1);
assertThat(dto.getConfigurationVisuelle()).isEqualTo("config visuelle");
assertThat(dto.getCouleurPrincipale()).isEqualTo("#FF0000");
assertThat(dto.getCouleurSecondaire()).isEqualTo("#00FF00");
assertThat(dto.getIcone()).isEqualTo("icon");
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getRedimensionnable()).isTrue();
assertThat(dto.getDeplacable()).isTrue();
assertThat(dto.getSupprimable()).isTrue();
assertThat(dto.getMiseAJourAutomatique()).isTrue();
assertThat(dto.getFrequenceMiseAJourSecondes()).isEqualTo(60);
assertThat(dto.getDateDerniereMiseAJour()).isEqualTo(now);
assertThat(dto.getProchaineMiseAJour()).isEqualTo(now.plusHours(1));
assertThat(dto.getDonneesWidget()).isEqualTo("donnees");
assertThat(dto.getConfigurationFiltres()).isEqualTo(filtres);
assertThat(dto.getConfigurationAlertes()).isEqualTo(alertes);
assertThat(dto.getSeuilAlerteBas()).isEqualTo(10.0);
assertThat(dto.getSeuilAlerteHaut()).isEqualTo(90.0);
assertThat(dto.getAlerteActive()).isTrue();
assertThat(dto.getMessageAlerte()).isEqualTo("Alerte");
assertThat(dto.getTypeAlerte()).isEqualTo("warning");
assertThat(dto.getPermissions()).isEqualTo("read");
assertThat(dto.getRolesAutorises()).isEqualTo("ADMIN");
assertThat(dto.getTemplatePersonnalise()).isEqualTo("template");
assertThat(dto.getCssPersonnalise()).isEqualTo("css");
assertThat(dto.getJavascriptPersonnalise()).isEqualTo("js");
assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
assertThat(dto.getNombreVues()).isEqualTo(100L);
assertThat(dto.getNombreInteractions()).isEqualTo(50L);
assertThat(dto.getTempsMoyenSecondes()).isEqualTo(30);
assertThat(dto.getTauxErreur()).isEqualTo(5.0);
assertThat(dto.getDateDerniereErreur()).isEqualTo(now.minusHours(1));
assertThat(dto.getMessageDerniereErreur()).isEqualTo("Erreur");
}
@Test
@DisplayName("Builder - valeurs par défaut")
void testBuilderValeursParDefaut() {
UUID userId = UUID.randomUUID();
DashboardWidgetDTO dto = DashboardWidgetDTO.builder()
.titre("Widget")
.typeWidget("kpi")
.utilisateurProprietaireId(userId)
.positionX(0)
.positionY(0)
.largeur(4)
.hauteur(2)
.build();
assertThat(dto.getOrdreAffichage()).isEqualTo(0);
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getRedimensionnable()).isTrue();
assertThat(dto.getDeplacable()).isTrue();
assertThat(dto.getSupprimable()).isTrue();
assertThat(dto.getMiseAJourAutomatique()).isTrue();
assertThat(dto.getFrequenceMiseAJourSecondes()).isEqualTo(300);
assertThat(dto.getAlerteActive()).isFalse();
assertThat(dto.getNombreVues()).isEqualTo(0L);
assertThat(dto.getNombreInteractions()).isEqualTo(0L);
assertThat(dto.getTauxErreur()).isEqualTo(0.0);
}
}
}

View File

@@ -0,0 +1,561 @@
package dev.lions.unionflow.server.api.dto.analytics;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO.PointDonneeDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@DisplayName("Tests pour KPITrendDTO")
class KPITrendDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
KPITrendDTO dto = new KPITrendDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
KPITrendDTO dto = new KPITrendDTO();
assertThat(dto).isNotNull();
assertThat(dto.getAlerteActive()).isFalse();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
LocalDateTime debut = LocalDateTime.of(2025, 1, 1, 0, 0);
LocalDateTime fin = LocalDateTime.of(2025, 1, 31, 23, 59);
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(debut)
.valeur(new BigDecimal("100.0"))
.build());
KPITrendDTO dto = KPITrendDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.dateDebut(debut)
.dateFin(fin)
.pointsDonnees(points)
.valeurActuelle(new BigDecimal("150.0"))
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getDateDebut()).isEqualTo(debut);
assertThat(dto.getDateFin()).isEqualTo(fin);
assertThat(dto.getPointsDonnees()).isEqualTo(points);
assertThat(dto.getValeurActuelle()).isEqualTo(new BigDecimal("150.0"));
}
}
@Nested
@DisplayName("Tests méthodes utilitaires")
class MethodesUtilitairesTests {
@Test
@DisplayName("getLibelleMetrique")
void testGetLibelleMetrique() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getLibelleMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
}
@Test
@DisplayName("getUnite")
void testGetUnite() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite());
}
@Test
@DisplayName("getIcone")
void testGetIcone() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getIcone()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
}
@Test
@DisplayName("getCouleur")
void testGetCouleur() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getCouleur()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
}
@Test
@DisplayName("isTendancePositive - tendance positive")
void testIsTendancePositiveTrue() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(new BigDecimal("5.5"));
assertThat(dto.isTendancePositive()).isTrue();
}
@Test
@DisplayName("isTendancePositive - tendance négative")
void testIsTendancePositiveFalse() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(new BigDecimal("-5.5"));
assertThat(dto.isTendancePositive()).isFalse();
}
@Test
@DisplayName("isTendancePositive - tendance null")
void testIsTendancePositiveNull() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(null);
assertThat(dto.isTendancePositive()).isFalse();
}
@Test
@DisplayName("isTendanceNegative - tendance négative")
void testIsTendanceNegativeTrue() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(new BigDecimal("-5.5"));
assertThat(dto.isTendanceNegative()).isTrue();
}
@Test
@DisplayName("isTendanceNegative - tendance positive")
void testIsTendanceNegativeFalse() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(new BigDecimal("5.5"));
assertThat(dto.isTendanceNegative()).isFalse();
}
@Test
@DisplayName("isTendanceNegative - tendance null")
void testIsTendanceNegativeNull() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(null);
assertThat(dto.isTendanceNegative()).isFalse();
}
@Test
@DisplayName("isTendanceStable - tendance zéro")
void testIsTendanceStableTrue() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(BigDecimal.ZERO);
assertThat(dto.isTendanceStable()).isTrue();
}
@Test
@DisplayName("isTendanceStable - tendance non zéro")
void testIsTendanceStableFalse() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(new BigDecimal("5.5"));
assertThat(dto.isTendanceStable()).isFalse();
}
@Test
@DisplayName("isTendanceStable - tendance null")
void testIsTendanceStableNull() {
KPITrendDTO dto = new KPITrendDTO();
dto.setTendanceGenerale(null);
assertThat(dto.isTendanceStable()).isFalse();
}
@ParameterizedTest
@CsvSource({
"0.05, faible",
"0.1, faible",
"0.2, moyenne",
"0.3, moyenne",
"0.4, élevée",
"null, inconnue"
})
@DisplayName("getVolatilite - tous les cas")
void testGetVolatilite(String cvStr, String expected) {
KPITrendDTO dto = new KPITrendDTO();
if ("null".equals(cvStr)) {
dto.setCoefficientVariation(null);
} else {
dto.setCoefficientVariation(new BigDecimal(cvStr));
}
assertThat(dto.getVolatilite()).isEqualTo(expected);
}
@Test
@DisplayName("isPredictionFiable - R² > 0.7")
void testIsPredictionFiableTrue() {
KPITrendDTO dto = new KPITrendDTO();
dto.setCoefficientCorrelation(new BigDecimal("0.8"));
assertThat(dto.isPredictionFiable()).isTrue();
}
@Test
@DisplayName("isPredictionFiable - R² = 0.7")
void testIsPredictionFiableTrueLimite() {
KPITrendDTO dto = new KPITrendDTO();
dto.setCoefficientCorrelation(new BigDecimal("0.7"));
assertThat(dto.isPredictionFiable()).isTrue();
}
@Test
@DisplayName("isPredictionFiable - R² < 0.7")
void testIsPredictionFiableFalse() {
KPITrendDTO dto = new KPITrendDTO();
dto.setCoefficientCorrelation(new BigDecimal("0.5"));
assertThat(dto.isPredictionFiable()).isFalse();
}
@Test
@DisplayName("isPredictionFiable - R² null")
void testIsPredictionFiableNull() {
KPITrendDTO dto = new KPITrendDTO();
dto.setCoefficientCorrelation(null);
assertThat(dto.isPredictionFiable()).isFalse();
}
@Test
@DisplayName("getNombrePointsDonnees - avec points")
void testGetNombrePointsDonneesAvecPoints() {
KPITrendDTO dto = new KPITrendDTO();
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(new BigDecimal("100")).build(),
PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(new BigDecimal("200")).build());
dto.setPointsDonnees(points);
assertThat(dto.getNombrePointsDonnees()).isEqualTo(2);
}
@Test
@DisplayName("getNombrePointsDonnees - sans points")
void testGetNombrePointsDonneesSansPoints() {
KPITrendDTO dto = new KPITrendDTO();
dto.setPointsDonnees(null);
assertThat(dto.getNombrePointsDonnees()).isEqualTo(0);
}
@Test
@DisplayName("hasAnomalies - avec anomalies")
void testHasAnomaliesTrue() {
KPITrendDTO dto = new KPITrendDTO();
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(LocalDateTime.now())
.valeur(new BigDecimal("100"))
.anomalie(true)
.build(),
PointDonneeDTO.builder()
.date(LocalDateTime.now())
.valeur(new BigDecimal("200"))
.anomalie(false)
.build());
dto.setPointsDonnees(points);
assertThat(dto.hasAnomalies()).isTrue();
}
@Test
@DisplayName("hasAnomalies - sans anomalies")
void testHasAnomaliesFalse() {
KPITrendDTO dto = new KPITrendDTO();
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(LocalDateTime.now())
.valeur(new BigDecimal("100"))
.anomalie(false)
.build());
dto.setPointsDonnees(points);
assertThat(dto.hasAnomalies()).isFalse();
}
@Test
@DisplayName("hasAnomalies - points null")
void testHasAnomaliesNull() {
KPITrendDTO dto = new KPITrendDTO();
dto.setPointsDonnees(null);
assertThat(dto.hasAnomalies()).isFalse();
}
}
@Nested
@DisplayName("Tests classe interne PointDonneeDTO")
class PointDonneeDTOTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
PointDonneeDTO point = new PointDonneeDTO();
assertThat(point).isNotNull();
assertThat(point.getAnomalie()).isFalse();
assertThat(point.getPrediction()).isFalse();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
LocalDateTime date = LocalDateTime.of(2025, 1, 15, 10, 0);
PointDonneeDTO point = PointDonneeDTO.builder()
.date(date)
.valeur(new BigDecimal("150.0"))
.libelle("Point 1")
.anomalie(true)
.prediction(false)
.metadonnees("meta")
.build();
assertThat(point.getDate()).isEqualTo(date);
assertThat(point.getValeur()).isEqualTo(new BigDecimal("150.0"));
assertThat(point.getLibelle()).isEqualTo("Point 1");
assertThat(point.getAnomalie()).isTrue();
assertThat(point.getPrediction()).isFalse();
assertThat(point.getMetadonnees()).isEqualTo("meta");
}
@Test
@DisplayName("Getters/setters")
void testGettersSetters() {
PointDonneeDTO point = new PointDonneeDTO();
LocalDateTime date = LocalDateTime.of(2025, 1, 15, 10, 0);
point.setDate(date);
point.setValeur(new BigDecimal("200.0"));
point.setLibelle("Test Point");
point.setAnomalie(true);
point.setPrediction(true);
point.setMetadonnees("test meta");
assertThat(point.getDate()).isEqualTo(date);
assertThat(point.getValeur()).isEqualTo(new BigDecimal("200.0"));
assertThat(point.getLibelle()).isEqualTo("Test Point");
assertThat(point.getAnomalie()).isTrue();
assertThat(point.getPrediction()).isTrue();
assertThat(point.getMetadonnees()).isEqualTo("test meta");
}
}
@Nested
@DisplayName("Tests getters/setters complets")
class GettersSettersCompletsTests {
@Test
@DisplayName("Test tous les getters/setters")
void testTousLesGettersSetters() {
KPITrendDTO dto = new KPITrendDTO();
UUID orgId = UUID.randomUUID();
LocalDateTime debut = LocalDateTime.of(2025, 1, 1, 0, 0);
LocalDateTime fin = LocalDateTime.of(2025, 1, 31, 23, 59);
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(debut)
.valeur(new BigDecimal("100.0"))
.build());
dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
dto.setOrganisationId(orgId);
dto.setNomOrganisation("Org Test");
dto.setDateDebut(debut);
dto.setDateFin(fin);
dto.setPointsDonnees(points);
dto.setValeurActuelle(new BigDecimal("150.0"));
dto.setValeurMinimale(new BigDecimal("50.0"));
dto.setValeurMaximale(new BigDecimal("200.0"));
dto.setValeurMoyenne(new BigDecimal("125.0"));
dto.setEcartType(new BigDecimal("25.0"));
dto.setCoefficientVariation(new BigDecimal("0.2"));
dto.setTendanceGenerale(new BigDecimal("5.5"));
dto.setCoefficientCorrelation(new BigDecimal("0.85"));
dto.setPourcentageEvolutionGlobale(new BigDecimal("50.0"));
dto.setPredictionProchainePeriode(new BigDecimal("160.0"));
dto.setMargeErreurPrediction(new BigDecimal("5.0"));
dto.setSeuilAlerteBas(new BigDecimal("80.0"));
dto.setSeuilAlerteHaut(new BigDecimal("180.0"));
dto.setAlerteActive(true);
dto.setTypeAlerte("haut");
dto.setMessageAlerte("Alerte haute");
dto.setConfigurationGraphique("config");
dto.setIntervalleRegroupement("jour");
dto.setFormatDate("yyyy-MM-dd");
dto.setDateDerniereMiseAJour(LocalDateTime.now());
dto.setFrequenceMiseAJourMinutes(60);
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getDateDebut()).isEqualTo(debut);
assertThat(dto.getDateFin()).isEqualTo(fin);
assertThat(dto.getPointsDonnees()).isEqualTo(points);
assertThat(dto.getValeurActuelle()).isEqualTo(new BigDecimal("150.0"));
assertThat(dto.getValeurMinimale()).isEqualTo(new BigDecimal("50.0"));
assertThat(dto.getValeurMaximale()).isEqualTo(new BigDecimal("200.0"));
assertThat(dto.getValeurMoyenne()).isEqualTo(new BigDecimal("125.0"));
assertThat(dto.getEcartType()).isEqualTo(new BigDecimal("25.0"));
assertThat(dto.getCoefficientVariation()).isEqualTo(new BigDecimal("0.2"));
assertThat(dto.getTendanceGenerale()).isEqualTo(new BigDecimal("5.5"));
assertThat(dto.getCoefficientCorrelation()).isEqualTo(new BigDecimal("0.85"));
assertThat(dto.getPourcentageEvolutionGlobale()).isEqualTo(new BigDecimal("50.0"));
assertThat(dto.getPredictionProchainePeriode()).isEqualTo(new BigDecimal("160.0"));
assertThat(dto.getMargeErreurPrediction()).isEqualTo(new BigDecimal("5.0"));
assertThat(dto.getSeuilAlerteBas()).isEqualTo(new BigDecimal("80.0"));
assertThat(dto.getSeuilAlerteHaut()).isEqualTo(new BigDecimal("180.0"));
assertThat(dto.getAlerteActive()).isTrue();
assertThat(dto.getTypeAlerte()).isEqualTo("haut");
assertThat(dto.getMessageAlerte()).isEqualTo("Alerte haute");
assertThat(dto.getConfigurationGraphique()).isEqualTo("config");
assertThat(dto.getIntervalleRegroupement()).isEqualTo("jour");
assertThat(dto.getFormatDate()).isEqualTo("yyyy-MM-dd");
assertThat(dto.getDateDerniereMiseAJour()).isNotNull();
assertThat(dto.getFrequenceMiseAJourMinutes()).isEqualTo(60);
}
}
@Nested
@DisplayName("Tests Builder complet - tous les champs")
class BuilderCompletTests {
@Test
@DisplayName("Builder - tous les champs")
void testBuilderTousChamps() {
UUID orgId = UUID.randomUUID();
LocalDateTime debut = LocalDateTime.of(2025, 1, 1, 0, 0);
LocalDateTime fin = LocalDateTime.of(2025, 1, 31, 23, 59);
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(debut)
.valeur(new BigDecimal("100.0"))
.build());
KPITrendDTO dto = KPITrendDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.organisationId(orgId)
.nomOrganisation("Org Test")
.dateDebut(debut)
.dateFin(fin)
.pointsDonnees(points)
.valeurActuelle(new BigDecimal("150.0"))
.valeurMinimale(new BigDecimal("50.0"))
.valeurMaximale(new BigDecimal("200.0"))
.valeurMoyenne(new BigDecimal("125.0"))
.ecartType(new BigDecimal("25.0"))
.coefficientVariation(new BigDecimal("0.2"))
.tendanceGenerale(new BigDecimal("5.5"))
.coefficientCorrelation(new BigDecimal("0.85"))
.pourcentageEvolutionGlobale(new BigDecimal("50.0"))
.predictionProchainePeriode(new BigDecimal("160.0"))
.margeErreurPrediction(new BigDecimal("5.0"))
.seuilAlerteBas(new BigDecimal("80.0"))
.seuilAlerteHaut(new BigDecimal("180.0"))
.alerteActive(true)
.typeAlerte("haut")
.messageAlerte("Alerte haute")
.configurationGraphique("config")
.intervalleRegroupement("jour")
.formatDate("yyyy-MM-dd")
.dateDerniereMiseAJour(LocalDateTime.now())
.frequenceMiseAJourMinutes(60)
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getDateDebut()).isEqualTo(debut);
assertThat(dto.getDateFin()).isEqualTo(fin);
assertThat(dto.getPointsDonnees()).isEqualTo(points);
assertThat(dto.getValeurActuelle()).isEqualTo(new BigDecimal("150.0"));
assertThat(dto.getValeurMinimale()).isEqualTo(new BigDecimal("50.0"));
assertThat(dto.getValeurMaximale()).isEqualTo(new BigDecimal("200.0"));
assertThat(dto.getValeurMoyenne()).isEqualTo(new BigDecimal("125.0"));
assertThat(dto.getEcartType()).isEqualTo(new BigDecimal("25.0"));
assertThat(dto.getCoefficientVariation()).isEqualTo(new BigDecimal("0.2"));
assertThat(dto.getTendanceGenerale()).isEqualTo(new BigDecimal("5.5"));
assertThat(dto.getCoefficientCorrelation()).isEqualTo(new BigDecimal("0.85"));
assertThat(dto.getPourcentageEvolutionGlobale()).isEqualTo(new BigDecimal("50.0"));
assertThat(dto.getPredictionProchainePeriode()).isEqualTo(new BigDecimal("160.0"));
assertThat(dto.getMargeErreurPrediction()).isEqualTo(new BigDecimal("5.0"));
assertThat(dto.getSeuilAlerteBas()).isEqualTo(new BigDecimal("80.0"));
assertThat(dto.getSeuilAlerteHaut()).isEqualTo(new BigDecimal("180.0"));
assertThat(dto.getAlerteActive()).isTrue();
assertThat(dto.getTypeAlerte()).isEqualTo("haut");
assertThat(dto.getMessageAlerte()).isEqualTo("Alerte haute");
assertThat(dto.getConfigurationGraphique()).isEqualTo("config");
assertThat(dto.getIntervalleRegroupement()).isEqualTo("jour");
assertThat(dto.getFormatDate()).isEqualTo("yyyy-MM-dd");
assertThat(dto.getDateDerniereMiseAJour()).isNotNull();
assertThat(dto.getFrequenceMiseAJourMinutes()).isEqualTo(60);
}
@Test
@DisplayName("Builder - valeurs par défaut")
void testBuilderValeursParDefaut() {
LocalDateTime debut = LocalDateTime.of(2025, 1, 1, 0, 0);
LocalDateTime fin = LocalDateTime.of(2025, 1, 31, 23, 59);
List<PointDonneeDTO> points = Arrays.asList(
PointDonneeDTO.builder()
.date(debut)
.valeur(new BigDecimal("100.0"))
.build());
KPITrendDTO dto = KPITrendDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.dateDebut(debut)
.dateFin(fin)
.pointsDonnees(points)
.valeurActuelle(new BigDecimal("150.0"))
.build();
assertThat(dto.getAlerteActive()).isFalse();
}
@Test
@DisplayName("PointDonneeDTO.Builder - tous les champs")
void testPointDonneeDTOBuilderTousChamps() {
LocalDateTime date = LocalDateTime.of(2025, 1, 15, 10, 0);
PointDonneeDTO point = PointDonneeDTO.builder()
.date(date)
.valeur(new BigDecimal("150.0"))
.libelle("Point 1")
.anomalie(true)
.prediction(false)
.metadonnees("meta")
.build();
assertThat(point.getDate()).isEqualTo(date);
assertThat(point.getValeur()).isEqualTo(new BigDecimal("150.0"));
assertThat(point.getLibelle()).isEqualTo("Point 1");
assertThat(point.getAnomalie()).isTrue();
assertThat(point.getPrediction()).isFalse();
assertThat(point.getMetadonnees()).isEqualTo("meta");
}
@Test
@DisplayName("PointDonneeDTO.Builder - valeurs par défaut")
void testPointDonneeDTOBuilderValeursParDefaut() {
LocalDateTime date = LocalDateTime.of(2025, 1, 15, 10, 0);
PointDonneeDTO point = PointDonneeDTO.builder()
.date(date)
.valeur(new BigDecimal("100.0"))
.build();
assertThat(point.getAnomalie()).isFalse();
assertThat(point.getPrediction()).isFalse();
}
}
}

View File

@@ -0,0 +1,683 @@
package dev.lions.unionflow.server.api.dto.analytics;
import static org.assertj.core.api.Assertions.assertThat;
import dev.lions.unionflow.server.api.dto.analytics.ReportConfigDTO.MetriqueConfigDTO;
import dev.lions.unionflow.server.api.dto.analytics.ReportConfigDTO.SectionRapportDTO;
import dev.lions.unionflow.server.api.enums.analytics.FormatExport;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@DisplayName("Tests pour ReportConfigDTO")
class ReportConfigDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
ReportConfigDTO dto = new ReportConfigDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
ReportConfigDTO dto = new ReportConfigDTO();
assertThat(dto).isNotNull();
assertThat(dto.getRapportPublic()).isFalse();
assertThat(dto.getRapportAutomatique()).isFalse();
assertThat(dto.getNiveauConfidentialite()).isEqualTo(1);
assertThat(dto.getNombreGenerations()).isEqualTo(0);
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
UUID userId = UUID.randomUUID();
List<MetriqueConfigDTO> metriques = Arrays.asList(
MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.position(1)
.build());
ReportConfigDTO dto = ReportConfigDTO.builder()
.nom("Rapport Test")
.typeRapport("executif")
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.utilisateurCreateurId(userId)
.metriques(metriques)
.formatExport(FormatExport.PDF)
.build();
assertThat(dto.getNom()).isEqualTo("Rapport Test");
assertThat(dto.getTypeRapport()).isEqualTo("executif");
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getUtilisateurCreateurId()).isEqualTo(userId);
assertThat(dto.getMetriques()).isEqualTo(metriques);
assertThat(dto.getFormatExport()).isEqualTo(FormatExport.PDF);
}
}
@Nested
@DisplayName("Tests méthodes utilitaires")
class MethodesUtilitairesTests {
@Test
@DisplayName("getNombreMetriques - avec métriques")
void testGetNombreMetriquesAvecMetriques() {
ReportConfigDTO dto = new ReportConfigDTO();
List<MetriqueConfigDTO> metriques = Arrays.asList(
MetriqueConfigDTO.builder().typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS).build(),
MetriqueConfigDTO.builder().typeMetrique(TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES).build());
dto.setMetriques(metriques);
assertThat(dto.getNombreMetriques()).isEqualTo(2);
}
@Test
@DisplayName("getNombreMetriques - sans métriques")
void testGetNombreMetriquesSansMetriques() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setMetriques(null);
assertThat(dto.getNombreMetriques()).isEqualTo(0);
}
@Test
@DisplayName("getNombreSections - avec sections")
void testGetNombreSectionsAvecSections() {
ReportConfigDTO dto = new ReportConfigDTO();
List<SectionRapportDTO> sections = Arrays.asList(
SectionRapportDTO.builder().nom("Section 1").typeSection("resume").position(1).build(),
SectionRapportDTO.builder().nom("Section 2").typeSection("metriques").position(2).build());
dto.setSections(sections);
assertThat(dto.getNombreSections()).isEqualTo(2);
}
@Test
@DisplayName("getNombreSections - sans sections")
void testGetNombreSectionsSansSections() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setSections(null);
assertThat(dto.getNombreSections()).isEqualTo(0);
}
@Test
@DisplayName("isPeriodePersonnalisee - période personnalisée")
void testIsPeriodePersonnaliseeTrue() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setPeriodeAnalyse(PeriodeAnalyse.PERIODE_PERSONNALISEE);
assertThat(dto.isPeriodePersonnalisee()).isTrue();
}
@Test
@DisplayName("isPeriodePersonnalisee - période non personnalisée")
void testIsPeriodePersonnaliseeFalse() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
assertThat(dto.isPeriodePersonnalisee()).isFalse();
}
@Test
@DisplayName("isConfidentiel - niveau >= 4")
void testIsConfidentielTrue() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setNiveauConfidentialite(4);
assertThat(dto.isConfidentiel()).isTrue();
}
@Test
@DisplayName("isConfidentiel - niveau < 4")
void testIsConfidentielFalse() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setNiveauConfidentialite(3);
assertThat(dto.isConfidentiel()).isFalse();
}
@Test
@DisplayName("isConfidentiel - niveau null")
void testIsConfidentielNull() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setNiveauConfidentialite(null);
assertThat(dto.isConfidentiel()).isFalse();
}
@Test
@DisplayName("necessiteGeneration - rapport automatique et prochaine génération passée")
void testNecessiteGenerationTrue() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setRapportAutomatique(true);
dto.setProchaineGeneration(LocalDateTime.now().minusHours(1));
assertThat(dto.necessiteGeneration()).isTrue();
}
@Test
@DisplayName("necessiteGeneration - rapport non automatique")
void testNecessiteGenerationFalseNonAutomatique() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setRapportAutomatique(false);
dto.setProchaineGeneration(LocalDateTime.now().minusHours(1));
assertThat(dto.necessiteGeneration()).isFalse();
}
@Test
@DisplayName("necessiteGeneration - prochaine génération future")
void testNecessiteGenerationFalseFuture() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setRapportAutomatique(true);
dto.setProchaineGeneration(LocalDateTime.now().plusHours(1));
assertThat(dto.necessiteGeneration()).isFalse();
}
@Test
@DisplayName("necessiteGeneration - prochaine génération null")
void testNecessiteGenerationFalseNull() {
ReportConfigDTO dto = new ReportConfigDTO();
dto.setRapportAutomatique(true);
dto.setProchaineGeneration(null);
assertThat(dto.necessiteGeneration()).isFalse();
}
@ParameterizedTest
@CsvSource({
"1, Toutes les heures",
"24, Quotidienne",
"168, Hebdomadaire",
"720, Mensuelle",
"48, Toutes les 48 heures",
"null, Manuelle"
})
@DisplayName("getFrequenceTexte - tous les cas")
void testGetFrequenceTexte(String heuresStr, String expected) {
ReportConfigDTO dto = new ReportConfigDTO();
if ("null".equals(heuresStr)) {
dto.setFrequenceGenerationHeures(null);
} else {
dto.setFrequenceGenerationHeures(Integer.parseInt(heuresStr));
}
assertThat(dto.getFrequenceTexte()).isEqualTo(expected);
}
}
@Nested
@DisplayName("Tests classe interne MetriqueConfigDTO")
class MetriqueConfigDTOTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
MetriqueConfigDTO dto = new MetriqueConfigDTO();
assertThat(dto).isNotNull();
assertThat(dto.getTailleAffichage()).isEqualTo(2);
assertThat(dto.getInclureGraphique()).isTrue();
assertThat(dto.getTypeGraphique()).isEqualTo("line");
assertThat(dto.getInclureTendance()).isTrue();
assertThat(dto.getInclureComparaison()).isTrue();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
Map<String, Object> seuils = new HashMap<>();
seuils.put("bas", 10.0);
Map<String, Object> filtres = new HashMap<>();
filtres.put("org", "org-1");
MetriqueConfigDTO dto = MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise("Membres Actifs")
.position(1)
.tailleAffichage(3)
.couleurPersonnalisee("#FF0000")
.inclureGraphique(false)
.typeGraphique("bar")
.inclureTendance(false)
.inclureComparaison(false)
.seuilsAlerte(seuils)
.filtresSpecifiques(filtres)
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getLibellePersonnalise()).isEqualTo("Membres Actifs");
assertThat(dto.getPosition()).isEqualTo(1);
assertThat(dto.getTailleAffichage()).isEqualTo(3);
assertThat(dto.getCouleurPersonnalisee()).isEqualTo("#FF0000");
assertThat(dto.getInclureGraphique()).isFalse();
assertThat(dto.getTypeGraphique()).isEqualTo("bar");
assertThat(dto.getInclureTendance()).isFalse();
assertThat(dto.getInclureComparaison()).isFalse();
assertThat(dto.getSeuilsAlerte()).isEqualTo(seuils);
assertThat(dto.getFiltresSpecifiques()).isEqualTo(filtres);
}
@Test
@DisplayName("Getters/setters")
void testGettersSetters() {
MetriqueConfigDTO dto = new MetriqueConfigDTO();
Map<String, Object> seuils = new HashMap<>();
Map<String, Object> filtres = new HashMap<>();
dto.setTypeMetrique(TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES);
dto.setLibellePersonnalise("Organisations");
dto.setPosition(2);
dto.setTailleAffichage(1);
dto.setCouleurPersonnalisee("#00FF00");
dto.setInclureGraphique(true);
dto.setTypeGraphique("pie");
dto.setInclureTendance(true);
dto.setInclureComparaison(true);
dto.setSeuilsAlerte(seuils);
dto.setFiltresSpecifiques(filtres);
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES);
assertThat(dto.getLibellePersonnalise()).isEqualTo("Organisations");
assertThat(dto.getPosition()).isEqualTo(2);
assertThat(dto.getTailleAffichage()).isEqualTo(1);
assertThat(dto.getCouleurPersonnalisee()).isEqualTo("#00FF00");
assertThat(dto.getInclureGraphique()).isTrue();
assertThat(dto.getTypeGraphique()).isEqualTo("pie");
assertThat(dto.getInclureTendance()).isTrue();
assertThat(dto.getInclureComparaison()).isTrue();
assertThat(dto.getSeuilsAlerte()).isEqualTo(seuils);
assertThat(dto.getFiltresSpecifiques()).isEqualTo(filtres);
}
}
@Nested
@DisplayName("Tests classe interne SectionRapportDTO")
class SectionRapportDTOTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
SectionRapportDTO dto = new SectionRapportDTO();
assertThat(dto).isNotNull();
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getPliable()).isFalse();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
List<TypeMetrique> metriques = Arrays.asList(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES);
Map<String, Object> config = new HashMap<>();
config.put("key", "value");
SectionRapportDTO dto = SectionRapportDTO.builder()
.nom("Section Résumé")
.description("Description de la section")
.position(1)
.typeSection("resume")
.metriquesIncluses(metriques)
.configurationSection(config)
.visible(true)
.pliable(true)
.build();
assertThat(dto.getNom()).isEqualTo("Section Résumé");
assertThat(dto.getDescription()).isEqualTo("Description de la section");
assertThat(dto.getPosition()).isEqualTo(1);
assertThat(dto.getTypeSection()).isEqualTo("resume");
assertThat(dto.getMetriquesIncluses()).isEqualTo(metriques);
assertThat(dto.getConfigurationSection()).isEqualTo(config);
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getPliable()).isTrue();
}
@Test
@DisplayName("Getters/setters")
void testGettersSetters() {
SectionRapportDTO dto = new SectionRapportDTO();
List<TypeMetrique> metriques = Arrays.asList(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
Map<String, Object> config = new HashMap<>();
dto.setNom("Section Test");
dto.setDescription("Description test");
dto.setPosition(2);
dto.setTypeSection("metriques");
dto.setMetriquesIncluses(metriques);
dto.setConfigurationSection(config);
dto.setVisible(false);
dto.setPliable(true);
assertThat(dto.getNom()).isEqualTo("Section Test");
assertThat(dto.getDescription()).isEqualTo("Description test");
assertThat(dto.getPosition()).isEqualTo(2);
assertThat(dto.getTypeSection()).isEqualTo("metriques");
assertThat(dto.getMetriquesIncluses()).isEqualTo(metriques);
assertThat(dto.getConfigurationSection()).isEqualTo(config);
assertThat(dto.getVisible()).isFalse();
assertThat(dto.getPliable()).isTrue();
}
}
@Nested
@DisplayName("Tests getters/setters complets")
class GettersSettersCompletsTests {
@Test
@DisplayName("Test tous les getters/setters")
void testTousLesGettersSetters() {
ReportConfigDTO dto = new ReportConfigDTO();
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
List<MetriqueConfigDTO> metriques = Arrays.asList(
MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.position(1)
.build());
List<SectionRapportDTO> sections = Arrays.asList(
SectionRapportDTO.builder()
.nom("Section 1")
.typeSection("resume")
.position(1)
.build());
List<FormatExport> formats = Arrays.asList(FormatExport.PDF, FormatExport.EXCEL);
Map<String, String> couleurs = new HashMap<>();
couleurs.put("primary", "#FF0000");
List<String> destinataires = Arrays.asList("email1@test.com", "email2@test.com");
Map<String, Object> filtres = new HashMap<>();
filtres.put("key", "value");
List<String> tags = Arrays.asList("tag1", "tag2");
dto.setNom("Rapport Complet");
dto.setDescription("Description complète");
dto.setTypeRapport("analytique");
dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
dto.setDateDebutPersonnalisee(LocalDateTime.of(2025, 1, 1, 0, 0));
dto.setDateFinPersonnalisee(LocalDateTime.of(2025, 1, 31, 23, 59));
dto.setOrganisationId(orgId);
dto.setNomOrganisation("Org Test");
dto.setUtilisateurCreateurId(userId);
dto.setNomUtilisateurCreateur("User Test");
dto.setMetriques(metriques);
dto.setSections(sections);
dto.setFormatExport(FormatExport.PDF);
dto.setFormatsExportAutorises(formats);
dto.setModeleRapport("modele1");
dto.setConfigurationMiseEnPage("config");
dto.setLogoPersonnalise("logo.png");
dto.setCouleursPersonnalisees(couleurs);
dto.setRapportPublic(true);
dto.setRapportAutomatique(true);
dto.setFrequenceGenerationHeures(24);
dto.setProchaineGeneration(LocalDateTime.now().plusHours(24));
dto.setDestinatairesEmail(destinataires);
dto.setObjetEmail("Rapport automatique");
dto.setCorpsEmail("Corps du rapport");
dto.setParametresFiltrage(filtres);
dto.setTags(tags);
dto.setNiveauConfidentialite(3);
dto.setDateDerniereGeneration(LocalDateTime.now().minusHours(1));
dto.setNombreGenerations(10);
dto.setTailleMoyenneKB(500L);
dto.setTempsMoyenGenerationSecondes(30);
assertThat(dto.getNom()).isEqualTo("Rapport Complet");
assertThat(dto.getDescription()).isEqualTo("Description complète");
assertThat(dto.getTypeRapport()).isEqualTo("analytique");
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getDateDebutPersonnalisee()).isNotNull();
assertThat(dto.getDateFinPersonnalisee()).isNotNull();
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getUtilisateurCreateurId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateurCreateur()).isEqualTo("User Test");
assertThat(dto.getMetriques()).isEqualTo(metriques);
assertThat(dto.getSections()).isEqualTo(sections);
assertThat(dto.getFormatExport()).isEqualTo(FormatExport.PDF);
assertThat(dto.getFormatsExportAutorises()).isEqualTo(formats);
assertThat(dto.getModeleRapport()).isEqualTo("modele1");
assertThat(dto.getConfigurationMiseEnPage()).isEqualTo("config");
assertThat(dto.getLogoPersonnalise()).isEqualTo("logo.png");
assertThat(dto.getCouleursPersonnalisees()).isEqualTo(couleurs);
assertThat(dto.getRapportPublic()).isTrue();
assertThat(dto.getRapportAutomatique()).isTrue();
assertThat(dto.getFrequenceGenerationHeures()).isEqualTo(24);
assertThat(dto.getProchaineGeneration()).isNotNull();
assertThat(dto.getDestinatairesEmail()).isEqualTo(destinataires);
assertThat(dto.getObjetEmail()).isEqualTo("Rapport automatique");
assertThat(dto.getCorpsEmail()).isEqualTo("Corps du rapport");
assertThat(dto.getParametresFiltrage()).isEqualTo(filtres);
assertThat(dto.getTags()).isEqualTo(tags);
assertThat(dto.getNiveauConfidentialite()).isEqualTo(3);
assertThat(dto.getDateDerniereGeneration()).isNotNull();
assertThat(dto.getNombreGenerations()).isEqualTo(10);
assertThat(dto.getTailleMoyenneKB()).isEqualTo(500L);
assertThat(dto.getTempsMoyenGenerationSecondes()).isEqualTo(30);
}
}
@Nested
@DisplayName("Tests Builder complet - tous les champs")
class BuilderCompletTests {
@Test
@DisplayName("Builder - tous les champs")
void testBuilderTousChamps() {
UUID orgId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
List<MetriqueConfigDTO> metriques = Arrays.asList(
MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.position(1)
.build());
List<SectionRapportDTO> sections = Arrays.asList(
SectionRapportDTO.builder()
.nom("Section 1")
.typeSection("resume")
.position(1)
.build());
List<FormatExport> formats = Arrays.asList(FormatExport.PDF, FormatExport.EXCEL);
Map<String, String> couleurs = new HashMap<>();
couleurs.put("primary", "#FF0000");
List<String> destinataires = Arrays.asList("email1@test.com", "email2@test.com");
Map<String, Object> filtres = new HashMap<>();
filtres.put("key", "value");
List<String> tags = Arrays.asList("tag1", "tag2");
ReportConfigDTO dto = ReportConfigDTO.builder()
.nom("Rapport Complet")
.description("Description complète")
.typeRapport("analytique")
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.dateDebutPersonnalisee(LocalDateTime.of(2025, 1, 1, 0, 0))
.dateFinPersonnalisee(LocalDateTime.of(2025, 1, 31, 23, 59))
.organisationId(orgId)
.nomOrganisation("Org Test")
.utilisateurCreateurId(userId)
.nomUtilisateurCreateur("User Test")
.metriques(metriques)
.sections(sections)
.formatExport(FormatExport.PDF)
.formatsExportAutorises(formats)
.modeleRapport("modele1")
.configurationMiseEnPage("config")
.logoPersonnalise("logo.png")
.couleursPersonnalisees(couleurs)
.rapportPublic(true)
.rapportAutomatique(true)
.frequenceGenerationHeures(24)
.prochaineGeneration(LocalDateTime.now().plusHours(24))
.destinatairesEmail(destinataires)
.objetEmail("Rapport automatique")
.corpsEmail("Corps du rapport")
.parametresFiltrage(filtres)
.tags(tags)
.niveauConfidentialite(3)
.dateDerniereGeneration(LocalDateTime.now().minusHours(1))
.nombreGenerations(10)
.tailleMoyenneKB(500L)
.tempsMoyenGenerationSecondes(30)
.build();
assertThat(dto.getNom()).isEqualTo("Rapport Complet");
assertThat(dto.getDescription()).isEqualTo("Description complète");
assertThat(dto.getTypeRapport()).isEqualTo("analytique");
assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
assertThat(dto.getDateDebutPersonnalisee()).isNotNull();
assertThat(dto.getDateFinPersonnalisee()).isNotNull();
assertThat(dto.getOrganisationId()).isEqualTo(orgId);
assertThat(dto.getNomOrganisation()).isEqualTo("Org Test");
assertThat(dto.getUtilisateurCreateurId()).isEqualTo(userId);
assertThat(dto.getNomUtilisateurCreateur()).isEqualTo("User Test");
assertThat(dto.getMetriques()).isEqualTo(metriques);
assertThat(dto.getSections()).isEqualTo(sections);
assertThat(dto.getFormatExport()).isEqualTo(FormatExport.PDF);
assertThat(dto.getFormatsExportAutorises()).isEqualTo(formats);
assertThat(dto.getModeleRapport()).isEqualTo("modele1");
assertThat(dto.getConfigurationMiseEnPage()).isEqualTo("config");
assertThat(dto.getLogoPersonnalise()).isEqualTo("logo.png");
assertThat(dto.getCouleursPersonnalisees()).isEqualTo(couleurs);
assertThat(dto.getRapportPublic()).isTrue();
assertThat(dto.getRapportAutomatique()).isTrue();
assertThat(dto.getFrequenceGenerationHeures()).isEqualTo(24);
assertThat(dto.getProchaineGeneration()).isNotNull();
assertThat(dto.getDestinatairesEmail()).isEqualTo(destinataires);
assertThat(dto.getObjetEmail()).isEqualTo("Rapport automatique");
assertThat(dto.getCorpsEmail()).isEqualTo("Corps du rapport");
assertThat(dto.getParametresFiltrage()).isEqualTo(filtres);
assertThat(dto.getTags()).isEqualTo(tags);
assertThat(dto.getNiveauConfidentialite()).isEqualTo(3);
assertThat(dto.getDateDerniereGeneration()).isNotNull();
assertThat(dto.getNombreGenerations()).isEqualTo(10);
assertThat(dto.getTailleMoyenneKB()).isEqualTo(500L);
assertThat(dto.getTempsMoyenGenerationSecondes()).isEqualTo(30);
}
@Test
@DisplayName("Builder - valeurs par défaut")
void testBuilderValeursParDefaut() {
UUID userId = UUID.randomUUID();
List<MetriqueConfigDTO> metriques = Arrays.asList(
MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.position(1)
.build());
ReportConfigDTO dto = ReportConfigDTO.builder()
.nom("Rapport")
.typeRapport("executif")
.periodeAnalyse(PeriodeAnalyse.CE_MOIS)
.utilisateurCreateurId(userId)
.metriques(metriques)
.formatExport(FormatExport.PDF)
.build();
assertThat(dto.getRapportPublic()).isFalse();
assertThat(dto.getRapportAutomatique()).isFalse();
assertThat(dto.getNiveauConfidentialite()).isEqualTo(1);
assertThat(dto.getNombreGenerations()).isEqualTo(0);
}
@Test
@DisplayName("MetriqueConfigDTO.Builder - tous les champs")
void testMetriqueConfigDTOBuilderTousChamps() {
Map<String, Object> seuils = new HashMap<>();
seuils.put("bas", 10.0);
Map<String, Object> filtres = new HashMap<>();
filtres.put("org", "org-1");
MetriqueConfigDTO dto = MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.libellePersonnalise("Membres Actifs")
.position(1)
.tailleAffichage(3)
.couleurPersonnalisee("#FF0000")
.inclureGraphique(false)
.typeGraphique("bar")
.inclureTendance(false)
.inclureComparaison(false)
.seuilsAlerte(seuils)
.filtresSpecifiques(filtres)
.build();
assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(dto.getLibellePersonnalise()).isEqualTo("Membres Actifs");
assertThat(dto.getPosition()).isEqualTo(1);
assertThat(dto.getTailleAffichage()).isEqualTo(3);
assertThat(dto.getCouleurPersonnalisee()).isEqualTo("#FF0000");
assertThat(dto.getInclureGraphique()).isFalse();
assertThat(dto.getTypeGraphique()).isEqualTo("bar");
assertThat(dto.getInclureTendance()).isFalse();
assertThat(dto.getInclureComparaison()).isFalse();
assertThat(dto.getSeuilsAlerte()).isEqualTo(seuils);
assertThat(dto.getFiltresSpecifiques()).isEqualTo(filtres);
}
@Test
@DisplayName("MetriqueConfigDTO.Builder - valeurs par défaut")
void testMetriqueConfigDTOBuilderValeursParDefaut() {
MetriqueConfigDTO dto = MetriqueConfigDTO.builder()
.typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
.position(1)
.build();
assertThat(dto.getTailleAffichage()).isEqualTo(2);
assertThat(dto.getInclureGraphique()).isTrue();
assertThat(dto.getTypeGraphique()).isEqualTo("line");
assertThat(dto.getInclureTendance()).isTrue();
assertThat(dto.getInclureComparaison()).isTrue();
}
@Test
@DisplayName("SectionRapportDTO.Builder - tous les champs")
void testSectionRapportDTOBuilderTousChamps() {
List<TypeMetrique> metriques = Arrays.asList(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES);
Map<String, Object> config = new HashMap<>();
config.put("key", "value");
SectionRapportDTO dto = SectionRapportDTO.builder()
.nom("Section Résumé")
.description("Description de la section")
.position(1)
.typeSection("resume")
.metriquesIncluses(metriques)
.configurationSection(config)
.visible(true)
.pliable(true)
.build();
assertThat(dto.getNom()).isEqualTo("Section Résumé");
assertThat(dto.getDescription()).isEqualTo("Description de la section");
assertThat(dto.getPosition()).isEqualTo(1);
assertThat(dto.getTypeSection()).isEqualTo("resume");
assertThat(dto.getMetriquesIncluses()).isEqualTo(metriques);
assertThat(dto.getConfigurationSection()).isEqualTo(config);
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getPliable()).isTrue();
}
@Test
@DisplayName("SectionRapportDTO.Builder - valeurs par défaut")
void testSectionRapportDTOBuilderValeursParDefaut() {
SectionRapportDTO dto = SectionRapportDTO.builder()
.nom("Section")
.typeSection("resume")
.position(1)
.build();
assertThat(dto.getVisible()).isTrue();
assertThat(dto.getPliable()).isFalse();
}
}
}

View File

@@ -0,0 +1,326 @@
package dev.lions.unionflow.server.api.dto.base;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests unitaires pour BaseDTO.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-10
*/
@DisplayName("Tests BaseDTO")
class BaseDTOTest {
private TestableBaseDTO baseDto;
@BeforeEach
void setUp() {
baseDto = new TestableBaseDTO();
}
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
assertThat(baseDto).isNotNull();
}
@Nested
@DisplayName("Tests de Construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut - Initialisation correcte")
void testConstructeurParDefaut() {
TestableBaseDTO newDto = new TestableBaseDTO();
assertThat(newDto.getId()).isNotNull();
assertThat(newDto.getDateCreation()).isNotNull();
assertThat(newDto.isActif()).isTrue();
assertThat(newDto.getVersion()).isEqualTo(0L);
assertThat(newDto.getDateModification()).isNull();
assertThat(newDto.getCreePar()).isNull();
assertThat(newDto.getModifiePar()).isNull();
}
}
@Nested
@DisplayName("Tests Getters/Setters")
class GettersSettersTests {
@Test
@DisplayName("Test tous les getters/setters")
void testGettersSetters() {
UUID id = UUID.randomUUID();
LocalDateTime dateCreation = LocalDateTime.now().minusDays(1);
LocalDateTime dateModification = LocalDateTime.now();
String creePar = "user1";
String modifiePar = "user2";
Long version = 5L;
baseDto.setId(id);
baseDto.setDateCreation(dateCreation);
baseDto.setDateModification(dateModification);
baseDto.setCreePar(creePar);
baseDto.setModifiePar(modifiePar);
baseDto.setVersion(version);
baseDto.setActif(false);
assertThat(baseDto.getId()).isEqualTo(id);
assertThat(baseDto.getDateCreation()).isEqualTo(dateCreation);
assertThat(baseDto.getDateModification()).isEqualTo(dateModification);
assertThat(baseDto.getCreePar()).isEqualTo(creePar);
assertThat(baseDto.getModifiePar()).isEqualTo(modifiePar);
assertThat(baseDto.getVersion()).isEqualTo(version);
assertThat(baseDto.isActif()).isFalse();
}
}
@Nested
@DisplayName("Tests Méthodes Métier")
class MethodesMetierTests {
@Test
@DisplayName("Test marquerCommeModifie")
void testMarquerCommeModifie() {
String utilisateur = "testUser";
LocalDateTime avant = LocalDateTime.now().minusSeconds(1);
baseDto.marquerCommeModifie(utilisateur);
assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
assertThat(baseDto.getDateModification()).isAfter(avant);
assertThat(baseDto.getVersion()).isEqualTo(1L);
}
@Test
@DisplayName("Test marquerCommeModifie - Incrémente version")
void testMarquerCommeModifieIncrementeVersion() {
baseDto.setVersion(3L);
baseDto.marquerCommeModifie("user");
assertThat(baseDto.getVersion()).isEqualTo(4L);
}
@Test
@DisplayName("Test desactiver")
void testDesactiver() {
baseDto.setActif(true);
baseDto.desactiver("user");
assertThat(baseDto.isActif()).isFalse();
}
@Test
@DisplayName("Test reactiver")
void testReactiver() {
baseDto.setActif(false);
baseDto.reactiver("user");
assertThat(baseDto.isActif()).isTrue();
}
@Test
@DisplayName("Test isNouveau")
void testIsNouveau() {
// Nouveau DTO (ID null)
baseDto.setId(null);
assertThat(baseDto.isNouveau()).isTrue();
// DTO existant (ID non null)
baseDto.setId(UUID.randomUUID());
assertThat(baseDto.isNouveau()).isFalse();
}
@Test
@DisplayName("Test isActif")
void testIsActif() {
// Test actif
baseDto.setActif(true);
assertThat(baseDto.isActif()).isTrue();
// Test inactif
baseDto.setActif(false);
assertThat(baseDto.isActif()).isFalse();
// Test avec null
baseDto.setActif(null);
assertThat(baseDto.isActif()).isFalse();
// Test avec Boolean.TRUE explicite
baseDto.setActif(Boolean.TRUE);
assertThat(baseDto.isActif()).isTrue();
// Test avec Boolean.FALSE explicite
baseDto.setActif(Boolean.FALSE);
assertThat(baseDto.isActif()).isFalse();
}
@Test
@DisplayName("Test marquerCommeNouveau")
void testMarquerCommeNouveau() {
String utilisateur = "testUser";
LocalDateTime avant = LocalDateTime.now().minusSeconds(1);
baseDto.marquerCommeNouveau(utilisateur);
assertThat(baseDto.getCreePar()).isEqualTo(utilisateur);
assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
assertThat(baseDto.getDateCreation()).isAfter(avant);
assertThat(baseDto.getDateModification()).isAfter(avant);
assertThat(baseDto.getVersion()).isEqualTo(0L);
assertThat(baseDto.isActif()).isTrue();
}
@Test
@DisplayName("Test marquerCommeModifie avec version null")
void testMarquerCommeModifieAvecVersionNull() {
// Given
baseDto.setVersion(null);
String utilisateur = "testUser";
LocalDateTime avant = LocalDateTime.now().minusSeconds(1);
// When
baseDto.marquerCommeModifie(utilisateur);
// Then
assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
assertThat(baseDto.getDateModification()).isAfter(avant);
assertThat(baseDto.getVersion()).isNull(); // Version reste null car elle était null
}
@Test
@DisplayName("Test accès et modification du champ actif")
void testAccesChampActif() {
// Given & When - Vérifier l'initialisation par défaut
assertThat(baseDto.getActif()).isTrue();
assertThat(baseDto.isActif()).isTrue(); // Test de la méthode isActif() pour Boolean
// Test modification du champ actif
baseDto.setActif(false);
assertThat(baseDto.getActif()).isFalse();
assertThat(baseDto.isActif()).isFalse();
baseDto.setActif(null);
assertThat(baseDto.getActif()).isNull();
assertThat(baseDto.isActif()).isFalse(); // isActif() retourne false pour null avec Lombok
baseDto.setActif(true);
assertThat(baseDto.getActif()).isTrue();
assertThat(baseDto.isActif()).isTrue();
}
}
@Nested
@DisplayName("Tests equals et hashCode")
class EqualsHashCodeTests {
@Test
@DisplayName("Test equals - Même ID")
void testEqualsMemeId() {
UUID id = UUID.randomUUID();
baseDto.setId(id);
TestableBaseDTO autre = new TestableBaseDTO();
autre.setId(id);
assertThat(baseDto).isEqualTo(autre);
assertThat(baseDto.hashCode()).isEqualTo(autre.hashCode());
}
@Test
@DisplayName("Test equals - IDs différents")
void testEqualsIdsDifferents() {
baseDto.setId(UUID.randomUUID());
TestableBaseDTO autre = new TestableBaseDTO();
autre.setId(UUID.randomUUID());
assertThat(baseDto).isNotEqualTo(autre);
}
@Test
@DisplayName("Test equals - ID null")
void testEqualsIdNull() {
baseDto.setId(null);
TestableBaseDTO autre = new TestableBaseDTO();
autre.setId(null);
assertThat(baseDto).isNotEqualTo(autre);
}
@Test
@DisplayName("Test equals - Objet null")
void testEqualsObjetNull() {
assertThat(baseDto).isNotEqualTo(null);
}
@Test
@DisplayName("Test equals - Classe différente")
void testEqualsClasseDifferente() {
assertThat(baseDto).isNotEqualTo("string");
}
@Test
@DisplayName("Test equals - Même objet")
void testEqualsMemeObjet() {
assertThat(baseDto).isEqualTo(baseDto);
}
@Test
@DisplayName("Test hashCode - ID null")
void testHashCodeIdNull() {
baseDto.setId(null);
assertThat(baseDto.hashCode()).isEqualTo(0);
}
@Test
@DisplayName("Test hashCode - ID non null")
void testHashCodeIdNonNull() {
UUID id = UUID.randomUUID();
baseDto.setId(id);
assertThat(baseDto.hashCode()).isEqualTo(id.hashCode());
}
}
@Nested
@DisplayName("Tests toString")
class ToStringTests {
@Test
@DisplayName("Test toString")
void testToString() {
UUID id = UUID.randomUUID();
baseDto.setId(id);
baseDto.setVersion(2L);
baseDto.setActif(true);
String result = baseDto.toString();
assertThat(result).contains("TestableBaseDTO");
assertThat(result).contains("id=" + id.toString());
assertThat(result).contains("version=2");
assertThat(result).contains("actif=true");
}
}
/** Classe de test concrète pour tester BaseDTO. */
private static class TestableBaseDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Override
public String toString() {
return "TestableBaseDTO{" + super.toString() + "}";
}
}
}

View File

@@ -0,0 +1,360 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import static dev.lions.unionflow.server.api.TestDataFactory.createDashboardDataDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.createRecentActivityDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.createUpcomingEventDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.daysFromNow;
import static dev.lions.unionflow.server.api.TestDataFactory.hoursAgo;
import static dev.lions.unionflow.server.api.TestDataFactory.now;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("Tests pour DashboardDataDTO")
class DashboardDataDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto).isNotNull();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
DashboardStatsDTO stats = DashboardStatsDTO.builder().build();
List<RecentActivityDTO> activities = List.of();
List<UpcomingEventDTO> events = List.of();
Map<String, Object> preferences = new HashMap<>();
DashboardDataDTO dto = DashboardDataDTO.builder()
.stats(stats)
.recentActivities(activities)
.upcomingEvents(events)
.userPreferences(preferences)
.organizationId("org-123")
.userId("user-456")
.build();
assertThat(dto.getStats()).isEqualTo(stats);
assertThat(dto.getRecentActivities()).isEqualTo(activities);
assertThat(dto.getUpcomingEvents()).isEqualTo(events);
assertThat(dto.getUserPreferences()).isEqualTo(preferences);
assertThat(dto.getOrganizationId()).isEqualTo("org-123");
assertThat(dto.getUserId()).isEqualTo("user-456");
}
}
@Nested
@DisplayName("Tests getTodayEventsCount")
class GetTodayEventsCountTests {
@Test
@DisplayName("getTodayEventsCount - avec événements d'aujourd'hui")
void testGetTodayEventsCountAvecEvenementsAujourdhui() {
UpcomingEventDTO event1 = createUpcomingEventDTO(now());
UpcomingEventDTO event2 = createUpcomingEventDTO(daysFromNow(1));
UpcomingEventDTO event3 = createUpcomingEventDTO(now());
DashboardDataDTO dto = DashboardDataDTO.builder()
.upcomingEvents(List.of(event1, event2, event3))
.build();
assertThat(dto.getTodayEventsCount()).isEqualTo(2);
}
@Test
@DisplayName("getTodayEventsCount - sans événements d'aujourd'hui")
void testGetTodayEventsCountSansEvenementsAujourdhui() {
UpcomingEventDTO event = createUpcomingEventDTO(daysFromNow(1));
DashboardDataDTO dto = DashboardDataDTO.builder()
.upcomingEvents(List.of(event))
.build();
assertThat(dto.getTodayEventsCount()).isEqualTo(0);
}
@Test
@DisplayName("getTodayEventsCount - upcomingEvents null")
void testGetTodayEventsCountNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getTodayEventsCount()).isEqualTo(0);
}
}
@Nested
@DisplayName("Tests getTomorrowEventsCount")
class GetTomorrowEventsCountTests {
@Test
@DisplayName("getTomorrowEventsCount - avec événements de demain")
void testGetTomorrowEventsCountAvecEvenementsDemain() {
UpcomingEventDTO event1 = createUpcomingEventDTO(daysFromNow(1));
UpcomingEventDTO event2 = createUpcomingEventDTO(daysFromNow(2));
DashboardDataDTO dto = DashboardDataDTO.builder()
.upcomingEvents(List.of(event1, event2))
.build();
assertThat(dto.getTomorrowEventsCount()).isEqualTo(1);
}
@Test
@DisplayName("getTomorrowEventsCount - upcomingEvents null")
void testGetTomorrowEventsCountNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getTomorrowEventsCount()).isEqualTo(0);
}
}
@Nested
@DisplayName("Tests getRecentActivitiesCount")
class GetRecentActivitiesCountTests {
@Test
@DisplayName("getRecentActivitiesCount - avec activités récentes")
void testGetRecentActivitiesCountAvecActivitesRecent() {
RecentActivityDTO activity1 = createRecentActivityDTO("member", hoursAgo(12));
RecentActivityDTO activity2 = createRecentActivityDTO("event", hoursAgo(25));
RecentActivityDTO activity3 = createRecentActivityDTO("contribution", hoursAgo(5));
DashboardDataDTO dto = DashboardDataDTO.builder()
.recentActivities(List.of(activity1, activity2, activity3))
.build();
assertThat(dto.getRecentActivitiesCount()).isEqualTo(2);
}
@Test
@DisplayName("getRecentActivitiesCount - recentActivities null")
void testGetRecentActivitiesCountNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getRecentActivitiesCount()).isEqualTo(0);
}
}
@Nested
@DisplayName("Tests getTodayActivitiesCount")
class GetTodayActivitiesCountTests {
@Test
@DisplayName("getTodayActivitiesCount - avec activités d'aujourd'hui")
void testGetTodayActivitiesCountAvecActivitesAujourdhui() {
RecentActivityDTO activity1 = createRecentActivityDTO("member", now());
RecentActivityDTO activity2 = createRecentActivityDTO("event", hoursAgo(25));
DashboardDataDTO dto = DashboardDataDTO.builder()
.recentActivities(List.of(activity1, activity2))
.build();
assertThat(dto.getTodayActivitiesCount()).isEqualTo(1);
}
@Test
@DisplayName("getTodayActivitiesCount - recentActivities null")
void testGetTodayActivitiesCountNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getTodayActivitiesCount()).isEqualTo(0);
}
}
@Nested
@DisplayName("Tests getHasUpcomingEvents")
class GetHasUpcomingEventsTests {
@Test
@DisplayName("getHasUpcomingEvents - avec événements")
void testGetHasUpcomingEventsAvecEvenements() {
DashboardDataDTO dto = DashboardDataDTO.builder()
.upcomingEvents(List.of(createUpcomingEventDTO()))
.build();
assertThat(dto.getHasUpcomingEvents()).isTrue();
}
@Test
@DisplayName("getHasUpcomingEvents - liste vide")
void testGetHasUpcomingEventsListeVide() {
DashboardDataDTO dto = DashboardDataDTO.builder()
.upcomingEvents(List.of())
.build();
assertThat(dto.getHasUpcomingEvents()).isFalse();
}
@Test
@DisplayName("getHasUpcomingEvents - null")
void testGetHasUpcomingEventsNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getHasUpcomingEvents()).isFalse();
}
}
@Nested
@DisplayName("Tests getHasRecentActivities")
class GetHasRecentActivitiesTests {
@Test
@DisplayName("getHasRecentActivities - avec activités")
void testGetHasRecentActivitiesAvecActivites() {
DashboardDataDTO dto = DashboardDataDTO.builder()
.recentActivities(List.of(createRecentActivityDTO()))
.build();
assertThat(dto.getHasRecentActivities()).isTrue();
}
@Test
@DisplayName("getHasRecentActivities - liste vide")
void testGetHasRecentActivitiesListeVide() {
DashboardDataDTO dto = DashboardDataDTO.builder()
.recentActivities(List.of())
.build();
assertThat(dto.getHasRecentActivities()).isFalse();
}
@Test
@DisplayName("getHasRecentActivities - null")
void testGetHasRecentActivitiesNull() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getHasRecentActivities()).isFalse();
}
}
@Nested
@DisplayName("Tests des préférences utilisateur")
class UserPreferencesTests {
@Test
@DisplayName("getThemePreference - avec préférence")
void testGetThemePreferenceAvecPreference() {
Map<String, Object> preferences = new HashMap<>();
preferences.put("theme", "dark");
DashboardDataDTO dto = DashboardDataDTO.builder()
.userPreferences(preferences)
.build();
assertThat(dto.getThemePreference()).isEqualTo("dark");
}
@Test
@DisplayName("getThemePreference - sans préférence")
void testGetThemePreferenceSansPreference() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getThemePreference()).isEqualTo("royal_teal");
}
@Test
@DisplayName("getLanguagePreference - avec préférence")
void testGetLanguagePreferenceAvecPreference() {
Map<String, Object> preferences = new HashMap<>();
preferences.put("language", "en");
DashboardDataDTO dto = DashboardDataDTO.builder()
.userPreferences(preferences)
.build();
assertThat(dto.getLanguagePreference()).isEqualTo("en");
}
@Test
@DisplayName("getLanguagePreference - sans préférence")
void testGetLanguagePreferenceSansPreference() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getLanguagePreference()).isEqualTo("fr");
}
@Test
@DisplayName("getNotificationsEnabled - avec préférence")
void testGetNotificationsEnabledAvecPreference() {
Map<String, Object> preferences = new HashMap<>();
preferences.put("notifications", false);
DashboardDataDTO dto = DashboardDataDTO.builder()
.userPreferences(preferences)
.build();
assertThat(dto.getNotificationsEnabled()).isFalse();
}
@Test
@DisplayName("getNotificationsEnabled - sans préférence")
void testGetNotificationsEnabledSansPreference() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getNotificationsEnabled()).isTrue();
}
@Test
@DisplayName("getAutoRefreshEnabled - avec préférence")
void testGetAutoRefreshEnabledAvecPreference() {
Map<String, Object> preferences = new HashMap<>();
preferences.put("autoRefresh", false);
DashboardDataDTO dto = DashboardDataDTO.builder()
.userPreferences(preferences)
.build();
assertThat(dto.getAutoRefreshEnabled()).isFalse();
}
@Test
@DisplayName("getAutoRefreshEnabled - sans préférence")
void testGetAutoRefreshEnabledSansPreference() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getAutoRefreshEnabled()).isTrue();
}
@Test
@DisplayName("getRefreshInterval - avec préférence")
void testGetRefreshIntervalAvecPreference() {
Map<String, Object> preferences = new HashMap<>();
preferences.put("refreshInterval", 600);
DashboardDataDTO dto = DashboardDataDTO.builder()
.userPreferences(preferences)
.build();
assertThat(dto.getRefreshInterval()).isEqualTo(600);
}
@Test
@DisplayName("getRefreshInterval - sans préférence")
void testGetRefreshIntervalSansPreference() {
DashboardDataDTO dto = new DashboardDataDTO();
assertThat(dto.getRefreshInterval()).isEqualTo(300);
}
}
}

View File

@@ -0,0 +1,301 @@
package dev.lions.unionflow.server.api.dto.dashboard;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("Tests pour DashboardStatsDTO")
class DashboardStatsDTOTest {
@Test
@DisplayName("Test de base - classe peut être instanciée")
void testClasseInstanciable() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto).isNotNull();
}
@Nested
@DisplayName("Tests de construction")
class ConstructionTests {
@Test
@DisplayName("Constructeur par défaut")
void testConstructeurParDefaut() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto).isNotNull();
}
@Test
@DisplayName("Builder pattern")
void testBuilder() {
LocalDateTime lastUpdated = LocalDateTime.now();
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(80)
.totalEvents(50)
.upcomingEvents(10)
.totalContributions(200)
.totalContributionAmount(50000.0)
.pendingRequests(5)
.completedProjects(15)
.monthlyGrowth(5.5)
.engagementRate(0.75)
.lastUpdated(lastUpdated)
.build();
assertThat(dto.getTotalMembers()).isEqualTo(100);
assertThat(dto.getActiveMembers()).isEqualTo(80);
assertThat(dto.getTotalEvents()).isEqualTo(50);
assertThat(dto.getUpcomingEvents()).isEqualTo(10);
assertThat(dto.getTotalContributions()).isEqualTo(200);
assertThat(dto.getTotalContributionAmount()).isEqualTo(50000.0);
assertThat(dto.getPendingRequests()).isEqualTo(5);
assertThat(dto.getCompletedProjects()).isEqualTo(15);
assertThat(dto.getMonthlyGrowth()).isEqualTo(5.5);
assertThat(dto.getEngagementRate()).isEqualTo(0.75);
assertThat(dto.getLastUpdated()).isEqualTo(lastUpdated);
}
}
@Nested
@DisplayName("Tests getFormattedContributionAmount")
class GetFormattedContributionAmountTests {
@Test
@DisplayName("getFormattedContributionAmount - montant >= 1M")
void testGetFormattedContributionAmountMillions() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalContributionAmount(1_500_000.0)
.build();
// Le format utilise la locale du système, peut être "1,5M" ou "1.5M"
assertThat(dto.getFormattedContributionAmount()).matches("1[.,]5M");
}
@Test
@DisplayName("getFormattedContributionAmount - montant >= 1K")
void testGetFormattedContributionAmountMilliers() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalContributionAmount(5_500.0)
.build();
assertThat(dto.getFormattedContributionAmount()).isEqualTo("6K");
}
@Test
@DisplayName("getFormattedContributionAmount - montant < 1K")
void testGetFormattedContributionAmountPetit() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalContributionAmount(500.0)
.build();
assertThat(dto.getFormattedContributionAmount()).isEqualTo("500");
}
@Test
@DisplayName("getFormattedContributionAmount - null")
void testGetFormattedContributionAmountNull() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto.getFormattedContributionAmount()).isEqualTo("0");
}
}
@Nested
@DisplayName("Tests getHasGrowth")
class GetHasGrowthTests {
@Test
@DisplayName("getHasGrowth - croissance positive")
void testGetHasGrowthPositif() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.monthlyGrowth(5.5)
.build();
assertThat(dto.getHasGrowth()).isTrue();
}
@Test
@DisplayName("getHasGrowth - croissance nulle")
void testGetHasGrowthNul() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.monthlyGrowth(0.0)
.build();
assertThat(dto.getHasGrowth()).isFalse();
}
@Test
@DisplayName("getHasGrowth - croissance négative")
void testGetHasGrowthNegatif() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.monthlyGrowth(-2.5)
.build();
assertThat(dto.getHasGrowth()).isFalse();
}
@Test
@DisplayName("getHasGrowth - null")
void testGetHasGrowthNull() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto.getHasGrowth()).isFalse();
}
}
@Nested
@DisplayName("Tests getIsHighEngagement")
class GetIsHighEngagementTests {
@Test
@DisplayName("getIsHighEngagement - engagement élevé")
void testGetIsHighEngagementEleve() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.engagementRate(0.75)
.build();
assertThat(dto.getIsHighEngagement()).isTrue();
}
@Test
@DisplayName("getIsHighEngagement - engagement à la limite")
void testGetIsHighEngagementLimite() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.engagementRate(0.7)
.build();
// 0.7 n'est pas > 0.7, donc false
assertThat(dto.getIsHighEngagement()).isFalse();
}
@Test
@DisplayName("getIsHighEngagement - engagement faible")
void testGetIsHighEngagementFaible() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.engagementRate(0.5)
.build();
assertThat(dto.getIsHighEngagement()).isFalse();
}
@Test
@DisplayName("getIsHighEngagement - null")
void testGetIsHighEngagementNull() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto.getIsHighEngagement()).isFalse();
}
}
@Nested
@DisplayName("Tests getInactiveMembers")
class GetInactiveMembersTests {
@Test
@DisplayName("getInactiveMembers - calcul correct")
void testGetInactiveMembers() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(80)
.build();
assertThat(dto.getInactiveMembers()).isEqualTo(20.0);
}
@Test
@DisplayName("getInactiveMembers - tous actifs")
void testGetInactiveMembersTousActifs() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(100)
.build();
assertThat(dto.getInactiveMembers()).isEqualTo(0.0);
}
@Test
@DisplayName("getInactiveMembers - null")
void testGetInactiveMembersNull() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto.getInactiveMembers()).isEqualTo(0.0);
}
}
@Nested
@DisplayName("Tests getActiveMemberPercentage")
class GetActiveMemberPercentageTests {
@Test
@DisplayName("getActiveMemberPercentage - calcul correct")
void testGetActiveMemberPercentage() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(80)
.build();
assertThat(dto.getActiveMemberPercentage()).isEqualTo(80.0);
}
@Test
@DisplayName("getActiveMemberPercentage - tous actifs")
void testGetActiveMemberPercentageTousActifs() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(100)
.activeMembers(100)
.build();
assertThat(dto.getActiveMemberPercentage()).isEqualTo(100.0);
}
@Test
@DisplayName("getActiveMemberPercentage - totalMembers null")
void testGetActiveMemberPercentageTotalNull() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.activeMembers(80)
.build();
assertThat(dto.getActiveMemberPercentage()).isEqualTo(0.0);
}
@Test
@DisplayName("getActiveMemberPercentage - totalMembers = 0")
void testGetActiveMemberPercentageTotalZero() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.totalMembers(0)
.activeMembers(0)
.build();
assertThat(dto.getActiveMemberPercentage()).isEqualTo(0.0);
}
}
@Nested
@DisplayName("Tests getEngagementPercentage")
class GetEngagementPercentageTests {
@Test
@DisplayName("getEngagementPercentage - calcul correct")
void testGetEngagementPercentage() {
DashboardStatsDTO dto = DashboardStatsDTO.builder()
.engagementRate(0.75)
.build();
assertThat(dto.getEngagementPercentage()).isEqualTo(75.0);
}
@Test
@DisplayName("getEngagementPercentage - null")
void testGetEngagementPercentageNull() {
DashboardStatsDTO dto = new DashboardStatsDTO();
assertThat(dto.getEngagementPercentage()).isEqualTo(0.0);
}
}
}

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