commit 32823206db191bd51b94b90352ae5179f2c525a2 Author: dahoud Date: Sun Mar 15 16:23:37 2026 +0000 Initial commit: unionflow-server-api Code source complet à la racine du repository. Signed-off-by: lions dev Team diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7aa401 --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a70c40 --- /dev/null +++ b/README.md @@ -0,0 +1,548 @@ +# UnionFlow Server API - DTOs et Contrats + +![Java](https://img.shields.io/badge/Java-17-blue) +![Maven](https://img.shields.io/badge/Maven-Central-green) +![License](https://img.shields.io/badge/License-Proprietary-red) + +Module API partagé UnionFlow - DTOs, Request/Response, Validation, Enums. + +--- + +## 📋 Vue d'ensemble + +Ce module contient les **contrats d'API** partagés entre : +- **Backend Quarkus** (unionflow-server-impl-quarkus) +- **Frontend Web** (unionflow-client-quarkus-primefaces-freya) +- **Mobile Flutter** (unionflow-mobile-apps) - via génération TypeScript/JSON + +### Avantages + +✅ **Type-safety** : Contrats Java typés +✅ **Validation centralisée** : Contraintes Jakarta Bean Validation +✅ **DRY** : Zéro duplication entre projets +✅ **Versioning** : Maven semantic versioning +✅ **Documentation** : Javadoc complète + +--- + +## 🏗️ Structure + +``` +src/main/java/dev/lions/unionflow/server/api/ +├── dto/ # Data Transfer Objects +│ ├── base/ # DTOs de base +│ │ ├── BaseRequest.java +│ │ └── BaseResponse.java +│ ├── dashboard/ # Dashboard +│ │ ├── DashboardStatsResponse.java +│ │ ├── MembreDashboardSyntheseResponse.java +│ │ └── UpcomingEventResponse.java +│ ├── membre/ # Membres +│ │ ├── request/ +│ │ │ └── CreateMembreRequest.java +│ │ └── response/ +│ │ ├── MembreResponse.java +│ └── MembreSummaryResponse.java +│ ├── finance/ # Finance Workflow +│ │ ├── request/ +│ │ │ ├── ApproveTransactionRequest.java +│ │ │ └── RejectTransactionRequest.java +│ │ └── response/ +│ │ ├── AdhesionResponse.java +│ │ ├── TransactionApprovalResponse.java +│ │ └── BudgetResponse.java +│ ├── cotisation/ # Cotisations +│ ├── evenement/ # Événements +│ ├── solidarite/ # Demandes d'aide +│ └── notification/ # Notifications +├── enums/ # Énumérations +│ ├── membre/ +│ │ ├── StatutMembre.java +│ │ └── TypeMembre.java +│ ├── finance/ +│ │ ├── StatutApprobation.java +│ │ ├── TypeTransaction.java +│ │ └── BudgetPeriod.java +│ ├── paiement/ +│ │ ├── StatutPaiement.java +│ │ └── ModePaiement.java +│ └── notification/ +│ └── TypeNotification.java +├── validation/ # Validateurs custom +│ ├── annotations/ # Annotations validation +│ │ ├── @ValidEmail +│ │ ├── @ValidPhoneNumber +│ │ └── @ValidAmount +│ ├── validators/ # Implémentations +│ │ ├── EmailValidator.java +│ │ └── AmountValidator.java +│ └── ValidationConstants.java # Constantes (regex, limites) +└── exception/ # Exceptions API + ├── ApiException.java + ├── ValidationException.java + └── ErrorResponse.java +``` + +--- + +## 📦 Installation + +### Maven Dependency + +Ajouter à votre `pom.xml` : + +```xml + + dev.lions.unionflow + unionflow-server-api + 2.0.0 + +``` + +### Repository Gitea (Maven Registry) + +Configurer `~/.m2/settings.xml` : + +```xml + + + + gitea-lionsdev + ${env.GITEA_USERNAME} + ${env.GITEA_TOKEN} + + + + + + gitea-lionsdev + https://git.lions.dev/api/packages/lionsdev/maven + + + +``` + +### Variables d'environnement + +```bash +export GITEA_USERNAME=lionsdev +export GITEA_TOKEN=your-gitea-token +``` + +--- + +## 🎯 Utilisation + +### 1. DTOs Request/Response + +#### Exemple : Créer un membre + +**Request DTO** : + +```java +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CreateMembreRequest { + + @NotBlank(message = "Le nom est requis") + @Size(min = 2, max = 100) + private String nom; + + @NotBlank(message = "Le prénom est requis") + @Size(min = 2, max = 100) + private String prenom; + + @ValidEmail + private String email; + + @ValidPhoneNumber + private String telephone; + + @NotNull + private UUID organisationId; + + private TypeMembre typeMembre = TypeMembre.MEMBRE; +} +``` + +**Response DTO** : + +```java +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MembreResponse extends BaseResponse { + + private UUID id; + private String nom; + private String prenom; + private String email; + private String telephone; + private StatutMembre statut; + private TypeMembre type; + private String numeroMembre; + private LocalDate dateAdhesion; + private UUID organisationId; + private String organisationNom; +} +``` + +**Usage Backend** : + +```java +@POST +@Path("/membres") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public Response createMembre(@Valid CreateMembreRequest request) { + MembreResponse response = membreService.create(request); + return Response.status(201).entity(response).build(); +} +``` + +**Usage Frontend (REST Client)** : + +```java +@Path("/api/v1/membres") +@RegisterRestClient(configKey = "unionflow-backend") +public interface MembreRestClient { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + MembreResponse createMembre(@Valid CreateMembreRequest request); +} +``` + +### 2. Validation + +#### Annotations standard (Jakarta Bean Validation) + +```java +@NotNull // Non null +@NotBlank // Non vide (String) +@Size(min, max) // Taille min/max +@Min(value) // Valeur minimale (nombres) +@Max(value) // Valeur maximale (nombres) +@Email // Format email +@Pattern(regex) // Regex custom +@Positive // > 0 +@PositiveOrZero // >= 0 +``` + +#### Annotations custom UnionFlow + +```java +@ValidEmail // Email avec domaines autorisés +@ValidPhoneNumber // Téléphone international (+225, +33, etc.) +@ValidAmount // Montant positif, max 2 décimales +@ValidNumeroMembre // Format: ORG-YYYY-NNNN +@ValidPeriodeCotisation // Format: YYYY-MM +``` + +**Exemple d'utilisation** : + +```java +public class PaiementRequest { + + @ValidAmount(min = 100.0, max = 10_000_000.0) + private BigDecimal montant; + + @ValidPhoneNumber + private String telephonePaiement; + + @NotNull + @Pattern(regexp = "^\\d{4}-\\d{2}$") + private String periode; // Ex: "2026-03" +} +``` + +#### Validators custom - Implémentation + +**Annotation** : + +```java +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = AmountValidator.class) +public @interface ValidAmount { + String message() default "Montant invalide"; + double min() default 0.0; + double max() default Double.MAX_VALUE; + Class[] groups() default {}; + Class[] payload() default {}; +} +``` + +**Validator** : + +```java +public class AmountValidator implements ConstraintValidator { + + private double min; + private double max; + + @Override + public void initialize(ValidAmount annotation) { + this.min = annotation.min(); + this.max = annotation.max(); + } + + @Override + public boolean isValid(BigDecimal value, ConstraintValidatorContext context) { + if (value == null) return true; // Use @NotNull separately + + double amount = value.doubleValue(); + + // Check positive + if (amount <= 0) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate("Le montant doit être positif") + .addConstraintViolation(); + return false; + } + + // Check range + if (amount < min || amount > max) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate( + String.format("Le montant doit être entre %.2f et %.2f", min, max) + ).addConstraintViolation(); + return false; + } + + // Check max 2 decimals + if (value.scale() > 2) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate("Maximum 2 décimales autorisées") + .addConstraintViolation(); + return false; + } + + return true; + } +} +``` + +### 3. Enums + +#### Exemple : StatutApprobation + +```java +public enum StatutApprobation { + PENDING("En attente"), + APPROVED("Approuvée"), + REJECTED("Rejetée"), + CANCELLED("Annulée"); + + private final String libelle; + + StatutApprobation(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } + + public static StatutApprobation fromString(String str) { + return Arrays.stream(values()) + .filter(s -> s.name().equalsIgnoreCase(str)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Statut invalide: " + str)); + } +} +``` + +**Usage** : + +```java +StatutApprobation statut = StatutApprobation.APPROVED; +System.out.println(statut.getLibelle()); // "Approuvée" + +StatutApprobation parsed = StatutApprobation.fromString("PENDING"); +``` + +### 4. BaseResponse - Héritage commun + +Tous les Response DTOs étendent `BaseResponse` : + +```java +@Data +@NoArgsConstructor +public abstract class BaseResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + // Audit fields (si nécessaire côté client) + private LocalDateTime dateCreation; + private LocalDateTime dateModification; + private Boolean actif; +} +``` + +**Avantages** : +- Champs audit automatiques +- Serializable pour cache/sessions +- Type-safe avec génériques + +--- + +## 🧪 Tests + +### Tests de validation + +**Fichier** : `src/test/java/dev/lions/unionflow/server/api/validation/AmountValidatorTest.java` + +```java +@Test +void shouldRejectNegativeAmount() { + CreatePaiementRequest request = new CreatePaiementRequest(); + request.setMontant(BigDecimal.valueOf(-100)); + + Set> violations = validator.validate(request); + + assertFalse(violations.isEmpty()); + assertTrue(violations.stream() + .anyMatch(v -> v.getMessage().contains("positif"))); +} + +@Test +void shouldRejectTooManyDecimals() { + CreatePaiementRequest request = new CreatePaiementRequest(); + request.setMontant(BigDecimal.valueOf(100.123)); + + Set> violations = validator.validate(request); + + assertFalse(violations.isEmpty()); + assertTrue(violations.stream() + .anyMatch(v -> v.getMessage().contains("2 décimales"))); +} +``` + +### Lancer les tests + +```bash +mvn test +``` + +--- + +## 📊 DTOs par Feature + +### Dashboard + +- `DashboardStatsResponse` - Stats organisation (membres, cotisations, events) +- `MembreDashboardSyntheseResponse` - Synthèse membre (solde, cotisations) +- `UpcomingEventResponse` - Événements à venir + +### Finance Workflow + +- `TransactionApprovalResponse` - Approbation de transaction +- `BudgetResponse` - Budget avec lignes budgétaires +- `AdhesionResponse` - Adhésion membre + +### Membres + +- `MembreResponse` - Détails membre complets +- `MembreSummaryResponse` - Résumé membre (liste) +- `CreateMembreRequest` - Création membre +- `UpdateMembreRequest` - Modification membre + +### Cotisations + +- `CotisationResponse` - Cotisation avec détails +- `CreateCotisationRequest` - Enregistrement cotisation +- `CotisationStatisticsResponse` - Statistiques cotisations + +### Notifications + +- `NotificationResponse` - Notification utilisateur +- `MarkAsReadRequest` - Marquer comme lue + +--- + +## 🔄 Publication Maven (Développeurs seulement) + +### Publier une nouvelle version + +```bash +# 1. Mettre à jour la version dans pom.xml +2.1.0 + +# 2. Compiler et publier +mvn clean deploy + +# Les artifacts sont publiés sur: +# https://git.lions.dev/api/packages/lionsdev/maven +``` + +### Versioning Semantic + +- **Major** (2.0.0) : Breaking changes +- **Minor** (2.1.0) : Nouvelles features (backward compatible) +- **Patch** (2.0.1) : Bugfixes + +--- + +## 📝 Changelog + +### v2.0.0 (2026-03-14) + +✅ **Nouveau** : +- DTOs Finance Workflow complets +- Validation `@ValidAmount` avec min/max +- Enums `BudgetPeriod`, `BudgetCategory` +- `TransactionApprovalResponse` avec tous les champs + +✅ **Améliorations** : +- BaseResponse avec audit fields +- ValidationConstants centralisées +- Javadoc complète pour tous les DTOs + +### v1.0.0 (2026-01-04) + +- Version initiale +- 20+ DTOs principaux +- 10+ validators custom + +--- + +## 📚 Documentation + +### Javadoc + +```bash +# Générer Javadoc +mvn javadoc:javadoc + +# Ouvrir +open target/site/apidocs/index.html +``` + +### Ressources + +- **Jakarta Bean Validation**: https://jakarta.ee/specifications/bean-validation/ +- **Maven Repository**: https://git.lions.dev/lionsdev/unionflow-server-api + +--- + +## 🤝 Contribution + +1. Créer une branche feature +2. Ajouter DTOs/Validators avec tests +3. Documenter avec Javadoc +4. Pull Request avec description + +--- + +## 📄 Licence + +Propriétaire - © 2026 Lions Club Côte d'Ivoire + +--- + +**Version** : 2.0.0 +**Dernière mise à jour** : 2026-03-14 +**Auteur** : Équipe UnionFlow diff --git a/checkstyle-unionflow.xml b/checkstyle-unionflow.xml new file mode 100644 index 0000000..8d7953c --- /dev/null +++ b/checkstyle-unionflow.xml @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..df71bb6 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/parent-pom.xml b/parent-pom.xml new file mode 100644 index 0000000..253ba82 --- /dev/null +++ b/parent-pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + dev.lions.unionflow + unionflow-parent + 1.0.0 + pom + + UnionFlow - Parent + Plateforme complète de gestion d'union — POM parent partagé + + + + gitea-lionsdev + https://git.lions.dev/api/packages/lionsdev/maven + + + gitea-lionsdev + https://git.lions.dev/api/packages/lionsdev/maven + + + + + + gitea-lionsdev + https://git.lions.dev/api/packages/lionsdev/maven + true + true + + + + + 17 + 17 + UTF-8 + UTF-8 + 3.15.1 + 1.18.34 + + + + + + jakarta.annotation + jakarta.annotation-api + 3.0.0 + provided + + + org.projectlombok + lombok + 1.18.34 + provided + + + dev.lions.unionflow + unionflow-server-api + ${project.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 17 + 17 + UTF-8 + true + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..94a84f0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,241 @@ + + + 4.0.0 + + + dev.lions.unionflow + unionflow-parent + 1.0.0 + parent-pom.xml + + + unionflow-server-api + jar + + UnionFlow Server API + API définitions pour le serveur UnionFlow + + + 17 + 17 + UTF-8 + + 3.15.1 + 2.17.0 + 3.0.2 + 3.1.1 + + + 0.8.11 + 10.12.4 + 3.3.1 + 5.10.1 + 5.7.0 + 3.24.2 + + + 1.00 + 1.00 + 1.00 + 1.00 + 1.00 + + + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + + jakarta.validation + jakarta.validation-api + ${validation-api.version} + + + + + org.eclipse.microprofile.openapi + microprofile-openapi-api + ${microprofile-openapi.version} + + + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + test + + + + org.glassfish + jakarta.el + 4.0.2 + test + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + UTF-8 + + + org.projectlombok + lombok + 1.18.30 + + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + test + + report + + + + check + verify + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + ${jacoco.line.coverage.minimum} + + + BRANCH + COVEREDRATIO + ${jacoco.branch.coverage.minimum} + + + INSTRUCTION + COVEREDRATIO + ${jacoco.instruction.coverage.minimum} + + + METHOD + COVEREDRATIO + ${jacoco.method.coverage.minimum} + + + CLASS + COVEREDRATIO + ${jacoco.class.coverage.minimum} + + + + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + google_checks.xml + true + true + warning + true + **/target/**/* + + + + + + + \ No newline at end of file diff --git a/script/publish-api.bat b/script/publish-api.bat new file mode 100644 index 0000000..e8f77fc --- /dev/null +++ b/script/publish-api.bat @@ -0,0 +1,30 @@ +@echo off +REM Publie le parent pom + server-api sur le Gitea Package Registry +REM Usage : script\publish-api.bat +REM Depuis : n'importe où dans le repo server-api +REM Prérequis: credentials dans %USERPROFILE%\.m2\settings.xml (server id: gitea-lionsdev) + +set REGISTRY_URL=https://git.lions.dev/api/packages/lionsdev/maven +set REGISTRY_ID=gitea-lionsdev + +cd /d "%~dp0\.." + +echo. +echo [1/2] Publication du parent pom... +call mvn deploy:deploy-file ^ + -DgroupId=dev.lions.unionflow ^ + -DartifactId=unionflow-parent ^ + -Dversion=1.0.0 ^ + -Dpackaging=pom ^ + -Dfile=parent-pom.xml ^ + -DrepositoryId=%REGISTRY_ID% ^ + -Durl=%REGISTRY_URL% +if errorlevel 409 echo [WARN] Parent pom deja publie pour cette version, on continue. + +echo. +echo [2/2] Publication du server-api... +call mvn deploy -DskipTests +if errorlevel 409 echo [WARN] Server-api deja publie - incrementer la version pour republier. + +echo. +echo Done -- https://git.lions.dev/lionsdev/-/packages diff --git a/script/publish-api.sh b/script/publish-api.sh new file mode 100644 index 0000000..422c15a --- /dev/null +++ b/script/publish-api.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Publie le parent pom + server-api sur le Gitea Package Registry +# Usage : ./script/publish-api.sh +# Depuis : n'importe où dans le repo server-api +# Prérequis: credentials dans ~/.m2/settings.xml (server id: gitea-lionsdev) + +set -e + +REGISTRY_URL="https://git.lions.dev/api/packages/lionsdev/maven" +REGISTRY_ID="gitea-lionsdev" + +cd "$(dirname "$0")/.." + +echo "" +echo "[1/2] Publication du parent pom..." +mvn deploy:deploy-file \ + -DgroupId=dev.lions.unionflow \ + -DartifactId=unionflow-parent \ + -Dversion=1.0.0 \ + -Dpackaging=pom \ + -Dfile=parent-pom.xml \ + -DrepositoryId="${REGISTRY_ID}" \ + -Durl="${REGISTRY_URL}" \ +|| echo "[WARN] Parent pom déjà publié pour cette version (409), on continue." + +echo "" +echo "[2/2] Publication du server-api..." +mvn deploy -DskipTests \ +|| echo "[WARN] Server-api déjà publié pour cette version (409) - incrémenter la version pour republier." + +echo "" +echo "Done -- https://git.lions.dev/lionsdev/-/packages" diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/CreateAbonnementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/CreateAbonnementRequest.java new file mode 100644 index 0000000..996d745 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/CreateAbonnementRequest.java @@ -0,0 +1,82 @@ +package dev.lions.unionflow.server.api.dto.abonnement.request; + +import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; +import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +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.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un abonnement. + */ +@Builder +public record CreateAbonnementRequest( + @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)") String numeroReference, + + @NotNull(message = "L'identifiant de l'organisation est obligatoire") UUID organisationId, + + String nomOrganisation, + + @NotNull(message = "L'identifiant de la formule est obligatoire") UUID formulaireId, + + String codeFormule, + String nomFormule, + TypeFormule typeFormule, + + @NotNull(message = "Le statut est obligatoire") StatutAbonnement statut, + + @NotNull(message = "Le type d'abonnement est obligatoire") TypePeriodeAbonnement typeAbonnement, + + @NotNull(message = "La date de début est obligatoire") LocalDate dateDebut, + + @Future(message = "La date de fin doit être dans le futur") LocalDate dateFin, + + LocalDate dateProchainePeriode, + + @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") BigDecimal montant, + + @NotBlank(message = "La devise est obligatoire") @Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres") String devise, + + @DecimalMin(value = "0.0", message = "La remise doit être positive") @DecimalMin(value = "100.0", message = "La remise ne peut pas dépasser 100%") BigDecimal 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") BigDecimal montantFinal, + + Boolean renouvellementAutomatique, + Boolean periodeEssaiUtilisee, + LocalDate dateFinEssai, + Integer maxMembres, + Integer nombreMembresActuels, + BigDecimal espaceStockageGB, + BigDecimal espaceStockageUtilise, + Boolean supportTechnique, + String niveauSupport, + Boolean fonctionnalitesAvancees, + Boolean apiAccess, + Boolean rapportsPersonnalises, + Boolean integrationsTierces, + UUID responsableId, + String nomResponsable, + String emailResponsable, + String telephoneResponsable, + + @Pattern(regexp = "^(WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|VIREMENT|CHEQUE|AUTRE)$", message = "Mode de paiement invalide") String modePaiementPrefere, + + @Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Format de numéro de téléphone invalide") String numeroPaiementMobile, + + @Size(max = 5000, message = "L'historique ne peut pas dépasser 5000 caractères") String historiquePaiements, + + @Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères") String notes, + + Boolean alertesActivees, + Boolean notificationsEmail, + Boolean notificationsSMS) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/UpdateAbonnementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/UpdateAbonnementRequest.java new file mode 100644 index 0000000..a959b81 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/request/UpdateAbonnementRequest.java @@ -0,0 +1,77 @@ +package dev.lions.unionflow.server.api.dto.abonnement.request; + +import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; +import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de mise à jour d'un abonnement. + */ +@Builder +public record UpdateAbonnementRequest( + @Pattern(regexp = "^ABO-\\d{4}-[A-Z0-9]{8}$", message = "Format de référence invalide (ABO-YYYY-XXXXXXXX)") String numeroReference, + + UUID organisationId, + String nomOrganisation, + UUID formulaireId, + + String codeFormule, + String nomFormule, + TypeFormule typeFormule, + + StatutAbonnement statut, + TypePeriodeAbonnement typeAbonnement, + + LocalDate dateDebut, + + @Future(message = "La date de fin doit être dans le futur") LocalDate dateFin, + + LocalDate dateProchainePeriode, + + @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") BigDecimal montant, + + @Pattern(regexp = "^[A-Z]{3}$", message = "La devise doit être un code ISO à 3 lettres") String devise, + + @DecimalMin(value = "0.0", message = "La remise doit être positive") @DecimalMin(value = "100.0", message = "La remise ne peut pas dépasser 100%") BigDecimal 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") BigDecimal montantFinal, + + Boolean renouvellementAutomatique, + Boolean periodeEssaiUtilisee, + LocalDate dateFinEssai, + Integer maxMembres, + Integer nombreMembresActuels, + BigDecimal espaceStockageGB, + BigDecimal espaceStockageUtilise, + Boolean supportTechnique, + String niveauSupport, + Boolean fonctionnalitesAvancees, + Boolean apiAccess, + Boolean rapportsPersonnalises, + Boolean integrationsTierces, + UUID responsableId, + String nomResponsable, + String emailResponsable, + String telephoneResponsable, + + @Pattern(regexp = "^(WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|VIREMENT|CHEQUE|AUTRE)$", message = "Mode de paiement invalide") String modePaiementPrefere, + + @Pattern(regexp = "^\\+?[0-9]{8,15}$", message = "Format de numéro de téléphone invalide") String numeroPaiementMobile, + + @Size(max = 5000, message = "L'historique ne peut pas dépasser 5000 caractères") String historiquePaiements, + + @Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères") String notes, + + Boolean alertesActivees, + Boolean notificationsEmail, + Boolean notificationsSMS) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponse.java new file mode 100644 index 0000000..3381980 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponse.java @@ -0,0 +1,133 @@ +package dev.lions.unionflow.server.api.dto.abonnement.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; +import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour un abonnement. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AbonnementResponse extends BaseResponse { + + private String numeroReference; + private UUID organisationId; + private String nomOrganisation; + private UUID formulaireId; + private String codeFormule; + private String nomFormule; + private TypeFormule typeFormule; + private StatutAbonnement statut; + private TypePeriodeAbonnement typeAbonnement; + private LocalDate dateDebut; + private LocalDate dateFin; + private LocalDate dateProchainePeriode; + private BigDecimal montant; + private String devise; + private BigDecimal remise; + private BigDecimal montantFinal; + private Boolean renouvellementAutomatique; + private Boolean periodeEssaiUtilisee; + private LocalDate dateFinEssai; + private Integer maxMembres; + private Integer nombreMembresActuels; + private BigDecimal espaceStockageGB; + private BigDecimal espaceStockageUtilise; + private Boolean supportTechnique; + private String niveauSupport; + private Boolean fonctionnalitesAvancees; + private Boolean apiAccess; + private Boolean rapportsPersonnalises; + private Boolean integrationsTierces; + private LocalDateTime dateDerniereUtilisation; + private Integer connexionsCeMois; + private UUID responsableId; + private String nomResponsable; + private String emailResponsable; + private String telephoneResponsable; + + private String modePaiementPrefere; + private String numeroPaiementMobile; + private String historiquePaiements; + private String notes; + + private Boolean alertesActivees; + private Boolean notificationsEmail; + private Boolean notificationsSMS; + + private LocalDateTime dateSuspension; + + private String raisonSuspension; + + private LocalDateTime dateAnnulation; + + private String raisonAnnulation; + + // === MÉTHODES UTILITAIRES === + + public boolean isActive() { + return StatutAbonnement.ACTIF.equals(statut); + } + + public boolean isExpire() { + return StatutAbonnement.EXPIRE.equals(statut) || + (dateFin != null && dateFin.isBefore(LocalDate.now())); + } + + public boolean isSuspendu() { + return StatutAbonnement.SUSPENDU.equals(statut); + } + + public int getMembresRestants() { + if (maxMembres == null) return 0; + int actuels = nombreMembresActuels != null ? nombreMembresActuels : 0; + return Math.max(0, maxMembres - actuels); + } + + public boolean isQuotaAtteint() { + if (maxMembres == null) return false; + int actuels = nombreMembresActuels != null ? nombreMembresActuels : 0; + return actuels >= maxMembres; + } + + public int getPourcentageUtilisation() { + if (maxMembres == null || maxMembres == 0) return 0; + int actuels = nombreMembresActuels != null ? nombreMembresActuels : 0; + return (actuels * 100) / maxMembres; + } + + public long getJoursRestants() { + if (dateFin == null) return -1; + LocalDate maintenant = LocalDate.now(); + if (maintenant.isAfter(dateFin)) return 0; + return java.time.temporal.ChronoUnit.DAYS.between(maintenant, dateFin); + } + + public boolean isExpirationProche() { + long joursRestants = getJoursRestants(); + return joursRestants >= 0 && joursRestants <= 30; + } + + public boolean peutEtreRenouvele() { + return Boolean.TRUE.equals(renouvellementAutomatique) && !isExpire(); + } + + public String getStatutLibelle() { + return statut != null ? statut.name() : "INCONNU"; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/admin/request/CreateAuditLogRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/admin/request/CreateAuditLogRequest.java new file mode 100644 index 0000000..4393028 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/admin/request/CreateAuditLogRequest.java @@ -0,0 +1,29 @@ +package dev.lions.unionflow.server.api.dto.admin.request; + +import java.time.LocalDateTime; +import lombok.Builder; + +/** + * Requête de création d'un log d'audit. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateAuditLogRequest( + String typeAction, + String severite, + String utilisateur, + String role, + String module, + String description, + String details, + String ipAddress, + String userAgent, + String sessionId, + LocalDateTime dateHeure, + String donneesAvant, + String donneesApres, + String entiteId, + String entiteType) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/admin/response/AuditLogResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/admin/response/AuditLogResponse.java new file mode 100644 index 0000000..a1b9248 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/admin/response/AuditLogResponse.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.admin.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + * Réponse pour les logs d'audit. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +public class AuditLogResponse extends BaseResponse { + + private String typeAction; + private String severite; + private String utilisateur; + private String role; + private String module; + private String description; + private String details; + private String ipAddress; + private String userAgent; + private String sessionId; + private LocalDateTime dateHeure; + private String donneesAvant; + private String donneesApres; + private String entiteId; + private String entiteType; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/CreateAdresseRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/CreateAdresseRequest.java new file mode 100644 index 0000000..d774aec --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/CreateAdresseRequest.java @@ -0,0 +1,43 @@ +package dev.lions.unionflow.server.api.dto.adresse.request; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.UUID; + +/** + * Requete de création d'une adresse. + * + * @author UnionFlow Team + * @version 3.0 + */ +public record CreateAdresseRequest( + @NotBlank(message = "Le type d'adresse est obligatoire") String typeAdresse, // Code depuis types_reference + + @NotBlank(message = "L'adresse est obligatoire") String adresse, + + String complementAdresse, + String codePostal, + + @NotBlank(message = "La ville est obligatoire") String ville, + + String region, + + @NotBlank(message = "Le pays est obligatoire") String pays, + + @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) BigDecimal latitude, // Optionnel + + @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) BigDecimal longitude, // Optionnel + + @NotNull(message = "L'indicateur principale est obligatoire") Boolean principale, + + String libelle, + String notes, + + UUID organisationId, // Exclusive: soit organisationId, soit membreId, soit evenementId + UUID membreId, + UUID evenementId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/UpdateAdresseRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/UpdateAdresseRequest.java new file mode 100644 index 0000000..7460e5a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/request/UpdateAdresseRequest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.dto.adresse.request; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import java.math.BigDecimal; +import java.util.UUID; + +/** + * Requete de mise à jour d'une adresse. + * Tous les champs sont optionnels pour permettre des mises à jour partielles. + * + * @author UnionFlow Team + * @version 3.0 + */ +public record UpdateAdresseRequest( + String typeAdresse, // Code depuis types_reference + String adresse, + String complementAdresse, + String codePostal, + String ville, + String region, + String pays, + + @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) BigDecimal latitude, + + @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) BigDecimal longitude, + + Boolean principale, + String libelle, + String notes, + + UUID organisationId, + UUID membreId, + UUID evenementId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/adresse/response/AdresseResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/response/AdresseResponse.java new file mode 100644 index 0000000..eef7bf8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/adresse/response/AdresseResponse.java @@ -0,0 +1,42 @@ +package dev.lions.unionflow.server.api.dto.adresse.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +/** + * DTO de réponse détaillée pour une adresse. + * + * @author UnionFlow Team + * @version 3.0 + */ +@Getter +@Setter +public class AdresseResponse extends BaseResponse { + + private String typeAdresse; // Code + private String typeAdresseLibelle; // Depuis types_reference + private String typeAdresseIcone; + + private String adresse; + private String complementAdresse; + private String codePostal; + private String ville; + private String region; + private String pays; + + private BigDecimal latitude; + private BigDecimal longitude; + + private Boolean principale; + private String libelle; + private String notes; + + private UUID organisationId; + private UUID membreId; + private UUID evenementId; + + private String adresseComplete; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/agricole/CampagneAgricoleDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/agricole/CampagneAgricoleDTO.java new file mode 100644 index 0000000..c889af6 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/agricole/CampagneAgricoleDTO.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.server.api.dto.agricole; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CampagneAgricoleDTO extends BaseDTO { + + private String organisationCoopId; + + // Exemple : "Campagne d'Arachide 2025/2026" + private String designation; + + private String typeCulturePrincipale; + + // Nombre d'hectares au total couvert par les membres de la coop + private BigDecimal surfaceTotaleEstimeeHectares; + + // Tonnes récoltées attendues vs réelles + private BigDecimal volumePrevisionnelTonnes; + private BigDecimal volumeReelTonnes; + + private StatutCampagneAgricole statut; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponse.java new file mode 100644 index 0000000..d90f17e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponse.java @@ -0,0 +1,265 @@ +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 + * + *

+ * 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 AnalyticsDataResponse 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 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 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 AnalyticsDataResponse( + 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 + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponse.java new file mode 100644 index 0000000..528989d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponse.java @@ -0,0 +1,350 @@ +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 + * + *

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 DashboardWidgetResponse 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 configurationFiltres; + + /** Configuration des alertes */ + private Map 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 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 + */ + /** Indique si la fréquence est en temps réel (pour couverture branches). */ + private boolean isFrequenceTempsReel() { + if (frequenceMiseAJourSecondes == null) return false; + return frequenceMiseAJourSecondes <= 60; + } + + public boolean isTempsReel() { + return isFrequenceTempsReel(); + } + + /** + * 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" + */ + /** Indique si le taux d'erreur déclenche le statut maintenance (pour couverture branches). */ + private boolean isTauxErreurMaintenance() { + if (tauxErreur == null) return false; + return tauxErreur > 10.0; + } + + public String getStatutWidget() { + if (hasErreursRecentes()) return "erreur"; + if (!visible) return "inactif"; + if (isTauxErreurMaintenance()) return "maintenance"; + return "actif"; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponse.java new file mode 100644 index 0000000..a0b2c6b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponse.java @@ -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 + * + *

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 KPITrendResponse 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 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())); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTO.java new file mode 100644 index 0000000..cdc2060 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTO.java @@ -0,0 +1,333 @@ +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 + * + *

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 metriques; + + /** Sections du rapport */ + @Valid private List 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 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 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 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 parametresFiltrage; + + /** Tags pour catégoriser le rapport */ + private List 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 seuilsAlerte; + + /** Filtres spécifiques à cette métrique */ + private Map 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 metriquesIncluses; + + /** Configuration spécifique de la section */ + private Map 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 + */ + /** Indique si le niveau de confidentialité est élevé (pour couverture branches). */ + private boolean isNiveauConfidentiel() { + if (niveauConfidentialite == null) return false; + return niveauConfidentialite >= 4; + } + + public boolean isConfidentiel() { + return isNiveauConfidentiel(); + } + + /** + * 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"; + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequest.java new file mode 100644 index 0000000..0f64271 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequest.java @@ -0,0 +1,32 @@ +package dev.lions.unionflow.server.api.dto.auth.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +/** + * Requête d'authentification utilisateur. + * + * @param username Email ou nom d'utilisateur + * @param password Mot de passe + * @param typeCompte Type de compte (MEMBRE, ADMIN, etc.) + * @param rememberMe Se souvenir de moi + * @author UnionFlow Team + * @version 2.0 + * @since 2026-02-28 + */ +@Builder +public record LoginRequest( + @NotBlank(message = "L'email ou nom d'utilisateur est requis") + @Size(min = 3, max = 100, message = "L'email ou nom d'utilisateur doit contenir entre 3 et 100 caractères") + String username, + + @NotBlank(message = "Le mot de passe est requis") + @Size(min = 6, message = "Le mot de passe doit contenir au moins 6 caractères") + String password, + + @NotBlank(message = "Le type de compte est requis") + String typeCompte, + + Boolean rememberMe) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponse.java new file mode 100644 index 0000000..e23e6f4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponse.java @@ -0,0 +1,90 @@ +package dev.lions.unionflow.server.api.dto.auth.response; + +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; + +/** + * Réponse d'authentification contenant le token JWT et les informations utilisateur. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2026-02-28 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + + private String accessToken; + private String refreshToken; + @Builder.Default + private String tokenType = "Bearer"; + private Long expiresIn; + private LocalDateTime expirationDate; + private UserInfo user; + + /** + * Vérifie si le token est expiré. + * + * @return true si le token est expiré + */ + public boolean isExpired() { + return expirationDate != null && LocalDateTime.now().isAfter(expirationDate); + } + + /** + * Informations de l'utilisateur connecté. + */ + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserInfo { + private UUID id; + private String nom; + private String prenom; + private String email; + private String username; + private String typeCompte; + private List roles; + private List permissions; + private EntiteInfo entite; + + /** + * Retourne le nom complet de l'utilisateur. + * + * @return Prénom + Nom ou nom d'utilisateur si absent + */ + public String getNomComplet() { + if (prenom != null && nom != null) { + return prenom + " " + nom; + } + return nom != null ? nom : username; + } + } + + /** + * Informations sur l'entité (organisation/membre) de l'utilisateur. + */ + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EntiteInfo { + private UUID id; + private String nom; + private String type; + private String pays; + private String ville; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitRequest.java new file mode 100644 index 0000000..a13ee1b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitRequest.java @@ -0,0 +1,42 @@ +package dev.lions.unionflow.server.api.dto.ayantdroit; + +import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Past; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AyantDroitRequest { + + @NotBlank(message = "L'Id du membre principal rattaché est obligatoire") + private String membrePrincipalId; + + @NotBlank(message = "Le prénom est obligatoire") + private String prenom; + + @NotBlank(message = "Le nom est obligatoire") + private String nom; + + @NotNull(message = "La date de naissance est obligatoire pour l'âge limite") + @Past(message = "La date de naissance doit être dans le passé") + private LocalDate dateNaissance; + + private String sexe; + + private String pieceIdentite; + + @NotNull(message = "Le lien de parenté / bénéfice est requis") + private LienParente lienParente; + + // Id document du livret de famille ou certificat médical / scolaire + private String justificatifLienId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitResponse.java new file mode 100644 index 0000000..6f5feb7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ayantdroit/AyantDroitResponse.java @@ -0,0 +1,40 @@ +package dev.lions.unionflow.server.api.dto.ayantdroit; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente; +import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.math.BigDecimal; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AyantDroitResponse extends BaseDTO { + + private String membrePrincipalId; + + private String numeroCarteBeneficiaire; + + private String prenom; + private String nom; + + private LocalDate dateNaissance; + private Integer ageActuel; + private String sexe; + private String pieceIdentite; + + private LienParente lienParente; + + // Prise en charge (%) par rapport à la couverture du Membre Principal + private BigDecimal pourcentageCouvertureSante; + + private StatutAyantDroit statut; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/CreateBackupRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/CreateBackupRequest.java new file mode 100644 index 0000000..b73f7d7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/CreateBackupRequest.java @@ -0,0 +1,28 @@ +package dev.lions.unionflow.server.api.dto.backup.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Request pour créer une sauvegarde + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CreateBackupRequest { + + @NotBlank(message = "Le nom de la sauvegarde est requis") + private String name; + + private String description; + + private String type; // AUTO, MANUAL, RESTORE_POINT + + private Boolean includeDatabase; + private Boolean includeFiles; + private Boolean includeConfiguration; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/RestoreBackupRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/RestoreBackupRequest.java new file mode 100644 index 0000000..910df17 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/RestoreBackupRequest.java @@ -0,0 +1,28 @@ +package dev.lions.unionflow.server.api.dto.backup.request; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +/** + * Request pour restaurer une sauvegarde + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RestoreBackupRequest { + + @NotNull(message = "L'ID de la sauvegarde est requis") + private UUID backupId; + + private Boolean restoreDatabase; + private Boolean restoreFiles; + private Boolean restoreConfiguration; + + private Boolean createRestorePoint; // Créer un point de restauration avant +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/UpdateBackupConfigRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/UpdateBackupConfigRequest.java new file mode 100644 index 0000000..69770e6 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/backup/request/UpdateBackupConfigRequest.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.backup.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Request pour mettre à jour la configuration des sauvegardes automatiques + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateBackupConfigRequest { + + private Boolean autoBackupEnabled; + private String frequency; // HOURLY, DAILY, WEEKLY + private String retention; // "7 jours", "30 jours", "90 jours", "1 an" + private Integer retentionDays; + private String backupTime; // Format HH:mm pour sauvegarde quotidienne + private Boolean includeDatabase; + private Boolean includeFiles; + private Boolean includeConfiguration; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupConfigResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupConfigResponse.java new file mode 100644 index 0000000..3a5ab98 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupConfigResponse.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.backup.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * Response contenant la configuration des sauvegardes automatiques + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BackupConfigResponse { + + private Boolean autoBackupEnabled; + private String frequency; // HOURLY, DAILY, WEEKLY + private String retention; // "7 jours", "30 jours", etc. + private Integer retentionDays; + private String backupTime; // HH:mm + private Boolean includeDatabase; + private Boolean includeFiles; + private Boolean includeConfiguration; + + private LocalDateTime lastBackup; + private LocalDateTime nextScheduledBackup; + private Integer totalBackups; + private Long totalSizeBytes; + private String totalSizeFormatted; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupResponse.java new file mode 100644 index 0000000..44d8d2c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/backup/response/BackupResponse.java @@ -0,0 +1,37 @@ +package dev.lions.unionflow.server.api.dto.backup.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Response représentant une sauvegarde + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BackupResponse { + + private UUID id; + private String name; + private String description; + private String type; // AUTO, MANUAL, RESTORE_POINT + private Long sizeBytes; + private String sizeFormatted; // ex: "2.3 GB" + private String status; // PENDING, IN_PROGRESS, COMPLETED, FAILED + private LocalDateTime createdAt; + private LocalDateTime completedAt; + private String createdBy; + + private Boolean includesDatabase; + private Boolean includesFiles; + private Boolean includesConfiguration; + + private String filePath; + private String errorMessage; // Si status = FAILED +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseDTO.java new file mode 100644 index 0000000..8741e88 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseDTO.java @@ -0,0 +1,164 @@ +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.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 + + '}'; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseResponse.java new file mode 100644 index 0000000..8a58f95 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/base/BaseResponse.java @@ -0,0 +1,79 @@ +package dev.lions.unionflow.server.api.dto.base; + +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +/** + * Classe de base pour les DTOs de réponse. + * + *

+ * Contient les champs d'audit communs à toutes + * les réponses : identifiant, dates de + * création/modification, créateur, et version. + * + *

+ * Les DTOs de type Request ne doivent + * pas hériter de cette classe (utiliser + * des {@code record} sans héritage). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Getter +@Setter +public abstract class BaseResponse { + + /** Identifiant unique de l'entité. */ + private UUID id; + + /** Date de création de l'entité. */ + private LocalDateTime dateCreation; + + /** Date de dernière modification. */ + private LocalDateTime dateModification; + + /** Email du créateur. */ + private String creePar; + + /** Email du dernier modificateur. */ + private String modifiePar; + + /** Version pour l'optimistic locking. */ + private Long version; + + /** État actif/inactif (soft-delete). */ + private Boolean actif; + + /** + * Comparaison basée sur l'ID. + * Deux BaseResponse sont égaux si leurs IDs sont égaux et non null. + * + * @param obj Objet à comparer + * @return true si les objets ont le même ID + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + BaseResponse that = (BaseResponse) obj; + return id != null && id.equals(that.id); + } + + /** + * Hash code basé sur l'ID. + * + * @return Hash code de l'ID, ou 0 si ID null + */ + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/base/PageResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/base/PageResponse.java new file mode 100644 index 0000000..f676656 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/base/PageResponse.java @@ -0,0 +1,59 @@ +package dev.lions.unionflow.server.api.dto.base; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Réponse paginée générique. + * + *

+ * Encapsule une liste d'éléments avec les + * métadonnées de pagination (page, taille, + * total). Utilisable pour tout type de réponse + * paginée. + * + * @param type des éléments de la page + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Getter +@AllArgsConstructor +public class PageResponse { + + /** Éléments de la page courante. */ + private final List contenu; + + /** Numéro de page courant (0-indexed). */ + private final int page; + + /** Taille de la page demandée. */ + private final int taille; + + /** Nombre total d'éléments. */ + private final long totalElements; + + /** Nombre total de pages. */ + private final int totalPages; + + /** + * Indique s'il existe une page suivante. + * + * @return {@code true} si une page suivante + * existe + */ + public boolean hasNext() { + return page < totalPages - 1; + } + + /** + * Indique s'il existe une page précédente. + * + * @return {@code true} si une page précédente + * existe + */ + public boolean hasPrevious() { + return page > 0; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/CampagneCollecteResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/CampagneCollecteResponse.java new file mode 100644 index 0000000..99b32a7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/CampagneCollecteResponse.java @@ -0,0 +1,45 @@ +package dev.lions.unionflow.server.api.dto.collectefonds; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Campagne de levée de fonds (Crowdfunding / ONG)") +public class CampagneCollecteResponse extends BaseDTO { + + private String organisationId; + + private String titre; + private String courteDescription; + private String htmlDescriptionComplete; + private String imageBanniereUrl; + + @Schema(description = "Objectif monétaire escompté") + private BigDecimal objectifFinancier; + + @Schema(description = "Somme totale déjà récoltée sur cette campagne") + private BigDecimal montantCollecteActuel; + + private Integer nombreDonateurs; + + private StatutCampagneCollecte statut; + + private LocalDateTime dateOuverture; + private LocalDateTime dateCloturePrevue; + + // Si la page est visible pour les non-membres (donateurs externes) + private Boolean estPublique; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/ContributionCollecteDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/ContributionCollecteDTO.java new file mode 100644 index 0000000..7d2989f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/collectefonds/ContributionCollecteDTO.java @@ -0,0 +1,42 @@ +package dev.lions.unionflow.server.api.dto.collectefonds; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Don transactionnel reçu pour une campagne de collecte") +public class ContributionCollecteDTO extends BaseDTO { + + private String campagneId; + + @Schema(description = "Id du membre (Null si le don est public/externe)") + private String membreDonateurId; + + @Schema(description = "Nom affiché si don public ou pour le mur de contributeurs") + private String aliasDonateur; + + private Boolean estAnonyme; + + private BigDecimal montantSoutien; + + private String messageSoutien; + + private LocalDateTime dateContribution; + + // Lien avec la passerelle de paiement + private String transactionPaiementId; + private StatutTransactionWave statutPaiement; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/common/PagedResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/common/PagedResponse.java new file mode 100644 index 0000000..c6f2b17 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/common/PagedResponse.java @@ -0,0 +1,125 @@ +package dev.lions.unionflow.server.api.dto.common; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * DTO générique pour les réponses paginées de l'API. + * Utilisé par tous les endpoints REST qui renvoient des données paginées. + * + * @param Type des éléments dans la liste + * @author UnionFlow Team + * @version 2.0 + */ +public class PagedResponse { + + @JsonProperty("data") + private List data; + + @JsonProperty("total") + private Long total; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("size") + private Integer size; + + @JsonProperty("totalPages") + private Integer totalPages; + + // Constructeurs + public PagedResponse() { + } + + public PagedResponse(List data, Long total, Integer page, Integer size) { + this.data = data; + this.total = total; + this.page = page; + this.size = size; + this.totalPages = calculateTotalPages(total, size); + } + + public PagedResponse(List data, Long total, Integer page, Integer size, Integer totalPages) { + this.data = data; + this.total = total; + this.page = page; + this.size = size; + this.totalPages = totalPages; + } + + // Méthode utilitaire pour calculer le nombre de pages + private Integer calculateTotalPages(Long total, Integer size) { + if (size == null || size == 0 || total == null) { + return 0; + } + return (int) Math.ceil((double) total / size); + } + + // Getters et setters + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + this.totalPages = calculateTotalPages(total, this.size); + } + + public Integer getPage() { + return page; + } + + public void setPage(Integer page) { + this.page = page; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + this.totalPages = calculateTotalPages(this.total, size); + } + + public Integer getTotalPages() { + return totalPages; + } + + public void setTotalPages(Integer totalPages) { + this.totalPages = totalPages; + } + + // Méthodes utilitaires + public boolean hasNext() { + return page != null && totalPages != null && page < totalPages - 1; + } + + public boolean hasPrevious() { + return page != null && page > 0; + } + + public boolean isEmpty() { + return data == null || data.isEmpty(); + } + + @Override + public String toString() { + return "PagedResponse{" + + "total=" + total + + ", page=" + page + + ", size=" + size + + ", totalPages=" + totalPages + + ", itemsCount=" + (data != null ? data.size() : 0) + + '}'; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateCompteComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateCompteComptableRequest.java new file mode 100644 index 0000000..7cb68b2 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateCompteComptableRequest.java @@ -0,0 +1,28 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import lombok.Builder; + +/** + * Requête de création d'un compte comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateCompteComptableRequest( + @NotBlank(message = "Le numéro de compte est obligatoire") String numeroCompte, + @NotBlank(message = "Le libellé est obligatoire") String libelle, + @NotNull(message = "Le type de compte est obligatoire") TypeCompteComptable typeCompte, + @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") Integer classeComptable, + BigDecimal soldeInitial, + BigDecimal soldeActuel, + Boolean compteCollectif, + Boolean compteAnalytique, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateEcritureComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateEcritureComptableRequest.java new file mode 100644 index 0000000..ddd15cc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateEcritureComptableRequest.java @@ -0,0 +1,41 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateEcritureComptableRequest( + @NotBlank(message = "Le numéro de pièce est obligatoire") String numeroPiece, + + @NotNull(message = "La date de l'écriture est obligatoire") LocalDate dateEcriture, + + @NotBlank(message = "Le libellé est obligatoire") String libelle, + + String reference, + String lettrage, + Boolean pointe, + + @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) BigDecimal montantDebit, + @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) BigDecimal montantCredit, + + String commentaire, + + @NotNull(message = "Le journal est obligatoire") UUID journalId, + UUID organisationId, + UUID paiementId, + + List lignes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateJournalComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateJournalComptableRequest.java new file mode 100644 index 0000000..5bef23d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateJournalComptableRequest.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +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.Builder; + +/** + * Requête de création d'un journal comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateJournalComptableRequest( + @NotBlank(message = "Le code du journal est obligatoire") String code, + @NotBlank(message = "Le libellé est obligatoire") String libelle, + @NotNull(message = "Le type de journal est obligatoire") TypeJournalComptable typeJournal, + LocalDate dateDebut, + LocalDate dateFin, + String statut, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateLigneEcritureRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateLigneEcritureRequest.java new file mode 100644 index 0000000..f097a19 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/CreateLigneEcritureRequest.java @@ -0,0 +1,30 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une ligne d'écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateLigneEcritureRequest( + @NotNull(message = "Le numéro de ligne est obligatoire") @Min(value = 1, message = "Le numéro de ligne doit être positif") Integer numeroLigne, + + @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul") @Digits(integer = 12, fraction = 2) BigDecimal montantDebit, + + @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul") @Digits(integer = 12, fraction = 2) BigDecimal montantCredit, + + String libelle, + String reference, + + @NotNull(message = "L'écriture est obligatoire") UUID ecritureId, + @NotNull(message = "Le compte comptable est obligatoire") UUID compteComptableId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateCompteComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateCompteComptableRequest.java new file mode 100644 index 0000000..0ecc684 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateCompteComptableRequest.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import java.math.BigDecimal; +import lombok.Builder; + +/** + * Requête de mise à jour d'un compte comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateCompteComptableRequest( + String numeroCompte, + String libelle, + TypeCompteComptable typeCompte, + @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") Integer classeComptable, + BigDecimal soldeInitial, + BigDecimal soldeActuel, + Boolean compteCollectif, + Boolean compteAnalytique, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateEcritureComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateEcritureComptableRequest.java new file mode 100644 index 0000000..322a388 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateEcritureComptableRequest.java @@ -0,0 +1,34 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de mise à jour d'une écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateEcritureComptableRequest( + String numeroPiece, + LocalDate dateEcriture, + String libelle, + String reference, + String lettrage, + Boolean pointe, + + @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) BigDecimal montantDebit, + @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) BigDecimal montantCredit, + + String commentaire, + UUID journalId, + UUID paiementId, + + List lignes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateJournalComptableRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateJournalComptableRequest.java new file mode 100644 index 0000000..ea96aaf --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateJournalComptableRequest.java @@ -0,0 +1,21 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable; +import java.time.LocalDate; +import lombok.Builder; + +/** + * Requête de mise à jour d'un journal comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateJournalComptableRequest( + String libelle, + TypeJournalComptable typeJournal, + LocalDate dateDebut, + LocalDate dateFin, + String statut, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateLigneEcritureRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateLigneEcritureRequest.java new file mode 100644 index 0000000..8d74a42 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/request/UpdateLigneEcritureRequest.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Min; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de mise à jour d'une ligne d'écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateLigneEcritureRequest( + @Min(value = 1, message = "Le numéro de ligne doit être positif") Integer numeroLigne, + @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul") @Digits(integer = 12, fraction = 2) BigDecimal montantDebit, + @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul") @Digits(integer = 12, fraction = 2) BigDecimal montantCredit, + String libelle, + String reference, + UUID compteComptableId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/CompteComptableResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/CompteComptableResponse.java new file mode 100644 index 0000000..a7aa558 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/CompteComptableResponse.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'un compte comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CompteComptableResponse extends BaseResponse { + + private String numeroCompte; + private String libelle; + private TypeCompteComptable typeCompte; + private Integer classeComptable; + private BigDecimal soldeInitial; + private BigDecimal soldeActuel; + private Boolean compteCollectif; + private Boolean compteAnalytique; + private String description; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/EcritureComptableResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/EcritureComptableResponse.java new file mode 100644 index 0000000..df50568 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/EcritureComptableResponse.java @@ -0,0 +1,41 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'une écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EcritureComptableResponse extends BaseResponse { + + private String numeroPiece; + private LocalDate dateEcriture; + private String libelle; + private String reference; + private String lettrage; + private Boolean pointe; + private BigDecimal montantDebit; + private BigDecimal montantCredit; + private String commentaire; + private UUID journalId; + private UUID organisationId; + private UUID paiementId; + private List lignes; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/JournalComptableResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/JournalComptableResponse.java new file mode 100644 index 0000000..c075576 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/JournalComptableResponse.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'un journal comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class JournalComptableResponse extends BaseResponse { + + private String code; + private String libelle; + private TypeJournalComptable typeJournal; + private LocalDate dateDebut; + private LocalDate dateFin; + private String statut; + private String description; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/LigneEcritureResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/LigneEcritureResponse.java new file mode 100644 index 0000000..e6bab58 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/comptabilite/response/LigneEcritureResponse.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.comptabilite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'une ligne d'écriture comptable. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LigneEcritureResponse extends BaseResponse { + + private Integer numeroLigne; + private BigDecimal montantDebit; + private BigDecimal montantCredit; + private String libelle; + private String reference; + private UUID ecritureId; + private UUID compteComptableId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/config/request/CreateConfigurationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/CreateConfigurationRequest.java new file mode 100644 index 0000000..cd71862 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/CreateConfigurationRequest.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.dto.config.request; + +import java.util.Map; +import lombok.Builder; + +/** + * Requête de création d'une configuration. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateConfigurationRequest( + String cle, + String valeur, + String type, + String categorie, + String description, + Boolean modifiable, + Boolean visible, + Map metadonnees) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/config/request/ParametresLcbFtRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/ParametresLcbFtRequest.java new file mode 100644 index 0000000..3df99f5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/ParametresLcbFtRequest.java @@ -0,0 +1,50 @@ +package dev.lions.unionflow.server.api.dto.config.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; + +/** + * Requête de création/mise à jour des paramètres LCB-FT (Lutte contre le Blanchiment et le Financement du Terrorisme). + * Définit les seuils au-dessus desquels les justifications d'origine des fonds sont obligatoires. + * + * @author lions dev Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Paramètres LCB-FT (seuils de vigilance)") +public class ParametresLcbFtRequest { + + @Schema(description = "ID de l'organisation (null pour paramètres plateforme)") + private String organisationId; + + @NotNull(message = "Le montant seuil de justification est obligatoire") + @DecimalMin(value = "0", message = "Le montant doit être positif ou nul") + @Schema(description = "Montant au-dessus duquel l'origine des fonds est obligatoire (ex. 500000 XOF)", example = "500000") + private BigDecimal montantSeuilJustification; + + @NotNull(message = "Le montant seuil de validation manuelle est obligatoire") + @DecimalMin(value = "0", message = "Le montant doit être positif ou nul") + @Schema(description = "Montant au-dessus duquel une validation manuelle est requise (ex. 1000000 XOF)", example = "1000000") + private BigDecimal montantSeuilValidationManuelle; + + @NotBlank(message = "Le code devise est obligatoire") + @Size(max = 3, message = "Le code devise doit faire 3 caractères (ISO 4217)") + @Schema(description = "Code devise ISO 4217 (ex. XOF, EUR, USD)", example = "XOF") + private String codeDevise; + + @Schema(description = "Notes ou commentaires sur la configuration") + private String notes; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/config/request/UpdateConfigurationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/UpdateConfigurationRequest.java new file mode 100644 index 0000000..5c89627 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/config/request/UpdateConfigurationRequest.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.dto.config.request; + +import java.util.Map; +import lombok.Builder; + +/** + * Requête de mise à jour d'une configuration. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateConfigurationRequest( + String cle, + String valeur, + String type, + String categorie, + String description, + Boolean modifiable, + Boolean visible, + Map metadonnees) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ConfigurationResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ConfigurationResponse.java new file mode 100644 index 0000000..70805c1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ConfigurationResponse.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.config.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'une configuration. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ConfigurationResponse extends BaseResponse { + + private String cle; + private String valeur; + private String type; + private String categorie; + private String description; + private Boolean modifiable; + private Boolean visible; + private Map metadonnees; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ParametresLcbFtResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ParametresLcbFtResponse.java new file mode 100644 index 0000000..96adda7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/config/response/ParametresLcbFtResponse.java @@ -0,0 +1,49 @@ +package dev.lions.unionflow.server.api.dto.config.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; + +/** + * Réponse contenant les paramètres LCB-FT (Lutte contre le Blanchiment et le Financement du Terrorisme). + * Retourne les seuils configurés pour une organisation ou la plateforme. + * + * @author lions dev Team + * @version 1.0 + * @since 2026-03-13 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Paramètres LCB-FT avec seuils de vigilance") +public class ParametresLcbFtResponse extends BaseResponse { + + @Schema(description = "ID de l'organisation (null si paramètres plateforme)") + private String organisationId; + + @Schema(description = "Nom de l'organisation (null si paramètres plateforme)") + private String organisationNom; + + @Schema(description = "Montant au-dessus duquel l'origine des fonds est obligatoire", example = "500000") + private BigDecimal montantSeuilJustification; + + @Schema(description = "Montant au-dessus duquel une validation manuelle est requise", example = "1000000") + private BigDecimal montantSeuilValidationManuelle; + + @Schema(description = "Code devise ISO 4217", example = "XOF") + private String codeDevise; + + @Schema(description = "Notes ou commentaires sur la configuration") + private String notes; + + @Schema(description = "Indique si ces paramètres s'appliquent à toute la plateforme") + private Boolean estParametrePlateforme; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/CreateCotisationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/CreateCotisationRequest.java new file mode 100644 index 0000000..9a161d1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/CreateCotisationRequest.java @@ -0,0 +1,62 @@ +package dev.lions.unionflow.server.api.dto.cotisation.request; + +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.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; + +import lombok.Builder; + +/** + * Requête de création d'une nouvelle cotisation. + * + * @param membreId Identifiant du membre concerné. + * @param typeCotisation Type de cotisation (MENSUELLE, etc.). + * @param libelle Libellé de la cotisation. + * @param description Description détaillée. + * @param montantDu Montant total à payer. + * @param codeDevise Code ISO de la devise (par défaut XOF). + * @param dateEcheance Date limite de paiement. + * @param periode Période concernée (ex: Janvier 2025). + * @param annee Année de référence. + * @param mois Mois de référence (1-12, optionnel). + * @param recurrente Indique si la cotisation est récurrente. + * @param observations Commentaires libres. + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-22 + */ +@Builder +public record CreateCotisationRequest( + @NotNull(message = "L'identifiant du membre est obligatoire") UUID membreId, + + @NotNull(message = "L'identifiant de l'organisation est obligatoire") UUID organisationId, + + @NotBlank(message = "Le type de cotisation est obligatoire") String typeCotisation, + + @NotBlank(message = "Le libellé est obligatoire") @Size(max = 100) String libelle, + + @Size(max = 500) String description, + + @NotNull(message = "Le montant dû est obligatoire") @DecimalMin(value = "0.0", inclusive = false) @Digits(integer = 10, fraction = 2) BigDecimal montantDu, + + @Size(min = 3, max = 3) String codeDevise, + + @NotNull(message = "La date d'échéance est obligatoire") LocalDate dateEcheance, + + @Size(max = 50) String periode, + + @Min(2020) @Max(2100) Integer annee, + + @Min(1) @Max(12) Integer mois, + + Boolean recurrente, + + @Size(max = 1000) String observations) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/UpdateCotisationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/UpdateCotisationRequest.java new file mode 100644 index 0000000..a3cfbd1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/request/UpdateCotisationRequest.java @@ -0,0 +1,48 @@ +package dev.lions.unionflow.server.api.dto.cotisation.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; + +import lombok.Builder; + +/** + * Requête de mise à jour d'une cotisation existante. + * + * @param libelle Nouveau libellé. + * @param description Nouvelle description. + * @param montantDu Nouveau montant dû (si non payé). + * @param dateEcheance Nouvelle date d'échéance. + * @param observations Nouvelles observations. + * @param statut Nouveau statut (validation métier requise). + * @param annee Année de référence. + * @param mois Mois de référence. + * @param recurrente État de récurrence. + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-22 + */ +@Builder +public record UpdateCotisationRequest( + @Size(max = 100) String libelle, + + @Size(max = 500) String description, + + @DecimalMin(value = "0.0", inclusive = false) @Digits(integer = 10, fraction = 2) BigDecimal montantDu, + + LocalDate dateEcheance, + + @Size(max = 1000) String observations, + + String statut, + + @Min(2020) @Max(2100) Integer annee, + + @Min(1) @Max(12) Integer mois, + + Boolean recurrente) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponse.java new file mode 100644 index 0000000..4c45b0b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponse.java @@ -0,0 +1,103 @@ +package dev.lions.unionflow.server.api.dto.cotisation.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour une cotisation. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-22 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CotisationResponse extends BaseResponse { + + private String numeroReference; + private UUID membreId; + private String nomMembre; + /** Nom complet (prénom + nom) pour affichage */ + private String nomCompletMembre; + private String numeroMembre; + /** Initiales du membre (ex: JD pour Jean Dupont) */ + private String initialesMembre; + /** Type / statut du membre (ex: Actif, En attente) */ + private String typeMembre; + private UUID organisationId; + private String nomOrganisation; + /** Région de l'organisation (affichage liste) */ + private String regionOrganisation; + /** Classe CSS icône PrimeFaces pour l'organisation (ex: pi-building) */ + private String iconeOrganisation; + private String typeCotisation; + /** Alias pour tri/filtre (type de cotisation) */ + private String type; + private String typeCotisationLibelle; + /** Libellé du type pour affichage (alias typeCotisationLibelle) */ + private String typeLibelle; + /** Sévérité PrimeFaces pour le tag type (info, success, warn, error, secondary) */ + private String typeSeverity; + /** Classe icône PrimeFaces pour le type (ex: pi-calendar) */ + private String typeIcon; + private String libelle; + private String description; + private BigDecimal montantDu; + /** Alias pour tri/filtre (montant du) */ + private BigDecimal montant; + /** Montant formaté pour affichage (ex: "5 000") */ + private String montantFormatte; + private BigDecimal montantPaye; + private BigDecimal montantRestant; + private String codeDevise; + private String statut; + private String statutLibelle; + /** Sévérité PrimeFaces pour le tag statut */ + private String statutSeverity; + /** Classe icône PrimeFaces pour le statut (ex: pi-check) */ + private String statutIcon; + private LocalDate dateEcheance; + /** Date d'échéance formatée pour affichage */ + private String dateEcheanceFormattee; + /** Classe CSS couleur pour le retard (ex: text-red-500) */ + private String retardCouleur; + /** Texte affiché pour le retard (ex: "X jours de retard") */ + private String retardTexte; + /** Date de paiement formatée pour affichage */ + private String datePaiementFormattee; + /** Icône PrimeFaces pour le mode de paiement */ + private String modePaiementIcon; + /** Libellé du mode de paiement */ + private String modePaiementLibelle; + private LocalDateTime datePaiement; + private String periode; + private Integer annee; + private Integer mois; + private String observations; + private Boolean recurrente; + private Integer nombreRappels; + private LocalDateTime dateDernierRappel; + private UUID valideParId; + private String nomValidateur; + private LocalDateTime dateValidation; + private Integer pourcentagePaiement; + private Long joursRetard; + private Boolean enRetard; + + // Informations de paiement + private String methodePaiement; // WAVE_MONEY, VIREMENT, ESPECES, CARTE, MOBILE_MONEY + private String referencePaiement; // Référence externe du paiement + private String waveSessionId; // ID de session Wave Money pour prélèvements +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationSummaryResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationSummaryResponse.java new file mode 100644 index 0000000..36d2db9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationSummaryResponse.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.server.api.dto.cotisation.response; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; + +/** + * Réponse simplifiée pour les listes de cotisations. + * + * @param id Identifiant unique. + * @param numeroReference Référence unique. + * @param nomMembre Nom du membre. + * @param montantDu Montant total dû. + * @param montantPaye Montant déjà réglé. + * @param statut Statut actuel (code). + * @param statutLibelle Libellé du statut pour l'UI. + * @param dateEcheance Date limite. + * @param annee Année concernée. + * @param actif Indique si l'entité est active. + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-22 + */ +public record CotisationSummaryResponse( + UUID id, + String numeroReference, + String nomMembre, + BigDecimal montantDu, + BigDecimal montantPaye, + String statut, + String statutLibelle, + LocalDate dateEcheance, + Integer annee, + Boolean actif) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/culte/DonReligieuxDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/culte/DonReligieuxDTO.java new file mode 100644 index 0000000..897482a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/culte/DonReligieuxDTO.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.culte; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class DonReligieuxDTO extends BaseDTO { + + private String institutionId; // Mosquée, Église, Paroisse... + + // Si relié spécifiquement à un fidèle enregistré + private String fideleId; + + private TypeDonReligieux typeDon; + + private BigDecimal montant; + private LocalDateTime dateEncaissement; + + // Utile pour la zakat (Nissab de l'année concernée) ou la dîme périodique + private String periodeOuNatureAssociee; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponse.java new file mode 100644 index 0000000..427b8e9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponse.java @@ -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 DashboardDataResponse { + + @JsonProperty("stats") + private DashboardStatsResponse stats; + + @JsonProperty("recentActivities") + private List recentActivities; + + @JsonProperty("upcomingEvents") + private List upcomingEvents; + + @JsonProperty("userPreferences") + private Map 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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponse.java new file mode 100644 index 0000000..3bfbe3d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponse.java @@ -0,0 +1,119 @@ +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.util.List; +import java.util.Map; + +/** + * DTO pour les statistiques du dashboard + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DashboardStatsResponse { + + @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; + + /** + * Nombre total d'organisations dans le système (SuperAdmin uniquement) + */ + @JsonProperty("totalOrganizations") + private Integer totalOrganizations; + + /** + * Répartition des organisations par type + * Exemple: {"Mutuelle": 15, "Coopérative": 8, "Tontine": 5, "Autre": 3} + */ + @JsonProperty("organizationTypeDistribution") + private Map organizationTypeDistribution; + + /** + * Données historiques mensuelles pour les graphiques (12 derniers mois) + */ + @JsonProperty("monthlyHistoricalData") + private List monthlyHistoricalData; + + // 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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponse.java new file mode 100644 index 0000000..bbf364a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponse.java @@ -0,0 +1,48 @@ +package dev.lions.unionflow.server.api.dto.dashboard; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * DTO de synthèse dashboard membre (cotisations, épargne, événements, aides). + * + * @author UnionFlow Team + * @version 1.0 + */ +public record MembreDashboardSyntheseResponse( + String prenom, + String nom, + LocalDate dateInscription, + + // Cotisations + BigDecimal mesCotisationsPaiement, + /** Total des cotisations payées sur l'année en cours (pour affichage dashboard). */ + BigDecimal totalCotisationsPayeesAnnee, + /** Total des cotisations payées tout temps (pour la carte « Contribution Totale »). */ + BigDecimal totalCotisationsPayeesToutTemps, + /** Nombre de cotisations PAYÉES (pour la carte « Cotisations »). */ + Integer nombreCotisationsPayees, + String statutCotisations, + /** Taux de cotisation en % (0-100). Calculé sur l'année courante ou toutes années si pas de cotisation 2026. */ + Integer tauxCotisationsPerso, + /** Nombre TOTAL de cotisations (toutes années, tous statuts) — pour calcul du taux d'engagement. */ + Integer nombreCotisationsTotal, + + // Epargne + BigDecimal monSoldeEpargne, + BigDecimal evolutionEpargneNombre, + String evolutionEpargne, + Integer objectifEpargne, + + // Evenements + Integer mesEvenementsInscrits, + Integer evenementsAVenir, + Integer tauxParticipationPerso, + + // Aides + Integer mesDemandesAide, + Integer aidesEnCours, + Integer tauxAidesApprouvees) implements Serializable { +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MonthlyStatDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MonthlyStatDTO.java new file mode 100644 index 0000000..5681248 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/MonthlyStatDTO.java @@ -0,0 +1,70 @@ +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; + +/** + * DTO pour les statistiques mensuelles historiques + * Utilisé pour générer des graphiques de croissance sur 12 mois + * + * @author UnionFlow Team + * @version 2.0 + * @since 2026-03-07 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MonthlyStatDTO { + + /** + * Mois au format "2026-01", "2026-02", etc. + */ + @JsonProperty("month") + private String month; + + /** + * Nombre de membres ce mois-là + */ + @JsonProperty("totalMembers") + private Integer totalMembers; + + /** + * Nombre de membres actifs ce mois-là + */ + @JsonProperty("activeMembers") + private Integer activeMembers; + + /** + * Montant total des contributions ce mois-là + */ + @JsonProperty("contributionAmount") + private Double contributionAmount; + + /** + * Nombre d'événements organisés ce mois-là + */ + @JsonProperty("eventsCount") + private Integer eventsCount; + + /** + * Taux d'engagement ce mois-là + */ + @JsonProperty("engagementRate") + private Double engagementRate; + + /** + * Nombre de nouveaux membres ce mois-là + */ + @JsonProperty("newMembers") + private Integer newMembers; + + /** + * Nombre de cotisations payées ce mois-là + */ + @JsonProperty("contributionsCount") + private Integer contributionsCount; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponse.java new file mode 100644 index 0000000..ddf2296 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponse.java @@ -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 RecentActivityResponse { + + @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()); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponse.java new file mode 100644 index 0000000..e446e97 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponse.java @@ -0,0 +1,183 @@ +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 UpcomingEventResponse { + + @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 tags; + + // Méthodes utilitaires + public String getDaysUntilEvent() { + return getDaysUntilEvent(LocalDateTime.now()); + } + + /** + * Version testable avec une date de référence fixe (même package). + */ + String getDaysUntilEvent(LocalDateTime now) { + if (startDate == null) { + return ""; + } + long days = ChronoUnit.DAYS.between(now.toLocalDate(), startDate.toLocalDate()); + long hours = ChronoUnit.HOURS.between(now, startDate); + + if (days < 0) { + return "En cours"; + } + if (days == 0) { + // Vérifier si l'événement est déjà passé (même si moins d'1h) + if (startDate.isBefore(now)) { + 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"; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreateDocumentRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreateDocumentRequest.java new file mode 100644 index 0000000..07667d6 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreateDocumentRequest.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.dto.document.request; + +import dev.lions.unionflow.server.api.enums.document.TypeDocument; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +/** + * Requête de création d'un document. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateDocumentRequest( + @NotBlank(message = "Le nom du fichier est obligatoire") String nomFichier, + String nomOriginal, + @NotBlank(message = "Le chemin de stockage est obligatoire") String cheminStockage, + String typeMime, + @NotNull(message = "La taille est obligatoire") @Min(value = 0, message = "La taille doit être positive") Long tailleOctets, + TypeDocument typeDocument, + String hashMd5, + String hashSha256, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreatePieceJointeRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreatePieceJointeRequest.java new file mode 100644 index 0000000..30f5770 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/CreatePieceJointeRequest.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.dto.document.request; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une pièce jointe. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreatePieceJointeRequest( + @NotNull(message = "L'ordre est obligatoire") @Min(value = 1, message = "L'ordre doit être positif") Integer ordre, + String libelle, + String commentaire, + @NotNull(message = "Le document est obligatoire") UUID documentId, + @NotNull(message = "Le type entité est obligatoire") String typeEntiteRattachee, + @NotNull(message = "L'ID entité est obligatoire") UUID entiteRattacheeId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdateDocumentRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdateDocumentRequest.java new file mode 100644 index 0000000..e449926 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdateDocumentRequest.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.dto.document.request; + +import dev.lions.unionflow.server.api.enums.document.TypeDocument; +import lombok.Builder; + +/** + * Requête de mise à jour d'un document. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateDocumentRequest( + String nomFichier, + String nomOriginal, + TypeDocument typeDocument, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdatePieceJointeRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdatePieceJointeRequest.java new file mode 100644 index 0000000..294a3de --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/request/UpdatePieceJointeRequest.java @@ -0,0 +1,17 @@ +package dev.lions.unionflow.server.api.dto.document.request; + +import jakarta.validation.constraints.Min; +import lombok.Builder; + +/** + * Requête de mise à jour d'une pièce jointe. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdatePieceJointeRequest( + @Min(value = 1, message = "L'ordre doit être positif") Integer ordre, + String libelle, + String commentaire) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/response/DocumentResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/response/DocumentResponse.java new file mode 100644 index 0000000..014081b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/response/DocumentResponse.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.dto.document.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.enums.document.TypeDocument; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'un document. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DocumentResponse extends BaseResponse { + + private String nomFichier; + private String nomOriginal; + private String cheminStockage; + private String typeMime; + private Long tailleOctets; + private TypeDocument typeDocument; + private String hashMd5; + private String hashSha256; + private String description; + private Integer nombreTelechargements; + private String tailleFormatee; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/document/response/PieceJointeResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/document/response/PieceJointeResponse.java new file mode 100644 index 0000000..7c48af2 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/document/response/PieceJointeResponse.java @@ -0,0 +1,31 @@ +package dev.lions.unionflow.server.api.dto.document.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'une pièce jointe. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PieceJointeResponse extends BaseResponse { + + private Integer ordre; + private String libelle; + private String commentaire; + private UUID documentId; + private String typeEntiteRattachee; + private UUID entiteRattacheeId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/CreateEvenementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/CreateEvenementRequest.java new file mode 100644 index 0000000..0855135 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/CreateEvenementRequest.java @@ -0,0 +1,70 @@ +package dev.lions.unionflow.server.api.dto.evenement.request; + +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.FutureOrPresent; +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.LocalTime; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un événement. + */ +@Builder +public record CreateEvenementRequest( + @NotBlank(message = "Le titre" + + ValidationConstants.OBLIGATOIRE_MESSAGE) @Size(min = ValidationConstants.TITRE_MIN_LENGTH, max = ValidationConstants.TITRE_MAX_LENGTH, message = ValidationConstants.TITRE_SIZE_MESSAGE) String titre, + + @Size(max = ValidationConstants.DESCRIPTION_COURTE_MAX_LENGTH, message = ValidationConstants.DESCRIPTION_COURTE_SIZE_MESSAGE) String description, + + @NotNull(message = "Le type d'événement est obligatoire") TypeEvenementMetier typeEvenement, + + @NotNull(message = "Le statut est obligatoire") StatutEvenement statut, + + PrioriteEvenement priorite, + @NotNull(message = "La date de début est obligatoire") @FutureOrPresent(message = "La date de début ne peut pas être dans le passé") LocalDate dateDebut, + LocalDate dateFin, + LocalTime heureDebut, + LocalTime heureFin, + @NotBlank(message = "Le lieu est obligatoire") @Size(max = 100) String lieu, + @Size(max = 200) String adresse, + @Size(max = 50) String ville, + @Size(max = 50) String region, + @DecimalMin(value = "-90.0") @DecimalMax(value = "90.0") BigDecimal latitude, + @DecimalMin(value = "-180.0") @DecimalMax(value = "180.0") BigDecimal longitude, + @NotNull(message = "L'association organisatrice est obligatoire") UUID associationId, + @Size(max = 100) String organisateur, + @Email(message = "Format d'email invalide") @Size(max = 100) String emailOrganisateur, + @Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$") String telephoneOrganisateur, + @Min(value = 1) @Max(value = 10000) Integer capaciteMax, + @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) BigDecimal budget, + + @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) BigDecimal coutReel, + + @Pattern(regexp = ValidationConstants.DEVISE_PATTERN, message = ValidationConstants.DEVISE_MESSAGE) String codeDevise, + Boolean inscriptionObligatoire, + LocalDate dateLimiteInscription, + Boolean evenementPublic, + Boolean recurrent, + String frequenceRecurrence, + @Size(max = 500) String instructions, + @Size(max = 500) String materielNecessaire, + @Size(max = 100) String conditionsMeteo, + @Size(max = 255) String imageUrl, + @Pattern(regexp = "^#[0-9A-Fa-f]{6}$") String couleurTheme) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/UpdateEvenementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/UpdateEvenementRequest.java new file mode 100644 index 0000000..58a6c21 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/request/UpdateEvenementRequest.java @@ -0,0 +1,65 @@ +package dev.lions.unionflow.server.api.dto.evenement.request; + +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.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import lombok.Builder; + +/** + * Requête de mise à jour d'un événement. + */ +@Builder +public record UpdateEvenementRequest( + @Size(min = ValidationConstants.TITRE_MIN_LENGTH, max = ValidationConstants.TITRE_MAX_LENGTH, message = ValidationConstants.TITRE_SIZE_MESSAGE) String titre, + + @Size(max = ValidationConstants.DESCRIPTION_COURTE_MAX_LENGTH, message = ValidationConstants.DESCRIPTION_COURTE_SIZE_MESSAGE) String description, + + TypeEvenementMetier typeEvenement, + StatutEvenement statut, + PrioriteEvenement priorite, + + LocalDate dateDebut, + LocalDate dateFin, + LocalTime heureDebut, + LocalTime heureFin, + @NotBlank(message = "Le lieu est obligatoire") @Size(max = 100) String lieu, + @Size(max = 200) String adresse, + @Size(max = 50) String ville, + @Size(max = 50) String region, + @DecimalMin(value = "-90.0") @DecimalMax(value = "90.0") BigDecimal latitude, + @DecimalMin(value = "-180.0") @DecimalMax(value = "180.0") BigDecimal longitude, + @Size(max = 100) String organisateur, + @Email(message = "Format d'email invalide") @Size(max = 100) String emailOrganisateur, + @Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$") String telephoneOrganisateur, + @Min(value = 1) @Max(value = 10000) Integer capaciteMax, + @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) BigDecimal budget, + + @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) BigDecimal coutReel, + + @Pattern(regexp = ValidationConstants.DEVISE_PATTERN, message = ValidationConstants.DEVISE_MESSAGE) String codeDevise, + Boolean inscriptionObligatoire, + LocalDate dateLimiteInscription, + Boolean evenementPublic, + Boolean recurrent, + String frequenceRecurrence, + @Size(max = 500) String instructions, + @Size(max = 500) String materielNecessaire, + @Size(max = 100) String conditionsMeteo, + @Size(max = 255) String imageUrl, + @Pattern(regexp = "^#[0-9A-Fa-f]{6}$") String couleurTheme) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponse.java new file mode 100644 index 0000000..14ef9c9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponse.java @@ -0,0 +1,204 @@ +package dev.lions.unionflow.server.api.dto.evenement.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +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 java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour un événement. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EvenementResponse extends BaseResponse { + + private String titre; + private String description; + private TypeEvenementMetier typeEvenement; + private StatutEvenement statut; + private PrioriteEvenement priorite; + + // Décommenter si l'on a besoin du @JsonFormat, sinon on garde le standard + // @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate dateDebut; + + // @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate dateFin; + + // @JsonFormat(pattern = "HH:mm") + private LocalTime heureDebut; + + // @JsonFormat(pattern = "HH:mm") + private LocalTime heureFin; + private String lieu; + private String adresse; + private String ville; + private String region; + private BigDecimal latitude; + private BigDecimal longitude; + private UUID associationId; + private String nomAssociation; + private String organisateur; + private String emailOrganisateur; + private String telephoneOrganisateur; + private Integer capaciteMax; + private Integer participantsInscrits; + private Integer participantsPresents; + private BigDecimal budget; + private BigDecimal coutReel; + private String codeDevise; + private Boolean inscriptionObligatoire; + + // @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate dateLimiteInscription; + + private Boolean evenementPublic; + private Boolean recurrent; + private String frequenceRecurrence; + private String instructions; + private String materielNecessaire; + private String conditionsMeteo; + private String imageUrl; + private String couleurTheme; + + // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime dateAnnulation; + private String raisonAnnulation; + private String nomAnnulateur; + private Long annulePar; + + // === METHODES UTILITAIRES === + + public boolean estEnCours() { + return StatutEvenement.EN_COURS.equals(statut); + } + + public boolean estTermine() { + return StatutEvenement.TERMINE.equals(statut); + } + + public boolean estAnnule() { + return StatutEvenement.ANNULE.equals(statut); + } + + public boolean estComplet() { + return capaciteMax != null + && participantsInscrits != null + && participantsInscrits >= capaciteMax; + } + + public int getPlacesDisponibles() { + if (capaciteMax == null || participantsInscrits == null) { + return 0; + } + return Math.max(0, capaciteMax - participantsInscrits); + } + + public int getTauxRemplissage() { + if (capaciteMax == null || capaciteMax == 0 || participantsInscrits == null) { + return 0; + } + return (participantsInscrits * 100) / capaciteMax; + } + + public int getTauxPresence() { + if (participantsInscrits == null || participantsInscrits == 0 || participantsPresents == null) { + return 0; + } + return (participantsPresents * 100) / participantsInscrits; + } + + public boolean sontInscriptionsOuvertes() { + if (estAnnule() || estTermine()) { + return false; + } + + if (dateLimiteInscription != null && LocalDate.now().isAfter(dateLimiteInscription)) { + return false; + } + + return !estComplet(); + } + + public long getDureeEnHeures() { + if (heureDebut == null || heureFin == null) { + return 0; + } + + return heureDebut.until(heureFin, java.time.temporal.ChronoUnit.HOURS); + } + + public boolean estEvenementMultiJours() { + return dateFin != null && !dateDebut.equals(dateFin); + } + + public String getTypeEvenementLibelle() { + return typeEvenement != null ? typeEvenement.getLibelle() : "Non défini"; + } + + public String getStatutLibelle() { + return statut != null ? statut.getLibelle() : "Non défini"; + } + + public String getPrioriteLibelle() { + return priorite != null ? priorite.getLibelle() : "Normale"; + } + + 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(); + } + + public boolean hasCoordonnees() { + return latitude != null && longitude != null; + } + + public BigDecimal getEcartBudgetaire() { + if (budget == null || coutReel == null) { + return BigDecimal.ZERO; + } + return budget.subtract(coutReel); + } + + public boolean estBudgetDepasse() { + return getEcartBudgetaire().compareTo(BigDecimal.ZERO) < 0; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/favoris/request/CreateFavoriRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/favoris/request/CreateFavoriRequest.java new file mode 100644 index 0000000..450df8c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/favoris/request/CreateFavoriRequest.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.dto.favoris.request; + +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un favori. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateFavoriRequest( + UUID utilisateurId, + String typeFavori, + String titre, + String description, + String url, + String icon, + String couleur, + String categorie, + Integer ordre, + Integer nbVisites, + String derniereVisite, + Boolean estPlusUtilise) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/favoris/response/FavoriResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/favoris/response/FavoriResponse.java new file mode 100644 index 0000000..c409747 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/favoris/response/FavoriResponse.java @@ -0,0 +1,37 @@ +package dev.lions.unionflow.server.api.dto.favoris.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; + +/** + * Réponse pour un favori utilisateur. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FavoriResponse extends BaseResponse { + + private UUID utilisateurId; + private String typeFavori; + private String titre; + private String description; + private String url; + private String icon; + private String couleur; + private String categorie; + private Integer ordre; + private Integer nbVisites; + private String derniereVisite; + private Boolean estPlusUtilise; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/CreateAdhesionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/CreateAdhesionRequest.java new file mode 100644 index 0000000..6bbcd72 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/CreateAdhesionRequest.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.finance.request; + +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.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une adhésion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateAdhesionRequest( + @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") String numeroReference, + + @NotNull(message = "L'identifiant du membre est obligatoire") UUID membreId, + @NotNull(message = "L'identifiant de l'organisation est obligatoire") UUID organisationId, + @NotNull(message = "La date de demande est obligatoire") LocalDate dateDemande, + + @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") BigDecimal fraisAdhesion, + + @NotBlank(message = "Le code devise est obligatoire") @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") String codeDevise, + + @Size(max = 1000, message = "Les observations ne peuvent pas dépasser 1000 caractères") String observations) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/UpdateAdhesionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/UpdateAdhesionRequest.java new file mode 100644 index 0000000..a277eb0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance/request/UpdateAdhesionRequest.java @@ -0,0 +1,38 @@ +package dev.lions.unionflow.server.api.dto.finance.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Builder; + +/** + * Requête de mise à jour d'une adhésion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateAdhesionRequest( + @DecimalMin(value = "0.0", message = "Le montant payé ne peut pas être négatif") @Digits(integer = 10, fraction = 2, message = "Format de montant invalide") BigDecimal montantPaye, + + @Pattern(regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$", message = "Statut invalide") String statut, + + LocalDate dateApprobation, + LocalDateTime datePaiement, + + @Pattern(regexp = "^(ESPECES|VIREMENT|CHEQUE|WAVE_MONEY|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$", message = "Méthode de paiement invalide") String methodePaiement, + + @Size(max = 100, message = "La référence de paiement ne peut pas dépasser 100 caractères") String referencePaiement, + + @Size(max = 1000, message = "Le motif de rejet ne peut pas dépasser 1000 caractères") String motifRejet, + + @Size(max = 1000, message = "Les observations ne peuvent pas dépasser 1000 caractères") String observations, + + @Size(max = 255, message = "Le nom de l'approbateur ne peut pas dépasser 255 caractères") String approuvePar, + + LocalDate dateValidation) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponse.java new file mode 100644 index 0000000..bfe3d04 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponse.java @@ -0,0 +1,171 @@ +package dev.lions.unionflow.server.api.dto.finance.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +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.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'une adhésion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AdhesionResponse extends BaseResponse { + + private String numeroReference; + private UUID membreId; + private String numeroMembre; + private String nomMembre; + private String emailMembre; + private UUID organisationId; + private String nomOrganisation; + private LocalDate dateDemande; + private BigDecimal fraisAdhesion; + private BigDecimal montantPaye; + private String codeDevise; + private String statut; + private LocalDate dateApprobation; + private LocalDateTime datePaiement; + private String methodePaiement; + private String referencePaiement; + private String motifRejet; + private String observations; + private String approuvePar; + private LocalDate dateValidation; + + // Méthodes utilitaires héritées de l'ancien DTO + public boolean isPayeeIntegralement() { + return montantPaye != null && fraisAdhesion != null && montantPaye.compareTo(fraisAdhesion) >= 0; + } + + public boolean isEnAttentePaiement() { + return "APPROUVEE".equals(statut) && !isPayeeIntegralement(); + } + + 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; + } + + 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(); + } + + public long getJoursDepuisDemande() { + if (dateDemande == null) + return 0; + return ChronoUnit.DAYS.between(dateDemande, LocalDate.now()); + } + + 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; + }; + } + + 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"; + }; + } + + 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"; + }; + } + + 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; + }; + } + + public String getDateDemandeFormatee() { + if (dateDemande == null) + return ""; + return dateDemande.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + + public String getDateApprobationFormatee() { + if (dateApprobation == null) + return ""; + return dateApprobation.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + + public String getDatePaiementFormatee() { + if (datePaiement == null) + return ""; + return datePaiement.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + } + + public String getFraisAdhesionFormatte() { + if (fraisAdhesion == null) + return "0 FCFA"; + return String.format("%,.0f FCFA", fraisAdhesion.doubleValue()); + } + + public String getMontantPayeFormatte() { + if (montantPaye == null) + return "0 FCFA"; + return String.format("%,.0f FCFA", montantPaye.doubleValue()); + } + + public String getMontantRestantFormatte() { + return String.format("%,.0f FCFA", getMontantRestant().doubleValue()); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/ApproveTransactionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/ApproveTransactionRequest.java new file mode 100644 index 0000000..0d53ad8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/ApproveTransactionRequest.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.request; + +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de requête pour approuver une transaction + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ApproveTransactionRequest { + + @Size(max = 1000, message = "Le commentaire ne peut pas dépasser 1000 caractères") + private String comment; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetLineRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetLineRequest.java new file mode 100644 index 0000000..a8c310b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetLineRequest.java @@ -0,0 +1,42 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.request; + +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de requête pour créer une ligne budgétaire + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CreateBudgetLineRequest { + + @NotBlank(message = "La catégorie est requise") + @Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$", + message = "Catégorie invalide") + private String category; + + @NotBlank(message = "Le nom est requis") + @Size(max = 200, message = "Le nom ne peut pas dépasser 200 caractères") + private String name; + + @Size(max = 500, message = "La description ne peut pas dépasser 500 caractères") + private String description; + + @NotNull(message = "Le montant prévu est requis") + @DecimalMin(value = "0.0", message = "Le montant prévu doit être positif") + @Digits(integer = 14, fraction = 2, message = "Format du montant invalide") + private BigDecimal amountPlanned; + + @Size(max = 1000, message = "Les notes ne peuvent pas dépasser 1000 caractères") + private String notes; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetRequest.java new file mode 100644 index 0000000..a634781 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/CreateBudgetRequest.java @@ -0,0 +1,58 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de requête pour créer un budget + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CreateBudgetRequest { + + @NotBlank(message = "Le nom est requis") + @Size(max = 200, message = "Le nom ne peut pas dépasser 200 caractères") + private String name; + + @Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères") + private String description; + + @NotNull(message = "L'ID de l'organisation est requis") + private UUID organizationId; + + @NotBlank(message = "La période est requise") + @Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$", + message = "Période invalide") + private String period; + + @NotNull(message = "L'année est requise") + @Min(value = 2020, message = "L'année doit être >= 2020") + @Max(value = 2100, message = "L'année doit être <= 2100") + private Integer year; + + @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 month; + + @NotNull(message = "Au moins une ligne budgétaire est requise") + @Size(min = 1, message = "Au moins une ligne budgétaire est requise") + @Valid + @Builder.Default + private List lines = new ArrayList<>(); + + @Pattern(regexp = "^[A-Z]{3}$", message = "Code devise invalide (doit être ISO 3 lettres)") + private String currency; // Optionnel, défaut XOF +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/RejectTransactionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/RejectTransactionRequest.java new file mode 100644 index 0000000..3d52701 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/request/RejectTransactionRequest.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de requête pour rejeter une transaction + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RejectTransactionRequest { + + @NotBlank(message = "La raison du rejet est requise") + @Size(min = 10, max = 1000, message = "La raison doit contenir entre 10 et 1000 caractères") + private String reason; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/ApproverActionResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/ApproverActionResponse.java new file mode 100644 index 0000000..d75f69e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/ApproverActionResponse.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de réponse pour une action d'approbateur + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ApproverActionResponse { + + private UUID id; + + @NotNull + private UUID approverId; + + @NotBlank + private String approverName; + + @NotBlank + private String approverRole; + + @NotBlank + private String decision; // PENDING, APPROVED, REJECTED + + private String comment; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime decidedAt; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetLineResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetLineResponse.java new file mode 100644 index 0000000..bddbbee --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetLineResponse.java @@ -0,0 +1,47 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.response; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de réponse pour une ligne budgétaire + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BudgetLineResponse { + + private UUID id; + + @NotBlank + private String category; // CONTRIBUTIONS, SAVINGS, SOLIDARITY, etc. + + @NotBlank + private String name; + + private String description; + + @NotNull + private BigDecimal amountPlanned; + + @NotNull + private BigDecimal amountRealized; + + private String notes; + + // Champs calculés + private Double realizationRate; + private BigDecimal variance; + private Boolean isOverBudget; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetResponse.java new file mode 100644 index 0000000..31d8f80 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/BudgetResponse.java @@ -0,0 +1,92 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de réponse pour un budget + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BudgetResponse { + + private UUID id; + + @NotBlank + private String name; + + private String description; + + @NotNull + private UUID organizationId; + + @NotBlank + private String period; // MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL + + @NotNull + private Integer year; + + private Integer month; + + @NotBlank + private String status; // DRAFT, ACTIVE, CLOSED, CANCELLED + + @Builder.Default + private List lines = new ArrayList<>(); + + @NotNull + private BigDecimal totalPlanned; + + @NotNull + private BigDecimal totalRealized; + + @NotBlank + private String currency; + + @NotNull + private UUID createdById; + + @NotNull + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime approvedAt; + + private UUID approvedById; + + @NotNull + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate startDate; + + @NotNull + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate endDate; + + private String metadata; + + // Champs calculés + private Double realizationRate; + private BigDecimal variance; + private Double varianceRate; + private Boolean isOverBudget; + private Boolean isActive; + private Boolean isCurrentPeriod; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/TransactionApprovalResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/TransactionApprovalResponse.java new file mode 100644 index 0000000..a5a9802 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/finance_workflow/response/TransactionApprovalResponse.java @@ -0,0 +1,81 @@ +package dev.lions.unionflow.server.api.dto.finance_workflow.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO de réponse pour une approbation de transaction + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TransactionApprovalResponse { + + private UUID id; + + @NotNull + private UUID transactionId; + + @NotBlank + private String transactionType; // CONTRIBUTION, DEPOSIT, WITHDRAWAL, etc. + + @NotNull + private BigDecimal amount; + + @NotBlank + private String currency; + + @NotNull + private UUID requesterId; + + @NotBlank + private String requesterName; + + private UUID organizationId; + + @NotBlank + private String requiredLevel; // NONE, LEVEL1, LEVEL2, LEVEL3 + + @NotBlank + private String status; // PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED + + @Builder.Default + private List approvers = new ArrayList<>(); + + private String rejectionReason; + + @NotNull + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime expiresAt; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime completedAt; + + private String metadata; + + // Champs calculés + private Integer approvalCount; + private Integer requiredApprovals; + private Boolean hasAllApprovals; + private Boolean isExpired; + private Boolean isPending; + private Boolean isCompleted; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/CreateFormuleAbonnementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/CreateFormuleAbonnementRequest.java new file mode 100644 index 0000000..a42cfa3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/CreateFormuleAbonnementRequest.java @@ -0,0 +1,51 @@ +package dev.lions.unionflow.server.api.dto.formuleabonnement.request; + +import dev.lions.unionflow.server.api.enums.formuleabonnement.StatutFormule; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +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.Builder; + +/** + * Requête de création d'une formule d'abonnement. + */ +@Builder +public record CreateFormuleAbonnementRequest( + @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") String nom, + @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") String code, + @Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères") String description, + @NotNull(message = "Le type de formule est obligatoire") TypeFormule type, + @NotNull(message = "Le statut est obligatoire") StatutFormule statut, + @NotNull(message = "Le prix mensuel est obligatoire") @DecimalMin(value = "0.0", inclusive = false) @Digits(integer = 10, fraction = 2) BigDecimal prixMensuel, + @DecimalMin(value = "0.0", inclusive = false) @Digits(integer = 10, fraction = 2) BigDecimal prixAnnuel, + @NotBlank(message = "La devise est obligatoire") @Pattern(regexp = "^[A-Z]{3}$") String devise, + @NotNull(message = "Le nombre maximum de membres est obligatoire") Integer maxMembres, + @NotNull(message = "Le nombre maximum d'administrateurs est obligatoire") Integer maxAdministrateurs, + @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") BigDecimal espaceStockageGB, + @NotNull(message = "Le support technique doit être spécifié") Boolean supportTechnique, + @Pattern(regexp = "^(EMAIL|CHAT|TELEPHONE|PREMIUM)$", message = "Le niveau de support doit être EMAIL, CHAT, TELEPHONE ou PREMIUM") String niveauSupport, + Boolean fonctionnalitesAvancees, + Boolean apiAccess, + Boolean rapportsPersonnalises, + Boolean integrationsTierces, + Boolean sauvegardeAutomatique, + Boolean multiLangues, + Boolean personnalisationInterface, + Boolean formationIncluse, + Integer heuresFormation, + Boolean populaire, + Boolean recommandee, + Integer periodeEssaiJours, + LocalDate dateDebutValidite, + LocalDate dateFinValidite, + Integer ordreAffichage, + @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "La couleur doit être un code hexadécimal valide") String couleur, + @Size(max = 50, message = "Le nom de l'icône ne peut pas dépasser 50 caractères") String icone, + @Size(max = 500, message = "Les notes ne peuvent pas dépasser 500 caractères") String notes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/UpdateFormuleAbonnementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/UpdateFormuleAbonnementRequest.java new file mode 100644 index 0000000..2db879b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/request/UpdateFormuleAbonnementRequest.java @@ -0,0 +1,49 @@ +package dev.lions.unionflow.server.api.dto.formuleabonnement.request; + +import dev.lions.unionflow.server.api.enums.formuleabonnement.StatutFormule; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Builder; + +/** + * Requête de mise à jour d'une formule d'abonnement. + */ +@Builder +public record UpdateFormuleAbonnementRequest( + @Size(min = 2, max = 100, message = "Le nom de la formule doit contenir entre 2 et 100 caractères") String nom, + @Pattern(regexp = "^[A-Z_]{2,20}$", message = "Le code doit contenir uniquement des lettres majuscules et underscores") String code, + @Size(max = 1000, message = "La description ne peut pas dépasser 1000 caractères") String description, + TypeFormule type, + StatutFormule statut, + @DecimalMin(value = "0.0", inclusive = false, message = "Le prix mensuel doit être positif") @Digits(integer = 10, fraction = 2) BigDecimal prixMensuel, + @DecimalMin(value = "0.0", inclusive = false, message = "Le prix annuel doit être positif") @Digits(integer = 10, fraction = 2) BigDecimal prixAnnuel, + @Pattern(regexp = "^[A-Z]{3}$") String devise, + Integer maxMembres, + Integer maxAdministrateurs, + @DecimalMin(value = "0.1", message = "L'espace de stockage doit être d'au moins 0.1 GB") BigDecimal espaceStockageGB, + Boolean supportTechnique, + @Pattern(regexp = "^(EMAIL|CHAT|TELEPHONE|PREMIUM)$", message = "Le niveau de support doit être EMAIL, CHAT, TELEPHONE ou PREMIUM") String niveauSupport, + Boolean fonctionnalitesAvancees, + Boolean apiAccess, + Boolean rapportsPersonnalises, + Boolean integrationsTierces, + Boolean sauvegardeAutomatique, + Boolean multiLangues, + Boolean personnalisationInterface, + Boolean formationIncluse, + Integer heuresFormation, + Boolean populaire, + Boolean recommandee, + Integer periodeEssaiJours, + LocalDate dateDebutValidite, + LocalDate dateFinValidite, + Integer ordreAffichage, + @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "La couleur doit être un code hexadécimal valide") String couleur, + @Size(max = 50, message = "Le nom de l'icône ne peut pas dépasser 50 caractères") String icone, + @Size(max = 500, message = "Les notes ne peuvent pas dépasser 500 caractères") String notes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponse.java new file mode 100644 index 0000000..8fc05a9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponse.java @@ -0,0 +1,161 @@ +package dev.lions.unionflow.server.api.dto.formuleabonnement.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.enums.formuleabonnement.StatutFormule; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour une formule d'abonnement. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FormuleAbonnementResponse extends BaseResponse { + + private String nom; + private String code; + private String description; + private TypeFormule type; + private StatutFormule statut; + private BigDecimal prixMensuel; + private BigDecimal prixAnnuel; + @Builder.Default + private String devise = "XOF"; + private Integer maxMembres; + private Integer maxAdministrateurs; + private BigDecimal espaceStockageGB; + private Boolean supportTechnique; + private String niveauSupport; + private Boolean fonctionnalitesAvancees; + private Boolean apiAccess; + private Boolean rapportsPersonnalises; + private Boolean integrationsTierces; + private Boolean sauvegardeAutomatique; + private Boolean multiLangues; + private Boolean personnalisationInterface; + private Boolean formationIncluse; + private Integer heuresFormation; + private Boolean populaire; + private Boolean recommandee; + private Integer periodeEssaiJours; + private LocalDate dateDebutValidite; + private LocalDate dateFinValidite; + private Integer ordreAffichage; + private String couleur; + private String icone; + private String notes; + + public boolean isActive() { + return StatutFormule.ACTIVE.equals(statut); + } + + public boolean isInactive() { + return StatutFormule.INACTIVE.equals(statut); + } + + public boolean isArchivee() { + return StatutFormule.ARCHIVEE.equals(statut); + } + + 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; + } + + public BigDecimal getEconomieAnnuelle() { + if (prixMensuel == null || prixAnnuel == null) + return BigDecimal.ZERO; + BigDecimal coutMensuelAnnuel = prixMensuel.multiply(BigDecimal.valueOf(12)); + return coutMensuelAnnuel.subtract(prixAnnuel); + } + + public int getPourcentageEconomieAnnuelle() { + if (prixMensuel == null || prixAnnuel == null) + return 0; + BigDecimal coutMensuelAnnuel = prixMensuel.multiply(BigDecimal.valueOf(12)); + if (coutMensuelAnnuel.compareTo(BigDecimal.ZERO) > 0) { + return getEconomieAnnuelle() + .multiply(BigDecimal.valueOf(100)) + .divide(coutMensuelAnnuel, 0, java.math.RoundingMode.HALF_UP) + .intValue(); + } + return 0; + } + + public boolean hasPeriodeEssai() { + return periodeEssaiJours != null && periodeEssaiJours > 0; + } + + public boolean hasFormation() { + return Boolean.TRUE.equals(formationIncluse) && heuresFormation != null && heuresFormation > 0; + } + + public boolean isMiseEnAvant() { + return Boolean.TRUE.equals(populaire) || Boolean.TRUE.equals(recommandee); + } + + 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; + } + + public int getScoreFonctionnalites() { + if (supportTechnique == null && sauvegardeAutomatique == null && fonctionnalitesAvancees == null + && apiAccess == null && rapportsPersonnalises == null && integrationsTierces == null + && multiLangues == null && personnalisationInterface == null) { + return 0; + } + int score = 0; + int total = 0; + if (Boolean.TRUE.equals(supportTechnique)) score += 10; + total += 10; + if (Boolean.TRUE.equals(sauvegardeAutomatique)) score += 10; + total += 10; + 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; + if (Boolean.TRUE.equals(multiLangues)) score += 10; + total += 10; + if (Boolean.TRUE.equals(personnalisationInterface)) score += 10; + total += 10; + return (score * 100) / total; + } + + 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"; + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/gouvernance/EchelonOrganigrammeDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/gouvernance/EchelonOrganigrammeDTO.java new file mode 100644 index 0000000..f33a349 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/gouvernance/EchelonOrganigrammeDTO.java @@ -0,0 +1,29 @@ +package dev.lions.unionflow.server.api.dto.gouvernance; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.gouvernance.NiveauEchelon; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EchelonOrganigrammeDTO extends BaseDTO { + + // L'Id physique de l'organisation dans le système (une organisation == un + // échelon) + private String organisationId; + + // L'organisation mère / chapeau de cet échelon + private String echelonParentId; + + private NiveauEchelon niveau; + + private String designation; + private String zoneGeographiqueOuDelegation; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/CompteAdherentResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/CompteAdherentResponse.java new file mode 100644 index 0000000..25d887d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/CompteAdherentResponse.java @@ -0,0 +1,74 @@ +package dev.lions.unionflow.server.api.dto.membre; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * DTO représentant le "compte adhérent" d'un membre de mutuelle/association. + * + *

Ce compte est une vue agrégée (non persistée en tant que telle) qui regroupe + * toutes les informations financières du membre : + *

    + *
  • Numéro de membre (identifiant lisible, ex : MUF-2026-001)
  • + *
  • Solde cotisations (total payé toutes années)
  • + *
  • Solde épargne (somme des comptes d'épargne actifs)
  • + *
  • Solde total disponible = cotisations + épargne
  • + *
  • Encours crédit (prêts en cours, 0 si pas encore implémenté)
  • + *
  • Capacité d'emprunt estimée (3× l'épargne — règle mutuelle classique)
  • + *
+ * + * @param numeroMembre Numéro unique du membre sur la plateforme (ex: MUF-2026-001) + * @param nomComplet Nom et prénom du membre + * @param organisationNom Nom de l'organisation principale (ou null si aucune) + * @param dateAdhesion Date d'inscription sur la plateforme + * @param statutCompte Statut actuel du compte (ACTIF, SUSPENDU, etc.) + * + * @param soldeCotisations Total des cotisations payées (toutes années confondues) + * @param soldeEpargne Solde disponible sur l'ensemble des comptes épargne actifs + * @param soldeBloque Montant bloqué (garantie de prêt) + * @param soldeTotalDisponible soldeCotisations + soldeEpargne - soldeBloque + * @param encoursCreditTotal Montant total des prêts en cours (0 si fonctionnalité non encore activée) + * @param capaciteEmprunt Capacité d'emprunt estimée (3 × soldeEpargne selon règle mutuelle standard) + * + * @param nombreCotisationsPayees Nombre de cotisations payées (historique complet) + * @param nombreCotisationsTotal Nombre total de cotisations (payées + en attente + retard) + * @param nombreCotisationsEnRetard Nombre de cotisations en retard + * @param tauxEngagement Taux de paiement global en % (0-100) + * + * @param nombreComptesEpargne Nombre de comptes épargne actifs + * @param dateCalcul Date/heure du calcul (pour information client) + * + * @author UnionFlow Team + * @version 1.0 + */ +public record CompteAdherentResponse( + + // ── Identité ────────────────────────────────────────────────────────── + String numeroMembre, + String nomComplet, + String organisationNom, + LocalDate dateAdhesion, + String statutCompte, + + // ── Soldes ──────────────────────────────────────────────────────────── + BigDecimal soldeCotisations, + BigDecimal soldeEpargne, + BigDecimal soldeBloque, + BigDecimal soldeTotalDisponible, + BigDecimal encoursCreditTotal, + BigDecimal capaciteEmprunt, + + // ── Cotisations ─────────────────────────────────────────────────────── + Integer nombreCotisationsPayees, + Integer nombreCotisationsTotal, + Integer nombreCotisationsEnRetard, + Integer tauxEngagement, + + // ── Épargne ─────────────────────────────────────────────────────────── + Integer nombreComptesEpargne, + + // ── Méta ────────────────────────────────────────────────────────────── + LocalDate dateCalcul + +) implements Serializable {} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteria.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteria.java new file mode 100644 index 0000000..b6dfd4e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteria.java @@ -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 organisationIds; + + /** Liste des rôles à rechercher */ + @Schema(description = "Liste des rôles à rechercher", example = "[\"PRESIDENT\", \"SECRETAIRE\"]") + private List 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(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTO.java new file mode 100644 index 0000000..14b9ecf --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTO.java @@ -0,0 +1,227 @@ +package dev.lions.unionflow.server.api.dto.membre; + +import com.fasterxml.jackson.annotation.JsonProperty; +import dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse; +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 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") + @JsonProperty("isFirst") + private boolean isFirst; + + /** Indique si c'est la dernière page */ + @Schema(description = "Indique si c'est la dernière page") + @JsonProperty("isLast") + 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) { + return empty(criteria, 20, 0); + } + + /** + * Factory method pour créer un résultat vide avec pageSize spécifique + * + * @param criteria Critères de recherche + * @param pageSize Taille de la page + * @param currentPage Page actuelle + * @return Résultat vide + */ + public static MembreSearchResultDTO empty(MembreSearchCriteria criteria, int pageSize, int currentPage) { + MembreSearchResultDTO result = new MembreSearchResultDTO(); + result.setMembres(List.of()); + result.setTotalElements(0L); + result.setTotalPages(0); + result.setCurrentPage(currentPage); + result.setPageSize(pageSize); + result.setNumberOfElements(0); + result.setHasNext(false); + result.setHasPrevious(false); + result.isFirst = true; // Assignation directe pour éviter les problèmes avec les setters Lombok + result.isLast = true; // Assignation directe pour éviter les problèmes avec les setters Lombok + result.setCriteria(criteria); + result.setExecutionTimeMs(0L); + // Initialiser statistics avec des valeurs vides + result.setStatistics(SearchStatistics.builder() + .membresActifs(0) + .membresInactifs(0) + .ageMoyen(0.0) + .ageMin(0) + .ageMax(0) + .nombreOrganisations(0) + .nombreRegions(0) + .ancienneteMoyenne(0.0) + .build()); + return result; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequest.java new file mode 100644 index 0000000..cd08559 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequest.java @@ -0,0 +1,49 @@ +package dev.lions.unionflow.server.api.dto.membre.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; + +import lombok.Builder; + +/** + * Requête de création d'un membre. + * + *

+ * Immutable via {@code record}. Exclut l'ID, + * les champs d'audit, et le numéroMembre + * (généré côté serveur). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + * @param prenom prénom + * @param nom nom de famille + * @param email email + * @param telephone téléphone + * @param telephoneWave numéro Wave + * @param dateNaissance date de naissance + * @param profession profession + * @param photoUrl URL de la photo + * @param statutMatrimonial statut matrimonial + * @param nationalite nationalité + * @param typeIdentite type pièce d'identité + * @param numeroIdentite numéro d'identité + */ +@Builder +public record CreateMembreRequest( + @NotBlank @Size(max = 100) String prenom, + @NotBlank @Size(max = 100) String nom, + @NotBlank @Email @Size(max = 255) String email, + @Size(max = 20) String telephone, + @Size(max = 13) String telephoneWave, + @NotNull LocalDate dateNaissance, + @Size(max = 100) String profession, + @Size(max = 500) String photoUrl, + @Size(max = 50) String statutMatrimonial, + @Size(max = 100) String nationalite, + @Size(max = 50) String typeIdentite, + @Size(max = 100) String numeroIdentite) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/UpdateMembreRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/UpdateMembreRequest.java new file mode 100644 index 0000000..8035c99 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/request/UpdateMembreRequest.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.dto.membre.request; + +import jakarta.validation.constraints.*; +import java.time.LocalDate; + +import lombok.Builder; + +/** + * Requête pour mettre à jour un membre existant. + */ +@Builder +public record UpdateMembreRequest( + @NotBlank @Size(max = 100) String prenom, + @NotBlank @Size(max = 100) String nom, + @NotBlank @Email @Size(max = 255) String email, + @Size(max = 20) String telephone, + @Size(max = 13) String telephoneWave, + @NotNull LocalDate dateNaissance, + @Size(max = 100) String profession, + @Size(max = 500) String photoUrl, + @Size(max = 50) String statutMatrimonial, + @Size(max = 100) String nationalite, + @Size(max = 50) String typeIdentite, + @Size(max = 100) String numeroIdentite, + Boolean actif) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreResponse.java new file mode 100644 index 0000000..a55a99f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreResponse.java @@ -0,0 +1,70 @@ +package dev.lions.unionflow.server.api.dto.membre.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDate; +import java.util.UUID; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'un membre. + * + *

+ * Classe Lombok (getters/setters) pour la + * compatibilité avec les frameworks d'affichage + * (PrimeFaces, etc.). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MembreResponse extends BaseResponse { + + // ── Identité ─────────────────────────────── + private String numeroMembre; + private UUID keycloakId; + private String prenom; + private String nom; + private String nomComplet; + private String email; + private String telephone; + private String telephoneWave; + private LocalDate dateNaissance; + private int age; + + // ── Personnel ────────────────────────────── + private String profession; + private String photoUrl; + private String statutMatrimonial; + private String statutMatrimonialLibelle; + private String nationalite; + private String typeIdentite; + private String typeIdentiteLibelle; + private String numeroIdentite; + + // ── KYC / LCB-FT ─────────────────────────── + private String niveauVigilanceKyc; + private String statutKyc; + private LocalDate dateVerificationIdentite; + + // ── Statut ───────────────────────────────── + private String statutCompte; + private String statutCompteLibelle; + private String statutCompteSeverity; + private List roles; + + // ── Adhésion (contexte organisation) ─────── + private UUID organisationId; + private String associationNom; + private LocalDate dateAdhesion; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreSummaryResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreSummaryResponse.java new file mode 100644 index 0000000..8a68bbc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/membre/response/MembreSummaryResponse.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.membre.response; + +import java.util.UUID; +import java.util.List; + +/** + * DTO de réponse résumé pour Membre (listes et optimisations). + */ +public record MembreSummaryResponse( + UUID id, + String numeroMembre, + String prenom, + String nom, + String email, + String telephone, + String profession, + String statutCompte, + String statutCompteLibelle, + String statutCompteSeverity, + Boolean actif, + List roles, + UUID organisationId, + String associationNom) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditRequest.java new file mode 100644 index 0000000..9574795 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditRequest.java @@ -0,0 +1,49 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.credit; + +import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit; +import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Requête initiale pour une demande de financement (Crédit)") +public class DemandeCreditRequest { + + @NotBlank(message = "L'ID du demandeur est obligatoire") + private String membreId; + + @NotNull(message = "Le type de crédit est obligatoire") + private TypeCredit typeCredit; + + @NotNull(message = "Le montant demandé est obligatoire") + @DecimalMin(value = "1.0", message = "Le montant demandé doit être positif") + private BigDecimal montantDemande; + + @Min(value = 1, message = "La durée (en mois) doit être au moins de 1 mois") + private Integer dureeMois; + + @Schema(description = "Compte épargne adossé (si remboursement automatique ou nantissement)") + private String compteLieId; + + @NotBlank(message = "Le motif détaillé du financement est requis (Plan d'affaires, usage...)") + private String justificationDetaillee; + + @Schema(description = "Liste des IDs de documents justificatifs attachés au dossier") + private List documentIds; + + @Schema(description = "Liste des garanties proposées avec la demande") + private List garantiesProposees; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditResponse.java new file mode 100644 index 0000000..b306201 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/DemandeCreditResponse.java @@ -0,0 +1,48 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.credit; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutDemandeCredit; +import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Réponse avec le tableau de bord résumé d'un dossier de crédit") +public class DemandeCreditResponse extends BaseDTO { + + private String numeroDossier; + private String membreId; + private TypeCredit typeCredit; + private String compteLieId; + + private BigDecimal montantDemande; + private Integer dureeMoisDemande; + + // Conditions actées par le comité de crédit (peuvent différer de la demande) + private BigDecimal montantApprouve; + private Integer dureeMoisApprouvee; + private BigDecimal tauxInteretAnnuel; // Pourcentage + private BigDecimal coutTotalCredit; // Total Intérêts + + private StatutDemandeCredit statut; + private String notesComite; + + private LocalDate dateSoumission; + private LocalDate dateValidation; + private LocalDate datePremierEcheance; + + @Schema(description = "Aperçu des échéances générées pour le tableau d'amortissement") + private List echeancier; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/EcheanceCreditDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/EcheanceCreditDTO.java new file mode 100644 index 0000000..3461bba --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/EcheanceCreditDTO.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.credit; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Représente une ligne unique du tableau d'amortissement d'un crédit") +public class EcheanceCreditDTO extends BaseDTO { + + private String demandeCreditId; + + @Schema(description = "Indice de l'échéance (ex: mois 1, 2, 3...)") + private Integer ordre; + + private LocalDate dateEcheancePrevue; + private LocalDate datePaiementEffectif; + + private BigDecimal capitalAmorti; + private BigDecimal interetsDeLaPeriode; + private BigDecimal montantTotalExigible; + + private BigDecimal capitalRestantDu; + + // Pénalités éventuelles additionnelles liées au retard + private BigDecimal penalitesRetard; + + // Somme physiquement encaissée pour cette traite à l'instant T + private BigDecimal montantRegle; + + private StatutEcheanceCredit statut; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/GarantieDemandeDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/GarantieDemandeDTO.java new file mode 100644 index 0000000..108fa61 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/credit/GarantieDemandeDTO.java @@ -0,0 +1,28 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.credit; + +import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GarantieDemandeDTO { + + @NotNull(message = "Le type de garantie est requis") + private TypeGarantie typeGarantie; + + private BigDecimal valeurEstimee; + + @NotBlank(message = "Description ou référence requise (Id Membre Caution, SN du Véhicule, Titre Foncier...)") + private String referenceOuDescription; + + private String documentPreuveId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneRequest.java new file mode 100644 index 0000000..76284da --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneRequest.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.epargne; + +import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Requête d'ouverture d'un compte d'épargne") +public class CompteEpargneRequest { + + @NotBlank(message = "L'ID du membre propriétaire est obligatoire") + @Schema(description = "ID UUID du Membre détenteur") + private String membreId; + + @NotBlank(message = "L'ID de l'organisation est obligatoire") + @Schema(description = "ID UUID de l'Organisation / Mutuelle") + private String organisationId; + + @NotNull(message = "Le type de compte est obligatoire") + @Schema(description = "Le type du compte d'épargne demandé") + private TypeCompteEpargne typeCompte; + + @Schema(description = "Notes ou détails éventuels lors de l'ouverture") + private String notesOuverture; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneResponse.java new file mode 100644 index 0000000..f04d8de --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/CompteEpargneResponse.java @@ -0,0 +1,42 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.epargne; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne; +import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Réponse des informations d'un compte épargne") +public class CompteEpargneResponse extends BaseDTO { + + private String membreId; + private String organisationId; + + @Schema(description = "Numéro de compte généré de manière unique (ex: MEC-00123)") + private String numeroCompte; + + private TypeCompteEpargne typeCompte; + + @Schema(description = "Solde principal disponible") + private BigDecimal soldeActuel; + + @Schema(description = "Fonds gelés pour garantie de crédits") + private BigDecimal soldeBloque; + + private StatutCompteEpargne statut; + private LocalDate dateOuverture; + private LocalDate dateDerniereTransaction; + private String description; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneRequest.java new file mode 100644 index 0000000..9942adc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneRequest.java @@ -0,0 +1,43 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.epargne; + +import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Requête d'initialisation d'une transaction sur un compte d'épargne") +public class TransactionEpargneRequest { + + @NotBlank(message = "L'ID du compte épargne cible est obligatoire") + private String compteId; + + @NotNull(message = "Le type de transaction est requis") + private TypeTransactionEpargne typeTransaction; + + @NotNull(message = "Le montant est obligatoire") + @DecimalMin(value = "0.01", message = "Le montant doit être supérieur à 0") + private BigDecimal montant; + + @Schema(description = "ID d'un compte de destination en cas de transfert interne") + private String compteDestinationId; + + @Schema(description = "Motif, libellé ou preuve de dépôt") + private String motif; + + @Schema(description = "Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré (ex. Salaire, Vente, Héritage)") + private String origineFonds; + + @Schema(description = "ID de la pièce justificative (document) — requis au-dessus du seuil LCB-FT") + private String pieceJustificativeId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneResponse.java new file mode 100644 index 0000000..bbd45b9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/mutuelle/epargne/TransactionEpargneResponse.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.mutuelle.epargne; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne; +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TransactionEpargneResponse extends BaseDTO { + + private String compteId; + private TypeTransactionEpargne type; + private BigDecimal montant; + + // Pour assurer un audit et les principes de comptabilité double entrée + private BigDecimal soldeAvant; + private BigDecimal soldeApres; + + private String motif; + private LocalDateTime dateTransaction; + private String operateurId; + + private String referenceExterne; + + // Status général d'une transaction (Validée, Rejetée, En traitement Wave) + private StatutTransactionWave statutExecution; + + /** Origine des fonds déclarée (LCB-FT) */ + private String origineFonds; + + /** Référence pièce justificative (LCB-FT) */ + private String pieceJustificativeId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTO.java new file mode 100644 index 0000000..e246178 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTO.java @@ -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 + * + *

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 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 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 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 getParametres() { + return parametres; + } + + public void setParametres(Map 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 getDonneesPersonnalisees() { + return donneesPersonnalisees; + } + + public void setDonneesPersonnalisees(Map 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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceCanalNotificationDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceCanalNotificationDTO.java new file mode 100644 index 0000000..0c77dba --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceCanalNotificationDTO.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceTypeNotificationDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceTypeNotificationDTO.java new file mode 100644 index 0000000..c251838 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferenceTypeNotificationDTO.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTO.java new file mode 100644 index 0000000..a15ffc4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTO.java @@ -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 + * + *

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 typesActives; + + /** Types de notifications désactivés */ + private Set typesDesactivees; + + /** Canaux de notification activés */ + private Set canauxActifs; + + /** Canaux de notification désactivés */ + private Set 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 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 preferencesParType; + + /** Préférences par canal de notification */ + private Map preferencesParCanal; + + /** Mots-clés pour filtrage automatique */ + private Set motsClesFiltre; + + /** Expéditeurs bloqués */ + private Set expediteursBloqués; + + /** Expéditeurs prioritaires */ + private Set 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 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 getTypesActives() { + return typesActives; + } + + public void setTypesActives(Set typesActives) { + this.typesActives = typesActives; + } + + public Set getTypesDesactivees() { + return typesDesactivees; + } + + public void setTypesDesactivees(Set typesDesactivees) { + this.typesDesactivees = typesDesactivees; + } + + public Set getCanauxActifs() { + return canauxActifs; + } + + public void setCanauxActifs(Set canauxActifs) { + this.canauxActifs = canauxActifs; + } + + public Set getCanauxDesactives() { + return canauxDesactives; + } + + public void setCanauxDesactives(Set 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 getJoursSilencieux() { + return joursSilencieux; + } + + public void setJoursSilencieux(Set 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 getPreferencesParType() { + return preferencesParType; + } + + public void setPreferencesParType( + Map preferencesParType) { + this.preferencesParType = preferencesParType; + } + + public Map getPreferencesParCanal() { + return preferencesParCanal; + } + + public void setPreferencesParCanal( + Map preferencesParCanal) { + this.preferencesParCanal = preferencesParCanal; + } + + public Set getMotsClesFiltre() { + return motsClesFiltre; + } + + public void setMotsClesFiltre(Set motsClesFiltre) { + this.motsClesFiltre = motsClesFiltre; + } + + public Set getExpediteursBloques() { + return expediteursBloqués; + } + + public void setExpediteursBloques(Set expediteursBloqués) { + this.expediteursBloqués = expediteursBloqués; + } + + public Set getExpediteursPrioritaires() { + return expediteursPrioritaires; + } + + public void setExpediteursPrioritaires(Set 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 getMetadonnees() { + return metadonnees; + } + + public void setMetadonnees(Map 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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateNotificationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateNotificationRequest.java new file mode 100644 index 0000000..5f55866 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateNotificationRequest.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.notification.request; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateNotificationRequest( + @NotNull(message = "Le type de notification est obligatoire") String typeNotification, + String priorite, + String sujet, + String corps, + LocalDateTime dateEnvoiPrevue, + String donneesAdditionnelles, + UUID membreId, + UUID organisationId, + UUID templateId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateTemplateNotificationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateTemplateNotificationRequest.java new file mode 100644 index 0000000..55d0ff2 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/CreateTemplateNotificationRequest.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.dto.notification.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +/** + * Requête de création d'un template de notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateTemplateNotificationRequest( + @NotBlank(message = "Le code est obligatoire") String code, + String sujet, + String corpsTexte, + String corpsHtml, + String variablesDisponibles, + String canauxSupportes, + String langue, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateNotificationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateNotificationRequest.java new file mode 100644 index 0000000..2ef77e3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateNotificationRequest.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.dto.notification.request; + +import java.time.LocalDateTime; +import lombok.Builder; + +/** + * Requête de mise à jour d'une notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateNotificationRequest( + String statut, + LocalDateTime dateLecture, + Integer nombreTentatives, + String messageErreur) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateTemplateNotificationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateTemplateNotificationRequest.java new file mode 100644 index 0000000..53939d8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/request/UpdateTemplateNotificationRequest.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.dto.notification.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +/** + * Requête de mise à jour d'un template de notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateTemplateNotificationRequest( + @NotBlank(message = "Le code est obligatoire") String code, + String sujet, + String corpsTexte, + String corpsHtml, + String variablesDisponibles, + String canauxSupportes, + String langue, + String description) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/NotificationResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/NotificationResponse.java new file mode 100644 index 0000000..74ce59a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/NotificationResponse.java @@ -0,0 +1,40 @@ +package dev.lions.unionflow.server.api.dto.notification.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'une notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NotificationResponse extends BaseResponse { + + private String typeNotification; + private String priorite; + private String statut; + private String sujet; + private String corps; + private LocalDateTime dateEnvoiPrevue; + private LocalDateTime dateEnvoi; + private LocalDateTime dateLecture; + private Integer nombreTentatives; + private String messageErreur; + private String donneesAdditionnelles; + private UUID membreId; + private UUID organisationId; + private UUID templateId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/TemplateNotificationResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/TemplateNotificationResponse.java new file mode 100644 index 0000000..1c3b726 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/notification/response/TemplateNotificationResponse.java @@ -0,0 +1,32 @@ +package dev.lions.unionflow.server.api.dto.notification.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'un template de notification. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TemplateNotificationResponse extends BaseResponse { + + private String code; + private String sujet; + private String corpsTexte; + private String corpsHtml; + private String variablesDisponibles; + private String canauxSupportes; + private String langue; + private String description; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ong/ProjetOngDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/ong/ProjetOngDTO.java new file mode 100644 index 0000000..4c4c876 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ong/ProjetOngDTO.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.server.api.dto.ong; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ProjetOngDTO extends BaseDTO { + + private String organisationId; + + private String nomProjet; + private String descriptionMandat; + + private String zoneGeographiqueIntervention; + + private BigDecimal budgetPrevisionnel; + private BigDecimal depensesReelles; + + private LocalDate dateLancement; + private LocalDate dateFinEstimee; + + private StatutProjetOng statut; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequest.java new file mode 100644 index 0000000..3cc4dc9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequest.java @@ -0,0 +1,74 @@ +package dev.lions.unionflow.server.api.dto.organisation.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; + +import lombok.Builder; + +/** + * Requête de création d'une organisation. + * + *

+ * Immutable via {@code record}. Exclut les + * champs d'audit et l'ID (générés côté serveur). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + * @param nom nom officiel + * @param nomCourt sigle + * @param description description + * @param email email principal + * @param telephone téléphone + * @param telephoneSecondaire téléphone 2 + * @param emailSecondaire email 2 + * @param siteWeb site web + * @param logo URL du logo + * @param reseauxSociaux réseaux sociaux + * @param typeOrganisation type (types_reference) + * @param statut statut initial + * @param dateFondation date de fondation + * @param numeroEnregistrement numéro légal + * @param devise code devise + * @param budgetAnnuel budget annuel + * @param cotisationObligatoire cotisation requise + * @param montantCotisationAnnuelle montant annuel + * @param objectifs objectifs + * @param activitesPrincipales activités + * @param certifications certifications + * @param partenaires partenaires + * @param notes notes internes + * @param latitude latitude GPS + * @param longitude longitude GPS + */ +@Builder +public record CreateOrganisationRequest( + @NotBlank @Size(max = 255) String nom, + @Size(max = 50) String nomCourt, + @Size(max = 2000) String description, + @NotBlank @Email @Size(max = 255) String email, + @Size(max = 20) String telephone, + @Size(max = 20) String telephoneSecondaire, + @Email @Size(max = 255) String emailSecondaire, + @Size(max = 500) String siteWeb, + @Size(max = 500) String logo, + @Size(max = 1000) String reseauxSociaux, + @Size(max = 50) String typeOrganisation, + @Size(max = 30) String statut, + LocalDate dateFondation, + @Size(max = 100) String numeroEnregistrement, + @Size(max = 3) String devise, + BigDecimal budgetAnnuel, + Boolean cotisationObligatoire, + BigDecimal montantCotisationAnnuelle, + @Size(max = 2000) String objectifs, + @Size(max = 2000) String activitesPrincipales, + @Size(max = 500) String certifications, + @Size(max = 1000) String partenaires, + @Size(max = 1000) String notes, + BigDecimal latitude, + BigDecimal longitude) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequest.java new file mode 100644 index 0000000..39ad6ca --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequest.java @@ -0,0 +1,48 @@ +package dev.lions.unionflow.server.api.dto.organisation.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; + +import lombok.Builder; + +/** + * Requête de mise à jour d'une organisation. + * + *

+ * ID passé dans le path de l'URL. + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-22 + */ +@Builder +public record UpdateOrganisationRequest( + @NotBlank @Size(max = 255) String nom, + @Size(max = 50) String nomCourt, + @Size(max = 2000) String description, + @NotBlank @Email @Size(max = 255) String email, + @Size(max = 20) String telephone, + @Size(max = 20) String telephoneSecondaire, + @Email @Size(max = 255) String emailSecondaire, + @Size(max = 500) String siteWeb, + @Size(max = 500) String logo, + @Size(max = 1000) String reseauxSociaux, + @Size(max = 50) String typeOrganisation, + @Size(max = 30) String statut, + LocalDate dateFondation, + @Size(max = 100) String numeroEnregistrement, + @Size(max = 3) String devise, + BigDecimal budgetAnnuel, + Boolean cotisationObligatoire, + BigDecimal montantCotisationAnnuelle, + @Size(max = 2000) String objectifs, + @Size(max = 2000) String activitesPrincipales, + @Size(max = 500) String certifications, + @Size(max = 1000) String partenaires, + @Size(max = 1000) String notes, + BigDecimal latitude, + BigDecimal longitude) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponse.java new file mode 100644 index 0000000..363d3a9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponse.java @@ -0,0 +1,116 @@ +package dev.lions.unionflow.server.api.dto.organisation.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'une organisation. + * + *

+ * Classe Lombok (getters/setters) pour la + * compatibilité avec les frameworks d'affichage + * (PrimeFaces, etc.). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OrganisationResponse extends BaseResponse { + + // ── Identité ─────────────────────────────── + private String nom; + private String nomCourt; + private String description; + private String email; + private String telephone; + private String telephoneSecondaire; + private String emailSecondaire; + + // ── Web ──────────────────────────────────── + private String siteWeb; + private String logo; + private String reseauxSociaux; + + // ── Classification ───────────────────────── + private String typeOrganisation; + /** Alias pour tri/filtre (type d'organisation) */ + private String typeAssociation; + private String typeOrganisationLibelle; + /** Libellé du type pour affichage (alias typeOrganisationLibelle, ex. p:tag) */ + private String typeLibelle; + private String statut; + private String statutLibelle; + private String statutSeverity; + private LocalDate dateFondation; + private String numeroEnregistrement; + + /** + * Alias pour la vue (detail.xhtml, organisation-form.xhtml) : même valeur que numeroEnregistrement. + */ + public String getNumeroRegistre() { + return getNumeroEnregistrement(); + } + + public void setNumeroRegistre(String numeroRegistre) { + setNumeroEnregistrement(numeroRegistre); + } + + // ── Géographie ───────────────────────────── + private String adresse; + private String quartier; + private String ville; + private String region; + private String pays; + private String codePostal; + private BigDecimal latitude; + private BigDecimal longitude; + + // ── Hiérarchie ───────────────────────────── + private UUID organisationParenteId; + private String organisationParenteNom; + /** Alias pour la vue (detail.xhtml) : même valeur que organisationParenteNom. */ + public String getNomOrganisationParente() { + return getOrganisationParenteNom(); + } + public void setNomOrganisationParente(String nomOrganisationParente) { + setOrganisationParenteNom(nomOrganisationParente); + } + private Integer niveauHierarchique; + private Boolean estOrganisationRacine; + + // ── Finances ─────────────────────────────── + private String devise; + private BigDecimal budgetAnnuel; + private Boolean cotisationObligatoire; + private BigDecimal montantCotisationAnnuelle; + + // ── Statistiques ─────────────────────────── + private Integer nombreMembres; + private Integer nombreAdministrateurs; + /** Nombre d'événements (actifs) de l'organisation. */ + private Integer nombreEvenements; + + // ── Contenu ──────────────────────────────── + private String objectifs; + private String activitesPrincipales; + private String certifications; + private String partenaires; + private String notes; + + // ── Paramètres ───────────────────────────── + private Boolean organisationPublique; + private Boolean accepteNouveauxMembres; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationSummaryResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationSummaryResponse.java new file mode 100644 index 0000000..7aa3353 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationSummaryResponse.java @@ -0,0 +1,23 @@ +package dev.lions.unionflow.server.api.dto.organisation.response; + +import java.util.UUID; + +/** + * Résumé d'une organisation pour les listes. + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-22 + */ +public record OrganisationSummaryResponse( + UUID id, + String nom, + String nomCourt, + String typeOrganisation, + String typeOrganisationLibelle, + String statut, + String statutLibelle, + String statutSeverity, + Integer nombreMembres, + Boolean actif) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTO.java new file mode 100644 index 0000000..4054b73 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTO.java @@ -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 + * + *

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(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTO.java new file mode 100644 index 0000000..0969c33 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTO.java @@ -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 + * + *

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 +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTO.java new file mode 100644 index 0000000..73cb9e0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTO.java @@ -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 + * + *

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(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/CreatePaiementRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/CreatePaiementRequest.java new file mode 100644 index 0000000..ebcd531 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/CreatePaiementRequest.java @@ -0,0 +1,31 @@ +package dev.lions.unionflow.server.api.dto.paiement.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.math.BigDecimal; +import java.util.UUID; + +import lombok.Builder; + +/** + * Requete de création d'un paiement. + * + * @author UnionFlow Team + * @version 3.0 + */ +@Builder +public record CreatePaiementRequest( + @NotBlank(message = "Le numéro de référence est obligatoire") String numeroReference, + + @NotNull(message = "Le montant est obligatoire") @DecimalMin(value = "0.0", message = "Le montant doit être positif") BigDecimal montant, + + @NotBlank(message = "Le code devise est obligatoire") @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") String codeDevise, + + @NotBlank(message = "La méthode de paiement est obligatoire") String methodePaiement, + + String commentaire, + + @NotNull(message = "Le membre payeur est obligatoire") UUID membreId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/DeclarerPaiementManuelRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/DeclarerPaiementManuelRequest.java new file mode 100644 index 0000000..902a12a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/DeclarerPaiementManuelRequest.java @@ -0,0 +1,46 @@ +package dev.lions.unionflow.server.api.dto.paiement.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête pour déclarer un paiement manuel (espèces, virement, chèque). + * Le paiement est créé avec le statut EN_ATTENTE_VALIDATION. + * Le trésorier devra le valider via une page admin. + * + * @param cotisationId ID de la cotisation payée + * @param methodePaiement Méthode de paiement (ESPECES, VIREMENT, CHEQUE, AUTRE) + * @param reference Référence du paiement (numéro de transaction, numéro de chèque, etc.) + * @param commentaire Commentaire optionnel + * @param origineFonds Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré + * @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Builder +public record DeclarerPaiementManuelRequest( + @NotNull(message = "L'ID de la cotisation est obligatoire") + UUID cotisationId, + + @NotBlank(message = "La méthode de paiement est obligatoire") + @Pattern(regexp = "^(ESPECES|VIREMENT|CHEQUE|AUTRE)$", + message = "Méthode de paiement invalide. Valeurs autorisées : ESPECES, VIREMENT, CHEQUE, AUTRE") + String methodePaiement, + + @Size(max = 100, message = "La référence ne doit pas dépasser 100 caractères") + String reference, + + @Size(max = 500, message = "Le commentaire ne doit pas dépasser 500 caractères") + String commentaire, + + String origineFonds, + + String justificationLcbFt +) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierDepotEpargneRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierDepotEpargneRequest.java new file mode 100644 index 0000000..4b1a82a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierDepotEpargneRequest.java @@ -0,0 +1,38 @@ +package dev.lions.unionflow.server.api.dto.paiement.request; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête pour initier un dépôt sur compte épargne via Wave (mobile money). + * Réutilise le même flux que les cotisations : Wave Checkout → redirection → deep link. + * + * @param compteId ID du compte épargne à créditer + * @param montant Montant du dépôt (XOF) + * @param numeroTelephone Numéro Wave du membre (9 chiffres) + * @param origineFonds Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré + * @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire + */ +@Builder +public record InitierDepotEpargneRequest( + @NotNull(message = "L'ID du compte épargne est obligatoire") + UUID compteId, + + @NotNull(message = "Le montant est obligatoire") + @DecimalMin(value = "1", message = "Le montant doit être strictement positif") + BigDecimal montant, + + @NotBlank(message = "Le numéro de téléphone Wave est obligatoire") + @Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)") + String numeroTelephone, + + String origineFonds, + + String justificationLcbFt +) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequest.java new file mode 100644 index 0000000..487ac54 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequest.java @@ -0,0 +1,40 @@ +package dev.lions.unionflow.server.api.dto.paiement.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête pour initier un paiement en ligne via un gateway (Wave, Orange, Free Money, Carte). + * + * @param cotisationId ID de la cotisation à payer + * @param methodePaiement Méthode de paiement (WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE) + * @param numeroTelephone Numéro de téléphone pour Wave/Orange/Free (format: 221771234567) + * @param origineFonds Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré + * @param justificationLcbFt Justification complémentaire LCB-FT si nécessaire + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Builder +public record InitierPaiementEnLigneRequest( + @NotNull(message = "L'ID de la cotisation est obligatoire") + UUID cotisationId, + + @NotBlank(message = "La méthode de paiement est obligatoire") + @Pattern(regexp = "^(WAVE|ORANGE_MONEY|FREE_MONEY|CARTE_BANCAIRE)$", + message = "Méthode de paiement invalide. Valeurs autorisées : WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE") + String methodePaiement, + + @NotBlank(message = "Le numéro de téléphone est obligatoire") + @Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)") + String numeroTelephone, + + String origineFonds, + + String justificationLcbFt +) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementGatewayResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementGatewayResponse.java new file mode 100644 index 0000000..a38cc51 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementGatewayResponse.java @@ -0,0 +1,77 @@ +package dev.lions.unionflow.server.api.dto.paiement.response; + +import java.math.BigDecimal; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * DTO de réponse pour l'initiation d'un paiement en ligne via un gateway. + * Retourne l'URL de redirection vers le gateway (Wave, Orange, Free Money, Carte). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class PaiementGatewayResponse { + /** + * ID de la transaction créée + */ + private UUID transactionId; + + /** + * URL de redirection vers le gateway de paiement + */ + private String redirectUrl; + + /** + * Montant à payer (en FCFA) + */ + private BigDecimal montant; + + /** + * Statut de la transaction (EN_ATTENTE, VALIDE, ECHOUE) + */ + private String statut; + + /** + * Méthode de paiement (WAVE, ORANGE_MONEY, FREE_MONEY, CARTE_BANCAIRE) + */ + private String methodePaiement; + + /** + * Référence de la cotisation + */ + private String referenceCotisation; + + /** + * Message d'information pour l'utilisateur + */ + private String message; + + /** + * URL Wave pour ouvrir l'app Wave (redirection automatique membre). + * Présent lorsque methodePaiement = WAVE. + */ + private String waveLaunchUrl; + + /** + * ID de session Checkout Wave (cos-xxx). Pour réconciliation / webhook. + */ + private String waveCheckoutSessionId; + + /** + * Référence client (UUID intention) pour le deep link de retour. + */ + private String clientReference; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementResponse.java new file mode 100644 index 0000000..fb9c2df --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementResponse.java @@ -0,0 +1,47 @@ +package dev.lions.unionflow.server.api.dto.paiement.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * DTO de réponse détaillée pour un paiement. + * + * @author UnionFlow Team + * @version 3.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PaiementResponse extends BaseResponse { + private String numeroReference; + private BigDecimal montant; + private String codeDevise; + private String methodePaiement; + private String methodePaiementLibelle; + private String statutPaiement; + private String statutPaiementLibelle; + private String statutPaiementSeverity; + private LocalDateTime datePaiement; + private LocalDateTime dateValidation; + private String validateur; + private String referenceExterne; + private String urlPreuve; + private String commentaire; + + // Informations relatives au payeur + private UUID membreId; + private String membreNom; + + // Intégration Wave + private UUID transactionWaveId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementSummaryResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementSummaryResponse.java new file mode 100644 index 0000000..e0a38ba --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/PaiementSummaryResponse.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.paiement.response; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * DTO de réponse résumé pour un paiement. + * Utilisé principalement pour l'affichage de listes. + * + * @author UnionFlow Team + * @version 3.0 + */ +public record PaiementSummaryResponse( + UUID id, + String numeroReference, + BigDecimal montant, + String codeDevise, + String methodePaiementLibelle, + String statutPaiement, + String statutPaiementLibelle, + String statutPaiementSeverity, + LocalDateTime datePaiement) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/CreateTypeReferenceRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/CreateTypeReferenceRequest.java new file mode 100644 index 0000000..ebfad22 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/CreateTypeReferenceRequest.java @@ -0,0 +1,56 @@ +package dev.lions.unionflow.server.api.dto.reference.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.util.UUID; + +import lombok.Builder; + +/** + * Requête de création d'une donnée de référence. + * + *

+ * Utilisé pour ajouter une nouvelle valeur dans + * un domaine existant de la table + * {@code types_reference}. + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Builder +public record CreateTypeReferenceRequest( + + /** Domaine fonctionnel (ex: STATUT_ORGANISATION). */ + @NotBlank @Size(max = 50) String domaine, + + /** Code technique unique dans le domaine. */ + @NotBlank @Size(max = 50) String code, + + /** Libellé affiché dans l'interface. */ + @NotBlank @Size(max = 200) String libelle, + + /** Description longue optionnelle. */ + @Size(max = 1000) String description, + + /** Classe d'icône PrimeFaces (ex: pi-check). */ + @Size(max = 100) String icone, + + /** Code couleur hexadécimal (ex: #22C55E). */ + @Size(max = 50) String couleur, + + /** Sévérité PrimeFaces (success, warning...). */ + @Size(max = 20) String severity, + + /** Ordre d'affichage dans les listes. */ + Integer ordreAffichage, + + /** Valeur par défaut pour ce domaine. */ + Boolean estDefaut, + + /** Protégée contre la suppression admin. */ + Boolean estSysteme, + + /** UUID de l'organisation (null = global). */ + UUID organisationId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/UpdateTypeReferenceRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/UpdateTypeReferenceRequest.java new file mode 100644 index 0000000..1472f60 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/reference/request/UpdateTypeReferenceRequest.java @@ -0,0 +1,45 @@ +package dev.lions.unionflow.server.api.dto.reference.request; + +import jakarta.validation.constraints.Size; + +/** + * Requête de mise à jour d'une donnée de référence. + * + *

+ * Tous les champs sont optionnels : seuls les + * champs renseignés seront mis à jour (patch + * partiel). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +public record UpdateTypeReferenceRequest( + + /** Nouveau code (optionnel). */ + @Size(max = 50) String code, + + /** Nouveau libellé (optionnel). */ + @Size(max = 200) String libelle, + + /** Nouvelle description (optionnel). */ + @Size(max = 1000) String description, + + /** Nouvelle icône (optionnel). */ + @Size(max = 100) String icone, + + /** Nouvelle couleur (optionnel). */ + @Size(max = 50) String couleur, + + /** Nouvelle sévérité (optionnel). */ + @Size(max = 20) String severity, + + /** Nouvel ordre d'affichage (optionnel). */ + Integer ordreAffichage, + + /** Nouveau flag par défaut (optionnel). */ + Boolean estDefaut, + + /** Nouvel état actif/inactif (optionnel). */ + Boolean actif) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/reference/response/TypeReferenceResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/reference/response/TypeReferenceResponse.java new file mode 100644 index 0000000..a418096 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/reference/response/TypeReferenceResponse.java @@ -0,0 +1,63 @@ +package dev.lions.unionflow.server.api.dto.reference.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse complète d'une donnée de référence. + * + *

+ * Inclut les champs d'audit (id, dates, version) + * et toutes les métadonnées UI (icône, couleur, + * sévérité). + * + * @author UnionFlow Team + * @version 3.0 + * @since 2026-02-21 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TypeReferenceResponse extends BaseResponse { + + /** Domaine fonctionnel. */ + private String domaine; + + /** Code technique. */ + private String code; + + /** Libellé affiché. */ + private String libelle; + + /** Description longue. */ + private String description; + + /** Classe d'icône PrimeFaces. */ + private String icone; + + /** Code couleur hexadécimal. */ + private String couleur; + + /** Sévérité PrimeFaces. */ + private String severity; + + /** Ordre d'affichage. */ + private Integer ordreAffichage; + + /** Valeur par défaut du domaine. */ + private Boolean estDefaut; + + /** Protégée contre la suppression. */ + private Boolean estSysteme; + + /** UUID de l'organisation (null = global). */ + private UUID organisationId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/registre/AgrementProfessionnelDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/registre/AgrementProfessionnelDTO.java new file mode 100644 index 0000000..90e227a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/registre/AgrementProfessionnelDTO.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.api.dto.registre; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.registre.StatutAgrement; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AgrementProfessionnelDTO extends BaseDTO { + + private String membreId; + private String organisationId; + + // Métier, Ordre (Médecins, Avocats, Artisans...) + private String secteurOuOrdre; + + private String numeroLicenceOuRegistre; + private String categorieClassement; + + private LocalDate dateDelivrance; + private LocalDate dateExpiration; + + private StatutAgrement statut; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/role/request/CreateRoleRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/role/request/CreateRoleRequest.java new file mode 100644 index 0000000..a826217 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/role/request/CreateRoleRequest.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.dto.role.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un rôle. + */ +@Builder +public record CreateRoleRequest( + @NotBlank(message = "Le code est obligatoire") @Size(max = 50) String code, + @NotBlank(message = "Le libellé est obligatoire") @Size(max = 100) String libelle, + @Size(max = 500) String description, + @NotBlank(message = "Le type de rôle est obligatoire") String typeRole, + @NotNull(message = "Le niveau hiérarchique est obligatoire") Integer niveauHierarchique, + UUID organisationId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/role/request/UpdateRoleRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/role/request/UpdateRoleRequest.java new file mode 100644 index 0000000..e076fbb --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/role/request/UpdateRoleRequest.java @@ -0,0 +1,17 @@ +package dev.lions.unionflow.server.api.dto.role.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +/** + * Requête de mise à jour d'un rôle. + */ +@Builder +public record UpdateRoleRequest( + @NotBlank(message = "Le libellé est obligatoire") @Size(max = 100) String libelle, + @Size(max = 500) String description, + @NotNull(message = "Le niveau hiérarchique est obligatoire") Integer niveauHierarchique, + Boolean actif) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/role/response/PermissionResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/role/response/PermissionResponse.java new file mode 100644 index 0000000..6ead6e5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/role/response/PermissionResponse.java @@ -0,0 +1,28 @@ +package dev.lions.unionflow.server.api.dto.role.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +/** + * Réponse contenant les données d'une permission. + * + * @author UnionFlow Team + * @version 3.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PermissionResponse extends BaseResponse { + private String code; + private String module; + private String ressource; + private String action; + private String libelle; + private String description; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/role/response/RoleResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/role/response/RoleResponse.java new file mode 100644 index 0000000..c504e73 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/role/response/RoleResponse.java @@ -0,0 +1,29 @@ +package dev.lions.unionflow.server.api.dto.role.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour un rôle. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RoleResponse extends BaseResponse { + + private String code; + private String libelle; + private String description; + private String typeRole; + private Integer niveauHierarchique; + private UUID organisationId; + private String nomOrganisation; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/BeneficiaireAideDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/BeneficiaireAideDTO.java new file mode 100644 index 0000000..0d9cab5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/BeneficiaireAideDTO.java @@ -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; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactProposantDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactProposantDTO.java new file mode 100644 index 0000000..3918e43 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactProposantDTO.java @@ -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 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 languesPreferees; + + /** Instructions spéciales pour le contact */ + @Size(max = 300, message = "Les instructions ne peuvent pas dépasser 300 caractères") + private String instructionsSpeciales; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactUrgenceDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactUrgenceDTO.java new file mode 100644 index 0000000..6d8e088 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/ContactUrgenceDTO.java @@ -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; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTO.java new file mode 100644 index 0000000..f84ba30 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTO.java @@ -0,0 +1,139 @@ +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(); + + boolean recurrentAvecJour = type == TypeCreneau.RECURRENT && jourSemaine != null; + boolean ponctuelAvecDate = type == TypeCreneau.PONCTUEL && dateSpecifique != null; + if (recurrentAvecJour) { + sb.append(jourSemaine.name()).append(" "); + } else if (ponctuelAvecDate) { + sb.append(dateSpecifique.toString()).append(" "); + } + + sb.append(heureDebut.toString()).append(" - ").append(heureFin.toString()); + + return sb.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CritereSelectionDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CritereSelectionDTO.java new file mode 100644 index 0000000..c8b89ac --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/CritereSelectionDTO.java @@ -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; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/HistoriqueStatutDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/HistoriqueStatutDTO.java new file mode 100644 index 0000000..84c8dc5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/HistoriqueStatutDTO.java @@ -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 donneesAdditionnelles; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/LocalisationDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/LocalisationDTO.java new file mode 100644 index 0000000..fb972b7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/LocalisationDTO.java @@ -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; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/PieceJustificativeDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/PieceJustificativeDTO.java new file mode 100644 index 0000000..3681531 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/PieceJustificativeDTO.java @@ -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; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateCommentaireAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateCommentaireAideRequest.java new file mode 100644 index 0000000..e418a07 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateCommentaireAideRequest.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un commentaire sur une aide. + */ +@Builder +public record CreateCommentaireAideRequest( + @NotBlank(message = "Le contenu du commentaire est obligatoire") @Size(min = 5, max = 2000, message = "Le commentaire doit contenir entre 5 et 2000 caractères") String contenu, + + @NotBlank(message = "Le type de commentaire est obligatoire") String typeCommentaire, + + @NotNull(message = "L'identifiant de l'auteur est obligatoire") UUID auteurId, + + Boolean estPrive, + Boolean estImportant, + UUID commentaireParentId, + List mentionsUtilisateurs) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateDemandeAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateDemandeAideRequest.java new file mode 100644 index 0000000..ded684a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateDemandeAideRequest.java @@ -0,0 +1,46 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.dto.solidarite.BeneficiaireAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.ContactUrgenceDTO; +import dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO; +import dev.lions.unionflow.server.api.dto.solidarite.PieceJustificativeDTO; +import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; +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.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une demande d'aide. + */ +@Builder +public record CreateDemandeAideRequest( + @NotNull(message = "Le type d'aide est obligatoire.") TypeAide typeAide, + @NotBlank(message = "Le titre" + ValidationConstants.OBLIGATOIRE_MESSAGE) @Size(max = 200) String titre, + @NotBlank(message = "La description" + + ValidationConstants.OBLIGATOIRE_MESSAGE) @Size(max = 2000) String description, + @Size(max = 1000) String justification, + @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 2) BigDecimal montantDemande, + @Pattern(regexp = "^[A-Z]{3}$") String devise, + @NotNull(message = "L'identifiant du demandeur est obligatoire") UUID membreDemandeurId, + @NotNull(message = "L'identifiant de l'organisation est obligatoire") UUID associationId, + PrioriteAide priorite, + List piecesJustificatives, + List beneficiaires, + Map donneesPersonnalisees, + List tags, + Boolean estConfidentielle, + LocalisationDTO localisation, + ContactUrgenceDTO contactUrgence, + LocalDate dateLimite) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateEvaluationAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateEvaluationAideRequest.java new file mode 100644 index 0000000..3e0fb5b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreateEvaluationAideRequest.java @@ -0,0 +1,45 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.enums.solidarite.TypeEvaluation; +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.util.Map; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une évaluation d'aide. + */ +@Builder +public record CreateEvaluationAideRequest( + @NotNull(message = "L'identifiant de la demande d'aide est obligatoire") UUID demandeAideId, + + UUID propositionAideId, + + @NotNull(message = "L'identifiant de l'évaluateur est obligatoire") UUID evaluateurId, + + @NotBlank(message = "Le rôle de l'évaluateur est obligatoire") String roleEvaluateur, + + @NotNull(message = "Le type d'évaluation est obligatoire") TypeEvaluation typeEvaluation, + + @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") Double noteGlobale, + + Map notesDetaillees, + + @NotBlank(message = "Le commentaire est obligatoire") @Size(min = 10, max = 1000, message = "Le commentaire doit contenir entre 10 et 1000 caractères") String commentairePrincipal, + + String pointsPositifs, + String pointsAmelioration, + String recommandations, + Boolean recommande, + Boolean aideUtile, + Boolean problemeResolu, + Double noteDelaiReponse, + Double noteCommunication, + Double noteProfessionnalisme, + Double noteRespectEngagements, + Boolean estAnonyme) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreatePropositionAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreatePropositionAideRequest.java new file mode 100644 index 0000000..eb62239 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/CreatePropositionAideRequest.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.Builder; + +/** + * Requête de création d'une proposition d'aide. + */ +@Builder +public record CreatePropositionAideRequest( + TypeAide typeAide, + String titre, + String description, + String conditions, + BigDecimal montantMaximum, + Integer nombreMaxBeneficiaires, + String devise, + String proposantId, + String organisationId, + String demandeAideId, + LocalDateTime dateExpiration, + Integer delaiReponseHeures) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateCommentaireAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateCommentaireAideRequest.java new file mode 100644 index 0000000..6ea383f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateCommentaireAideRequest.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import jakarta.validation.constraints.Size; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de mise à jour d'un commentaire sur une aide. + */ +@Builder +public record UpdateCommentaireAideRequest( + @Size(min = 5, max = 2000, message = "Le commentaire doit contenir entre 5 et 2000 caractères") String contenu, + + Boolean estPrive, + Boolean estImportant, + Boolean estResolu, + UUID resoluteurId) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateDemandeAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateDemandeAideRequest.java new file mode 100644 index 0000000..f80bfec --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateDemandeAideRequest.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.dto.solidarite.BeneficiaireAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.ContactUrgenceDTO; +import dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO; +import dev.lions.unionflow.server.api.dto.solidarite.PieceJustificativeDTO; +import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import lombok.Builder; + +/** + * Requête de mise à jour d'une demande d'aide. + */ +@Builder +public record UpdateDemandeAideRequest( + TypeAide typeAide, + @Size(max = 200) String titre, + @Size(max = 2000) String description, + @Size(max = 1000) String justification, + BigDecimal montantDemande, + String devise, + PrioriteAide priorite, + List piecesJustificatives, + List beneficiaires, + Map donneesPersonnalisees, + List tags, + Boolean estConfidentielle, + Boolean necessiteSuivi, + LocalisationDTO localisation, + ContactUrgenceDTO contactUrgence, + LocalDate dateLimite, + dev.lions.unionflow.server.api.enums.solidarite.StatutAide statut, + BigDecimal montantApprouve, + String commentairesEvaluateur, + String documentsJoints, + java.time.LocalDateTime dateSoumission, + java.time.LocalDateTime dateEvaluation, + java.time.LocalDateTime dateVersement) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateEvaluationAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateEvaluationAideRequest.java new file mode 100644 index 0000000..ffb71db --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdateEvaluationAideRequest.java @@ -0,0 +1,34 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.enums.solidarite.StatutEvaluation; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Size; +import java.util.Map; +import lombok.Builder; + +/** + * Requête de mise à jour d'une évaluation d'aide. + */ +@Builder +public record UpdateEvaluationAideRequest( + @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") Double noteGlobale, + + Map notesDetaillees, + + @Size(min = 10, max = 1000, message = "Le commentaire doit contenir entre 10 et 1000 caractères") String commentairePrincipal, + + String pointsPositifs, + String pointsAmelioration, + String recommandations, + Boolean recommande, + Boolean aideUtile, + Boolean problemeResolu, + Double noteDelaiReponse, + Double noteCommunication, + Double noteProfessionnalisme, + Double noteRespectEngagements, + Boolean estPublique, + Boolean estAnonyme, + StatutEvaluation statut) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdatePropositionAideRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdatePropositionAideRequest.java new file mode 100644 index 0000000..e69c8cf --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/request/UpdatePropositionAideRequest.java @@ -0,0 +1,27 @@ +package dev.lions.unionflow.server.api.dto.solidarite.request; + +import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.Builder; + +/** + * Requête de mise à jour d'une proposition d'aide. + */ +@Builder +public record UpdatePropositionAideRequest( + TypeAide typeAide, + String titre, + String description, + String conditions, + BigDecimal montantMaximum, + Integer nombreMaxBeneficiaires, + String devise, + StatutProposition statut, + Boolean estDisponible, + Boolean estRecurrente, + String frequenceRecurrence, + LocalDateTime dateExpiration, + Integer delaiReponseHeures) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponse.java new file mode 100644 index 0000000..fa9de5c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponse.java @@ -0,0 +1,41 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.dto.solidarite.PieceJustificativeDTO; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse pour un commentaire sur une aide. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CommentaireAideResponse extends BaseResponse { + + private String contenu; + private String typeCommentaire; + private UUID auteurId; + private String auteurNom; + private String auteurRole; + private Boolean estPrive; + private Boolean estImportant; + private UUID commentaireParentId; + private List reponses; + private List piecesJointes; + private List mentionsUtilisateurs; + private Boolean estModifie; + private Integer nombreReactions; + private Boolean estResolu; + private LocalDateTime dateResolution; + private UUID resoluteurId; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponse.java new file mode 100644 index 0000000..9605c36 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponse.java @@ -0,0 +1,171 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.dto.solidarite.BeneficiaireAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.response.CommentaireAideResponse; +import dev.lions.unionflow.server.api.dto.solidarite.ContactUrgenceDTO; +import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO; +import dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO; +import dev.lions.unionflow.server.api.dto.solidarite.PieceJustificativeDTO; +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 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.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour une demande d'aide. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DemandeAideResponse extends BaseResponse { + + private String numeroReference; + private TypeAide typeAide; + private String titre; + private String description; + private String justification; + + private BigDecimal montantDemande; + private BigDecimal montantApprouve; + private BigDecimal montantVerse; + private String devise; + + private UUID membreDemandeurId; + private String nomDemandeur; + private String numeroMembreDemandeur; + + private String evaluateurId; + private String evaluateurNom; + private String approvateurId; + private String approvateurNom; + + private UUID associationId; + private String nomAssociation; + + private StatutAide statut; + private PrioriteAide priorite; + + private String motifRejet; + private String commentairesEvaluateur; + + private LocalDateTime dateSoumission; + private LocalDateTime dateLimiteTraitement; + private LocalDateTime dateEvaluation; + private LocalDateTime dateApprobation; + private LocalDateTime dateVersement; + private LocalDateTime dateCloture; + + private List piecesJustificatives; + private List beneficiaires; + private List historiqueStatuts; + private List commentaires; + private Map donneesPersonnalisees; + private List tags; + + private Boolean estConfidentielle; + private Boolean necessiteSuivi; + private Double scorePriorite; + private Integer nombreVues; + + private LocalisationDTO localisation; + private ContactUrgenceDTO contactUrgence; + + private LocalDate dateLimite; + private Boolean justificatifsFournis; + private String documentsJoints; + private LocalDate dateDebutAide; + private LocalDate dateFinAide; + + private UUID membreAidantId; + private String nomAidant; + private String modeVersement; + private String numeroTransaction; + + private UUID rejeteParId; + private String rejetePar; + private LocalDateTime dateRejet; + private String raisonRejet; + + // === MÉTHODES UTILITAIRES === + + public boolean estModifiable() { + return statut != null && statut.permetModification(); + } + + public boolean peutEtreAnnulee() { + return statut != null && statut.permetAnnulation(); + } + + public boolean estUrgente() { + return priorite != null && priorite.isUrgente(); + } + + public boolean estTerminee() { + return statut != null && statut.isEstFinal(); + } + + public boolean estEnSucces() { + return statut != null && statut.isSucces(); + } + + 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; + }; + } + + 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(); + } + + public boolean estDelaiDepasse() { + return getDelaiRestantHeures() == 0; + } + + public String getStatutLibelle() { + return statut != null ? statut.getLibelle() : "Non défini"; + } + + public String getPrioriteLibelle() { + return priorite != null ? priorite.getLibelle() : "Normale"; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponse.java new file mode 100644 index 0000000..b91a13f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponse.java @@ -0,0 +1,125 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.dto.solidarite.PieceJustificativeDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutEvaluation; +import dev.lions.unionflow.server.api.enums.solidarite.TypeEvaluation; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour une évaluation d'aide. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EvaluationAideResponse extends BaseResponse { + + private UUID demandeAideId; + private UUID propositionAideId; + private UUID evaluateurId; + private String evaluateurNom; + private String roleEvaluateur; + private TypeEvaluation typeEvaluation; + private Double noteGlobale; + private Map notesDetaillees; + private String commentairePrincipal; + private String pointsPositifs; + private String pointsAmelioration; + private String recommandations; + private Boolean recommande; + private Boolean aideUtile; + private Boolean problemeResolu; + private Double noteDelaiReponse; + private Double noteCommunication; + private Double noteProfessionnalisme; + private Double noteRespectEngagements; + private Boolean estPublique; + private Boolean estAnonyme; + private Boolean estVerifiee; + private LocalDateTime dateVerification; + private UUID verificateurId; + private List piecesJointes; + private List tags; + private Map donneesAdditionnelles; + private Integer nombreUtile; + private Integer nombreSignalements; + private StatutEvaluation statut; + + // === MÉTHODES UTILITAIRES === + + public Double getNoteMoyenneDetaillees() { + if (notesDetaillees == null || notesDetaillees.isEmpty()) { + return noteGlobale; + } + + return notesDetaillees.values().stream() + .mapToDouble(Double::doubleValue) + .average() + .orElse(noteGlobale != null ? noteGlobale : 0.0); + } + + public boolean isPositive() { + return noteGlobale != null && noteGlobale >= 4.0; + } + + public boolean isNegative() { + return noteGlobale != null && noteGlobale <= 2.0; + } + + public double getScoreQualite() { + double score = noteGlobale != null ? noteGlobale : 0.0; + + 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; + + if (recommande != null && recommande) + score += 0.2; + if (problemeResolu != null && problemeResolu) + score += 0.3; + + if (nombreSignalements != null && nombreSignalements > 0) + score -= nombreSignalements * 0.1; + + return Math.min(5.0, Math.max(0.0, score)); + } + + public boolean isComplete() { + boolean noteOk = noteGlobale != null; + boolean commentaireNonVide = commentairePrincipal != null && !commentairePrincipal.trim().isEmpty(); + boolean recommandeOk = recommande != null; + boolean aideUtileOk = aideUtile != null; + boolean problemeResoluOk = problemeResolu != null; + return noteOk & commentaireNonVide & recommandeOk & aideUtileOk & problemeResoluOk; + } + + 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é"; + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponse.java new file mode 100644 index 0000000..9bb629e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponse.java @@ -0,0 +1,99 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import dev.lions.unionflow.server.api.dto.solidarite.ContactProposantDTO; +import dev.lions.unionflow.server.api.dto.solidarite.CreneauDisponibiliteDTO; +import dev.lions.unionflow.server.api.dto.solidarite.CritereSelectionDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée pour une proposition d'aide. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PropositionAideResponse extends BaseResponse { + + private String numeroReference; + private TypeAide typeAide; + private String titre; + private String description; + private String conditions; + private BigDecimal montantMaximum; + private Integer nombreMaxBeneficiaires; + private String devise; + + private String proposantId; + private String proposantNom; + private String organisationId; + private String demandeAideId; + + private StatutProposition statut; + private Boolean estDisponible; + private Boolean estRecurrente; + private String frequenceRecurrence; + + private LocalDateTime dateExpiration; + private Integer delaiReponseHeures; + + private List criteresSelection; + private List zonesGeographiques; + private List groupesCibles; + private List competencesRessources; + + private ContactProposantDTO contactProposant; + private List creneauxDisponibilite; + private String modeContactPrefere; + + private Integer nombreDemandesTraitees; + private Integer nombreBeneficiairesAides; + private Double montantTotalVerse; + private Double noteMoyenne; + private Integer nombreEvaluations; + + private List tags; + private Map donneesPersonnalisees; + private Boolean estMiseEnAvant; + private Double scorePertinence; + private Integer nombreVues; + private Integer nombreCandidatures; + + // === MÉTHODES UTILITAIRES === + + public boolean isActiveEtDisponible() { + return statut == StatutProposition.ACTIVE && estDisponible && !isExpiree(); + } + + public boolean isExpiree() { + return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration); + } + + public boolean peutAccepterBeneficiaires() { + return isActiveEtDisponible() && nombreBeneficiairesAides < nombreMaxBeneficiaires; + } + + public double getPourcentageCapaciteUtilisee() { + if (nombreMaxBeneficiaires == null || nombreMaxBeneficiaires == 0) + return 100.0; + return (nombreBeneficiairesAides * 100.0) / nombreMaxBeneficiaires; + } + + public int getPlacesRestantes() { + if (nombreMaxBeneficiaires == null) + return 0; + return Math.max(0, nombreMaxBeneficiaires - (nombreBeneficiairesAides != null ? nombreBeneficiairesAides : 0)); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/CreateSuggestionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/CreateSuggestionRequest.java new file mode 100644 index 0000000..29e62ac --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/CreateSuggestionRequest.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.suggestion.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'une suggestion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateSuggestionRequest( + @NotNull(message = "L'utilisateur est obligatoire") UUID utilisateurId, + String utilisateurNom, + @NotBlank(message = "Le titre est obligatoire") String titre, + String description, + String justification, + String categorie, + String prioriteEstimee, + List piecesJointes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/UpdateSuggestionRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/UpdateSuggestionRequest.java new file mode 100644 index 0000000..fde421c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/request/UpdateSuggestionRequest.java @@ -0,0 +1,23 @@ +package dev.lions.unionflow.server.api.dto.suggestion.request; + +import java.util.List; +import lombok.Builder; + +/** + * Requête de mise à jour d'une suggestion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateSuggestionRequest( + String titre, + String description, + String justification, + String categorie, + String prioriteEstimee, + String statut, + String versionCiblee, + List piecesJointes, + String miseAJour) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/response/SuggestionResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/response/SuggestionResponse.java new file mode 100644 index 0000000..f087648 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/suggestion/response/SuggestionResponse.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.suggestion.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'une suggestion. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SuggestionResponse extends BaseResponse { + + private UUID utilisateurId; + private String utilisateurNom; + private String titre; + private String description; + private String justification; + private String categorie; + private String prioriteEstimee; + private String statut; + private Integer nbVotes; + private Integer nbCommentaires; + private Integer nbVues; + private LocalDateTime dateSoumission; + private LocalDateTime dateEvaluation; + private LocalDateTime dateImplementation; + private String versionCiblee; + private List piecesJointes; + private String miseAJour; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/system/request/UpdateSystemConfigRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/system/request/UpdateSystemConfigRequest.java new file mode 100644 index 0000000..935c5bc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/system/request/UpdateSystemConfigRequest.java @@ -0,0 +1,64 @@ +package dev.lions.unionflow.server.api.dto.system.request; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Request pour mettre à jour la configuration système + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateSystemConfigRequest { + + // Configuration générale + private String applicationName; + private String timezone; + private String defaultLanguage; + private Boolean maintenanceMode; + + // Configuration réseau + private Integer networkTimeout; + private Integer maxRetries; + private Integer connectionPoolSize; + + // Configuration sécurité + private Boolean twoFactorAuthEnabled; + private Integer sessionTimeoutMinutes; + private Boolean auditLoggingEnabled; + + // Configuration performance + private Boolean metricsCollectionEnabled; + private Integer metricsIntervalSeconds; + private Boolean performanceOptimizationEnabled; + + // Configuration backup + private Boolean autoBackupEnabled; + private String backupFrequency; // HOURLY, DAILY, WEEKLY + private Integer backupRetentionDays; + + // Configuration logs + private String logLevel; // TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL + private Integer logRetentionDays; + private Boolean detailedLoggingEnabled; + private Boolean logCompressionEnabled; + + // Configuration monitoring + private Boolean realTimeMonitoringEnabled; + private Integer monitoringIntervalSeconds; + private Boolean emailAlertsEnabled; + private Boolean pushAlertsEnabled; + + // Configuration alertes + private Boolean cpuHighAlertEnabled; + private Integer cpuThresholdPercent; + private Boolean memoryLowAlertEnabled; + private Integer memoryThresholdPercent; + private Boolean criticalErrorAlertEnabled; + private Boolean connectionFailureAlertEnabled; + private Integer connectionFailureThreshold; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/system/response/CacheStatsResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/CacheStatsResponse.java new file mode 100644 index 0000000..734f237 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/CacheStatsResponse.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.system.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * Response contenant les statistiques du cache système + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CacheStatsResponse { + + private Long totalSizeBytes; + private String totalSizeFormatted; // ex: "2.3 GB" + private Integer totalEntries; + private Double hitRate; // pourcentage (0-100) + private Long hits; + private Long misses; + private LocalDateTime lastCleared; + + // Statistiques par cache + private Map caches; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CacheEntry { + private String name; + private Long sizeBytes; + private Integer entries; + private Double hitRate; + private Long hits; + private Long misses; + private LocalDateTime lastAccessed; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemConfigResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemConfigResponse.java new file mode 100644 index 0000000..078c2ca --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemConfigResponse.java @@ -0,0 +1,72 @@ +package dev.lions.unionflow.server.api.dto.system.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * Response contenant la configuration système complète + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemConfigResponse { + + // Configuration générale + private String applicationName; + private String timezone; + private String defaultLanguage; + private Boolean maintenanceMode; + private String version; + private LocalDateTime lastUpdated; + + // Configuration réseau + private Integer networkTimeout; + private Integer maxRetries; + private Integer connectionPoolSize; + + // Configuration sécurité + private Boolean twoFactorAuthEnabled; + private Integer sessionTimeoutMinutes; + private Boolean auditLoggingEnabled; + + // Configuration performance + private Boolean metricsCollectionEnabled; + private Integer metricsIntervalSeconds; + private Boolean performanceOptimizationEnabled; + + // Configuration backup + private Boolean autoBackupEnabled; + private String backupFrequency; + private Integer backupRetentionDays; + private LocalDateTime lastBackup; + + // Configuration logs + private String logLevel; + private Integer logRetentionDays; + private Boolean detailedLoggingEnabled; + private Boolean logCompressionEnabled; + + // Configuration monitoring + private Boolean realTimeMonitoringEnabled; + private Integer monitoringIntervalSeconds; + private Boolean emailAlertsEnabled; + private Boolean pushAlertsEnabled; + + // Configuration alertes + private Boolean cpuHighAlertEnabled; + private Integer cpuThresholdPercent; + private Boolean memoryLowAlertEnabled; + private Integer memoryThresholdPercent; + private Boolean criticalErrorAlertEnabled; + private Boolean connectionFailureAlertEnabled; + private Integer connectionFailureThreshold; + + // Statut système + private String systemStatus; // OPERATIONAL, DEGRADED, DOWN + private Long uptime; // en millisecondes +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponse.java new file mode 100644 index 0000000..f22481d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponse.java @@ -0,0 +1,129 @@ +package dev.lions.unionflow.server.api.dto.system.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * Response contenant les métriques système en temps réel + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemMetricsResponse { + + // Métriques CPU + private Double cpuUsagePercent; + private Integer availableProcessors; + private Double systemLoadAverage; + + // Métriques mémoire + private Long totalMemoryBytes; + private Long usedMemoryBytes; + private Long freeMemoryBytes; + private Long maxMemoryBytes; + private Double memoryUsagePercent; + private String totalMemoryFormatted; // ex: "8.0 GB" + private String usedMemoryFormatted; // ex: "5.4 GB" + private String freeMemoryFormatted; // ex: "2.6 GB" + + // Métriques disque + private Long totalDiskBytes; + private Long usedDiskBytes; + private Long freeDiskBytes; + private Double diskUsagePercent; + private String totalDiskFormatted; // ex: "500 GB" + private String usedDiskFormatted; // ex: "225 GB" + private String freeDiskFormatted; // ex: "275 GB" + + // Métriques utilisateurs + private Integer activeUsersCount; // Utilisateurs connectés actuellement + private Integer totalUsersCount; // Total utilisateurs dans le système + private Integer activeSessionsCount; // Sessions actives + private Integer failedLoginAttempts24h; // Échecs login dernières 24h + + // Métriques API + private Long apiRequestsLastHour; // Requêtes API dernière heure + private Long apiRequestsToday; // Requêtes API aujourd'hui + private Double averageResponseTimeMs; // Temps réponse moyen (ms) + private Long totalRequestsCount; // Total requêtes depuis démarrage + + // Métriques base de données + private Integer dbConnectionPoolSize; // Taille pool connexions + private Integer dbActiveConnections; // Connexions actives + private Integer dbIdleConnections; // Connexions en attente + private Boolean dbHealthy; // État santé DB + + // Métriques erreurs et logs + private Integer criticalErrorsCount; // Erreurs critiques + private Integer warningsCount; // Avertissements + private Integer infoLogsCount; // Logs info + private Integer debugLogsCount; // Logs debug + private Long totalLogsCount; // Total logs + + // Métriques réseau + private Double networkBytesReceivedPerSec; // Octets reçus/sec + private Double networkBytesSentPerSec; // Octets envoyés/sec + private String networkInFormatted; // ex: "12.5 MB/s" + private String networkOutFormatted; // ex: "8.2 MB/s" + + // Métriques système + private String systemStatus; // OPERATIONAL, DEGRADED, MAINTENANCE, DOWN + private Long uptimeMillis; // Uptime en ms + private String uptimeFormatted; // ex: "5j 12h 34m" + private LocalDateTime startTime; // Date/heure démarrage + private LocalDateTime currentTime; // Date/heure actuelle + private String javaVersion; // Version Java + private String quarkusVersion; // Version Quarkus + private String applicationVersion; // Version application + + // Métriques maintenance + private LocalDateTime lastBackup; // Dernière sauvegarde + private LocalDateTime nextScheduledMaintenance; // Prochaine maintenance + private LocalDateTime lastMaintenance; // Dernière maintenance + + // URLs et configuration réseau + private String apiBaseUrl; // URL API + private String authServerUrl; // URL serveur auth (Keycloak) + private String cdnUrl; // URL CDN (si applicable) + + // Statistiques de stockage (cache) + private Long totalCacheSizeBytes; + private String totalCacheSizeFormatted; // ex: "450 MB" + private Integer totalCacheEntries; + + /** + * Helper pour formater les bytes en taille lisible + */ + public static String formatBytes(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + String pre = "KMGTPE".charAt(exp - 1) + ""; + return String.format(java.util.Locale.US, "%.1f %sB", bytes / Math.pow(1024, exp), pre); + } + + /** + * Helper pour formater l'uptime + */ + public static String formatUptime(long millis) { + long seconds = millis / 1000; + long minutes = seconds / 60; + long hours = minutes / 60; + long days = hours / 24; + + hours = hours % 24; + minutes = minutes % 60; + + if (days > 0) { + return String.format("%dj %dh %dm", days, hours, minutes); + } else if (hours > 0) { + return String.format("%dh %dm", hours, minutes); + } else { + return String.format("%dm", minutes); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemTestResultResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemTestResultResponse.java new file mode 100644 index 0000000..12df543 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/system/response/SystemTestResultResponse.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.dto.system.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * Response pour les résultats de test système (email, database, etc.) + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemTestResultResponse { + + private String testType; // EMAIL, DATABASE, NETWORK, CACHE + private Boolean success; + private String message; + private Long responseTimeMs; + private LocalDateTime testedAt; + private String details; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/CreateTicketRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/CreateTicketRequest.java new file mode 100644 index 0000000..cdca3fe --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/CreateTicketRequest.java @@ -0,0 +1,29 @@ +package dev.lions.unionflow.server.api.dto.ticket.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de création d'un ticket de support. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record CreateTicketRequest( + @NotNull(message = "L'ID de l'utilisateur est obligatoire") UUID utilisateurId, + + @NotBlank(message = "Le sujet est obligatoire") @Size(max = 200) String sujet, + + @NotBlank(message = "La description est obligatoire") @Size(max = 2000) String description, + + @NotBlank(message = "La catégorie est obligatoire") String categorie, + + @NotBlank(message = "La priorité est obligatoire") String priorite, + + List piecesJointes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/UpdateTicketRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/UpdateTicketRequest.java new file mode 100644 index 0000000..665d529 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/request/UpdateTicketRequest.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.ticket.request; + +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +/** + * Requête de mise à jour d'un ticket de support. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Builder +public record UpdateTicketRequest( + @Size(max = 200) String sujet, + @Size(max = 2000) String description, + String categorie, + String priorite, + String statut, + UUID agentId, + String resolution, + List piecesJointes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/ticket/response/TicketResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/response/TicketResponse.java new file mode 100644 index 0000000..226ef3b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/ticket/response/TicketResponse.java @@ -0,0 +1,51 @@ +package dev.lions.unionflow.server.api.dto.ticket.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse détaillée d'un ticket de support. + * + * @author UnionFlow Team + * @version 1.0 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TicketResponse extends BaseResponse { + + private String numeroTicket; + private UUID utilisateurId; + private String sujet; + private String description; + + /** TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE */ + private String categorie; + + /** BASSE, NORMALE, HAUTE, URGENTE */ + private String priorite; + + /** OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME */ + private String statut; + + private UUID agentId; + private String agentNom; + private LocalDateTime dateDerniereReponse; + private LocalDateTime dateResolution; + private LocalDateTime dateFermeture; + private Integer nbMessages; + private Integer nbFichiers; + private Integer noteSatisfaction; + private String resolution; + private List piecesJointes; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineRequest.java new file mode 100644 index 0000000..f01a78f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineRequest.java @@ -0,0 +1,49 @@ +package dev.lions.unionflow.server.api.dto.tontine; + +import dev.lions.unionflow.server.api.enums.tontine.FrequenceTour; +import dev.lions.unionflow.server.api.enums.tontine.TypeTontine; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Requête de création/paramétrage d'une tontine") +public class TontineRequest { + + @NotBlank(message = "Le nom de la tontine est obligatoire") + private String nom; + + private String description; + + @NotBlank(message = "L'ID de l'organisation est obligatoire") + private String organisationId; + + @NotNull(message = "Le type de tontine est obligatoire") + private TypeTontine typeTontine; + + @NotNull(message = "La fréquence des tours est obligatoire") + private FrequenceTour frequence; + + @NotNull(message = "La date de début souhaitée est obligatoire") + private LocalDate dateDebutPrevue; + + @Schema(description = "Montant fixe si tontine classique") + @DecimalMin(value = "0.0", message = "Le montant de cotisation ne peut pas être négatif") + private BigDecimal montantMiseParTour; + + @Schema(description = "Le nombre maximum de membres pouvant rejoindre cette tontine (Optionnel)") + @Min(value = 2, message = "Une tontine nécessite au moins 2 membres") + private Integer limiteParticipants; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineResponse.java new file mode 100644 index 0000000..6d09f56 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TontineResponse.java @@ -0,0 +1,46 @@ +package dev.lions.unionflow.server.api.dto.tontine; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.tontine.FrequenceTour; +import dev.lions.unionflow.server.api.enums.tontine.StatutTontine; +import dev.lions.unionflow.server.api.enums.tontine.TypeTontine; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Réponse contenant la structure d'une tontine") +public class TontineResponse extends BaseDTO { + + private String nom; + private String description; + private String organisationId; + + private TypeTontine typeTontine; + private FrequenceTour frequence; + private StatutTontine statut; + + private LocalDate dateDebutEffective; + private LocalDate dateFinPrevue; + + private BigDecimal montantMiseParTour; + private Integer limiteParticipants; + + @Schema(description = "Statistiques directes : nombre de participants actifs et fonds totaux levés à ce jour") + private Integer nombreParticipantsActuels; + private BigDecimal fondTotalCollecte; + + @Schema(description = "Calendrier généré des tours de cette tontine") + private List calendrierTours; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TourTontineDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TourTontineDTO.java new file mode 100644 index 0000000..6e274a0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/tontine/TourTontineDTO.java @@ -0,0 +1,41 @@ +package dev.lions.unionflow.server.api.dto.tontine; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Informations d'un tour spécifique d'une tontine rotative") +public class TourTontineDTO extends BaseDTO { + + private String tontineId; + + @Schema(description = "Ordre cronologique (Tour 1, Tour 2...)") + private Integer ordreTour; + + private LocalDate dateOuvertureCotisations; + private LocalDate dateTirageOuRemise; + + @Schema(description = "Montant cible total (Ex: 10 membres x 10 000 = 100 000 cibles)") + private BigDecimal montantCible; + + @Schema(description = "Montant de la cagnotte réellement collectée pour ce tour") + private BigDecimal cagnotteCollectee; + + @Schema(description = "Membre désigné pour remporter ce tour (bénéficiaire de la caisse)") + private String membreBeneficiaireId; + + @Schema(description = "Statut: A_VENIR, EN_COURS, CLOTURE/REMIS") + private String statutInterne; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/user/request/CreateUserRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/user/request/CreateUserRequest.java new file mode 100644 index 0000000..db45c63 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/user/request/CreateUserRequest.java @@ -0,0 +1,45 @@ +package dev.lions.unionflow.server.api.dto.user.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.Map; +import lombok.Builder; + +/** + * Requête de création d'un nouvel utilisateur Keycloak. + * + * @param username Nom d'utilisateur unique. + * @param prenom Prénom de l'utilisateur. + * @param nom Nom de l'utilisateur. + * @param email Adresse email (doit être unique). + * @param password Mot de passe initial. + * @param enabled Indique si le compte est activé (par défaut true). + * @param emailVerified Indique si l'email est vérifié (par défaut false). + * @param realmRoles Liste des rôles à assigner. + * @param attributes Attributs personnalisés (optionnel). + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-28 + */ +@Builder +public record CreateUserRequest( + @NotBlank(message = "Le nom d'utilisateur est obligatoire") @Size(min = 3, max = 50) String username, + + @NotBlank(message = "Le prénom est obligatoire") @Size(max = 100) String prenom, + + @NotBlank(message = "Le nom est obligatoire") @Size(max = 100) String nom, + + @NotBlank(message = "L'email est obligatoire") @Email(message = "Format d'email invalide") String email, + + @NotBlank(message = "Le mot de passe est obligatoire") @Size(min = 8, message = "Le mot de passe doit contenir au moins 8 caractères") String password, + + Boolean enabled, + + Boolean emailVerified, + + List realmRoles, + + Map> attributes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/user/request/UpdateUserRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/user/request/UpdateUserRequest.java new file mode 100644 index 0000000..18a97de --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/user/request/UpdateUserRequest.java @@ -0,0 +1,38 @@ +package dev.lions.unionflow.server.api.dto.user.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.Map; +import lombok.Builder; + +/** + * Requête de mise à jour d'un utilisateur Keycloak. + * + * @param prenom Prénom de l'utilisateur (optionnel). + * @param nom Nom de l'utilisateur (optionnel). + * @param email Adresse email (optionnel). + * @param enabled Indique si le compte est activé (optionnel). + * @param emailVerified Indique si l'email est vérifié (optionnel). + * @param realmRoles Liste des rôles à assigner (optionnel - écrase les rôles existants si fourni). + * @param attributes Attributs personnalisés (optionnel). + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-28 + */ +@Builder +public record UpdateUserRequest( + @Size(max = 100) String prenom, + + @Size(max = 100) String nom, + + @Email(message = "Format d'email invalide") String email, + + Boolean enabled, + + Boolean emailVerified, + + List realmRoles, + + Map> attributes) { +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/user/response/UserResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/user/response/UserResponse.java new file mode 100644 index 0000000..739bd11 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/user/response/UserResponse.java @@ -0,0 +1,52 @@ +package dev.lions.unionflow.server.api.dto.user.response; + +import dev.lions.unionflow.server.api.dto.base.BaseResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Réponse contenant les données d'un utilisateur Keycloak. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-28 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserResponse extends BaseResponse { + + // ── Identité ─────────────────────────────── + private String username; + private String prenom; + private String nom; + private String email; + private Boolean emailVerified; + + // ── État du compte ───────────────────────── + private Boolean enabled; + private String statut; // ACTIF, INACTIF, SUSPENDU + private LocalDateTime dateCreation; + private LocalDateTime derniereConnexion; + + // ── Rôles et permissions ─────────────────── + private List roles; + private List realmRoles; + private String primaryRole; // Rôle principal + + // ── Attributs personnalisés ──────────────── + private Map> attributes; + + // ── Métadonnées ──────────────────────────── + private Boolean totp; // 2FA activé + private Integer failedLoginAttempts; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteRequest.java new file mode 100644 index 0000000..1c9f2a7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteRequest.java @@ -0,0 +1,52 @@ +package dev.lions.unionflow.server.api.dto.vote; + +import dev.lions.unionflow.server.api.enums.vote.ModeScrutin; +import dev.lions.unionflow.server.api.enums.vote.TypeVote; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "Requête de création d'une campagne de vote électronique") +public class CampagneVoteRequest { + + @NotBlank(message = "Le titre de la campagne est obligatoire") + private String titre; + + private String descriptionOuResolution; + + @NotBlank(message = "L'ID de l'organisation est obligatoire") + private String organisationId; + + @NotNull(message = "Le type de vote est requis") + private TypeVote typeVote; + + @NotNull(message = "Le mode de scrutin est requis") + private ModeScrutin modeScrutin; + + @NotNull(message = "La date d'ouverture des scrutins est obligatoire") + @Future(message = "L'ouverture des votes doit être une date future") + private LocalDateTime dateOuverture; + + @NotNull(message = "La date de fermeture est obligatoire") + @Future(message = "La fermeture des votes doit être une date future") + private LocalDateTime dateFermeture; + + @Schema(description = "Restreindre le vote aux membres à jour de cotisations (Optionnel)") + @Builder.Default + private Boolean restreindreMembresAJour = true; + + @Schema(description = "Permettre le vote blanc (Optionnel)") + @Builder.Default + private Boolean autoriserVoteBlanc = true; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteResponse.java new file mode 100644 index 0000000..b03c68f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CampagneVoteResponse.java @@ -0,0 +1,44 @@ +package dev.lions.unionflow.server.api.dto.vote; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.vote.ModeScrutin; +import dev.lions.unionflow.server.api.enums.vote.StatutVote; +import dev.lions.unionflow.server.api.enums.vote.TypeVote; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CampagneVoteResponse extends BaseDTO { + + private String titre; + private String descriptionOuResolution; + private String organisationId; + + private TypeVote typeVote; + private ModeScrutin modeScrutin; + private StatutVote statut; + + private LocalDateTime dateOuverture; + private LocalDateTime dateFermeture; + + private Boolean restreindreMembresAJour; + private Boolean autoriserVoteBlanc; + + // Statistiques macro pour l'administrateur + private Integer totalElecteursInscrits; + private Integer totalVotantsEffectifs; + private Integer totalVotesBlancsOuNuls; + private Double tauxDeParticipation; + + private List candidatsExposes; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/vote/CandidatDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CandidatDTO.java new file mode 100644 index 0000000..ef61a9c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/vote/CandidatDTO.java @@ -0,0 +1,34 @@ +package dev.lions.unionflow.server.api.dto.vote; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import java.math.BigDecimal; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CandidatDTO extends BaseDTO { + + private String campagneVoteId; + + // Si l'élection concerne une liste ou un individu (ex: "OUI", "NON", "Liste A", + // ou Membre) + private String nomCandidatureOuChoix; + + // Facultatif : si le choix est relié à un membre / un ticket + private String membreIdAssocie; + + private String professionDeFoi; + private String photoUrl; + + // Nombre de voix accumulées (souvent caché tant que StatutVote != + // RESULTATS_PUBLIES) + private Integer nombreDeVoix; + private BigDecimal pourcentageObtenu; +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/wave/CompteWaveDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/wave/CompteWaveDTO.java new file mode 100644 index 0000000..6c5b437 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/wave/CompteWaveDTO.java @@ -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; +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/wave/TransactionWaveDTO.java b/src/main/java/dev/lions/unionflow/server/api/dto/wave/TransactionWaveDTO.java new file mode 100644 index 0000000..6d62848 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/wave/TransactionWaveDTO.java @@ -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; +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnement.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnement.java new file mode 100644 index 0000000..3a6fb52 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnement.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormule.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormule.java new file mode 100644 index 0000000..ca379ce --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormule.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscription.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscription.java new file mode 100644 index 0000000..c2a1976 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscription.java @@ -0,0 +1,14 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +public enum StatutSouscription { + ACTIVE("Active"), + EXPIREE("Expirée"), + SUSPENDUE("Suspendue — quota dépassé ou impayé"), + RESILIEE("Résiliée"); + + private final String libelle; + + StatutSouscription(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormule.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormule.java new file mode 100644 index 0000000..0810e4d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormule.java @@ -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 { + STARTER("Starter — 1 à 50 membres — 5 000 XOF/mois"), + STANDARD("Standard — 51 à 200 membres — 7 000 XOF/mois"), + PREMIUM("Premium — 201 à 500 membres — 9 000 XOF/mois"), + CRYSTAL("Crystal — 501+ membres — 10 000 XOF/mois"); + + private final String libelle; + + TypeFormule(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnement.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnement.java new file mode 100644 index 0000000..3258154 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnement.java @@ -0,0 +1,12 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +public enum TypePeriodeAbonnement { + MENSUEL("Mensuel"), + ANNUEL("Annuel — 2 mois offerts"); + + private final String libelle; + + TypePeriodeAbonnement(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/agricole/StatutCampagneAgricole.java b/src/main/java/dev/lions/unionflow/server/api/enums/agricole/StatutCampagneAgricole.java new file mode 100644 index 0000000..06ed5ee --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/agricole/StatutCampagneAgricole.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.enums.agricole; + +public enum StatutCampagneAgricole { + PREPARATION("Distribution des intrants et semences"), + LABOUR_SEMIS("Saison des semis et du labour"), + ENTRETIEN("Croissance et traitement phytosanitaire"), + RECOLTE("Saison des récoltes"), + COMMERCIALISATION("Stockage et vente des coopérateurs"), + CLOTUREE("Campagne soldée"); + + private final String libelle; + + StatutCampagneAgricole(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/analytics/FormatExport.java b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/FormatExport.java new file mode 100644 index 0000000..c93a6a9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/FormatExport.java @@ -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 + * + *

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}; + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyse.java b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyse.java new file mode 100644 index 0000000..899353d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyse.java @@ -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 + * + *

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"; + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetrique.java b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetrique.java new file mode 100644 index 0000000..1f382cd --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetrique.java @@ -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 + * + *

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 + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/audit/PorteeAudit.java b/src/main/java/dev/lions/unionflow/server/api/enums/audit/PorteeAudit.java new file mode 100644 index 0000000..5313135 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/audit/PorteeAudit.java @@ -0,0 +1,12 @@ +package dev.lions.unionflow.server.api.enums.audit; + +public enum PorteeAudit { + ORGANISATION("Visible par le manager de l'organisation sur ses propres données"), + PLATEFORME("Visible uniquement par le Super Admin UnionFlow — audit global"); + + private final String libelle; + + PorteeAudit(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/LienParente.java b/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/LienParente.java new file mode 100644 index 0000000..1389b58 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/LienParente.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.enums.ayantdroit; + +public enum LienParente { + CONJOINT("Conjoint(e) légal(e)"), + ENFANT("Enfant"), + PARENT("Père ou Mère"), + FRATRIE("Frère ou Sœur"), + TUTEUR_LEGAL("Tuteur ou Tutrice légal(e)"), + AUTRE("Autre bénéficiaire désigné"); + + private final String libelle; + + LienParente(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/StatutAyantDroit.java b/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/StatutAyantDroit.java new file mode 100644 index 0000000..cb5bc64 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/ayantdroit/StatutAyantDroit.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.enums.ayantdroit; + +public enum StatutAyantDroit { + EN_ATTENTE("En attente de validation documentaire"), + ACTIF("Actif et couvert"), + INACTIF("Inactif (Couverture suspendue)"), + REJETE("Dossier rejeté"), + DECEDE("Déclaré décédé"), + MAJORITE_ATTEINTE("Majorité atteinte (Sortie du statut Enfant)"); + + private final String libelle; + + StatutAyantDroit(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/collectefonds/StatutCampagneCollecte.java b/src/main/java/dev/lions/unionflow/server/api/enums/collectefonds/StatutCampagneCollecte.java new file mode 100644 index 0000000..e270968 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/collectefonds/StatutCampagneCollecte.java @@ -0,0 +1,19 @@ +package dev.lions.unionflow.server.api.enums.collectefonds; + +public enum StatutCampagneCollecte { + BROUILLON("Paramétrage de la page de don en cours"), + EN_COURS("Active et ouverte aux dons"), + ATTEINTE("Objectif financier atteint (Optionnel de fermer)"), + EXPIREE("Date de fin de campagne dépassée"), + SUSPENDUE("Suspendue temporairement"); + + private final String libelle; + + StatutCampagneCollecte(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptable.java b/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptable.java new file mode 100644 index 0000000..8001190 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptable.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptable.java b/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptable.java new file mode 100644 index 0000000..804499a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptable.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/culte/TypeDonReligieux.java b/src/main/java/dev/lions/unionflow/server/api/enums/culte/TypeDonReligieux.java new file mode 100644 index 0000000..7c377a8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/culte/TypeDonReligieux.java @@ -0,0 +1,19 @@ +package dev.lions.unionflow.server.api.enums.culte; + +public enum TypeDonReligieux { + QUETE_ORDINAIRE("Quête ordinaire de l'office religieux"), + DIME("Obligation ou Dîme régulière"), + ZAKAT("Zakat (Aumône légale)"), + OFFRANDE_SPECIALE("Offrande spéciale d'action de grâce"), + INTENTION_PRIERE("Participation pour intention de prière"); + + private final String libelle; + + TypeDonReligieux(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/document/TypeDocument.java b/src/main/java/dev/lions/unionflow/server/api/enums/document/TypeDocument.java new file mode 100644 index 0000000..0a646be --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/document/TypeDocument.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenement.java b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenement.java new file mode 100644 index 0000000..57c9349 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenement.java @@ -0,0 +1,159 @@ +package dev.lions.unionflow.server.api.enums.evenement; + +/** + * Énumération des priorités d'événements dans UnionFlow + * + *

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 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 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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenement.java b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenement.java new file mode 100644 index 0000000..9018cfa --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenement.java @@ -0,0 +1,233 @@ +package dev.lions.unionflow.server.api.enums.evenement; + +/** + * Énumération des statuts d'événements dans UnionFlow + * + *

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 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 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 + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetier.java b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetier.java new file mode 100644 index 0000000..bb70b36 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetier.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisation.java b/src/main/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisation.java new file mode 100644 index 0000000..9e4768e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisation.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/StatutFormule.java b/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/StatutFormule.java new file mode 100644 index 0000000..e943ca5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/StatutFormule.java @@ -0,0 +1,21 @@ +package dev.lions.unionflow.server.api.enums.formuleabonnement; + +/** + * Statuts des formules d'abonnement. + */ +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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/TypeFormule.java b/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/TypeFormule.java new file mode 100644 index 0000000..aef4eb5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/formuleabonnement/TypeFormule.java @@ -0,0 +1,21 @@ +package dev.lions.unionflow.server.api.enums.formuleabonnement; + +/** + * Types de formules d'abonnement 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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/gouvernance/NiveauEchelon.java b/src/main/java/dev/lions/unionflow/server/api/enums/gouvernance/NiveauEchelon.java new file mode 100644 index 0000000..0d7552e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/gouvernance/NiveauEchelon.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.enums.gouvernance; + +public enum NiveauEchelon { + SIEGE_MONDIAL("Siège / Direction Globale"), + NATIONAL("Fédération ou Bureau National"), + REGIONAL("Ligue ou Section Régionale/Départementale"), + LOCAL("Antenne / Cellule de base locale"); + + private final String libelle; + + NiveauEchelon(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/membre/LienParente.java b/src/main/java/dev/lions/unionflow/server/api/enums/membre/LienParente.java new file mode 100644 index 0000000..3b02848 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/membre/LienParente.java @@ -0,0 +1,14 @@ +package dev.lions.unionflow.server.api.enums.membre; + +public enum LienParente { + CONJOINT("Conjoint(e)"), + ENFANT("Enfant"), + PARENT("Parent"), + AUTRE("Autre"); + + private final String libelle; + + LienParente(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/membre/NiveauVigilanceKyc.java b/src/main/java/dev/lions/unionflow/server/api/enums/membre/NiveauVigilanceKyc.java new file mode 100644 index 0000000..ef50f6c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/membre/NiveauVigilanceKyc.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.membre; + +/** + * Niveau de vigilance KYC (Know Your Customer) pour la LCB-FT. + * Détermine l'exigence d'identification et de justification des opérations. + */ +public enum NiveauVigilanceKyc { + /** Vigilance simplifiée — identification de base */ + SIMPLIFIE("Vigilance simplifiée"), + /** Vigilance renforcée — justifications et pièces supplémentaires requises */ + RENFORCE("Vigilance renforcée"); + + private final String libelle; + + NiveauVigilanceKyc(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateur.java b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateur.java new file mode 100644 index 0000000..37c91c9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateur.java @@ -0,0 +1,14 @@ +package dev.lions.unionflow.server.api.enums.membre; + +public enum StatutCompteUtilisateur { + EN_ATTENTE_VALIDATION("En attente — inscription soumise, validation admin requise"), + ACTIF("Actif — peut se connecter"), + SUSPENDU("Suspendu — accès bloqué temporairement"), + DESACTIVE("Désactivé — compte supprimé logiquement"); + + private final String libelle; + + StatutCompteUtilisateur(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutKyc.java b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutKyc.java new file mode 100644 index 0000000..8772498 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutKyc.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.enums.membre; + +/** + * Statut de la vérification d'identité (KYC) d'un membre — LCB-FT. + */ +public enum StatutKyc { + /** Identité non encore vérifiée */ + NON_VERIFIE("Non vérifié"), + /** Vérification en cours */ + EN_COURS("En cours"), + /** Identité vérifiée — éligible aux opérations */ + VERIFIE("Vérifié"), + /** Vérification refusée ou dossier incomplet */ + REFUSE("Refusé"); + + private final String libelle; + + StatutKyc(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutMembre.java b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutMembre.java new file mode 100644 index 0000000..a4ca328 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/membre/StatutMembre.java @@ -0,0 +1,29 @@ +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 { + EN_ATTENTE_VALIDATION("En attente de validation"), + ACTIF("Actif"), + INACTIF("Inactif — cotisations en retard"), + SUSPENDU("Suspendu — décision disciplinaire"), + DEMISSIONNAIRE("Démissionnaire"), + RADIE("Radié — exclusion définitive"), + HONORAIRE("Honoraire — sans cotisation obligatoire"), + DECEDE("Décédé — archivage / gestion ayants droit"); + + private final String libelle; + + StatutMembre(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/module/TypeModule.java b/src/main/java/dev/lions/unionflow/server/api/enums/module/TypeModule.java new file mode 100644 index 0000000..5a09218 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/module/TypeModule.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.enums.module; + +public enum TypeModule { + COTISATIONS("Gestion des cotisations"), + EVENEMENTS("Gestion des événements"), + SOLIDARITE("Fonds de solidarité"), + COMPTABILITE("Comptabilité simplifiée OHADA"), + DOCUMENTS("Gestion documentaire — 1 Go max"), + NOTIFICATIONS("Notifications multi-canal"), + CREDIT_EPARGNE("Épargne & crédit MEC"), + AYANTS_DROIT("Gestion ayants droit — mutuelles santé"), + TONTINE("Tontine / épargne rotative"), + ONG_PROJETS("Projets humanitaires / ONG"), + COOP_AGRICOLE("Coopérative agricole"), + VOTE_INTERNE("Vote interne électronique"), + COLLECTE_FONDS("Collecte de fonds"), + REGISTRE_PROFESSIONNEL("Registre officiel membres agréés"), + CULTES_RELIGIEUX("Gestion cultes et dîmes"), + GOUVERNANCE_MULTI("Gouvernance multi-niveaux"); + + private final String libelle; + + TypeModule(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutDemandeCredit.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutDemandeCredit.java new file mode 100644 index 0000000..17a8622 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutDemandeCredit.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.credit; + +/** + * Cycle de vie global d'une demande de financement. + */ +public enum StatutDemandeCredit { + BROUILLON("Brouillon / En constitution"), + SOUMISE("Soumise (Attente analyse)"), + EN_EVALUATION("En cours d'étude par le comité"), + INFORMATIONS_REQUISES("Informations complémenaires requises"), + APPROUVEE("Approuvée (Attente déblocage)"), + REJETEE("Rejetée par le comité"), + DECAISSEE("Décaissement effectué / En cours de remboursement"), + SOLDEE("Crédit totalement remboursé et soldé"), + EN_CONTENTIEUX("En défaut de paiement / Contentieux juridique"); + + private final String libelle; + + StatutDemandeCredit(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutEcheanceCredit.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutEcheanceCredit.java new file mode 100644 index 0000000..c6fefc3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/StatutEcheanceCredit.java @@ -0,0 +1,25 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.credit; + +/** + * Évolution temporelle d'une traite ou d'une échéance prévue au tableau + * d'amortissement. + */ +public enum StatutEcheanceCredit { + A_VENIR("Échéance à venir"), + EXIGIBLE("Échéance exigible ce jour"), + PAYEE("Intégralement payée"), + PAYEE_PARTIELLEMENT("Partiellement payée"), + EN_RETARD("En retard de paiement"), + IMPAYEE("Déclarée impayée / Recouvrement"), + RESTRUCTUREE("Échéance restructurée / reportée"); + + private final String libelle; + + StatutEcheanceCredit(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeCredit.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeCredit.java new file mode 100644 index 0000000..40f8a8a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeCredit.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.credit; + +/** + * Types de produits de crédit ou de financement. + */ +public enum TypeCredit { + CONSOMMATION("Crédit à la consommation"), + IMMOBILIER("Crédit immobilier / Construction"), + PROFESSIONNEL("Financement d'activité génératrice de revenus (AGR)"), + AGRICOLE("Campagne agricole / Matériel"), + SCOLAIRE("Crédit scolaire / Études"), + URGENCE("Prêt d'urgence (Santé, Social)"), + DECOUVERT("Découvert autorisé"); + + private final String libelle; + + TypeCredit(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeGarantie.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeGarantie.java new file mode 100644 index 0000000..546b11f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/credit/TypeGarantie.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.credit; + +/** + * Types de garanties apportées pour sécuriser un crédit. + */ +public enum TypeGarantie { + EPARGNE_BLOQUEE("Fonds bloqués sur compte d'épargne (Nantissement)"), + CAUTION_SOLIDAIRE("Avaliseurs / Cautions solidaires (Membres phys.)"), + MATERIELLE("Garantie sur équipement / Véhicule"), + IMMOBILIERE("Hypothèque / Titre foncier"), + FOND_GARANTIE("Fonds de garantie externe (ex. FONGIP)"); + + private final String libelle; + + TypeGarantie(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/StatutCompteEpargne.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/StatutCompteEpargne.java new file mode 100644 index 0000000..a8ee174 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/StatutCompteEpargne.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.epargne; + +/** + * Statuts possibles pour un compte d'épargne. + */ +public enum StatutCompteEpargne { + ACTIF("Actif et fonctionnel"), + INACTIF("Inactif (sans mouvement prolongé)"), + BLOQUE("Bloqué (saisie, garantie, ou décision CA)"), + EN_CLOTURE("En cours de clôture"), + CLOTURE("Clôturé"); + + private final String libelle; + + StatutCompteEpargne(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeCompteEpargne.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeCompteEpargne.java new file mode 100644 index 0000000..c1fd53b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeCompteEpargne.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.epargne; + +/** + * Types de comptes d'épargne gérés par la mutuelle. + */ +public enum TypeCompteEpargne { + COURANT("Compte courant / de transit"), + EPARGNE_LIBRE("Épargne libre / classique"), + EPARGNE_BLOQUEE("Épargne bloquée (Garantie de crédit)"), + DEPOT_A_TERME("Dépôt à terme (DAT)"), + EPARGNE_PROJET("Épargne projet / tontine"); + + private final String libelle; + + TypeCompteEpargne(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeTransactionEpargne.java b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeTransactionEpargne.java new file mode 100644 index 0000000..4d456bf --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/mutuelle/epargne/TypeTransactionEpargne.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.enums.mutuelle.epargne; + +/** + * Nature opérationnelle des transactions d'épargne + */ +public enum TypeTransactionEpargne { + DEPOT("Dépôt sur le compte"), + RETRAIT("Retrait d'espèces"), + TRANSFERT_ENTRANT("Virement/Transfert entrant"), + TRANSFERT_SORTANT("Virement/Transfert sortant"), + PAIEMENT_INTERETS("Paiement des intérêts créditeurs"), + PRELEVEMENT_FRAIS("Prélèvement de frais de tenue de compte"), + RETENUE_GARANTIE("Gel/Retenue pour garantie de prêt"), + LIBERATION_GARANTIE("Libération de fonds de garantie"), + REMBOURSEMENT_CREDIT("Remboursement d'une échéance de crédit"); + + private final String libelle; + + TypeTransactionEpargne(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/notification/CanalNotification.java b/src/main/java/dev/lions/unionflow/server/api/enums/notification/CanalNotification.java new file mode 100644 index 0000000..97b552c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/notification/CanalNotification.java @@ -0,0 +1,467 @@ +package dev.lions.unionflow.server.api.enums.notification; + +/** + * Énumération des canaux de notification pour Android et iOS + * + *

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() { + boolean pasDeSon = !sonActive; + boolean pasDeVibration = !vibrationActive; + return pasDeSon & pasDeVibration; + } + + /** + * 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 + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java b/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java new file mode 100644 index 0000000..3a7f6ef --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/notification/StatutNotification.java b/src/main/java/dev/lions/unionflow/server/api/enums/notification/StatutNotification.java new file mode 100644 index 0000000..a913c3c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/notification/StatutNotification.java @@ -0,0 +1,459 @@ +package dev.lions.unionflow.server.api.enums.notification; + +/** + * Énumération des statuts de notification dans UnionFlow + * + *

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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java b/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java new file mode 100644 index 0000000..71429ab --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/ong/StatutProjetOng.java b/src/main/java/dev/lions/unionflow/server/api/enums/ong/StatutProjetOng.java new file mode 100644 index 0000000..419322b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/ong/StatutProjetOng.java @@ -0,0 +1,19 @@ +package dev.lions.unionflow.server.api.enums.ong; + +public enum StatutProjetOng { + EN_ETUDE("Étude de faisabilité / Évaluation"), + FINANCEMENT("En recherche de financements"), + EN_COURS("Déploiement opérationnel sur le terrain"), + EVALUE("Opérations terminées, en cours d'évaluation"), + CLOTURE("Projet définitivement clôturé"); + + private final String libelle; + + StatutProjetOng(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisation.java b/src/main/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisation.java new file mode 100644 index 0000000..74aa9dd --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisation.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisation.java b/src/main/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisation.java new file mode 100644 index 0000000..363ea7c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisation.java @@ -0,0 +1,35 @@ +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 { + ASSOCIATION("Association"), + MUTUELLE_EPARGNE_CREDIT("Mutuelle d'épargne et de crédit"), + MUTUELLE_SANTE("Mutuelle de santé"), + TONTINE("Tontine / épargne rotative"), + ONG("ONG / Association humanitaire"), + COOPERATIVE_AGRICOLE("Coopérative agricole / production"), + ASSOCIATION_PROFESSIONNELLE("Association professionnelle / Ordre"), + ASSOCIATION_COMMUNAUTAIRE("Association communautaire / quartier"), + ORGANISATION_RELIGIEUSE("Organisation religieuse"), + FEDERATION("Fédération / Union d'associations"), + SYNDICAT("Syndicat non partisan"), + LIONS_CLUB("Lions Club"), + ROTARY_CLUB("Rotary Club"), + AUTRE("Autre"); + + private final String libelle; + + TypeOrganisation(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiement.java new file mode 100644 index 0000000..6661358 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiement.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiement.java new file mode 100644 index 0000000..d4c761e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiement.java @@ -0,0 +1,15 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +public enum StatutIntentionPaiement { + INITIEE("Initiée — en attente d'appel Wave"), + EN_COURS("En cours — membre en train de confirmer dans Wave"), + COMPLETEE("Complétée — webhook Wave reçu et validé"), + EXPIREE("Expirée — TTL 30 min dépassé"), + ECHOUEE("Échouée — erreur Wave ou annulation"); + + private final String libelle; + + StatutIntentionPaiement(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiement.java new file mode 100644 index 0000000..7522a5b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiement.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutSession.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutSession.java new file mode 100644 index 0000000..9c14092 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutSession.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitement.java new file mode 100644 index 0000000..6f6fd1a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitement.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenement.java new file mode 100644 index 0000000..6baaa1d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenement.java @@ -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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiement.java b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiement.java new file mode 100644 index 0000000..f77a8a8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiement.java @@ -0,0 +1,17 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +public enum TypeObjetIntentionPaiement { + COTISATION("Cotisation membre"), + ADHESION("Frais d'adhésion"), + EVENEMENT("Participation événement"), + ABONNEMENT_UNIONFLOW("Abonnement forfait UnionFlow"), + DEPOT_EPARGNE("Dépôt compte épargne"), + RETRAIT_EPARGNE("Retrait compte épargne"), + CREDIT_REMBOURSEMENT("Remboursement crédit"); + + private final String libelle; + + TypeObjetIntentionPaiement(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/registre/StatutAgrement.java b/src/main/java/dev/lions/unionflow/server/api/enums/registre/StatutAgrement.java new file mode 100644 index 0000000..d86b185 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/registre/StatutAgrement.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.enums.registre; + +public enum StatutAgrement { + PROVISOIRE("Agréé provisoirement sous condition"), + VALIDE("Agréé - Dossier complet et vérifié"), + SUSPENDU("Agrément temporairement suspendu"), + RETRETIRE("Agrément définitivement radié"); + + private final String libelle; + + StatutAgrement(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAide.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAide.java new file mode 100644 index 0000000..431caf4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAide.java @@ -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é + * + *

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 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 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 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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAide.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAide.java new file mode 100644 index 0000000..1d9ae11 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAide.java @@ -0,0 +1,298 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +/** + * Énumération des statuts d'aide dans le système de solidarité + * + *

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 (les deux conditions sont évaluées pour couverture) */ + public boolean permetAnnulation() { + boolean nonFinal = !estFinal; + boolean pasAnnulee = (this != ANNULEE); + return nonFinal & pasAnnulee; + } + + /** Retourne les statuts finaux */ + public static java.util.List 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 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 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 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 + }; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluation.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluation.java new file mode 100644 index 0000000..2187061 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluation.java @@ -0,0 +1,21 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import lombok.Getter; + +/** + * Énumération des statuts d'évaluation + */ +@Getter +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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutProposition.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutProposition.java new file mode 100644 index 0000000..1ba97ef --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutProposition.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import lombok.Getter; + +/** + * Énumération des statuts d'une proposition d'aide. + */ +@Getter +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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtape.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtape.java new file mode 100644 index 0000000..37dffc0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtape.java @@ -0,0 +1,15 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +public enum StatutValidationEtape { + EN_ATTENTE("En attente du valideur"), + APPROUVEE("Approuvée"), + REJETEE("Rejetée"), + DELEGUEE("Déléguée — véto désactivé par supérieur, tracé BCEAO/OHADA"), + EXPIREE("Expirée — délai SLA dépassé"); + + private final String libelle; + + StatutValidationEtape(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAide.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAide.java new file mode 100644 index 0000000..d40bed1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAide.java @@ -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é + * + *

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 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 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 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 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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluation.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluation.java new file mode 100644 index 0000000..6ea49c0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluation.java @@ -0,0 +1,22 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import lombok.Getter; + +/** + * Énumération des types d'évaluation + */ +@Getter +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; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflow.java b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflow.java new file mode 100644 index 0000000..a4571cd --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflow.java @@ -0,0 +1,13 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +public enum TypeWorkflow { + DEMANDE_AIDE("Workflow demande d'aide solidarité"), + ADHESION("Workflow validation adhésion"), + AUTRE("Workflow personnalisé"); + + private final String libelle; + + TypeWorkflow(String libelle) { this.libelle = libelle; } + + public String getLibelle() { return libelle; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/tontine/FrequenceTour.java b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/FrequenceTour.java new file mode 100644 index 0000000..0db8e47 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/FrequenceTour.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.enums.tontine; + +public enum FrequenceTour { + JOURNALIERE("Chaque jour"), + HEBDOMADAIRE("Chaque semaine"), + DECADE("Tous les 10 jours"), + QUINZAINE("Chaque quinzaine (tous les 15 jours)"), + MENSUELLE("Chaque mois"), + TRIMESTRIELLE("Chaque trimestre"); + + private final String libelle; + + FrequenceTour(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/tontine/StatutTontine.java b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/StatutTontine.java new file mode 100644 index 0000000..2660762 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/StatutTontine.java @@ -0,0 +1,19 @@ +package dev.lions.unionflow.server.api.enums.tontine; + +public enum StatutTontine { + PLANIFIEE("En cours de planification / Enrôlement des membres"), + EN_COURS("Active et en cours de déroulement"), + EN_PAUSE("Suspendue temporairement par l'administration"), + CLOTUREE("Terminée avec succès (tous les tours finis)"), + ANNULEE("Annulée avant son terme"); + + private final String libelle; + + StatutTontine(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/tontine/TypeTontine.java b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/TypeTontine.java new file mode 100644 index 0000000..65dfa11 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/tontine/TypeTontine.java @@ -0,0 +1,17 @@ +package dev.lions.unionflow.server.api.enums.tontine; + +public enum TypeTontine { + ROTATIVE_CLASSIQUE("Tontine rotative classique avec cotisations fixes"), + VARIABLE("Tontine à cotisations variables (Vente aux enchères/Mise)"), + ACCUMULATIVE("Tontine accumulative / Caisse de secours"); + + private final String libelle; + + TypeTontine(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/vote/ModeScrutin.java b/src/main/java/dev/lions/unionflow/server/api/enums/vote/ModeScrutin.java new file mode 100644 index 0000000..13ea052 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/vote/ModeScrutin.java @@ -0,0 +1,18 @@ +package dev.lions.unionflow.server.api.enums.vote; + +public enum ModeScrutin { + MAJORITAIRE_UN_TOUR("Scrutin majoritaire à un seul tour"), + MAJORITAIRE_DEUX_TOURS("Scrutin majoritaire à deux tours"), + PROPORTIONNEL("Scrutin proportionnel de listes"), + BUREAU_CONSENSUEL("Vote d'approbation globale de bureau (OUI/NON)"); + + private final String libelle; + + ModeScrutin(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/vote/StatutVote.java b/src/main/java/dev/lions/unionflow/server/api/enums/vote/StatutVote.java new file mode 100644 index 0000000..6d639a9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/vote/StatutVote.java @@ -0,0 +1,20 @@ +package dev.lions.unionflow.server.api.enums.vote; + +public enum StatutVote { + BROUILLON("Paramétrage de la campagne en cours"), + PLANIFIE("Campagne planifiée (pas encore ouverte aux votes)"), + OUVERT("Les membres peuvent voter activement"), + SUSPENDU("Campagne de vote suspendue suite à une contestation"), + CLOTURE("Dispositif de vote fermé (Calcul en cours)"), + RESULTATS_PUBLIES("Résultats définitifs publiés et accessibles"); + + private final String libelle; + + StatutVote(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/vote/TypeVote.java b/src/main/java/dev/lions/unionflow/server/api/enums/vote/TypeVote.java new file mode 100644 index 0000000..5b46785 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/vote/TypeVote.java @@ -0,0 +1,19 @@ +package dev.lions.unionflow.server.api.enums.vote; + +public enum TypeVote { + ELECTION_BUREAU("Élection des membres du bureau exécutif"), + ADOPTION_RESOLUTION("Vote pour ou contre une résolution"), + MODIFICATION_STATUTS("Vote d'amendement des statuts de la mutuelle"), + EXCLUSION_MEMBRE("Vote d'exclusion d'un membre pour manquements"), + REFERENDUM("Référendum interne ou consultation générale"); + + private final String libelle; + + TypeVote(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWave.java b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWave.java new file mode 100644 index 0000000..acbcaae --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWave.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWave.java b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWave.java new file mode 100644 index 0000000..9b54a8e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWave.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhook.java b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhook.java new file mode 100644 index 0000000..3d96b71 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhook.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhook.java b/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhook.java new file mode 100644 index 0000000..336ce3d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhook.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWave.java b/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWave.java new file mode 100644 index 0000000..b57e453 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWave.java @@ -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; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/api/service/dashboard/DashboardService.java b/src/main/java/dev/lions/unionflow/server/api/service/dashboard/DashboardService.java new file mode 100644 index 0000000..febf3b9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/service/dashboard/DashboardService.java @@ -0,0 +1,59 @@ +package dev.lions.unionflow.server.api.service.dashboard; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataResponse; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsResponse; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityResponse; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventResponse; + +import java.util.List; + +/** + * Interface de service pour la gestion des données du dashboard + * + *

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 + */ + DashboardDataResponse 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 + */ + DashboardStatsResponse 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 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 getUpcomingEvents(String organizationId, String userId, int limit); +} diff --git a/src/main/java/dev/lions/unionflow/server/api/validation/ValidationConstants.java b/src/main/java/dev/lions/unionflow/server/api/validation/ValidationConstants.java new file mode 100644 index 0000000..a4f750d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/validation/ValidationConstants.java @@ -0,0 +1,244 @@ +package dev.lions.unionflow.server.api.validation; + +/** + * Constantes pour la validation des DTOs + * + *

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"; + + // === LCB-FT / Anti-blanchiment (mutuelles) === + + /** Origine des fonds — libellé court (obligatoire au-dessus du seuil configuré) */ + public static final int ORIGINE_FONDS_MAX_LENGTH = 200; + + public static final String ORIGINE_FONDS_SIZE_MESSAGE = + "L'origine des fonds ne peut pas dépasser " + ORIGINE_FONDS_MAX_LENGTH + " caractères"; + + public static final String ORIGINE_FONDS_OBLIGATOIRE_SEUIL_MESSAGE = + "L'origine des fonds est obligatoire pour les opérations au-dessus du seuil LCB-FT configuré"; +} diff --git a/src/main/java/lombok.config b/src/main/java/lombok.config new file mode 100644 index 0000000..df71bb6 --- /dev/null +++ b/src/main/java/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/src/test/java/dev/lions/unionflow/server/api/CompilationTest.java b/src/test/java/dev/lions/unionflow/server/api/CompilationTest.java new file mode 100644 index 0000000..3c8b2d6 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/CompilationTest.java @@ -0,0 +1,138 @@ +package dev.lions.unionflow.server.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.api.dto.evenement.response.EvenementResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse; + +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 EvenementResponse") + void testCompilationEvenementResponse() { + EvenementResponse evenement = new EvenementResponse(); + 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 DemandeAideResponse") + void testCompilationDemandeAideResponse() { + DemandeAideResponse demande = new DemandeAideResponse(); + demande.setTitre("Test Demande"); + demande.setMontantDemande(new BigDecimal("50000")); + + // Test des méthodes métier + assertThat(demande.getId()).isNull(); + assertThat(demande.getVersion()).isNull(); // Unset version is null in BaseResponse + + // Test des setters + demande.setCreePar("testUser"); + assertThat(demande.getCreePar()).isEqualTo("testUser"); + } + + @Test + @DisplayName("Test compilation PropositionAideResponse") + void testCompilationPropositionAideResponse() { + PropositionAideResponse proposition = new PropositionAideResponse(); + 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 + EvenementResponse evenement = new EvenementResponse(); + evenement.setTitre("Formation Leadership"); + evenement.setTypeEvenement(TypeEvenementMetier.FORMATION); + evenement.setDateDebut(LocalDate.now().plusDays(30)); + evenement.setLieu("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 + DemandeAideResponse demande = new DemandeAideResponse(); + demande.setTitre("Aide Médicale Urgente"); + demande.setDescription("Besoin d'aide pour frais médicaux"); + demande.setMontantDemande(new BigDecimal("250000")); + demande.setMembreDemandeurId(UUID.randomUUID()); + demande.setAssociationId(UUID.randomUUID()); + + // Vérifier que tout fonctionne + assertThat(demande.getId()).isNull(); + assertThat(demande.getMontantDemande()).isEqualTo(new BigDecimal("250000")); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java b/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java new file mode 100644 index 0000000..21d7bb5 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java @@ -0,0 +1,244 @@ +package dev.lions.unionflow.server.api; + +import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataResponse; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataResponse; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsResponse; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityResponse; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventResponse; +import dev.lions.unionflow.server.api.dto.membre.request.CreateMembreRequest; +import dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse; +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.response.NotificationResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.CommentaireAideResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.EvaluationAideResponse; +import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse; +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 DTOs ===== + + public static MembreSummaryResponse createMembreSummaryResponse() { + return createMembreSummaryResponse("UF-2025-000001", "Dupont", "Jean", "jean@example.com"); + } + + public static MembreSummaryResponse createMembreSummaryResponse(String numero, String nom, String prenom, + String email) { + return new MembreSummaryResponse( + UUID.randomUUID(), numero, prenom, nom, email, "0102030405", "Profession", "ACTIF", "Actif", "success", true, + List.of("MEMBRE"), null, null); + } + + public static CreateMembreRequest createCreateMembreRequest(int age) { + return new CreateMembreRequest( + "Jean", "Dupont", "jean@example.com", "0102030405", "+22501020304", + LocalDate.now().minusYears(age), "Profession", "photo.jpg", "CELIBATAIRE", "Ivoirienne", "CNI", "123456789"); + } + + // ===== 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 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 DashboardStatsResponse createDashboardStatsResponse() { + return DashboardStatsResponse.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 RecentActivityResponse createRecentActivityResponse() { + return createRecentActivityResponse("member", LocalDateTime.now().minusHours(1)); + } + + public static RecentActivityResponse createRecentActivityResponse(String type, LocalDateTime timestamp) { + return RecentActivityResponse.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 UpcomingEventResponse createUpcomingEventResponse() { + return createUpcomingEventResponse(LocalDateTime.now().plusDays(1)); + } + + public static UpcomingEventResponse createUpcomingEventResponse(LocalDateTime startDate) { + return UpcomingEventResponse.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 DashboardDataResponse createDashboardDataResponse() { + return DashboardDataResponse.builder() + .stats(createDashboardStatsResponse()) + .recentActivities(List.of(createRecentActivityResponse())) + .upcomingEvents(List.of(createUpcomingEventResponse())) + .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 NotificationResponse createNotificationResponse() { + NotificationResponse notification = new NotificationResponse(); + notification.setId(UUID.randomUUID()); + notification.setDateCreation(now()); + return notification; + } + + // ===== ANALYTICS DATA DTO ===== + + public static AnalyticsDataResponse createAnalyticsDataResponse() { + return AnalyticsDataResponse.builder() + .dateCalcul(now()) + .dateDebut(now().minusDays(30)) + .dateFin(now()) + .build(); + } + + // ===== SOLIDARITE DTOs ===== + + public static DemandeAideResponse createDemandeAideResponse() { + DemandeAideResponse dto = new DemandeAideResponse(); + dto.setId(UUID.randomUUID()); + 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 PropositionAideResponse createPropositionAideResponse() { + PropositionAideResponse dto = new PropositionAideResponse(); + dto.setId(UUID.randomUUID()); + dto.setNumeroReference("PA-2025-000001"); + dto.setTypeAide(dev.lions.unionflow.server.api.enums.solidarite.TypeAide.AIDE_FINANCIERE_URGENTE); + dto.setTitre("Proposition d'aide financière"); + dto.setDescription("Je propose une aide financière pour les membres en difficulté"); + dto.setProposantId(UUID.randomUUID().toString()); + dto.setProposantNom("Marie Martin"); + dto.setOrganisationId(UUID.randomUUID().toString()); + return dto; + } + + public static EvaluationAideResponse createEvaluationAideResponse() { + EvaluationAideResponse dto = new EvaluationAideResponse(); + dto.setId(UUID.randomUUID()); + dto.setDemandeAideId(UUID.randomUUID()); + dto.setEvaluateurId(UUID.randomUUID()); + dto.setEvaluateurNom("Évaluateur Test"); + dto.setRoleEvaluateur("beneficiaire"); + dto.setNoteGlobale(4.5); + dto.setCommentairePrincipal("Très satisfait de l'aide reçue"); + dto.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutEvaluation.ACTIVE); + return dto; + } + + public static CommentaireAideResponse createCommentaireAideResponse() { + CommentaireAideResponse dto = new CommentaireAideResponse(); + dto.setId(UUID.randomUUID()); + dto.setAuteurId(UUID.randomUUID()); + dto.setAuteurNom("Auteur Test"); + dto.setContenu("Ceci est un commentaire de test"); + dto.setDateCreation(now()); + return dto; + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponseTest.java new file mode 100644 index 0000000..3d5f17a --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/abonnement/response/AbonnementResponseTest.java @@ -0,0 +1,213 @@ +package dev.lions.unionflow.server.api.dto.abonnement.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour AbonnementResponse. + * Couvre toutes les méthodes utilitaires avec toutes les branches. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-01 + */ +@DisplayName("Tests AbonnementResponse") +class AbonnementResponseTest { + + @Nested + @DisplayName("isExpire") + class IsExpire { + + @Test + @DisplayName("retourne true quand statut EXPIRE") + void testStatutExpire() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.EXPIRE) + .dateFin(LocalDate.now().plusDays(10)) // même avec dateFin dans le futur + .build(); + + assertThat(response.isExpire()).isTrue(); + } + + @Test + @DisplayName("retourne true quand dateFin dans le passé") + void testDateFinPassee() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.ACTIF) // statut != EXPIRE + .dateFin(LocalDate.now().minusDays(5)) + .build(); + + assertThat(response.isExpire()).isTrue(); + } + + @Test + @DisplayName("retourne false quand statut != EXPIRE et dateFin null") + void testStatutActifDateFinNull() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.ACTIF) + .dateFin(null) + .build(); + + assertThat(response.isExpire()).isFalse(); + } + + @Test + @DisplayName("retourne false quand statut != EXPIRE et dateFin future") + void testStatutActifDateFinFuture() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.ACTIF) + .dateFin(LocalDate.now().plusDays(10)) + .build(); + + assertThat(response.isExpire()).isFalse(); + } + } + + @Nested + @DisplayName("isExpirationProche") + class IsExpirationProche { + + @Test + @DisplayName("retourne true quand joursRestants entre 0 et 30") + void testExpirationProche() { + AbonnementResponse response = AbonnementResponse.builder() + .dateFin(LocalDate.now().plusDays(15)) + .build(); + + assertThat(response.isExpirationProche()).isTrue(); + } + + @Test + @DisplayName("retourne false quand joursRestants > 30") + void testExpirationLoin() { + AbonnementResponse response = AbonnementResponse.builder() + .dateFin(LocalDate.now().plusDays(60)) + .build(); + + assertThat(response.isExpirationProche()).isFalse(); + } + + @Test + @DisplayName("retourne false quand joursRestants < 0 (dateFin null)") + void testDateFinNull() { + AbonnementResponse response = AbonnementResponse.builder() + .dateFin(null) + .build(); + + assertThat(response.isExpirationProche()).isFalse(); + } + + @Test + @DisplayName("retourne true quand joursRestants = 0 (déjà expiré)") + void testExpireAujourdhui() { + AbonnementResponse response = AbonnementResponse.builder() + .dateFin(LocalDate.now().minusDays(1)) + .build(); + + assertThat(response.isExpirationProche()).isTrue(); // joursRestants = 0, et 0 >= 0 && 0 <= 30 + } + } + + @Nested + @DisplayName("peutEtreRenouvele") + class PeutEtreRenouvele { + + @Test + @DisplayName("retourne true quand renouvellement auto et non expiré") + void testRenouvellable() { + AbonnementResponse response = AbonnementResponse.builder() + .renouvellementAutomatique(true) + .statut(StatutAbonnement.ACTIF) + .dateFin(LocalDate.now().plusDays(30)) + .build(); + + assertThat(response.peutEtreRenouvele()).isTrue(); + } + + @Test + @DisplayName("retourne false quand renouvellement auto false") + void testRenouvellementAutoFalse() { + AbonnementResponse response = AbonnementResponse.builder() + .renouvellementAutomatique(false) + .statut(StatutAbonnement.ACTIF) + .dateFin(LocalDate.now().plusDays(30)) + .build(); + + assertThat(response.peutEtreRenouvele()).isFalse(); + } + + @Test + @DisplayName("retourne false quand renouvellement auto null") + void testRenouvellementAutoNull() { + AbonnementResponse response = AbonnementResponse.builder() + .renouvellementAutomatique(null) + .statut(StatutAbonnement.ACTIF) + .dateFin(LocalDate.now().plusDays(30)) + .build(); + + assertThat(response.peutEtreRenouvele()).isFalse(); + } + + @Test + @DisplayName("retourne false quand abonnement expiré") + void testExpire() { + AbonnementResponse response = AbonnementResponse.builder() + .renouvellementAutomatique(true) + .statut(StatutAbonnement.EXPIRE) + .dateFin(LocalDate.now().minusDays(10)) + .build(); + + assertThat(response.peutEtreRenouvele()).isFalse(); + } + } + + @Nested + @DisplayName("Autres méthodes utilitaires") + class AutresMethodes { + + @Test + @DisplayName("isActive retourne true pour ACTIF") + void testIsActive() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.ACTIF) + .build(); + + assertThat(response.isActive()).isTrue(); + } + + @Test + @DisplayName("isSuspendu retourne true pour SUSPENDU") + void testIsSuspendu() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.SUSPENDU) + .build(); + + assertThat(response.isSuspendu()).isTrue(); + } + + @Test + @DisplayName("getStatutLibelle retourne le nom du statut") + void testGetStatutLibelle() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(StatutAbonnement.ACTIF) + .build(); + + assertThat(response.getStatutLibelle()).isEqualTo("ACTIF"); + } + + @Test + @DisplayName("getStatutLibelle retourne INCONNU quand statut null") + void testGetStatutLibelleNull() { + AbonnementResponse response = AbonnementResponse.builder() + .statut(null) + .build(); + + assertThat(response.getStatutLibelle()).isEqualTo("INCONNU"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponseTest.java new file mode 100644 index 0000000..1950e55 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/AnalyticsDataResponseTest.java @@ -0,0 +1,344 @@ +package dev.lions.unionflow.server.api.dto.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; + +/** + * Tests unitaires pour AnalyticsDataResponse. + * Couvre getLibelleAffichage, hasEvolutionPositive/Negative, isStable, getTendance, + * isDonneesFiables, isCritique, constructeur 3 args. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests AnalyticsDataResponse") +class AnalyticsDataResponseTest { + + private static final TypeMetrique TYPE = TypeMetrique.NOMBRE_MEMBRES_ACTIFS; + private static final PeriodeAnalyse PERIODE = PeriodeAnalyse.CETTE_SEMAINE; + + @Nested + @DisplayName("getLibelleAffichage") + class GetLibelleAffichage { + + @Test + @DisplayName("retourne typeMetrique.getLibelle() quand libellePersonnalise null") + void testLibelleNull() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()).build(); + assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle()); + } + + @Test + @DisplayName("retourne typeMetrique.getLibelle() quand libellePersonnalise vide ou blanc") + void testLibelleVide() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .libellePersonnalise("").build(); + assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle()); + r.setLibellePersonnalise(" "); + assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle()); + } + + @Test + @DisplayName("retourne libellePersonnalise quand non vide") + void testLibellePersonnalise() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .libellePersonnalise("Mon libellé").build(); + assertThat(r.getLibelleAffichage()).isEqualTo("Mon libellé"); + } + } + + @Nested + @DisplayName("getUnite, getIcone, getCouleur") + class DelegationTypeMetrique { + + @Test + @DisplayName("délèguent à typeMetrique") + void testDelegation() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()).build(); + assertThat(r.getUnite()).isEqualTo(TYPE.getUnite()); + assertThat(r.getIcone()).isEqualTo(TYPE.getIcone()); + assertThat(r.getCouleur()).isEqualTo(TYPE.getCouleur()); + } + } + + @Nested + @DisplayName("hasEvolutionPositive, hasEvolutionNegative, isStable") + class Evolution { + + @Test + @DisplayName("hasEvolutionPositive true quand pourcentageEvolution > 0") + void testEvolutionPositive() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.valueOf(5.5)).build(); + assertThat(r.hasEvolutionPositive()).isTrue(); + assertThat(r.hasEvolutionNegative()).isFalse(); + assertThat(r.isStable()).isFalse(); + } + + @Test + @DisplayName("hasEvolutionNegative true quand pourcentageEvolution < 0") + void testEvolutionNegative() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.valueOf(-3.2)).build(); + assertThat(r.hasEvolutionNegative()).isTrue(); + assertThat(r.hasEvolutionPositive()).isFalse(); + assertThat(r.isStable()).isFalse(); + } + + @Test + @DisplayName("isStable true quand pourcentageEvolution == 0") + void testStable() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.ZERO).build(); + assertThat(r.isStable()).isTrue(); + assertThat(r.hasEvolutionPositive()).isFalse(); + assertThat(r.hasEvolutionNegative()).isFalse(); + } + + @Test + @DisplayName("retournent false / false / false quand pourcentageEvolution null") + void testEvolutionNull() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()).build(); + assertThat(r.hasEvolutionPositive()).isFalse(); + assertThat(r.hasEvolutionNegative()).isFalse(); + assertThat(r.isStable()).isFalse(); + } + } + + @Nested + @DisplayName("getTendance") + class GetTendance { + + @Test + @DisplayName("retourne hausse quand évolution positive") + void testHausse() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.ONE).build(); + assertThat(r.getTendance()).isEqualTo("hausse"); + } + + @Test + @DisplayName("retourne baisse quand évolution négative") + void testBaisse() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.ONE.negate()).build(); + assertThat(r.getTendance()).isEqualTo("baisse"); + } + + @Test + @DisplayName("retourne stable sinon") + void testStable() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .pourcentageEvolution(BigDecimal.ZERO).build(); + assertThat(r.getTendance()).isEqualTo("stable"); + r.setPourcentageEvolution(null); + assertThat(r.getTendance()).isEqualTo("stable"); + } + } + + @Nested + @DisplayName("isDonneesFiables") + class IsDonneesFiables { + + @Test + @DisplayName("retourne false quand indicateurFiabilite null") + void testNull() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()).build(); + assertThat(r.isDonneesFiables()).isFalse(); + } + + @Test + @DisplayName("retourne true quand indicateurFiabilite >= 80") + void testFiable() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .indicateurFiabilite(new BigDecimal("80.0")).build(); + assertThat(r.isDonneesFiables()).isTrue(); + r.setIndicateurFiabilite(new BigDecimal("95.5")); + assertThat(r.isDonneesFiables()).isTrue(); + } + + @Test + @DisplayName("retourne false quand indicateurFiabilite < 80") + void testNonFiable() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .indicateurFiabilite(new BigDecimal("79.9")).build(); + assertThat(r.isDonneesFiables()).isFalse(); + } + } + + @Nested + @DisplayName("isCritique") + class IsCritique { + + @Test + @DisplayName("retourne false quand niveauPriorite null ou < 4") + void testNonCritique() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()).build(); + assertThat(r.isCritique()).isFalse(); + r.setNiveauPriorite(3); + assertThat(r.isCritique()).isFalse(); + } + + @Test + @DisplayName("retourne true quand niveauPriorite >= 4") + void testCritique() { + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .niveauPriorite(4).build(); + assertThat(r.isCritique()).isTrue(); + r.setNiveauPriorite(5); + assertThat(r.isCritique()).isTrue(); + } + } + + @Nested + @DisplayName("Constructeur 3 arguments") + class Constructeur3Args { + + @Test + @DisplayName("initialise typeMetrique, periodeAnalyse, valeur, dateCalcul, dateDebut, dateFin, défauts") + void testConstructeur() { + AnalyticsDataResponse r = new AnalyticsDataResponse(TYPE, PERIODE, BigDecimal.valueOf(42)); + assertThat(r.getTypeMetrique()).isEqualTo(TYPE); + assertThat(r.getPeriodeAnalyse()).isEqualTo(PERIODE); + assertThat(r.getValeur()).isEqualByComparingTo(BigDecimal.valueOf(42)); + assertThat(r.getDateCalcul()).isNotNull(); + assertThat(r.getDateDebut()).isEqualTo(PERIODE.getDateDebut()); + assertThat(r.getDateFin()).isEqualTo(PERIODE.getDateFin()); + assertThat(r.getTempsReel()).isFalse(); + assertThat(r.getNecessiteMiseAJour()).isFalse(); + assertThat(r.getNiveauPriorite()).isEqualTo(3); + assertThat(r.getIndicateurFiabilite()).isEqualByComparingTo(new BigDecimal("95.0")); + } + } + + @Nested + @DisplayName("Builder et getters") + class BuilderGetters { + + @Test + @DisplayName("champs optionnels accessibles") + void testBuilder() { + UUID orgId = UUID.randomUUID(); + AnalyticsDataResponse r = AnalyticsDataResponse.builder() + .typeMetrique(TYPE) + .periodeAnalyse(PERIODE) + .valeur(BigDecimal.ONE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .dateCalcul(LocalDateTime.now()) + .organisationId(orgId) + .nomOrganisation("Org") + .tempsReel(true) + .necessiteMiseAJour(true).build(); + assertThat(r.getOrganisationId()).isEqualTo(orgId); + assertThat(r.getNomOrganisation()).isEqualTo("Org"); + assertThat(r.getTempsReel()).isTrue(); + assertThat(r.getNecessiteMiseAJour()).isTrue(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponseTest.java new file mode 100644 index 0000000..371ec4c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/DashboardWidgetResponseTest.java @@ -0,0 +1,350 @@ +package dev.lions.unionflow.server.api.dto.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; + +/** + * Tests unitaires pour DashboardWidgetResponse. + * Couvre getLibelleMetrique, getUnite, getIconeAffichage, getCouleurAffichage, + * necessiteMiseAJour, isInteractif, isTempsReel, getTailleWidget, isWidgetGrand, + * hasErreursRecentes, getStatutWidget. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests DashboardWidgetResponse") +class DashboardWidgetResponseTest { + + private static DashboardWidgetResponse minimalWidget() { + return DashboardWidgetResponse.builder() + .titre("Widget") + .typeWidget("kpi") + .utilisateurProprietaireId(UUID.randomUUID()) + .positionX(0) + .positionY(0) + .largeur(2) + .hauteur(2).build(); + } + + @Nested + @DisplayName("getLibelleMetrique") + class GetLibelleMetrique { + + @Test + @DisplayName("retourne null quand typeMetrique null") + void testNull() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.getLibelleMetrique()).isNull(); + } + + @Test + @DisplayName("retourne typeMetrique.getLibelle() quand défini") + void testLibelle() { + DashboardWidgetResponse w = minimalWidget(); + w.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS); + assertThat(w.getLibelleMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle()); + } + } + + @Nested + @DisplayName("getUnite") + class GetUnite { + + @Test + @DisplayName("retourne chaîne vide quand typeMetrique null") + void testNull() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.getUnite()).isEmpty(); + } + + @Test + @DisplayName("retourne typeMetrique.getUnite() quand défini") + void testUnite() { + DashboardWidgetResponse w = minimalWidget(); + w.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS); + assertThat(w.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite()); + } + } + + @Nested + @DisplayName("getIconeAffichage") + class GetIconeAffichage { + + @Test + @DisplayName("retourne icone personnalisée si non vide") + void testPersonnalise() { + DashboardWidgetResponse w = minimalWidget(); + w.setIcone("custom_icon"); + assertThat(w.getIconeAffichage()).isEqualTo("custom_icon"); + } + + @Test + @DisplayName("retourne typeMetrique.getIcone() si icone null") + void testTypeMetrique() { + DashboardWidgetResponse w = minimalWidget(); + w.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS); + assertThat(w.getIconeAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone()); + } + + @Test + @DisplayName("retourne dashboard si icone vide et typeMetrique null") + void testDefault() { + DashboardWidgetResponse w = minimalWidget(); + w.setIcone(" "); + assertThat(w.getIconeAffichage()).isEqualTo("dashboard"); + } + + @Test + @DisplayName("retourne dashboard si icone null et typeMetrique null") + void testIconeNullTypeMetriqueNull() { + DashboardWidgetResponse w = minimalWidget(); + w.setIcone(null); + w.setTypeMetrique(null); + assertThat(w.getIconeAffichage()).isEqualTo("dashboard"); + } + } + + @Nested + @DisplayName("getCouleurAffichage") + class GetCouleurAffichage { + + @Test + @DisplayName("retourne couleurPrincipale si non vide") + void testPersonnalise() { + DashboardWidgetResponse w = minimalWidget(); + w.setCouleurPrincipale("#FF0000"); + assertThat(w.getCouleurAffichage()).isEqualTo("#FF0000"); + } + + @Test + @DisplayName("retourne typeMetrique.getCouleur() si couleurPrincipale null") + void testTypeMetrique() { + DashboardWidgetResponse w = minimalWidget(); + w.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS); + assertThat(w.getCouleurAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur()); + } + + @Test + @DisplayName("retourne #757575 si couleurPrincipale vide et typeMetrique null") + void testDefault() { + DashboardWidgetResponse w = minimalWidget(); + w.setCouleurPrincipale(" "); + assertThat(w.getCouleurAffichage()).isEqualTo("#757575"); + } + + @Test + @DisplayName("retourne #757575 si couleurPrincipale null et typeMetrique null") + void testCouleurNullTypeMetriqueNull() { + DashboardWidgetResponse w = minimalWidget(); + w.setCouleurPrincipale(null); + w.setTypeMetrique(null); + assertThat(w.getCouleurAffichage()).isEqualTo("#757575"); + } + } + + @Nested + @DisplayName("necessiteMiseAJour") + class NecessiteMiseAJour { + + @Test + @DisplayName("retourne true quand miseAJourAutomatique et prochaineMiseAJour dans le passé") + void testTrue() { + DashboardWidgetResponse w = minimalWidget(); + w.setMiseAJourAutomatique(true); + w.setProchaineMiseAJour(LocalDateTime.now().minusMinutes(5)); + assertThat(w.necessiteMiseAJour()).isTrue(); + } + + @Test + @DisplayName("retourne false quand miseAJourAutomatique false") + void testFalseNonAuto() { + DashboardWidgetResponse w = minimalWidget(); + w.setMiseAJourAutomatique(false); + w.setProchaineMiseAJour(LocalDateTime.now().minusMinutes(5)); + assertThat(w.necessiteMiseAJour()).isFalse(); + } + + @Test + @DisplayName("retourne false quand prochaineMiseAJour null ou futur") + void testFalseFutur() { + DashboardWidgetResponse w = minimalWidget(); + w.setMiseAJourAutomatique(true); + w.setProchaineMiseAJour(null); + assertThat(w.necessiteMiseAJour()).isFalse(); + w.setProchaineMiseAJour(LocalDateTime.now().plusHours(1)); + assertThat(w.necessiteMiseAJour()).isFalse(); + } + } + + @Nested + @DisplayName("isInteractif") + class IsInteractif { + + @Test + @DisplayName("retourne true pour chart, table, gauge") + void testInteractif() { + assertThat(DashboardWidgetResponse.builder().titre("x").typeWidget("chart").utilisateurProprietaireId(UUID.randomUUID()).positionX(0).positionY(0).largeur(1).hauteur(1).build().isInteractif()).isTrue(); + DashboardWidgetResponse w = minimalWidget(); + w.setTypeWidget("table"); + assertThat(w.isInteractif()).isTrue(); + w.setTypeWidget("gauge"); + assertThat(w.isInteractif()).isTrue(); + } + + @Test + @DisplayName("retourne false pour kpi, progress, text") + void testNonInteractif() { + DashboardWidgetResponse w = minimalWidget(); + w.setTypeWidget("kpi"); + assertThat(w.isInteractif()).isFalse(); + w.setTypeWidget("text"); + assertThat(w.isInteractif()).isFalse(); + } + } + + @Nested + @DisplayName("isTempsReel") + class IsTempsReel { + + @Test + @DisplayName("retourne true quand frequenceMiseAJourSecondes <= 60") + void testTempsReel() { + DashboardWidgetResponse w = minimalWidget(); + w.setFrequenceMiseAJourSecondes(30); + assertThat(w.isTempsReel()).isTrue(); + w.setFrequenceMiseAJourSecondes(60); + assertThat(w.isTempsReel()).isTrue(); + } + + @Test + @DisplayName("retourne false quand frequenceMiseAJourSecondes > 60 ou null") + void testNonTempsReel() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.isTempsReel()).isFalse(); + w.setFrequenceMiseAJourSecondes(300); + assertThat(w.isTempsReel()).isFalse(); + w.setFrequenceMiseAJourSecondes(61); + assertThat(w.isTempsReel()).isFalse(); + } + + @Test + @DisplayName("retourne false quand frequenceMiseAJourSecondes explicitement null") + void testTempsReelFrequenceNull() { + DashboardWidgetResponse w = minimalWidget(); + w.setFrequenceMiseAJourSecondes(30); + w.setFrequenceMiseAJourSecondes(null); + assertThat(w.isTempsReel()).isFalse(); + } + } + + @Nested + @DisplayName("getTailleWidget et isWidgetGrand") + class TailleWidget { + + @Test + @DisplayName("getTailleWidget retourne largeur * hauteur") + void testTaille() { + DashboardWidgetResponse w = minimalWidget(); + w.setLargeur(3); + w.setHauteur(2); + assertThat(w.getTailleWidget()).isEqualTo(6); + } + + @Test + @DisplayName("isWidgetGrand true quand surface > 6") + void testGrand() { + DashboardWidgetResponse w = minimalWidget(); + w.setLargeur(4); + w.setHauteur(2); + assertThat(w.getTailleWidget()).isEqualTo(8); + assertThat(w.isWidgetGrand()).isTrue(); + } + + @Test + @DisplayName("isWidgetGrand false quand surface <= 6") + void testPetit() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.getTailleWidget()).isEqualTo(4); + assertThat(w.isWidgetGrand()).isFalse(); + } + } + + @Nested + @DisplayName("hasErreursRecentes et getStatutWidget") + class ErreursEtStatut { + + @Test + @DisplayName("hasErreursRecentes true quand dateDerniereErreur < 24h") + void testErreurRecente() { + DashboardWidgetResponse w = minimalWidget(); + w.setDateDerniereErreur(LocalDateTime.now().minusHours(2)); + assertThat(w.hasErreursRecentes()).isTrue(); + } + + @Test + @DisplayName("hasErreursRecentes false quand dateDerniereErreur null ou > 24h") + void testPasErreurRecente() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.hasErreursRecentes()).isFalse(); + w.setDateDerniereErreur(LocalDateTime.now().minusHours(25)); + assertThat(w.hasErreursRecentes()).isFalse(); + } + + @Test + @DisplayName("getStatutWidget retourne erreur si hasErreursRecentes") + void testStatutErreur() { + DashboardWidgetResponse w = minimalWidget(); + w.setDateDerniereErreur(LocalDateTime.now().minusHours(1)); + assertThat(w.getStatutWidget()).isEqualTo("erreur"); + } + + @Test + @DisplayName("getStatutWidget retourne inactif si visible false") + void testStatutInactif() { + DashboardWidgetResponse w = minimalWidget(); + w.setVisible(false); + assertThat(w.getStatutWidget()).isEqualTo("inactif"); + } + + @Test + @DisplayName("getStatutWidget retourne maintenance si tauxErreur > 10") + void testStatutMaintenance() { + DashboardWidgetResponse w = minimalWidget(); + w.setTauxErreur(15.0); + assertThat(w.getStatutWidget()).isEqualTo("maintenance"); + } + + @Test + @DisplayName("getStatutWidget retourne actif sinon") + void testStatutActif() { + DashboardWidgetResponse w = minimalWidget(); + assertThat(w.getStatutWidget()).isEqualTo("actif"); + } + + @Test + @DisplayName("getStatutWidget retourne actif quand tauxErreur non null mais <= 10") + void testStatutActifTauxErreurFaible() { + DashboardWidgetResponse w = minimalWidget(); + w.setTauxErreur(5.0); + w.setVisible(true); + assertThat(w.getStatutWidget()).isEqualTo("actif"); + } + + @Test + @DisplayName("getStatutWidget retourne actif quand tauxErreur explicitement null") + void testStatutActifTauxErreurNull() { + DashboardWidgetResponse w = minimalWidget(); + w.setTauxErreur(15.0); + w.setTauxErreur(null); + assertThat(w.getStatutWidget()).isEqualTo("actif"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponseTest.java new file mode 100644 index 0000000..615e75f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/KPITrendResponseTest.java @@ -0,0 +1,212 @@ +package dev.lions.unionflow.server.api.dto.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +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 dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; + +/** + * Tests unitaires pour KPITrendResponse. + * Couvre getLibelleMetrique, getUnite, getIcone, getCouleur, isTendancePositive/Negative/Stable, + * getVolatilite, isPredictionFiable, getNombrePointsDonnees, hasAnomalies. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests KPITrendResponse") +class KPITrendResponseTest { + + private static KPITrendResponse minimalKpi() { + return KPITrendResponse.builder() + .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS) + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .pointsDonnees(List.of()) + .valeurActuelle(BigDecimal.ONE).build(); + } + + @Nested + @DisplayName("getLibelleMetrique, getUnite, getIcone, getCouleur") + class DelegationTypeMetrique { + + @Test + @DisplayName("délèguent à typeMetrique") + void testDelegation() { + KPITrendResponse k = minimalKpi(); + assertThat(k.getLibelleMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle()); + assertThat(k.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite()); + assertThat(k.getIcone()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone()); + assertThat(k.getCouleur()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur()); + } + } + + @Nested + @DisplayName("isTendancePositive, isTendanceNegative, isTendanceStable") + class Tendance { + + @Test + @DisplayName("isTendancePositive true quand tendanceGenerale > 0") + void testPositive() { + KPITrendResponse k = minimalKpi(); + k.setTendanceGenerale(BigDecimal.ONE); + assertThat(k.isTendancePositive()).isTrue(); + assertThat(k.isTendanceNegative()).isFalse(); + assertThat(k.isTendanceStable()).isFalse(); + } + + @Test + @DisplayName("isTendanceNegative true quand tendanceGenerale < 0") + void testNegative() { + KPITrendResponse k = minimalKpi(); + k.setTendanceGenerale(BigDecimal.ONE.negate()); + assertThat(k.isTendanceNegative()).isTrue(); + assertThat(k.isTendancePositive()).isFalse(); + assertThat(k.isTendanceStable()).isFalse(); + } + + @Test + @DisplayName("isTendanceStable true quand tendanceGenerale == 0") + void testStable() { + KPITrendResponse k = minimalKpi(); + k.setTendanceGenerale(BigDecimal.ZERO); + assertThat(k.isTendanceStable()).isTrue(); + assertThat(k.isTendancePositive()).isFalse(); + assertThat(k.isTendanceNegative()).isFalse(); + } + + @Test + @DisplayName("toutes false quand tendanceGenerale null") + void testNull() { + KPITrendResponse k = minimalKpi(); + assertThat(k.isTendancePositive()).isFalse(); + assertThat(k.isTendanceNegative()).isFalse(); + assertThat(k.isTendanceStable()).isFalse(); + } + } + + @Nested + @DisplayName("getVolatilite") + class GetVolatilite { + + @Test + @DisplayName("retourne inconnue quand coefficientVariation null") + void testNull() { + KPITrendResponse k = minimalKpi(); + assertThat(k.getVolatilite()).isEqualTo("inconnue"); + } + + @Test + @DisplayName("retourne faible quand cv <= 0.1") + void testFaible() { + KPITrendResponse k = minimalKpi(); + k.setCoefficientVariation(new BigDecimal("0.05")); + assertThat(k.getVolatilite()).isEqualTo("faible"); + k.setCoefficientVariation(new BigDecimal("0.1")); + assertThat(k.getVolatilite()).isEqualTo("faible"); + } + + @Test + @DisplayName("retourne moyenne quand cv <= 0.3 et > 0.1") + void testMoyenne() { + KPITrendResponse k = minimalKpi(); + k.setCoefficientVariation(new BigDecimal("0.2")); + assertThat(k.getVolatilite()).isEqualTo("moyenne"); + k.setCoefficientVariation(new BigDecimal("0.3")); + assertThat(k.getVolatilite()).isEqualTo("moyenne"); + } + + @Test + @DisplayName("retourne élevée quand cv > 0.3") + void testElevee() { + KPITrendResponse k = minimalKpi(); + k.setCoefficientVariation(new BigDecimal("0.5")); + assertThat(k.getVolatilite()).isEqualTo("élevée"); + } + } + + @Nested + @DisplayName("isPredictionFiable") + class IsPredictionFiable { + + @Test + @DisplayName("retourne false quand coefficientCorrelation null ou < 0.7") + void testFalse() { + KPITrendResponse k = minimalKpi(); + assertThat(k.isPredictionFiable()).isFalse(); + k.setCoefficientCorrelation(new BigDecimal("0.5")); + assertThat(k.isPredictionFiable()).isFalse(); + } + + @Test + @DisplayName("retourne true quand coefficientCorrelation >= 0.7") + void testTrue() { + KPITrendResponse k = minimalKpi(); + k.setCoefficientCorrelation(new BigDecimal("0.7")); + assertThat(k.isPredictionFiable()).isTrue(); + k.setCoefficientCorrelation(new BigDecimal("0.95")); + assertThat(k.isPredictionFiable()).isTrue(); + } + } + + @Nested + @DisplayName("getNombrePointsDonnees") + class GetNombrePointsDonnees { + + @Test + @DisplayName("retourne 0 quand pointsDonnees null") + void testNull() { + KPITrendResponse k = KPITrendResponse.builder() + .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS) + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .dateDebut(LocalDateTime.now()) + .dateFin(LocalDateTime.now()) + .valeurActuelle(BigDecimal.ONE) + .pointsDonnees(null).build(); + assertThat(k.getNombrePointsDonnees()).isEqualTo(0); + } + + @Test + @DisplayName("retourne la taille de pointsDonnees") + void testSize() { + KPITrendResponse k = minimalKpi(); + k.setPointsDonnees(List.of( + KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).build(), + KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.TEN).build())); + assertThat(k.getNombrePointsDonnees()).isEqualTo(2); + } + } + + @Nested + @DisplayName("hasAnomalies") + class HasAnomalies { + + @Test + @DisplayName("retourne false quand pointsDonnees null ou aucun point anomalie") + void testFalse() { + KPITrendResponse k = minimalKpi(); + assertThat(k.hasAnomalies()).isFalse(); + k.setPointsDonnees(List.of( + KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).anomalie(false).build())); + assertThat(k.hasAnomalies()).isFalse(); + } + + @Test + @DisplayName("retourne true quand au moins un point a anomalie true") + void testTrue() { + KPITrendResponse k = minimalKpi(); + k.setPointsDonnees(List.of( + KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).anomalie(false).build(), + KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.TEN).anomalie(true).build())); + assertThat(k.hasAnomalies()).isTrue(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTOTest.java new file mode 100644 index 0000000..3441bc5 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/analytics/ReportConfigDTOTest.java @@ -0,0 +1,341 @@ +package dev.lions.unionflow.server.api.dto.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +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 dev.lions.unionflow.server.api.enums.analytics.FormatExport; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; + +/** + * Tests unitaires pour ReportConfigDTO. + * Couvre getNombreMetriques, getNombreSections, isPeriodePersonnalisee, isConfidentiel, + * necessiteGeneration, getFrequenceTexte (toutes branches). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests ReportConfigDTO") +class ReportConfigDTOTest { + + @Nested + @DisplayName("getNombreMetriques") + class GetNombreMetriques { + + @Test + @DisplayName("retourne 0 quand metriques null") + void testNull() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(null) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.getNombreMetriques()).isEqualTo(0); + } + + @Test + @DisplayName("retourne la taille de metriques") + void testSize() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of( + ReportConfigDTO.MetriqueConfigDTO.builder().typeMetrique(dev.lions.unionflow.server.api.enums.analytics.TypeMetrique.NOMBRE_MEMBRES_ACTIFS).build(), + ReportConfigDTO.MetriqueConfigDTO.builder().typeMetrique(dev.lions.unionflow.server.api.enums.analytics.TypeMetrique.TOTAL_COTISATIONS_COLLECTEES).build())) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.getNombreMetriques()).isEqualTo(2); + } + } + + @Nested + @DisplayName("getNombreSections") + class GetNombreSections { + + @Test + @DisplayName("retourne 0 quand sections null") + void testNull() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .sections(null) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.getNombreSections()).isEqualTo(0); + } + + @Test + @DisplayName("retourne la taille de sections") + void testSize() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .sections(List.of( + ReportConfigDTO.SectionRapportDTO.builder().nom("S1").typeSection("resume").build())) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.getNombreSections()).isEqualTo(1); + } + } + + @Nested + @DisplayName("isPeriodePersonnalisee") + class IsPeriodePersonnalisee { + + @Test + @DisplayName("retourne true quand periodeAnalyse PERIODE_PERSONNALISEE") + void testTrue() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.PERIODE_PERSONNALISEE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.isPeriodePersonnalisee()).isTrue(); + } + + @Test + @DisplayName("retourne false pour autre période") + void testFalse() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.isPeriodePersonnalisee()).isFalse(); + } + } + + @Nested + @DisplayName("isConfidentiel") + class IsConfidentiel { + + @Test + @DisplayName("retourne false quand niveauConfidentialite null ou < 4") + void testFalse() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.isConfidentiel()).isFalse(); + dto.setNiveauConfidentialite(3); + assertThat(dto.isConfidentiel()).isFalse(); + } + + @Test + @DisplayName("retourne false quand niveauConfidentialite explicitement null") + void testConfidentielNiveauNull() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .niveauConfidentialite(5).build(); + dto.setNiveauConfidentialite(null); + assertThat(dto.isConfidentiel()).isFalse(); + } + + @Test + @DisplayName("retourne true quand niveauConfidentialite >= 4") + void testTrue() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .niveauConfidentialite(4).build(); + assertThat(dto.isConfidentiel()).isTrue(); + dto.setNiveauConfidentialite(5); + assertThat(dto.isConfidentiel()).isTrue(); + } + + @Test + @DisplayName("retourne false quand niveauConfidentialite non null mais < 4") + void testNiveauNonNullInferieurA4() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .niveauConfidentialite(3).build(); + assertThat(dto.isConfidentiel()).isFalse(); + } + } + + @Nested + @DisplayName("necessiteGeneration") + class NecessiteGeneration { + + @Test + @DisplayName("retourne false quand rapportAutomatique false") + void testNonAuto() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .rapportAutomatique(false) + .prochaineGeneration(LocalDateTime.now().minusMinutes(1)).build(); + assertThat(dto.necessiteGeneration()).isFalse(); + } + + @Test + @DisplayName("retourne false quand prochaineGeneration null") + void testProchaineNull() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .rapportAutomatique(true) + .prochaineGeneration(null).build(); + assertThat(dto.necessiteGeneration()).isFalse(); + } + + @Test + @DisplayName("retourne true quand automatique et prochaineGeneration dans le passé") + void testNecessite() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .rapportAutomatique(true) + .prochaineGeneration(LocalDateTime.now().minusMinutes(10)).build(); + assertThat(dto.necessiteGeneration()).isTrue(); + } + + @Test + @DisplayName("retourne false quand prochaineGeneration dans le futur") + void testFutur() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .rapportAutomatique(true) + .prochaineGeneration(LocalDateTime.now().plusHours(1)).build(); + assertThat(dto.necessiteGeneration()).isFalse(); + } + } + + @Nested + @DisplayName("getFrequenceTexte") + class GetFrequenceTexte { + + @Test + @DisplayName("retourne Manuelle quand frequenceGenerationHeures null") + void testNull() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Manuelle"); + } + + @Test + @DisplayName("retourne Toutes les heures pour 1") + void test1() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .frequenceGenerationHeures(1).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Toutes les heures"); + } + + @Test + @DisplayName("retourne Quotidienne pour 24") + void test24() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .frequenceGenerationHeures(24).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Quotidienne"); + } + + @Test + @DisplayName("retourne Hebdomadaire pour 168") + void test168() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .frequenceGenerationHeures(168).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Hebdomadaire"); + } + + @Test + @DisplayName("retourne Mensuelle pour 720") + void test720() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .frequenceGenerationHeures(720).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Mensuelle"); + } + + @Test + @DisplayName("retourne Toutes les X heures pour autre valeur") + void testDefault() { + ReportConfigDTO dto = ReportConfigDTO.builder() + .nom("Rapport") + .typeRapport("executif") + .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE) + .utilisateurCreateurId(UUID.randomUUID()) + .metriques(List.of()) + .formatExport(FormatExport.PDF) + .frequenceGenerationHeures(12).build(); + assertThat(dto.getFrequenceTexte()).isEqualTo("Toutes les 12 heures"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequestTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequestTest.java new file mode 100644 index 0000000..5180091 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/auth/request/LoginRequestTest.java @@ -0,0 +1,212 @@ +package dev.lions.unionflow.server.api.dto.auth.request; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour LoginRequest (record). + * Couvre constructeur via builder, accesseurs, et tous les champs. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-01 + */ +@DisplayName("Tests LoginRequest") +class LoginRequestTest { + + @Nested + @DisplayName("Builder et construction") + class Builder { + + @Test + @DisplayName("builder avec tous les champs") + void testBuilderComplet() { + LoginRequest request = LoginRequest.builder() + .username("admin@test.com") + .password("secret123") + .typeCompte("ADMIN") + .rememberMe(true) + .build(); + + assertThat(request.username()).isEqualTo("admin@test.com"); + assertThat(request.password()).isEqualTo("secret123"); + assertThat(request.typeCompte()).isEqualTo("ADMIN"); + assertThat(request.rememberMe()).isTrue(); + } + + @Test + @DisplayName("builder avec champs minimum (rememberMe null)") + void testBuilderMinimum() { + LoginRequest request = LoginRequest.builder() + .username("user@test.com") + .password("pass123") + .typeCompte("MEMBRE") + .build(); + + assertThat(request.username()).isEqualTo("user@test.com"); + assertThat(request.password()).isEqualTo("pass123"); + assertThat(request.typeCompte()).isEqualTo("MEMBRE"); + assertThat(request.rememberMe()).isNull(); + } + + @Test + @DisplayName("builder avec rememberMe false") + void testBuilderRememberMeFalse() { + LoginRequest request = LoginRequest.builder() + .username("test@test.com") + .password("test123") + .typeCompte("ADMIN_ENTITE") + .rememberMe(false) + .build(); + + assertThat(request.rememberMe()).isFalse(); + } + } + + @Nested + @DisplayName("Accesseurs (record)") + class Accesseurs { + + @Test + @DisplayName("username() retourne la valeur") + void testUsername() { + LoginRequest request = LoginRequest.builder() + .username("john.doe@test.com") + .password("pass") + .typeCompte("MEMBRE") + .build(); + + assertThat(request.username()).isEqualTo("john.doe@test.com"); + } + + @Test + @DisplayName("password() retourne la valeur") + void testPassword() { + LoginRequest request = LoginRequest.builder() + .username("user") + .password("mySecretPass123!") + .typeCompte("MEMBRE") + .build(); + + assertThat(request.password()).isEqualTo("mySecretPass123!"); + } + + @Test + @DisplayName("typeCompte() retourne la valeur") + void testTypeCompte() { + LoginRequest request = LoginRequest.builder() + .username("super@test.com") + .password("pass") + .typeCompte("SUPER_ADMIN") + .build(); + + assertThat(request.typeCompte()).isEqualTo("SUPER_ADMIN"); + } + + @Test + @DisplayName("rememberMe() retourne la valeur") + void testRememberMe() { + LoginRequest request = LoginRequest.builder() + .username("user") + .password("pass") + .typeCompte("MEMBRE") + .rememberMe(true) + .build(); + + assertThat(request.rememberMe()).isTrue(); + } + } + + @Nested + @DisplayName("Equals et HashCode") + class EqualsHashCode { + + @Test + @DisplayName("equals retourne true pour même instance") + void testEqualsMemeInstance() { + LoginRequest request = LoginRequest.builder() + .username("user") + .password("pass") + .typeCompte("MEMBRE") + .build(); + + assertThat(request).isEqualTo(request); + } + + @Test + @DisplayName("equals retourne true pour mêmes valeurs") + void testEqualsMemeValeurs() { + LoginRequest r1 = LoginRequest.builder() + .username("user") + .password("pass") + .typeCompte("MEMBRE") + .rememberMe(true) + .build(); + + LoginRequest r2 = LoginRequest.builder() + .username("user") + .password("pass") + .typeCompte("MEMBRE") + .rememberMe(true) + .build(); + + assertThat(r1).isEqualTo(r2); + assertThat(r1.hashCode()).isEqualTo(r2.hashCode()); + } + + @Test + @DisplayName("equals retourne false pour valeurs différentes") + void testEqualsValeursDifferentes() { + LoginRequest r1 = LoginRequest.builder() + .username("user1") + .password("pass") + .typeCompte("MEMBRE") + .build(); + + LoginRequest r2 = LoginRequest.builder() + .username("user2") + .password("pass") + .typeCompte("MEMBRE") + .build(); + + assertThat(r1).isNotEqualTo(r2); + } + + @Test + @DisplayName("equals retourne false pour null") + void testEqualsNull() { + LoginRequest request = LoginRequest.builder() + .username("user") + .password("pass") + .typeCompte("MEMBRE") + .build(); + + assertThat(request).isNotEqualTo(null); + } + } + + @Nested + @DisplayName("ToString") + class ToString { + + @Test + @DisplayName("toString contient tous les champs") + void testToString() { + LoginRequest request = LoginRequest.builder() + .username("admin") + .password("secret") + .typeCompte("ADMIN") + .rememberMe(true) + .build(); + + String str = request.toString(); + assertThat(str).contains("username=admin"); + assertThat(str).contains("password=secret"); + assertThat(str).contains("typeCompte=ADMIN"); + assertThat(str).contains("rememberMe=true"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponseTest.java new file mode 100644 index 0000000..236b3fd --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/auth/response/LoginResponseTest.java @@ -0,0 +1,403 @@ +package dev.lions.unionflow.server.api.dto.auth.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +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; + +/** + * Tests unitaires pour LoginResponse. + * Couvre builder, getters/setters, isExpired(), UserInfo, EntiteInfo. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-01 + */ +@DisplayName("Tests LoginResponse") +class LoginResponseTest { + + @Nested + @DisplayName("Builder et construction") + class Builder { + + @Test + @DisplayName("builder avec tous les champs") + void testBuilderComplet() { + LocalDateTime expiration = LocalDateTime.now().plusHours(1); + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + + LoginResponse response = LoginResponse.builder() + .accessToken("access_token_123") + .refreshToken("refresh_token_456") + .tokenType("Bearer") + .expiresIn(3600L) + .expirationDate(expiration) + .user(user) + .build(); + + assertThat(response.getAccessToken()).isEqualTo("access_token_123"); + assertThat(response.getRefreshToken()).isEqualTo("refresh_token_456"); + assertThat(response.getTokenType()).isEqualTo("Bearer"); + assertThat(response.getExpiresIn()).isEqualTo(3600L); + assertThat(response.getExpirationDate()).isEqualTo(expiration); + assertThat(response.getUser()).isEqualTo(user); + } + + @Test + @DisplayName("builder avec tokenType par défaut") + void testBuilderTokenTypeParDefaut() { + LoginResponse response = LoginResponse.builder() + .accessToken("token") + .refreshToken("refresh") + .expiresIn(3600L) + .build(); + + assertThat(response.getTokenType()).isEqualTo("Bearer"); + } + + @Test + @DisplayName("constructeur NoArgs") + void testConstructeurNoArgs() { + LoginResponse response = new LoginResponse(); + assertThat(response).isNotNull(); + assertThat(response.getAccessToken()).isNull(); + } + + @Test + @DisplayName("constructeur AllArgs") + void testConstructeurAllArgs() { + LocalDateTime expiration = LocalDateTime.now().plusHours(1); + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + + LoginResponse response = new LoginResponse( + "access", + "refresh", + "Bearer", + 3600L, + expiration, + user + ); + + assertThat(response.getAccessToken()).isEqualTo("access"); + assertThat(response.getRefreshToken()).isEqualTo("refresh"); + assertThat(response.getTokenType()).isEqualTo("Bearer"); + assertThat(response.getExpiresIn()).isEqualTo(3600L); + assertThat(response.getExpirationDate()).isEqualTo(expiration); + assertThat(response.getUser()).isEqualTo(user); + } + } + + @Nested + @DisplayName("Getters et Setters") + class GettersSetters { + + @Test + @DisplayName("setters et getters fonctionnent") + void testSettersGetters() { + LoginResponse response = new LoginResponse(); + LocalDateTime expiration = LocalDateTime.now().plusHours(2); + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + + response.setAccessToken("new_access"); + response.setRefreshToken("new_refresh"); + response.setTokenType("Custom"); + response.setExpiresIn(7200L); + response.setExpirationDate(expiration); + response.setUser(user); + + assertThat(response.getAccessToken()).isEqualTo("new_access"); + assertThat(response.getRefreshToken()).isEqualTo("new_refresh"); + assertThat(response.getTokenType()).isEqualTo("Custom"); + assertThat(response.getExpiresIn()).isEqualTo(7200L); + assertThat(response.getExpirationDate()).isEqualTo(expiration); + assertThat(response.getUser()).isEqualTo(user); + } + } + + @Nested + @DisplayName("isExpired") + class IsExpired { + + @Test + @DisplayName("retourne false quand expirationDate dans le futur") + void testNonExpire() { + LoginResponse response = LoginResponse.builder() + .accessToken("token") + .refreshToken("refresh") + .expirationDate(LocalDateTime.now().plusHours(1)) + .build(); + + assertThat(response.isExpired()).isFalse(); + } + + @Test + @DisplayName("retourne true quand expirationDate dans le passé") + void testExpire() { + LoginResponse response = LoginResponse.builder() + .accessToken("token") + .refreshToken("refresh") + .expirationDate(LocalDateTime.now().minusHours(1)) + .build(); + + assertThat(response.isExpired()).isTrue(); + } + + @Test + @DisplayName("retourne false quand expirationDate null") + void testExpirationNull() { + LoginResponse response = LoginResponse.builder() + .accessToken("token") + .refreshToken("refresh") + .expirationDate(null) + .build(); + + assertThat(response.isExpired()).isFalse(); + } + } + + @Nested + @DisplayName("UserInfo - Builder et construction") + class UserInfoBuilder { + + @Test + @DisplayName("builder UserInfo avec tous les champs") + void testBuilderComplet() { + UUID userId = UUID.randomUUID(); + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo(); + List roles = Arrays.asList("ADMIN", "MEMBRE"); + List permissions = Arrays.asList("READ", "WRITE"); + + LoginResponse.UserInfo user = LoginResponse.UserInfo.builder() + .id(userId) + .nom("Diallo") + .prenom("Amadou") + .email("amadou@test.com") + .username("adiallo") + .typeCompte("ADMIN") + .roles(roles) + .permissions(permissions) + .entite(entite) + .build(); + + assertThat(user.getId()).isEqualTo(userId); + assertThat(user.getNom()).isEqualTo("Diallo"); + assertThat(user.getPrenom()).isEqualTo("Amadou"); + assertThat(user.getEmail()).isEqualTo("amadou@test.com"); + assertThat(user.getUsername()).isEqualTo("adiallo"); + assertThat(user.getTypeCompte()).isEqualTo("ADMIN"); + assertThat(user.getRoles()).isEqualTo(roles); + assertThat(user.getPermissions()).isEqualTo(permissions); + assertThat(user.getEntite()).isEqualTo(entite); + } + + @Test + @DisplayName("constructeur NoArgs UserInfo") + void testConstructeurNoArgs() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + assertThat(user).isNotNull(); + assertThat(user.getId()).isNull(); + } + + @Test + @DisplayName("constructeur AllArgs UserInfo") + void testConstructeurAllArgs() { + UUID userId = UUID.randomUUID(); + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo(); + List roles = Collections.singletonList("MEMBRE"); + List permissions = Collections.singletonList("READ"); + + LoginResponse.UserInfo user = new LoginResponse.UserInfo( + userId, + "Traoré", + "Fatou", + "fatou@test.com", + "ftraore", + "MEMBRE", + roles, + permissions, + entite + ); + + assertThat(user.getId()).isEqualTo(userId); + assertThat(user.getNom()).isEqualTo("Traoré"); + assertThat(user.getPrenom()).isEqualTo("Fatou"); + assertThat(user.getEmail()).isEqualTo("fatou@test.com"); + assertThat(user.getUsername()).isEqualTo("ftraore"); + assertThat(user.getTypeCompte()).isEqualTo("MEMBRE"); + assertThat(user.getRoles()).isEqualTo(roles); + assertThat(user.getPermissions()).isEqualTo(permissions); + assertThat(user.getEntite()).isEqualTo(entite); + } + } + + @Nested + @DisplayName("UserInfo - Getters et Setters") + class UserInfoGettersSetters { + + @Test + @DisplayName("setters et getters fonctionnent") + void testSettersGetters() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + UUID userId = UUID.randomUUID(); + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo(); + List roles = Arrays.asList("ROLE1", "ROLE2"); + List permissions = Arrays.asList("PERM1", "PERM2"); + + user.setId(userId); + user.setNom("Ndiaye"); + user.setPrenom("Moussa"); + user.setEmail("moussa@test.com"); + user.setUsername("mndiaye"); + user.setTypeCompte("SUPER_ADMIN"); + user.setRoles(roles); + user.setPermissions(permissions); + user.setEntite(entite); + + assertThat(user.getId()).isEqualTo(userId); + assertThat(user.getNom()).isEqualTo("Ndiaye"); + assertThat(user.getPrenom()).isEqualTo("Moussa"); + assertThat(user.getEmail()).isEqualTo("moussa@test.com"); + assertThat(user.getUsername()).isEqualTo("mndiaye"); + assertThat(user.getTypeCompte()).isEqualTo("SUPER_ADMIN"); + assertThat(user.getRoles()).isEqualTo(roles); + assertThat(user.getPermissions()).isEqualTo(permissions); + assertThat(user.getEntite()).isEqualTo(entite); + } + } + + @Nested + @DisplayName("UserInfo - getNomComplet") + class UserInfoGetNomComplet { + + @Test + @DisplayName("retourne prenom + nom quand les deux sont présents") + void testPrenomEtNom() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + user.setPrenom("Amadou"); + user.setNom("Diallo"); + user.setUsername("adiallo"); + + assertThat(user.getNomComplet()).isEqualTo("Amadou Diallo"); + } + + @Test + @DisplayName("retourne nom quand prenom null") + void testNomSeul() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + user.setNom("Diallo"); + user.setUsername("adiallo"); + + assertThat(user.getNomComplet()).isEqualTo("Diallo"); + } + + @Test + @DisplayName("retourne username quand nom null") + void testUsernameSeul() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + user.setPrenom("Amadou"); + user.setUsername("adiallo"); + + assertThat(user.getNomComplet()).isEqualTo("adiallo"); + } + + @Test + @DisplayName("retourne username quand nom et prenom null") + void testUsernameParDefaut() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + user.setUsername("admin"); + + assertThat(user.getNomComplet()).isEqualTo("admin"); + } + + @Test + @DisplayName("retourne null quand tout est null") + void testToutNull() { + LoginResponse.UserInfo user = new LoginResponse.UserInfo(); + + assertThat(user.getNomComplet()).isNull(); + } + } + + @Nested + @DisplayName("EntiteInfo - Builder et construction") + class EntiteInfoBuilder { + + @Test + @DisplayName("builder EntiteInfo avec tous les champs") + void testBuilderComplet() { + UUID entiteId = UUID.randomUUID(); + + LoginResponse.EntiteInfo entite = LoginResponse.EntiteInfo.builder() + .id(entiteId) + .nom("Association ABC") + .type("Association") + .pays("Sénégal") + .ville("Dakar") + .build(); + + assertThat(entite.getId()).isEqualTo(entiteId); + assertThat(entite.getNom()).isEqualTo("Association ABC"); + assertThat(entite.getType()).isEqualTo("Association"); + assertThat(entite.getPays()).isEqualTo("Sénégal"); + assertThat(entite.getVille()).isEqualTo("Dakar"); + } + + @Test + @DisplayName("constructeur NoArgs EntiteInfo") + void testConstructeurNoArgs() { + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo(); + assertThat(entite).isNotNull(); + assertThat(entite.getId()).isNull(); + } + + @Test + @DisplayName("constructeur AllArgs EntiteInfo") + void testConstructeurAllArgs() { + UUID entiteId = UUID.randomUUID(); + + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo( + entiteId, + "Mutuelle XYZ", + "Mutuelle", + "Mali", + "Bamako" + ); + + assertThat(entite.getId()).isEqualTo(entiteId); + assertThat(entite.getNom()).isEqualTo("Mutuelle XYZ"); + assertThat(entite.getType()).isEqualTo("Mutuelle"); + assertThat(entite.getPays()).isEqualTo("Mali"); + assertThat(entite.getVille()).isEqualTo("Bamako"); + } + } + + @Nested + @DisplayName("EntiteInfo - Getters et Setters") + class EntiteInfoGettersSetters { + + @Test + @DisplayName("setters et getters fonctionnent") + void testSettersGetters() { + LoginResponse.EntiteInfo entite = new LoginResponse.EntiteInfo(); + UUID entiteId = UUID.randomUUID(); + + entite.setId(entiteId); + entite.setNom("Coopérative DEF"); + entite.setType("Coopérative"); + entite.setPays("Côte d'Ivoire"); + entite.setVille("Abidjan"); + + assertThat(entite.getId()).isEqualTo(entiteId); + assertThat(entite.getNom()).isEqualTo("Coopérative DEF"); + assertThat(entite.getType()).isEqualTo("Coopérative"); + assertThat(entite.getPays()).isEqualTo("Côte d'Ivoire"); + assertThat(entite.getVille()).isEqualTo("Abidjan"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseDTOTest.java new file mode 100644 index 0000000..aa9a938 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseDTOTest.java @@ -0,0 +1,256 @@ +package dev.lions.unionflow.server.api.dto.base; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour BaseDTO. + * Couvre constructeur, marquerCommeNouveau, marquerCommeModifie (version null et non null), + * desactiver, reactiver, isNouveau, isActif, equals, hashCode, toString. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests BaseDTO") +class BaseDTOTest { + + /** Sous-classe concrète pour tester BaseDTO (classe abstraite). */ + private static final class ConcreteBaseDTO extends BaseDTO { + // aucune propriété additionnelle + } + + @Nested + @DisplayName("Constructeur et état initial") + class Constructeur { + + @Test + @DisplayName("constructeur par défaut initialise dateCreation, actif et version") + void testConstructeurParDefaut() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.getDateCreation()).isNotNull(); + assertThat(dto.getActif()).isTrue(); + assertThat(dto.getVersion()).isEqualTo(0L); + assertThat(dto.getId()).isNull(); + } + } + + @Nested + @DisplayName("marquerCommeNouveau") + class MarquerCommeNouveau { + + @Test + @DisplayName("marquerCommeNouveau remplit dates, creePar, modifiePar, version et actif") + void testMarquerCommeNouveau() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setVersion(5L); + dto.setActif(false); + dto.marquerCommeNouveau("user@test.com"); + assertThat(dto.getDateCreation()).isNotNull(); + assertThat(dto.getDateModification()).isNotNull(); + assertThat(dto.getDateCreation()).isEqualTo(dto.getDateModification()); + assertThat(dto.getCreePar()).isEqualTo("user@test.com"); + assertThat(dto.getModifiePar()).isEqualTo("user@test.com"); + assertThat(dto.getVersion()).isEqualTo(0L); + assertThat(dto.getActif()).isTrue(); + } + } + + @Nested + @DisplayName("marquerCommeModifie") + class MarquerCommeModifie { + + @Test + @DisplayName("marquerCommeModifie quand version non null incrémente version") + void testMarquerCommeModifieVersionNonNull() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setVersion(3L); + dto.marquerCommeModifie("other@test.com"); + assertThat(dto.getModifiePar()).isEqualTo("other@test.com"); + assertThat(dto.getDateModification()).isNotNull(); + assertThat(dto.getVersion()).isEqualTo(4L); + } + + @Test + @DisplayName("marquerCommeModifie quand version null ne lance pas NPE") + void testMarquerCommeModifieVersionNull() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setVersion(null); + dto.marquerCommeModifie("other@test.com"); + assertThat(dto.getModifiePar()).isEqualTo("other@test.com"); + assertThat(dto.getVersion()).isNull(); + } + } + + @Nested + @DisplayName("desactiver et reactiver") + class DesactiverReactiver { + + @Test + @DisplayName("desactiver met actif à false et appelle marquerCommeModifie") + void testDesactiver() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.desactiver("admin@test.com"); + assertThat(dto.getActif()).isFalse(); + assertThat(dto.getModifiePar()).isEqualTo("admin@test.com"); + } + + @Test + @DisplayName("reactiver met actif à true et appelle marquerCommeModifie") + void testReactiver() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setActif(false); + dto.reactiver("admin@test.com"); + assertThat(dto.getActif()).isTrue(); + assertThat(dto.getModifiePar()).isEqualTo("admin@test.com"); + } + } + + @Nested + @DisplayName("isNouveau") + class IsNouveau { + + @Test + @DisplayName("isNouveau retourne true quand id est null") + void testIsNouveauTrue() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.isNouveau()).isTrue(); + } + + @Test + @DisplayName("isNouveau retourne false quand id est non null") + void testIsNouveauFalse() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setId(UUID.randomUUID()); + assertThat(dto.isNouveau()).isFalse(); + } + } + + @Nested + @DisplayName("isActif") + class IsActif { + + @Test + @DisplayName("isActif retourne true quand actif est Boolean.TRUE") + void testIsActifTrue() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setActif(true); + assertThat(dto.isActif()).isTrue(); + } + + @Test + @DisplayName("isActif retourne false quand actif est false ou null") + void testIsActifFalse() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setActif(false); + assertThat(dto.isActif()).isFalse(); + dto.setActif(null); + assertThat(dto.isActif()).isFalse(); + } + } + + @Nested + @DisplayName("equals") + class Equals { + + @Test + @DisplayName("equals retourne true pour même instance") + void testEqualsMemeInstance() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.equals(dto)).isTrue(); + } + + @Test + @DisplayName("equals retourne false pour null") + void testEqualsNull() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.equals(null)).isFalse(); + } + + @Test + @DisplayName("equals retourne false pour classe différente") + void testEqualsClasseDifferent() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.equals("not a DTO")).isFalse(); + } + + @Test + @DisplayName("equals retourne true quand ids égaux (non null)") + void testEqualsIdsEgaux() { + UUID id = UUID.randomUUID(); + ConcreteBaseDTO a = new ConcreteBaseDTO(); + a.setId(id); + ConcreteBaseDTO b = new ConcreteBaseDTO(); + b.setId(id); + assertThat(a).isEqualTo(b); + } + + @Test + @DisplayName("equals retourne false quand id de this est null") + void testEqualsIdThisNull() { + ConcreteBaseDTO a = new ConcreteBaseDTO(); + a.setId(null); + ConcreteBaseDTO b = new ConcreteBaseDTO(); + b.setId(UUID.randomUUID()); + assertThat(a.equals(b)).isFalse(); + } + + @Test + @DisplayName("equals retourne false quand ids différents") + void testEqualsIdsDifferents() { + ConcreteBaseDTO a = new ConcreteBaseDTO(); + a.setId(UUID.randomUUID()); + ConcreteBaseDTO b = new ConcreteBaseDTO(); + b.setId(UUID.randomUUID()); + assertThat(a).isNotEqualTo(b); + } + } + + @Nested + @DisplayName("hashCode") + class HashCode { + + @Test + @DisplayName("hashCode retourne 0 quand id est null") + void testHashCodeIdNull() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + assertThat(dto.hashCode()).isEqualTo(0); + } + + @Test + @DisplayName("hashCode retourne id.hashCode() quand id non null") + void testHashCodeIdNonNull() { + UUID id = UUID.randomUUID(); + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + dto.setId(id); + assertThat(dto.hashCode()).isEqualTo(id.hashCode()); + } + } + + @Nested + @DisplayName("toString") + class ToString { + + @Test + @DisplayName("toString contient le nom de la classe et les champs") + void testToString() { + ConcreteBaseDTO dto = new ConcreteBaseDTO(); + UUID id = UUID.randomUUID(); + dto.setId(id); + dto.setCreePar("u1"); + dto.setModifiePar("u2"); + dto.setVersion(1L); + String s = dto.toString(); + assertThat(s).contains("ConcreteBaseDTO"); + assertThat(s).contains("id=" + id); + assertThat(s).contains("creePar='u1'"); + assertThat(s).contains("modifiePar='u2'"); + assertThat(s).contains("version=1"); + assertThat(s).contains("actif=true"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseResponseTest.java new file mode 100644 index 0000000..e73d693 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/base/BaseResponseTest.java @@ -0,0 +1,136 @@ +package dev.lions.unionflow.server.api.dto.base; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour BaseResponse. + * Couvre equals() et hashCode() basés sur l'ID. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-01 + */ +@DisplayName("Tests BaseResponse") +class BaseResponseTest { + + /** Sous-classe concrète pour tester BaseResponse (classe abstraite). */ + private static final class ConcreteBaseResponse extends BaseResponse { + // aucune propriété additionnelle + } + + @Nested + @DisplayName("equals") + class Equals { + + @Test + @DisplayName("retourne true pour même instance") + void testEqualsMemeInstance() { + ConcreteBaseResponse response = new ConcreteBaseResponse(); + assertThat(response.equals(response)).isTrue(); + } + + @Test + @DisplayName("retourne false pour null") + void testEqualsNull() { + ConcreteBaseResponse response = new ConcreteBaseResponse(); + assertThat(response.equals(null)).isFalse(); + } + + @Test + @DisplayName("retourne false pour classe différente") + void testEqualsClasseDifferente() { + ConcreteBaseResponse response = new ConcreteBaseResponse(); + assertThat(response.equals("not a Response")).isFalse(); + } + + @Test + @DisplayName("retourne true quand ids égaux (non null)") + void testEqualsIdsEgaux() { + UUID id = UUID.randomUUID(); + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(id); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(id); + assertThat(a).isEqualTo(b); + } + + @Test + @DisplayName("retourne false quand id de this est null") + void testEqualsIdThisNull() { + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(null); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(UUID.randomUUID()); + assertThat(a.equals(b)).isFalse(); + } + + @Test + @DisplayName("retourne false quand id de that est null") + void testEqualsIdThatNull() { + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(UUID.randomUUID()); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(null); + assertThat(a.equals(b)).isFalse(); + } + + @Test + @DisplayName("retourne false quand les deux ids sont null") + void testEqualsIdsDeuxNull() { + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(null); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(null); + assertThat(a.equals(b)).isFalse(); + } + + @Test + @DisplayName("retourne false quand ids différents") + void testEqualsIdsDifferents() { + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(UUID.randomUUID()); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(UUID.randomUUID()); + assertThat(a).isNotEqualTo(b); + } + } + + @Nested + @DisplayName("hashCode") + class HashCode { + + @Test + @DisplayName("retourne 0 quand id est null") + void testHashCodeIdNull() { + ConcreteBaseResponse response = new ConcreteBaseResponse(); + assertThat(response.hashCode()).isEqualTo(0); + } + + @Test + @DisplayName("retourne id.hashCode() quand id non null") + void testHashCodeIdNonNull() { + UUID id = UUID.randomUUID(); + ConcreteBaseResponse response = new ConcreteBaseResponse(); + response.setId(id); + assertThat(response.hashCode()).isEqualTo(id.hashCode()); + } + + @Test + @DisplayName("objets égaux ont même hashCode") + void testHashCodeConsistentAvecEquals() { + UUID id = UUID.randomUUID(); + ConcreteBaseResponse a = new ConcreteBaseResponse(); + a.setId(id); + ConcreteBaseResponse b = new ConcreteBaseResponse(); + b.setId(id); + + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/base/PageResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/base/PageResponseTest.java new file mode 100644 index 0000000..cfa5e1a --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/base/PageResponseTest.java @@ -0,0 +1,82 @@ +package dev.lions.unionflow.server.api.dto.base; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour PageResponse. + * Couvre constructeur, getters, hasNext (true/false), hasPrevious (true/false). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests PageResponse") +class PageResponseTest { + + @Nested + @DisplayName("Constructeur et getters") + class ConstructeurEtGetters { + + @Test + @DisplayName("constructeur initialise tous les champs") + void testConstructeur() { + List contenu = List.of("a", "b"); + PageResponse page = new PageResponse<>(contenu, 0, 10, 25L, 3); + assertThat(page.getContenu()).isSameAs(contenu); + assertThat(page.getPage()).isEqualTo(0); + assertThat(page.getTaille()).isEqualTo(10); + assertThat(page.getTotalElements()).isEqualTo(25L); + assertThat(page.getTotalPages()).isEqualTo(3); + } + } + + @Nested + @DisplayName("hasNext") + class HasNext { + + @Test + @DisplayName("hasNext retourne true quand page < totalPages - 1") + void testHasNextTrue() { + PageResponse page = new PageResponse<>(List.of("a"), 0, 10, 25L, 3); + assertThat(page.hasNext()).isTrue(); + } + + @Test + @DisplayName("hasNext retourne false quand page == totalPages - 1") + void testHasNextFalseDernierePage() { + PageResponse page = new PageResponse<>(List.of("a"), 2, 10, 25L, 3); + assertThat(page.hasNext()).isFalse(); + } + + @Test + @DisplayName("hasNext retourne false quand une seule page") + void testHasNextFalseUneSeulePage() { + PageResponse page = new PageResponse<>(List.of("a", "b"), 0, 10, 2L, 1); + assertThat(page.hasNext()).isFalse(); + } + } + + @Nested + @DisplayName("hasPrevious") + class HasPrevious { + + @Test + @DisplayName("hasPrevious retourne false sur la première page") + void testHasPreviousFalse() { + PageResponse page = new PageResponse<>(List.of("a"), 0, 10, 25L, 3); + assertThat(page.hasPrevious()).isFalse(); + } + + @Test + @DisplayName("hasPrevious retourne true quand page > 0") + void testHasPreviousTrue() { + PageResponse page = new PageResponse<>(List.of("a"), 1, 10, 25L, 3); + assertThat(page.hasPrevious()).isTrue(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/common/PagedResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/common/PagedResponseTest.java new file mode 100644 index 0000000..e9a9b21 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/common/PagedResponseTest.java @@ -0,0 +1,220 @@ +package dev.lions.unionflow.server.api.dto.common; + +import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitaires pour PagedResponse + */ +class PagedResponseTest { + + @Test + void testConstructeurVide() { + PagedResponse response = new PagedResponse<>(); + assertNull(response.getData()); + assertNull(response.getTotal()); + assertNull(response.getPage()); + assertNull(response.getSize()); + assertNull(response.getTotalPages()); + } + + @Test + void testConstructeurAvecParametres() { + List data = Arrays.asList("A", "B", "C"); + PagedResponse response = new PagedResponse<>(data, 100L, 2, 20); + + assertEquals(data, response.getData()); + assertEquals(100L, response.getTotal()); + assertEquals(2, response.getPage()); + assertEquals(20, response.getSize()); + assertEquals(5, response.getTotalPages()); // 100 / 20 = 5 + } + + @Test + void testConstructeurComplet() { + List data = Arrays.asList("A", "B"); + PagedResponse response = new PagedResponse<>(data, 50L, 1, 10, 5); + + assertEquals(data, response.getData()); + assertEquals(50L, response.getTotal()); + assertEquals(1, response.getPage()); + assertEquals(10, response.getSize()); + assertEquals(5, response.getTotalPages()); + } + + @Test + void testCalculTotalPages() { + // Cas normal : 100 éléments, 20 par page = 5 pages + PagedResponse response1 = new PagedResponse<>(Collections.emptyList(), 100L, 0, 20); + assertEquals(5, response1.getTotalPages()); + + // Cas avec reste : 101 éléments, 20 par page = 6 pages (arrondi supérieur) + PagedResponse response2 = new PagedResponse<>(Collections.emptyList(), 101L, 0, 20); + assertEquals(6, response2.getTotalPages()); + + // Cas avec 0 élément + PagedResponse response3 = new PagedResponse<>(Collections.emptyList(), 0L, 0, 20); + assertEquals(0, response3.getTotalPages()); + + // Cas avec size = 0 (éviter division par zéro) + PagedResponse response4 = new PagedResponse<>(Collections.emptyList(), 100L, 0, 0); + assertEquals(0, response4.getTotalPages()); + + // Cas avec total null + PagedResponse response5 = new PagedResponse<>(Collections.emptyList(), null, 0, 20); + assertEquals(0, response5.getTotalPages()); + } + + @Test + void testSetters() { + PagedResponse response = new PagedResponse<>(); + List data = Arrays.asList(1, 2, 3); + + response.setData(data); + response.setTotal(50L); + response.setPage(2); + response.setSize(10); + response.setTotalPages(5); + + assertEquals(data, response.getData()); + assertEquals(50L, response.getTotal()); + assertEquals(2, response.getPage()); + assertEquals(10, response.getSize()); + assertEquals(5, response.getTotalPages()); + } + + @Test + void testSetTotalRecalculeTotalPages() { + PagedResponse response = new PagedResponse<>(); + response.setSize(10); + response.setTotal(100L); + + // Doit recalculer totalPages automatiquement + assertEquals(10, response.getTotalPages()); + } + + @Test + void testSetSizeRecalculeTotalPages() { + PagedResponse response = new PagedResponse<>(); + response.setTotal(100L); + response.setSize(25); + + // Doit recalculer totalPages automatiquement + assertEquals(4, response.getTotalPages()); + } + + @Test + void testHasNext() { + // Page 0 sur 5 pages totales → hasNext = true + PagedResponse response1 = new PagedResponse<>(Collections.emptyList(), 100L, 0, 20); + assertTrue(response1.hasNext()); + + // Page 4 (dernière) sur 5 pages → hasNext = false + PagedResponse response2 = new PagedResponse<>(Collections.emptyList(), 100L, 4, 20); + assertFalse(response2.hasNext()); + + // Page null → hasNext = false + PagedResponse response3 = new PagedResponse<>(); + response3.setTotal(100L); + response3.setSize(20); + assertFalse(response3.hasNext()); + } + + @Test + void testHasPrevious() { + // Page 0 → hasPrevious = false + PagedResponse response1 = new PagedResponse<>(Collections.emptyList(), 100L, 0, 20); + assertFalse(response1.hasPrevious()); + + // Page 1 → hasPrevious = true + PagedResponse response2 = new PagedResponse<>(Collections.emptyList(), 100L, 1, 20); + assertTrue(response2.hasPrevious()); + + // Page null → hasPrevious = false + PagedResponse response3 = new PagedResponse<>(); + assertFalse(response3.hasPrevious()); + } + + @Test + void testIsEmpty() { + // Liste vide → isEmpty = true + PagedResponse response1 = new PagedResponse<>(Collections.emptyList(), 0L, 0, 20); + assertTrue(response1.isEmpty()); + + // Liste null → isEmpty = true + PagedResponse response2 = new PagedResponse<>(); + assertTrue(response2.isEmpty()); + + // Liste avec données → isEmpty = false + PagedResponse response3 = new PagedResponse<>(Arrays.asList("A", "B"), 2L, 0, 20); + assertFalse(response3.isEmpty()); + } + + @Test + void testToString() { + List data = Arrays.asList("A", "B", "C"); + PagedResponse response = new PagedResponse<>(data, 100L, 2, 20); + + String result = response.toString(); + + assertTrue(result.contains("total=100")); + assertTrue(result.contains("page=2")); + assertTrue(result.contains("size=20")); + assertTrue(result.contains("totalPages=5")); + assertTrue(result.contains("itemsCount=3")); + } + + @Test + void testToStringAvecDataNull() { + PagedResponse response = new PagedResponse<>(); + String result = response.toString(); + + assertTrue(result.contains("itemsCount=0")); + } + + @Test + void testCasBordure_TotalPagesAvecValeursPetites() { + // 1 élément, 10 par page = 1 page + PagedResponse response = new PagedResponse<>(Collections.emptyList(), 1L, 0, 10); + assertEquals(1, response.getTotalPages()); + } + + @Test + void testCasBordure_TotalPagesAvecValeursGrandes() { + // 999999 éléments, 100 par page = 10000 pages + PagedResponse response = new PagedResponse<>(Collections.emptyList(), 999999L, 0, 100); + assertEquals(10000, response.getTotalPages()); + } + + @Test + void testGenerique() { + // Test avec différents types génériques + PagedResponse intResponse = new PagedResponse<>(Arrays.asList(1, 2, 3), 3L, 0, 10); + assertEquals(3, intResponse.getData().size()); + + PagedResponse doubleResponse = new PagedResponse<>(Arrays.asList(1.5, 2.5), 2L, 0, 10); + assertEquals(2, doubleResponse.getData().size()); + + PagedResponse customResponse = new PagedResponse<>( + Arrays.asList(new CustomDTO("test")), 1L, 0, 10 + ); + assertEquals(1, customResponse.getData().size()); + } + + // Classe DTO personnalisée pour tester le générique + private static class CustomDTO { + private final String value; + + public CustomDTO(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponseTest.java new file mode 100644 index 0000000..953a1c9 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/cotisation/response/CotisationResponseTest.java @@ -0,0 +1,189 @@ +package dev.lions.unionflow.server.api.dto.cotisation.response; + +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.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour CotisationResponse. + * Couvre les propriétés d'affichage membre/organisation (nomCompletMembre, + * initialesMembre, typeMembre, regionOrganisation, iconeOrganisation). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-04 + */ +@DisplayName("Tests CotisationResponse") +class CotisationResponseTest { + + @Nested + @DisplayName("Builder et getters – propriétés membre/organisation") + class BuilderEtGetters { + + @Test + @DisplayName("builder remplit nomCompletMembre, initialesMembre, typeMembre") + void builder_remplitProprietesMembre() { + CotisationResponse r = CotisationResponse.builder() + .nomCompletMembre("Jean Dupont") + .initialesMembre("JD") + .typeMembre("Actif") + .nomMembre("Jean Dupont") + .numeroMembre("M-001") + .build(); + + assertThat(r.getNomCompletMembre()).isEqualTo("Jean Dupont"); + assertThat(r.getInitialesMembre()).isEqualTo("JD"); + assertThat(r.getTypeMembre()).isEqualTo("Actif"); + } + + @Test + @DisplayName("builder remplit regionOrganisation et iconeOrganisation") + void builder_remplitProprietesOrganisation() { + CotisationResponse r = CotisationResponse.builder() + .nomOrganisation("Club Lions Abidjan") + .regionOrganisation("Abidjan") + .iconeOrganisation("pi-users") + .build(); + + assertThat(r.getRegionOrganisation()).isEqualTo("Abidjan"); + assertThat(r.getIconeOrganisation()).isEqualTo("pi-users"); + } + + @Test + @DisplayName("propriétés null acceptées") + void builder_accepteNull() { + CotisationResponse r = CotisationResponse.builder() + .numeroReference("REF-1") + .build(); + + assertThat(r.getNomCompletMembre()).isNull(); + assertThat(r.getInitialesMembre()).isNull(); + assertThat(r.getTypeMembre()).isNull(); + assertThat(r.getRegionOrganisation()).isNull(); + assertThat(r.getIconeOrganisation()).isNull(); + } + + @Test + @DisplayName("setters mettent à jour les propriétés") + void setters_mettentAJour() { + CotisationResponse r = new CotisationResponse(); + r.setNomCompletMembre("Marie Martin"); + r.setInitialesMembre("MM"); + r.setTypeMembre("En attente"); + r.setRegionOrganisation("Dakar"); + r.setIconeOrganisation("pi-building"); + + assertThat(r.getNomCompletMembre()).isEqualTo("Marie Martin"); + assertThat(r.getInitialesMembre()).isEqualTo("MM"); + assertThat(r.getTypeMembre()).isEqualTo("En attente"); + assertThat(r.getRegionOrganisation()).isEqualTo("Dakar"); + assertThat(r.getIconeOrganisation()).isEqualTo("pi-building"); + } + + @Test + @DisplayName("builder remplit typeLibelle, typeSeverity, typeIcon, statutSeverity, statutIcon") + void builder_remplitTypeEtStatutTag() { + CotisationResponse r = CotisationResponse.builder() + .type("ANNUELLE") + .typeLibelle("Annuelle") + .typeSeverity("success") + .typeIcon("pi-star") + .statut("PAYEE") + .statutSeverity("success") + .statutIcon("pi-check") + .build(); + + assertThat(r.getType()).isEqualTo("ANNUELLE"); + assertThat(r.getTypeLibelle()).isEqualTo("Annuelle"); + assertThat(r.getTypeSeverity()).isEqualTo("success"); + assertThat(r.getTypeIcon()).isEqualTo("pi-star"); + assertThat(r.getStatutSeverity()).isEqualTo("success"); + assertThat(r.getStatutIcon()).isEqualTo("pi-check"); + } + + @Test + @DisplayName("builder remplit montant, montantFormatte, dates formatées, retard, mode paiement") + void builder_remplitAffichage() { + CotisationResponse r = CotisationResponse.builder() + .montant(BigDecimal.valueOf(10000)) + .montantFormatte("10 000") + .dateEcheanceFormattee("15/03/2026") + .retardCouleur("text-green-600") + .retardTexte("À jour") + .datePaiementFormattee("01/03/2026 14:30") + .modePaiementIcon("pi-wallet") + .modePaiementLibelle("Espèces") + .build(); + + assertThat(r.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(10000)); + assertThat(r.getMontantFormatte()).isEqualTo("10 000"); + assertThat(r.getDateEcheanceFormattee()).isEqualTo("15/03/2026"); + assertThat(r.getRetardCouleur()).isEqualTo("text-green-600"); + assertThat(r.getRetardTexte()).isEqualTo("À jour"); + assertThat(r.getDatePaiementFormattee()).isEqualTo("01/03/2026 14:30"); + assertThat(r.getModePaiementIcon()).isEqualTo("pi-wallet"); + assertThat(r.getModePaiementLibelle()).isEqualTo("Espèces"); + } + } + + @Nested + @DisplayName("Builder complet") + class BuilderComplet { + + @Test + @DisplayName("builder avec toutes les propriétés principales") + void builder_complet() { + UUID id = UUID.randomUUID(); + UUID membreId = UUID.randomUUID(); + UUID orgId = UUID.randomUUID(); + LocalDate dateEcheance = LocalDate.now().plusMonths(1); + LocalDateTime datePaiement = LocalDateTime.now(); + + CotisationResponse r = CotisationResponse.builder() + .numeroReference("COT-2026-001") + .membreId(membreId) + .nomMembre("Jean Dupont") + .nomCompletMembre("Jean Dupont") + .initialesMembre("JD") + .typeMembre("Actif") + .numeroMembre("M-001") + .organisationId(orgId) + .nomOrganisation("Club Test") + .regionOrganisation("Abidjan") + .iconeOrganisation("pi-star") + .typeCotisation("MENSUELLE") + .libelle("Cotisation mars") + .montantDu(BigDecimal.valueOf(5000)) + .montantPaye(BigDecimal.ZERO) + .montantRestant(BigDecimal.valueOf(5000)) + .codeDevise("XOF") + .statut("EN_ATTENTE") + .dateEcheance(dateEcheance) + .datePaiement(datePaiement) + .annee(2026) + .mois(3) + .build(); + r.setId(id); + + assertThat(r.getId()).isEqualTo(id); + assertThat(r.getNumeroReference()).isEqualTo("COT-2026-001"); + assertThat(r.getMembreId()).isEqualTo(membreId); + assertThat(r.getNomCompletMembre()).isEqualTo("Jean Dupont"); + assertThat(r.getInitialesMembre()).isEqualTo("JD"); + assertThat(r.getTypeMembre()).isEqualTo("Actif"); + assertThat(r.getOrganisationId()).isEqualTo(orgId); + assertThat(r.getNomOrganisation()).isEqualTo("Club Test"); + assertThat(r.getRegionOrganisation()).isEqualTo("Abidjan"); + assertThat(r.getIconeOrganisation()).isEqualTo("pi-star"); + assertThat(r.getMontantDu()).isEqualByComparingTo(BigDecimal.valueOf(5000)); + assertThat(r.getStatut()).isEqualTo("EN_ATTENTE"); + assertThat(r.getDateEcheance()).isEqualTo(dateEcheance); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponseTest.java new file mode 100644 index 0000000..cf9197f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardDataResponseTest.java @@ -0,0 +1,444 @@ +package dev.lions.unionflow.server.api.dto.dashboard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +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; + +/** + * Tests unitaires pour DashboardDataResponse. + * Couvre tous les getters et méthodes utilitaires (toutes branches). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests DashboardDataResponse") +class DashboardDataResponseTest { + + /** Sous-classe pour couvrir la branche getIsToday() == null dans le filtre. */ + private static final class EventWithNullIsToday extends UpcomingEventResponse { + @Override + public Boolean getIsToday() { return null; } + } + + /** Sous-classe pour couvrir la branche getIsTomorrow() == null dans le filtre. */ + private static final class EventWithNullIsTomorrow extends UpcomingEventResponse { + @Override + public Boolean getIsTomorrow() { return null; } + } + + /** Sous-classe pour couvrir la branche getIsRecent() == null dans le filtre. */ + private static final class ActivityWithNullIsRecent extends RecentActivityResponse { + @Override + public Boolean getIsRecent() { return null; } + } + + /** Sous-classe pour couvrir la branche getIsToday() == null (activité) dans le filtre. */ + private static final class ActivityWithNullIsToday extends RecentActivityResponse { + @Override + public Boolean getIsToday() { return null; } + } + + @Nested + @DisplayName("Constructeur et getters/setters") + class ConstructeurEtGetters { + + @Test + @DisplayName("builder et getters fonctionnent") + void testBuilderEtGetters() { + DashboardStatsResponse stats = new DashboardStatsResponse(); + List activities = List.of(); + List events = List.of(); + Map prefs = Map.of("theme", "dark"); + DashboardDataResponse dto = DashboardDataResponse.builder() + .stats(stats) + .recentActivities(activities) + .upcomingEvents(events) + .userPreferences(prefs) + .organizationId("org1") + .userId("user1") + .build(); + assertThat(dto.getStats()).isSameAs(stats); + assertThat(dto.getRecentActivities()).isSameAs(activities); + assertThat(dto.getUpcomingEvents()).isSameAs(events); + assertThat(dto.getUserPreferences()).isEqualTo(prefs); + assertThat(dto.getOrganizationId()).isEqualTo("org1"); + assertThat(dto.getUserId()).isEqualTo("user1"); + } + + @Test + @DisplayName("no-args et setters") + void testNoArgsEtSetters() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setStats(new DashboardStatsResponse()); + dto.setOrganizationId("o2"); + assertThat(dto.getOrganizationId()).isEqualTo("o2"); + } + } + + @Nested + @DisplayName("getTodayEventsCount") + class GetTodayEventsCount { + + @Test + @DisplayName("retourne 0 quand upcomingEvents est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(null); + assertThat(dto.getTodayEventsCount()).isEqualTo(0); + } + + @Test + @DisplayName("compte les événements avec isToday true") + void testCompteToday() { + LocalDateTime now = LocalDateTime.now(); + UpcomingEventResponse today = UpcomingEventResponse.builder().startDate(now).build(); + UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(now.plusDays(2)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(today, other)); + assertThat(dto.getTodayEventsCount()).isEqualTo(1); + } + + @Test + @DisplayName("exclut les événements dont getIsToday() est null") + void testFiltreIsTodayNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(new EventWithNullIsToday())); + assertThat(dto.getTodayEventsCount()).isEqualTo(0); + } + + @Test + @DisplayName("une liste mixte (null, true, false) compte uniquement les isToday true") + void testFiltreMixteToday() { + LocalDateTime now = LocalDateTime.now(); + UpcomingEventResponse today = UpcomingEventResponse.builder().startDate(now).build(); + UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(now.plusDays(2)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(new EventWithNullIsToday(), today, other)); + assertThat(dto.getTodayEventsCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("getTomorrowEventsCount") + class GetTomorrowEventsCount { + + @Test + @DisplayName("retourne 0 quand upcomingEvents est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(null); + assertThat(dto.getTomorrowEventsCount()).isEqualTo(0); + } + + @Test + @DisplayName("compte les événements avec isTomorrow true") + void testCompteTomorrow() { + UpcomingEventResponse tomorrow = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now().plusDays(1)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(tomorrow)); + assertThat(dto.getTomorrowEventsCount()).isEqualTo(1); + } + + @Test + @DisplayName("exclut les événements dont getIsTomorrow() est null") + void testFiltreIsTomorrowNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(new EventWithNullIsTomorrow())); + assertThat(dto.getTomorrowEventsCount()).isEqualTo(0); + } + + @Test + @DisplayName("une liste mixte pour demain compte uniquement les isTomorrow true") + void testFiltreMixteTomorrow() { + UpcomingEventResponse tomorrow = UpcomingEventResponse.builder().startDate(LocalDateTime.now().plusDays(1)).build(); + UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(LocalDateTime.now().plusDays(3)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(new EventWithNullIsTomorrow(), tomorrow, other)); + assertThat(dto.getTomorrowEventsCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("getRecentActivitiesCount") + class GetRecentActivitiesCount { + + @Test + @DisplayName("retourne 0 quand recentActivities est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(null); + assertThat(dto.getRecentActivitiesCount()).isEqualTo(0); + } + + @Test + @DisplayName("compte les activités avec isRecent true") + void testCompteRecent() { + RecentActivityResponse recent = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusHours(1)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(recent)); + assertThat(dto.getRecentActivitiesCount()).isEqualTo(1); + } + + @Test + @DisplayName("exclut les activités dont getIsRecent() est null") + void testFiltreIsRecentNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(new ActivityWithNullIsRecent())); + assertThat(dto.getRecentActivitiesCount()).isEqualTo(0); + } + + @Test + @DisplayName("une liste mixte pour isRecent compte uniquement les isRecent true") + void testFiltreMixteRecent() { + RecentActivityResponse recent = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusHours(1)).build(); + RecentActivityResponse old = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusDays(2)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(new ActivityWithNullIsRecent(), recent, old)); + assertThat(dto.getRecentActivitiesCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("getTodayActivitiesCount") + class GetTodayActivitiesCount { + + @Test + @DisplayName("retourne 0 quand recentActivities est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(null); + assertThat(dto.getTodayActivitiesCount()).isEqualTo(0); + } + + @Test + @DisplayName("compte les activités avec isToday true") + void testCompteToday() { + RecentActivityResponse today = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now()).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(today)); + assertThat(dto.getTodayActivitiesCount()).isEqualTo(1); + } + + @Test + @DisplayName("exclut les activités dont getIsToday() est null") + void testFiltreIsTodayNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(new ActivityWithNullIsToday())); + assertThat(dto.getTodayActivitiesCount()).isEqualTo(0); + } + + @Test + @DisplayName("une liste mixte pour isToday activité compte uniquement les isToday true") + void testFiltreMixteTodayActivity() { + RecentActivityResponse today = RecentActivityResponse.builder().timestamp(LocalDateTime.now()).build(); + RecentActivityResponse other = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusDays(1)).build(); + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(new ActivityWithNullIsToday(), today, other)); + assertThat(dto.getTodayActivitiesCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("getHasUpcomingEvents") + class GetHasUpcomingEvents { + + @Test + @DisplayName("retourne false quand upcomingEvents est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(null); + assertThat(dto.getHasUpcomingEvents()).isFalse(); + } + + @Test + @DisplayName("retourne false quand upcomingEvents est vide") + void testVide() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of()); + assertThat(dto.getHasUpcomingEvents()).isFalse(); + } + + @Test + @DisplayName("retourne true quand upcomingEvents non vide") + void testNonVide() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUpcomingEvents(List.of(UpcomingEventResponse.builder().build())); + assertThat(dto.getHasUpcomingEvents()).isTrue(); + } + } + + @Nested + @DisplayName("getHasRecentActivities") + class GetHasRecentActivities { + + @Test + @DisplayName("retourne false quand recentActivities est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(null); + assertThat(dto.getHasRecentActivities()).isFalse(); + } + + @Test + @DisplayName("retourne false quand recentActivities est vide") + void testVide() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of()); + assertThat(dto.getHasRecentActivities()).isFalse(); + } + + @Test + @DisplayName("retourne true quand recentActivities non vide") + void testNonVide() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setRecentActivities(List.of(RecentActivityResponse.builder().build())); + assertThat(dto.getHasRecentActivities()).isTrue(); + } + } + + @Nested + @DisplayName("getThemePreference") + class GetThemePreference { + + @Test + @DisplayName("retourne royal_teal quand userPreferences est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(null); + assertThat(dto.getThemePreference()).isEqualTo("royal_teal"); + } + + @Test + @DisplayName("retourne la valeur theme si présente") + void testAvecTheme() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("theme", "dark")); + assertThat(dto.getThemePreference()).isEqualTo("dark"); + } + + @Test + @DisplayName("retourne royal_teal par défaut si theme absent") + void testDefaut() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("other", "x")); + assertThat(dto.getThemePreference()).isEqualTo("royal_teal"); + } + } + + @Nested + @DisplayName("getLanguagePreference") + class GetLanguagePreference { + + @Test + @DisplayName("retourne fr quand userPreferences est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(null); + assertThat(dto.getLanguagePreference()).isEqualTo("fr"); + } + + @Test + @DisplayName("retourne la valeur language si présente") + void testAvecLanguage() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("language", "en")); + assertThat(dto.getLanguagePreference()).isEqualTo("en"); + } + + @Test + @DisplayName("retourne fr par défaut si language absent") + void testDefaut() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of()); + assertThat(dto.getLanguagePreference()).isEqualTo("fr"); + } + } + + @Nested + @DisplayName("getNotificationsEnabled") + class GetNotificationsEnabled { + + @Test + @DisplayName("retourne true quand userPreferences est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(null); + assertThat(dto.getNotificationsEnabled()).isTrue(); + } + + @Test + @DisplayName("retourne la valeur notifications si présente") + void testAvecValeur() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("notifications", false)); + assertThat(dto.getNotificationsEnabled()).isFalse(); + } + + @Test + @DisplayName("retourne true par défaut si notifications absent") + void testDefaut() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of()); + assertThat(dto.getNotificationsEnabled()).isTrue(); + } + } + + @Nested + @DisplayName("getAutoRefreshEnabled") + class GetAutoRefreshEnabled { + + @Test + @DisplayName("retourne true quand userPreferences est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(null); + assertThat(dto.getAutoRefreshEnabled()).isTrue(); + } + + @Test + @DisplayName("retourne la valeur autoRefresh si présente") + void testAvecValeur() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("autoRefresh", false)); + assertThat(dto.getAutoRefreshEnabled()).isFalse(); + } + } + + @Nested + @DisplayName("getRefreshInterval") + class GetRefreshInterval { + + @Test + @DisplayName("retourne 300 quand userPreferences est null") + void testNull() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(null); + assertThat(dto.getRefreshInterval()).isEqualTo(300); + } + + @Test + @DisplayName("retourne la valeur refreshInterval si présente") + void testAvecValeur() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of("refreshInterval", 60)); + assertThat(dto.getRefreshInterval()).isEqualTo(60); + } + + @Test + @DisplayName("retourne 300 par défaut si refreshInterval absent") + void testDefaut() { + DashboardDataResponse dto = new DashboardDataResponse(); + dto.setUserPreferences(Map.of()); + assertThat(dto.getRefreshInterval()).isEqualTo(300); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponseTest.java new file mode 100644 index 0000000..1a99c3e --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/DashboardStatsResponseTest.java @@ -0,0 +1,223 @@ +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; + +/** + * Tests unitaires pour DashboardStatsResponse. + * Couvre getFormattedContributionAmount (null, >= 1M, >= 1K, else), + * getHasGrowth, getIsHighEngagement, getInactiveMembers, getActiveMemberPercentage, + * getEngagementPercentage. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests DashboardStatsResponse") +class DashboardStatsResponseTest { + + @Nested + @DisplayName("Constructeur et getters/setters") + class ConstructeurEtGetters { + + @Test + @DisplayName("builder et getters") + void testBuilder() { + DashboardStatsResponse dto = DashboardStatsResponse.builder() + .totalMembers(100) + .activeMembers(80) + .totalEvents(10) + .lastUpdated(LocalDateTime.now()) + .build(); + assertThat(dto.getTotalMembers()).isEqualTo(100); + assertThat(dto.getActiveMembers()).isEqualTo(80); + assertThat(dto.getTotalEvents()).isEqualTo(10); + } + } + + @Nested + @DisplayName("getFormattedContributionAmount") + class GetFormattedContributionAmount { + + @Test + @DisplayName("retourne 0 quand totalContributionAmount est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalContributionAmount(null); + assertThat(dto.getFormattedContributionAmount()).isEqualTo("0"); + } + + @Test + @DisplayName("retourne format M quand >= 1_000_000") + void testMillions() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalContributionAmount(2_500_000.0); + String formatted = dto.getFormattedContributionAmount(); + assertThat(formatted).matches("2[.,]5M"); + } + + @Test + @DisplayName("retourne format K quand >= 1_000 et < 1_000_000") + void testMilliers() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalContributionAmount(5_000.0); + assertThat(dto.getFormattedContributionAmount()).isEqualTo("5K"); + } + + @Test + @DisplayName("retourne format entier quand < 1_000") + void testPetitMontant() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalContributionAmount(99.0); + assertThat(dto.getFormattedContributionAmount()).isEqualTo("99"); + } + } + + @Nested + @DisplayName("getHasGrowth") + class GetHasGrowth { + + @Test + @DisplayName("retourne false quand monthlyGrowth est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setMonthlyGrowth(null); + assertThat(dto.getHasGrowth()).isFalse(); + } + + @Test + @DisplayName("retourne false quand monthlyGrowth <= 0") + void testZeroOuNegatif() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setMonthlyGrowth(0.0); + assertThat(dto.getHasGrowth()).isFalse(); + dto.setMonthlyGrowth(-1.0); + assertThat(dto.getHasGrowth()).isFalse(); + } + + @Test + @DisplayName("retourne true quand monthlyGrowth > 0") + void testPositif() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setMonthlyGrowth(0.5); + assertThat(dto.getHasGrowth()).isTrue(); + } + } + + @Nested + @DisplayName("getIsHighEngagement") + class GetIsHighEngagement { + + @Test + @DisplayName("retourne false quand engagementRate est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setEngagementRate(null); + assertThat(dto.getIsHighEngagement()).isFalse(); + } + + @Test + @DisplayName("retourne false quand engagementRate <= 0.7") + void testFaible() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setEngagementRate(0.5); + assertThat(dto.getIsHighEngagement()).isFalse(); + dto.setEngagementRate(0.7); + assertThat(dto.getIsHighEngagement()).isFalse(); + } + + @Test + @DisplayName("retourne true quand engagementRate > 0.7") + void testEleve() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setEngagementRate(0.85); + assertThat(dto.getIsHighEngagement()).isTrue(); + } + } + + @Nested + @DisplayName("getInactiveMembers") + class GetInactiveMembers { + + @Test + @DisplayName("retourne 0 quand totalMembers ou activeMembers est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalMembers(null); + dto.setActiveMembers(10); + assertThat(dto.getInactiveMembers()).isEqualTo(0.0); + dto.setTotalMembers(100); + dto.setActiveMembers(null); + assertThat(dto.getInactiveMembers()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne totalMembers - activeMembers") + void testCalcul() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalMembers(100); + dto.setActiveMembers(30); + assertThat(dto.getInactiveMembers()).isEqualTo(70.0); + } + } + + @Nested + @DisplayName("getActiveMemberPercentage") + class GetActiveMemberPercentage { + + @Test + @DisplayName("retourne 0 quand totalMembers ou activeMembers est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalMembers(null); + dto.setActiveMembers(50); + assertThat(dto.getActiveMemberPercentage()).isEqualTo(0.0); + dto.setTotalMembers(100); + dto.setActiveMembers(null); + assertThat(dto.getActiveMemberPercentage()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne 0 quand totalMembers est 0") + void testTotalZero() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalMembers(0); + dto.setActiveMembers(0); + assertThat(dto.getActiveMemberPercentage()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne le pourcentage actif") + void testPourcentage() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setTotalMembers(100); + dto.setActiveMembers(25); + assertThat(dto.getActiveMemberPercentage()).isEqualTo(25.0); + } + } + + @Nested + @DisplayName("getEngagementPercentage") + class GetEngagementPercentage { + + @Test + @DisplayName("retourne 0 quand engagementRate est null") + void testNull() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setEngagementRate(null); + assertThat(dto.getEngagementPercentage()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne engagementRate * 100") + void testCalcul() { + DashboardStatsResponse dto = new DashboardStatsResponse(); + dto.setEngagementRate(0.75); + assertThat(dto.getEngagementPercentage()).isEqualTo(75.0); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponseTest.java new file mode 100644 index 0000000..ba4cd64 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/MembreDashboardSyntheseResponseTest.java @@ -0,0 +1,54 @@ +package dev.lions.unionflow.server.api.dto.dashboard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MembreDashboardSyntheseResponseTest { + + @Test + @DisplayName("Record MembreDashboardSyntheseResponse : construction et accesseurs") + void record_constructionAndAccessors() { + MembreDashboardSyntheseResponse dto = new MembreDashboardSyntheseResponse( + "Jean", + "Dupont", + LocalDate.of(2023, 1, 15), + BigDecimal.valueOf(50000), + BigDecimal.valueOf(150000), + BigDecimal.valueOf(200000), + 5, + "À jour", + 100, + 7, // nombreCotisationsTotal (toutes années, tous statuts) + BigDecimal.valueOf(250000), + BigDecimal.valueOf(10000), + "+4%", + 500000, + 3, + 2, + 75, + 1, + 0, + 100 + ); + + assertThat(dto.prenom()).isEqualTo("Jean"); + assertThat(dto.nom()).isEqualTo("Dupont"); + assertThat(dto.dateInscription()).isEqualTo(LocalDate.of(2023, 1, 15)); + assertThat(dto.mesCotisationsPaiement()).isEqualByComparingTo(BigDecimal.valueOf(50000)); + assertThat(dto.totalCotisationsPayeesAnnee()).isEqualByComparingTo(BigDecimal.valueOf(150000)); + assertThat(dto.totalCotisationsPayeesToutTemps()).isEqualByComparingTo(BigDecimal.valueOf(200000)); + assertThat(dto.nombreCotisationsPayees()).isEqualTo(5); + assertThat(dto.statutCotisations()).isEqualTo("À jour"); + assertThat(dto.tauxCotisationsPerso()).isEqualTo(100); + assertThat(dto.monSoldeEpargne()).isEqualByComparingTo(BigDecimal.valueOf(250000)); + assertThat(dto.objectifEpargne()).isEqualTo(500000); + assertThat(dto.mesEvenementsInscrits()).isEqualTo(3); + assertThat(dto.tauxParticipationPerso()).isEqualTo(75); + assertThat(dto.mesDemandesAide()).isEqualTo(1); + assertThat(dto.tauxAidesApprouvees()).isEqualTo(100); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponseTest.java new file mode 100644 index 0000000..cd68b28 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/RecentActivityResponseTest.java @@ -0,0 +1,213 @@ +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; + +/** + * Tests unitaires pour RecentActivityResponse. + * Couvre getTimeAgo (toutes branches), getActivityIcon, getActivityColor, getIsRecent, getIsToday. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests RecentActivityResponse") +class RecentActivityResponseTest { + + @Nested + @DisplayName("getTimeAgo") + class GetTimeAgo { + + @Test + @DisplayName("retourne chaîne vide quand timestamp null") + void testNull() { + RecentActivityResponse dto = RecentActivityResponse.builder().build(); + assertThat(dto.getTimeAgo()).isEmpty(); + } + + @Test + @DisplayName("retourne Xmin quand moins de 60 minutes") + void testMinutes() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusMinutes(30)).build(); + assertThat(dto.getTimeAgo()).isEqualTo("30min"); + } + + @Test + @DisplayName("retourne Xh quand moins de 24h") + void testHeures() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusHours(5)).build(); + assertThat(dto.getTimeAgo()).isEqualTo("5h"); + } + + @Test + @DisplayName("retourne Xj quand moins de 7 jours") + void testJours() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusDays(3)).build(); + assertThat(dto.getTimeAgo()).isEqualTo("3j"); + } + + @Test + @DisplayName("retourne Xsem quand 7 jours ou plus") + void testSemaines() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusDays(14)).build(); + assertThat(dto.getTimeAgo()).isEqualTo("2sem"); + } + } + + @Nested + @DisplayName("getActivityIcon") + class GetActivityIcon { + + @Test + @DisplayName("retourne help_outline quand type null") + void testNull() { + RecentActivityResponse dto = RecentActivityResponse.builder().build(); + assertThat(dto.getActivityIcon()).isEqualTo("help_outline"); + } + + @Test + @DisplayName("retourne icône selon type member, event, contribution, organization, system") + void testIcons() { + assertThat(RecentActivityResponse.builder().type("member").build().getActivityIcon()).isEqualTo("person"); + assertThat(RecentActivityResponse.builder().type("event").build().getActivityIcon()).isEqualTo("event"); + assertThat(RecentActivityResponse.builder().type("contribution").build().getActivityIcon()).isEqualTo("payment"); + assertThat(RecentActivityResponse.builder().type("organization").build().getActivityIcon()).isEqualTo("business"); + assertThat(RecentActivityResponse.builder().type("system").build().getActivityIcon()).isEqualTo("settings"); + } + + @Test + @DisplayName("retourne info pour type inconnu") + void testDefault() { + RecentActivityResponse dto = RecentActivityResponse.builder().type("other").build(); + assertThat(dto.getActivityIcon()).isEqualTo("info"); + } + + @Test + @DisplayName("type insensible à la casse") + void testCaseInsensitive() { + assertThat(RecentActivityResponse.builder().type("MEMBER").build().getActivityIcon()).isEqualTo("person"); + } + } + + @Nested + @DisplayName("getActivityColor") + class GetActivityColor { + + @Test + @DisplayName("retourne grey quand type null") + void testNull() { + RecentActivityResponse dto = RecentActivityResponse.builder().build(); + assertThat(dto.getActivityColor()).isEqualTo("#6B7280"); + } + + @Test + @DisplayName("retourne couleur selon type") + void testCouleurs() { + assertThat(RecentActivityResponse.builder().type("member").build().getActivityColor()).isEqualTo("#10B981"); + assertThat(RecentActivityResponse.builder().type("event").build().getActivityColor()).isEqualTo("#3B82F6"); + assertThat(RecentActivityResponse.builder().type("contribution").build().getActivityColor()).isEqualTo("#008B8B"); + assertThat(RecentActivityResponse.builder().type("organization").build().getActivityColor()).isEqualTo("#4169E1"); + assertThat(RecentActivityResponse.builder().type("system").build().getActivityColor()).isEqualTo("#6B7280"); + } + + @Test + @DisplayName("retourne grey pour type inconnu") + void testDefault() { + RecentActivityResponse dto = RecentActivityResponse.builder().type("other").build(); + assertThat(dto.getActivityColor()).isEqualTo("#6B7280"); + } + } + + @Nested + @DisplayName("getIsRecent") + class GetIsRecent { + + @Test + @DisplayName("retourne false quand timestamp null") + void testNull() { + RecentActivityResponse dto = RecentActivityResponse.builder().build(); + assertThat(dto.getIsRecent()).isFalse(); + } + + @Test + @DisplayName("retourne true quand moins de 24h") + void testRecent() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusHours(2)).build(); + assertThat(dto.getIsRecent()).isTrue(); + } + + @Test + @DisplayName("retourne false quand plus de 24h") + void testNotRecent() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusHours(25)).build(); + assertThat(dto.getIsRecent()).isFalse(); + } + } + + @Nested + @DisplayName("getIsToday") + class GetIsToday { + + @Test + @DisplayName("retourne false quand timestamp null") + void testNull() { + RecentActivityResponse dto = RecentActivityResponse.builder().build(); + assertThat(dto.getIsToday()).isFalse(); + } + + @Test + @DisplayName("retourne true quand timestamp aujourd'hui") + void testToday() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now()).build(); + assertThat(dto.getIsToday()).isTrue(); + } + + @Test + @DisplayName("retourne false quand timestamp autre jour") + void testOtherDay() { + RecentActivityResponse dto = RecentActivityResponse.builder() + .timestamp(LocalDateTime.now().minusDays(1)).build(); + assertThat(dto.getIsToday()).isFalse(); + } + } + + @Nested + @DisplayName("Getters standards") + class GettersStandards { + + @Test + @DisplayName("tous les champs builder sont accessibles") + void testBuilderGetters() { + LocalDateTime ts = LocalDateTime.now(); + RecentActivityResponse dto = RecentActivityResponse.builder() + .id("act1") + .type("event") + .title("Titre") + .description("Desc") + .userName("User") + .timestamp(ts) + .userAvatar("http://avatar") + .actionUrl("http://action") + .build(); + assertThat(dto.getId()).isEqualTo("act1"); + assertThat(dto.getType()).isEqualTo("event"); + assertThat(dto.getTitle()).isEqualTo("Titre"); + assertThat(dto.getDescription()).isEqualTo("Desc"); + assertThat(dto.getUserName()).isEqualTo("User"); + assertThat(dto.getTimestamp()).isEqualTo(ts); + assertThat(dto.getUserAvatar()).isEqualTo("http://avatar"); + assertThat(dto.getActionUrl()).isEqualTo("http://action"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponseTest.java new file mode 100644 index 0000000..7caa3db --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/dashboard/UpcomingEventResponseTest.java @@ -0,0 +1,416 @@ +package dev.lions.unionflow.server.api.dto.dashboard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour UpcomingEventResponse. + * Couvre getDaysUntilEvent (toutes branches), getFillPercentage, getIsFull, getIsAlmostFull, + * getIsToday, getIsTomorrow, getStatusColor, getStatusLabel, getAvailableSpots, getParticipationSummary. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests UpcomingEventResponse") +class UpcomingEventResponseTest { + + @Nested + @DisplayName("getDaysUntilEvent") + class GetDaysUntilEvent { + + @Test + @DisplayName("retourne chaîne vide quand startDate est null") + void testStartDateNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getDaysUntilEvent()).isEmpty(); + } + + @Test + @DisplayName("retourne En cours quand événement aujourd'hui déjà commencé (now fixe pour éviter passage à minuit)") + void testEnCours() { + LocalDateTime now = LocalDateTime.of(2025, 6, 10, 14, 0); + LocalDateTime startDate = now.minusHours(1); // même jour, commencé il y a 1 h + UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build(); + assertThat(dto.getDaysUntilEvent(now)).isEqualTo("En cours"); + } + + @Test + @DisplayName("retourne En cours quand startDate est la veille (days < 0)") + void testEnCoursStartDateHier() { + LocalDateTime now = LocalDateTime.of(2025, 6, 10, 0, 30); + LocalDateTime startDate = LocalDateTime.of(2025, 6, 9, 23, 0); // veille 23h + UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build(); + assertThat(dto.getDaysUntilEvent(now)).isEqualTo("En cours"); + } + + @Test + @DisplayName("retourne Bientôt quand aujourd'hui dans moins de 2h (now fixe pour éviter passage à minuit)") + void testBientot() { + LocalDateTime now = LocalDateTime.of(2025, 6, 10, 14, 0); + LocalDateTime startDate = now.plusMinutes(30); + UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build(); + assertThat(dto.getDaysUntilEvent(now)).isEqualTo("Bientôt"); + } + + @Test + @DisplayName("retourne Aujourd'hui quand aujourd'hui dans plus de 2h") + void testAujourdhui() { + // Date fixe « aujourd'hui » à 15h pour éviter la dépendance à l'heure d'exécution + // (now().plusHours(5) peut tomber le lendemain après 19h et renvoyer « Demain ») + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startDate = LocalDate.now().atTime(15, 0); + UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build(); + // Utiliser le même 'now' capturé pour éviter les décalages temporels + String result = dto.getDaysUntilEvent(now); + // Selon l'heure d'exécution : >= 2h dans le futur → Aujourd'hui, < 2h → Bientôt, passé → En cours + boolean atLeast2hFromNow = !startDate.isBefore(now.plusHours(2)); + boolean inFuture = startDate.isAfter(now); + if (atLeast2hFromNow) { + assertThat(result).isEqualTo("Aujourd'hui"); + } else if (inFuture) { + assertThat(result).isEqualTo("Bientôt"); + } else { + assertThat(result).isEqualTo("En cours"); + } + } + + @Test + @DisplayName("retourne Aujourd'hui quand même jour et plus de 2h dans le futur (appel avec now fixe pour couverture Jacoco)") + void testAujourdhuiAvecNowFixe() { + LocalDateTime now = LocalDateTime.of(2025, 6, 10, 10, 0); + LocalDateTime startDate = LocalDateTime.of(2025, 6, 10, 15, 0); + UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build(); + assertThat(dto.getDaysUntilEvent(now)).isEqualTo("Aujourd'hui"); + } + + @Test + @DisplayName("retourne Demain quand startDate est demain") + void testDemain() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now().plusDays(1)).build(); + assertThat(dto.getDaysUntilEvent()).isEqualTo("Demain"); + } + + @Test + @DisplayName("retourne Dans X jours quand moins de 7 jours") + void testDansJours() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now().plusDays(3)).build(); + assertThat(dto.getDaysUntilEvent()).isEqualTo("Dans 3 jours"); + } + + @Test + @DisplayName("retourne Dans 1 semaine quand 7 jours ou plus (jours < 7 false)") + void testUneSemaine() { + LocalDateTime now = LocalDateTime.now(); + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(now.plusDays(8)).build(); + String result = dto.getDaysUntilEvent(); + assertThat(result).isEqualTo("Dans 1 semaine"); + } + + @Test + @DisplayName("retourne Dans X semaines quand au moins 14 jours") + void testSemaines() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now().plusDays(15)).build(); + assertThat(dto.getDaysUntilEvent()).isEqualTo("Dans 2 semaines"); + } + } + + @Nested + @DisplayName("getFillPercentage") + class GetFillPercentage { + + @Test + @DisplayName("retourne 0 quand maxParticipants ou currentParticipants est null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().maxParticipants(10).build(); + assertThat(dto.getFillPercentage()).isEqualTo(0.0); + dto.setCurrentParticipants(5); + dto.setMaxParticipants(null); + assertThat(dto.getFillPercentage()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne 0 quand maxParticipants est 0") + void testMaxZero() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(0).currentParticipants(0).build(); + assertThat(dto.getFillPercentage()).isEqualTo(0.0); + } + + @Test + @DisplayName("retourne le pourcentage de remplissage") + void testPourcentage() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(10).currentParticipants(4).build(); + assertThat(dto.getFillPercentage()).isEqualTo(40.0); + } + } + + @Nested + @DisplayName("getIsFull") + class GetIsFull { + + @Test + @DisplayName("retourne false quand max ou current null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getIsFull()).isFalse(); + } + + @Test + @DisplayName("retourne true quand current >= max") + void testFull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(5).currentParticipants(5).build(); + assertThat(dto.getIsFull()).isTrue(); + } + + @Test + @DisplayName("retourne false quand current < max") + void testNotFull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(5).currentParticipants(3).build(); + assertThat(dto.getIsFull()).isFalse(); + } + } + + @Nested + @DisplayName("getIsAlmostFull") + class GetIsAlmostFull { + + @Test + @DisplayName("retourne true quand fillPercentage entre 80 et 100") + void testAlmostFull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(10).currentParticipants(9).build(); + assertThat(dto.getIsAlmostFull()).isTrue(); + } + + @Test + @DisplayName("retourne false quand fillPercentage < 80") + void testNotAlmostFull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(10).currentParticipants(5).build(); + assertThat(dto.getIsAlmostFull()).isFalse(); + } + + @Test + @DisplayName("retourne false quand fillPercentage 100") + void testFullNotAlmostFull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(10).currentParticipants(10).build(); + assertThat(dto.getIsAlmostFull()).isFalse(); + } + } + + @Nested + @DisplayName("getIsToday et getIsTomorrow") + class GetIsTodayTomorrow { + + @Test + @DisplayName("getIsToday retourne false quand startDate null") + void testIsTodayNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getIsToday()).isFalse(); + } + + @Test + @DisplayName("getIsToday retourne true quand startDate aujourd'hui") + void testIsTodayTrue() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now()).build(); + assertThat(dto.getIsToday()).isTrue(); + } + + @Test + @DisplayName("getIsTomorrow retourne false quand startDate null") + void testIsTomorrowNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getIsTomorrow()).isFalse(); + } + + @Test + @DisplayName("getIsTomorrow retourne true quand startDate demain") + void testIsTomorrowTrue() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .startDate(LocalDateTime.now().plusDays(1)).build(); + assertThat(dto.getIsTomorrow()).isTrue(); + } + } + + @Nested + @DisplayName("getStatusColor") + class GetStatusColor { + + @Test + @DisplayName("retourne grey quand status null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getStatusColor()).isEqualTo("#6B7280"); + } + + @Test + @DisplayName("retourne couleur selon status confirmed") + void testConfirmed() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("confirmed").build(); + assertThat(dto.getStatusColor()).isEqualTo("#10B981"); + } + + @Test + @DisplayName("retourne couleur selon status open") + void testOpen() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("open").build(); + assertThat(dto.getStatusColor()).isEqualTo("#3B82F6"); + } + + @Test + @DisplayName("retourne couleur selon status cancelled") + void testCancelled() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("cancelled").build(); + assertThat(dto.getStatusColor()).isEqualTo("#EF4444"); + } + + @Test + @DisplayName("retourne couleur selon status postponed") + void testPostponed() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("postponed").build(); + assertThat(dto.getStatusColor()).isEqualTo("#F59E0B"); + } + + @Test + @DisplayName("retourne grey pour status inconnu") + void testDefault() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("unknown").build(); + assertThat(dto.getStatusColor()).isEqualTo("#6B7280"); + } + + @Test + @DisplayName("status insensible à la casse") + void testCaseInsensitive() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("CONFIRMED").build(); + assertThat(dto.getStatusColor()).isEqualTo("#10B981"); + } + } + + @Nested + @DisplayName("getStatusLabel") + class GetStatusLabel { + + @Test + @DisplayName("retourne Inconnu quand status null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getStatusLabel()).isEqualTo("Inconnu"); + } + + @Test + @DisplayName("retourne libellé pour confirmed, open, cancelled, postponed") + void testLabels() { + assertThat(UpcomingEventResponse.builder().status("confirmed").build().getStatusLabel()).isEqualTo("Confirmé"); + assertThat(UpcomingEventResponse.builder().status("open").build().getStatusLabel()).isEqualTo("Ouvert"); + assertThat(UpcomingEventResponse.builder().status("cancelled").build().getStatusLabel()).isEqualTo("Annulé"); + assertThat(UpcomingEventResponse.builder().status("postponed").build().getStatusLabel()).isEqualTo("Reporté"); + } + + @Test + @DisplayName("retourne status tel quel pour valeur inconnue") + void testDefault() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().status("custom").build(); + assertThat(dto.getStatusLabel()).isEqualTo("custom"); + } + } + + @Nested + @DisplayName("getAvailableSpots") + class GetAvailableSpots { + + @Test + @DisplayName("retourne 0 quand max ou current null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getAvailableSpots()).isEqualTo(0); + } + + @Test + @DisplayName("retourne max - current") + void testCalcul() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(10).currentParticipants(7).build(); + assertThat(dto.getAvailableSpots()).isEqualTo(3); + } + + @Test + @DisplayName("retourne 0 quand current > max (Math.max)") + void testNegatif() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(5).currentParticipants(10).build(); + assertThat(dto.getAvailableSpots()).isEqualTo(0); + } + } + + @Nested + @DisplayName("getParticipationSummary") + class GetParticipationSummary { + + @Test + @DisplayName("retourne 0/0 quand max ou current null") + void testNull() { + UpcomingEventResponse dto = UpcomingEventResponse.builder().build(); + assertThat(dto.getParticipationSummary()).isEqualTo("0/0 participants"); + } + + @Test + @DisplayName("retourne current/max participants") + void testSummary() { + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .maxParticipants(20).currentParticipants(12).build(); + assertThat(dto.getParticipationSummary()).isEqualTo("12/20 participants"); + } + } + + @Nested + @DisplayName("Getters standards") + class GettersStandards { + + @Test + @DisplayName("tous les champs builder sont accessibles") + void testBuilderGetters() { + LocalDateTime start = LocalDateTime.now(); + LocalDateTime end = start.plusHours(2); + UpcomingEventResponse dto = UpcomingEventResponse.builder() + .id("ev1") + .title("Titre") + .description("Desc") + .startDate(start) + .endDate(end) + .location("Paris") + .maxParticipants(50) + .currentParticipants(10) + .status("open") + .imageUrl("http://img") + .tags(List.of("a", "b")) + .build(); + assertThat(dto.getId()).isEqualTo("ev1"); + assertThat(dto.getTitle()).isEqualTo("Titre"); + assertThat(dto.getDescription()).isEqualTo("Desc"); + assertThat(dto.getStartDate()).isEqualTo(start); + assertThat(dto.getEndDate()).isEqualTo(end); + assertThat(dto.getLocation()).isEqualTo("Paris"); + assertThat(dto.getMaxParticipants()).isEqualTo(50); + assertThat(dto.getCurrentParticipants()).isEqualTo(10); + assertThat(dto.getStatus()).isEqualTo("open"); + assertThat(dto.getImageUrl()).isEqualTo("http://img"); + assertThat(dto.getTags()).containsExactly("a", "b"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponseTest.java new file mode 100644 index 0000000..3008224 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/evenement/response/EvenementResponseTest.java @@ -0,0 +1,308 @@ +package dev.lions.unionflow.server.api.dto.evenement.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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; + +/** + * Tests unitaires pour EvenementResponse. + * Couvre getAdresseComplete (toutes branches avec séparateur), getTauxPresence, + * sontInscriptionsOuvertes, estBudgetDepasse, getEcartBudgetaire, et autres méthodes. + */ +@DisplayName("Tests EvenementResponse") +class EvenementResponseTest { + + private static EvenementResponse base() { + return EvenementResponse.builder() + .titre("Event") + .typeEvenement(TypeEvenementMetier.CONFERENCE) + .statut(StatutEvenement.PLANIFIE) + .priorite(PrioriteEvenement.NORMALE) + .dateDebut(LocalDate.now().plusDays(1)) + .dateFin(LocalDate.now().plusDays(1)) + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(18, 0)) + .capaciteMax(100) + .participantsInscrits(10) + .build(); + } + + @Nested + @DisplayName("getAdresseComplete") + class GetAdresseComplete { + + @Test + @DisplayName("retourne chaîne vide quand tout null") + void testVide() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getAdresseComplete()).isEmpty(); + } + + @Test + @DisplayName("concatène lieu, adresse, ville, region avec séparateur") + void testAvecSeparateur() { + EvenementResponse r = EvenementResponse.builder() + .lieu("Salle A") + .adresse("1 rue Test") + .ville("Paris") + .region("Île-de-France") + .build(); + assertThat(r.getAdresseComplete()) + .isEqualTo("Salle A, 1 rue Test, Paris, Île-de-France"); + } + + @Test + @DisplayName("lieu seul sans virgule") + void testLieuSeul() { + EvenementResponse r = EvenementResponse.builder().lieu("Salle A").build(); + assertThat(r.getAdresseComplete()).isEqualTo("Salle A"); + } + + @Test + @DisplayName("adresse après lieu avec virgule") + void testLieuEtAdresse() { + EvenementResponse r = EvenementResponse.builder() + .lieu("Salle A") + .adresse("1 rue Test") + .build(); + assertThat(r.getAdresseComplete()).isEqualTo("Salle A, 1 rue Test"); + } + + @Test + @DisplayName("ville après adresse avec virgule") + void testAvecVille() { + EvenementResponse r = EvenementResponse.builder() + .lieu("Salle A") + .ville("Lyon") + .build(); + assertThat(r.getAdresseComplete()).isEqualTo("Salle A, Lyon"); + } + + @Test + @DisplayName("ignore champs vides ou blancs") + void testChampsVides() { + EvenementResponse r = EvenementResponse.builder() + .lieu(" ") + .adresse("") + .ville("Lyon") + .build(); + assertThat(r.getAdresseComplete()).isEqualTo("Lyon"); + } + } + + @Nested + @DisplayName("getTauxPresence") + class GetTauxPresence { + + @Test + @DisplayName("retourne 0 quand participantsInscrits null") + void testInscritsNull() { + EvenementResponse r = base(); + r.setParticipantsInscrits(null); + r.setParticipantsPresents(5); + assertThat(r.getTauxPresence()).isEqualTo(0); + } + + @Test + @DisplayName("retourne 0 quand participantsInscrits 0") + void testInscritsZero() { + EvenementResponse r = base(); + r.setParticipantsInscrits(0); + r.setParticipantsPresents(0); + assertThat(r.getTauxPresence()).isEqualTo(0); + } + + @Test + @DisplayName("retourne 0 quand participantsPresents null") + void testPresentsNull() { + EvenementResponse r = base(); + r.setParticipantsInscrits(10); + r.setParticipantsPresents(null); + assertThat(r.getTauxPresence()).isEqualTo(0); + } + + @Test + @DisplayName("calcule pourcentage présence") + void testCalcul() { + EvenementResponse r = base(); + r.setParticipantsInscrits(100); + r.setParticipantsPresents(75); + assertThat(r.getTauxPresence()).isEqualTo(75); + } + } + + @Nested + @DisplayName("sontInscriptionsOuvertes") + class SontInscriptionsOuvertes { + + @Test + @DisplayName("false quand événement annulé") + void testAnnule() { + EvenementResponse r = base(); + r.setStatut(StatutEvenement.ANNULE); + assertThat(r.sontInscriptionsOuvertes()).isFalse(); + } + + @Test + @DisplayName("false quand événement terminé") + void testTermine() { + EvenementResponse r = base(); + r.setStatut(StatutEvenement.TERMINE); + assertThat(r.sontInscriptionsOuvertes()).isFalse(); + } + + @Test + @DisplayName("false quand dateLimiteInscription dépassée") + void testDateLimitePassee() { + EvenementResponse r = base(); + r.setDateLimiteInscription(LocalDate.now().minusDays(1)); + assertThat(r.sontInscriptionsOuvertes()).isFalse(); + } + + @Test + @DisplayName("false quand événement complet") + void testComplet() { + EvenementResponse r = base(); + r.setCapaciteMax(10); + r.setParticipantsInscrits(10); + assertThat(r.sontInscriptionsOuvertes()).isFalse(); + } + + @Test + @DisplayName("true quand inscriptions ouvertes") + void testOuvertes() { + EvenementResponse r = base(); + r.setDateLimiteInscription(LocalDate.now().plusDays(5)); + r.setCapaciteMax(100); + r.setParticipantsInscrits(50); + assertThat(r.sontInscriptionsOuvertes()).isTrue(); + } + } + + @Nested + @DisplayName("getDureeEnHeures, estEvenementMultiJours, getTypeEvenementLibelle, getStatutLibelle, getPrioriteLibelle") + class AutresMethodes { + + @Test + @DisplayName("getDureeEnHeures retourne 0 si heureDebut ou heureFin null") + void testDureeNull() { + EvenementResponse r = base(); + r.setHeureDebut(null); + assertThat(r.getDureeEnHeures()).isEqualTo(0); + r.setHeureDebut(LocalTime.of(9, 0)); + r.setHeureFin(null); + assertThat(r.getDureeEnHeures()).isEqualTo(0); + } + + @Test + @DisplayName("getDureeEnHeures calcule la durée") + void testDuree() { + EvenementResponse r = base(); + r.setHeureDebut(LocalTime.of(9, 0)); + r.setHeureFin(LocalTime.of(18, 0)); + assertThat(r.getDureeEnHeures()).isEqualTo(9); + } + + @Test + @DisplayName("estEvenementMultiJours true quand dateDebut != dateFin") + void testMultiJours() { + EvenementResponse r = base(); + r.setDateDebut(LocalDate.now()); + r.setDateFin(LocalDate.now().plusDays(2)); + assertThat(r.estEvenementMultiJours()).isTrue(); + } + + @Test + @DisplayName("getTypeEvenementLibelle retourne Non défini si type null") + void testTypeNull() { + EvenementResponse r = base(); + r.setTypeEvenement(null); + assertThat(r.getTypeEvenementLibelle()).isEqualTo("Non défini"); + } + + @Test + @DisplayName("getStatutLibelle retourne Non défini si statut null") + void testStatutNull() { + EvenementResponse r = base(); + r.setStatut(null); + assertThat(r.getStatutLibelle()).isEqualTo("Non défini"); + } + + @Test + @DisplayName("getPrioriteLibelle retourne Normale si priorite null") + void testPrioriteNull() { + EvenementResponse r = base(); + r.setPriorite(null); + assertThat(r.getPrioriteLibelle()).isEqualTo("Normale"); + } + } + + @Nested + @DisplayName("hasCoordonnees, getEcartBudgetaire, estBudgetDepasse") + class BudgetEtCoordonnees { + + @Test + @DisplayName("hasCoordonnees true quand latitude et longitude non null") + void testCoordonnees() { + EvenementResponse r = base(); + r.setLatitude(BigDecimal.ONE); + r.setLongitude(BigDecimal.ONE); + assertThat(r.hasCoordonnees()).isTrue(); + } + + @Test + @DisplayName("hasCoordonnees false si un des deux null") + void testCoordonneesNull() { + EvenementResponse r = base(); + assertThat(r.hasCoordonnees()).isFalse(); + r.setLatitude(BigDecimal.ONE); + assertThat(r.hasCoordonnees()).isFalse(); + } + + @Test + @DisplayName("getEcartBudgetaire retourne ZERO si budget ou coutReel null") + void testEcartNull() { + EvenementResponse r = base(); + assertThat(r.getEcartBudgetaire()).isEqualTo(BigDecimal.ZERO); + r.setBudget(BigDecimal.valueOf(1000)); + assertThat(r.getEcartBudgetaire()).isEqualTo(BigDecimal.ZERO); + } + + @Test + @DisplayName("getEcartBudgetaire calcule budget - coutReel") + void testEcart() { + EvenementResponse r = base(); + r.setBudget(BigDecimal.valueOf(1000)); + r.setCoutReel(BigDecimal.valueOf(800)); + assertThat(r.getEcartBudgetaire()).isEqualByComparingTo(BigDecimal.valueOf(200)); + } + + @Test + @DisplayName("estBudgetDepasse true quand coutReel > budget") + void testBudgetDepasse() { + EvenementResponse r = base(); + r.setBudget(BigDecimal.valueOf(1000)); + r.setCoutReel(BigDecimal.valueOf(1200)); + assertThat(r.estBudgetDepasse()).isTrue(); + } + + @Test + @DisplayName("estBudgetDepasse false quand budget >= coutReel") + void testBudgetOk() { + EvenementResponse r = base(); + r.setBudget(BigDecimal.valueOf(1000)); + r.setCoutReel(BigDecimal.valueOf(1000)); + assertThat(r.estBudgetDepasse()).isFalse(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponseTest.java new file mode 100644 index 0000000..61ea2df --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/finance/response/AdhesionResponseTest.java @@ -0,0 +1,413 @@ +package dev.lions.unionflow.server.api.dto.finance.response; + +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.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour AdhesionResponse. + * Couvre toutes les méthodes utilitaires et toutes les branches des switch (statut, méthode paiement). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests AdhesionResponse") +class AdhesionResponseTest { + + @Nested + @DisplayName("isPayeeIntegralement") + class IsPayeeIntegralement { + + @Test + @DisplayName("retourne false quand montantPaye ou fraisAdhesion null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.isPayeeIntegralement()).isFalse(); + r.setFraisAdhesion(BigDecimal.TEN); + assertThat(r.isPayeeIntegralement()).isFalse(); + r.setMontantPaye(BigDecimal.TEN); + r.setFraisAdhesion(null); + assertThat(r.isPayeeIntegralement()).isFalse(); + } + + @Test + @DisplayName("retourne true quand montantPaye >= fraisAdhesion") + void testPaye() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(100)).build(); + assertThat(r.isPayeeIntegralement()).isTrue(); + r.setMontantPaye(BigDecimal.valueOf(150)); + assertThat(r.isPayeeIntegralement()).isTrue(); + } + + @Test + @DisplayName("retourne false quand montantPaye < fraisAdhesion") + void testNonPaye() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(50)).build(); + assertThat(r.isPayeeIntegralement()).isFalse(); + } + } + + @Nested + @DisplayName("isEnAttentePaiement") + class IsEnAttentePaiement { + + @Test + @DisplayName("retourne true quand APPROUVEE et pas payé intégralement") + void testEnAttente() { + AdhesionResponse r = AdhesionResponse.builder() + .statut("APPROUVEE") + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(50)).build(); + assertThat(r.isEnAttentePaiement()).isTrue(); + } + + @Test + @DisplayName("retourne false quand payé intégralement") + void testPaye() { + AdhesionResponse r = AdhesionResponse.builder() + .statut("APPROUVEE") + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(100)).build(); + assertThat(r.isEnAttentePaiement()).isFalse(); + } + + @Test + @DisplayName("retourne false quand statut différent de APPROUVEE") + void testAutreStatut() { + AdhesionResponse r = AdhesionResponse.builder() + .statut("EN_ATTENTE") + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.ZERO).build(); + assertThat(r.isEnAttentePaiement()).isFalse(); + } + } + + @Nested + @DisplayName("getMontantRestant") + class GetMontantRestant { + + @Test + @DisplayName("retourne 0 quand fraisAdhesion null") + void testFraisNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO); + } + + @Test + @DisplayName("retourne fraisAdhesion quand montantPaye null") + void testMontantPayeNull() { + AdhesionResponse r = AdhesionResponse.builder().fraisAdhesion(BigDecimal.valueOf(100)).build(); + assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.valueOf(100)); + } + + @Test + @DisplayName("retourne frais - payé si positif") + void testRestant() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(30)).build(); + assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.valueOf(70)); + } + + @Test + @DisplayName("retourne 0 quand payé >= frais") + void testZeroRestant() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(100)).build(); + assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO); + r.setMontantPaye(BigDecimal.valueOf(150)); + assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO); + } + } + + @Nested + @DisplayName("getPourcentagePaiement") + class GetPourcentagePaiement { + + @Test + @DisplayName("retourne 0 quand fraisAdhesion null ou zéro") + void testZero() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getPourcentagePaiement()).isEqualTo(0); + r.setFraisAdhesion(BigDecimal.ZERO); + r.setMontantPaye(BigDecimal.TEN); + assertThat(r.getPourcentagePaiement()).isEqualTo(0); + } + + @Test + @DisplayName("retourne 0 quand montantPaye null") + void testMontantNull() { + AdhesionResponse r = AdhesionResponse.builder().fraisAdhesion(BigDecimal.valueOf(100)).build(); + assertThat(r.getPourcentagePaiement()).isEqualTo(0); + } + + @Test + @DisplayName("retourne le pourcentage arrondi") + void testPourcentage() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(50)).build(); + assertThat(r.getPourcentagePaiement()).isEqualTo(50); + r.setMontantPaye(BigDecimal.valueOf(100)); + assertThat(r.getPourcentagePaiement()).isEqualTo(100); + } + } + + @Nested + @DisplayName("getJoursDepuisDemande") + class GetJoursDepuisDemande { + + @Test + @DisplayName("retourne 0 quand dateDemande null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getJoursDepuisDemande()).isEqualTo(0); + } + + @Test + @DisplayName("retourne les jours entre dateDemande et aujourd'hui") + void testJours() { + AdhesionResponse r = AdhesionResponse.builder() + .dateDemande(LocalDate.now().minusDays(5)).build(); + assertThat(r.getJoursDepuisDemande()).isEqualTo(5); + } + } + + @Nested + @DisplayName("getStatutLibelle") + class GetStatutLibelle { + + @Test + @DisplayName("retourne Non défini quand statut null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getStatutLibelle()).isEqualTo("Non défini"); + } + + @Test + @DisplayName("retourne le libellé pour chaque statut connu") + void testStatuts() { + assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutLibelle()).isEqualTo("En attente"); + assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutLibelle()).isEqualTo("Approuvée"); + assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutLibelle()).isEqualTo("Rejetée"); + assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutLibelle()).isEqualTo("Annulée"); + assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutLibelle()).isEqualTo("En paiement"); + assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutLibelle()).isEqualTo("Payée"); + } + + @Test + @DisplayName("retourne statut tel quel pour valeur inconnue") + void testDefault() { + AdhesionResponse r = AdhesionResponse.builder().statut("AUTRE").build(); + assertThat(r.getStatutLibelle()).isEqualTo("AUTRE"); + } + } + + @Nested + @DisplayName("getStatutSeverity") + class GetStatutSeverity { + + @Test + @DisplayName("retourne secondary quand statut null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getStatutSeverity()).isEqualTo("secondary"); + } + + @Test + @DisplayName("retourne success pour APPROUVEE et PAYEE") + void testSuccess() { + assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutSeverity()).isEqualTo("success"); + assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutSeverity()).isEqualTo("success"); + } + + @Test + @DisplayName("retourne warning pour EN_ATTENTE et EN_PAIEMENT") + void testWarning() { + assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutSeverity()).isEqualTo("warning"); + assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutSeverity()).isEqualTo("warning"); + } + + @Test + @DisplayName("retourne danger pour REJETEE") + void testDanger() { + assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutSeverity()).isEqualTo("danger"); + } + + @Test + @DisplayName("retourne secondary pour ANNULEE et défaut") + void testSecondary() { + assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutSeverity()).isEqualTo("secondary"); + assertThat(AdhesionResponse.builder().statut("INCONNU").build().getStatutSeverity()).isEqualTo("secondary"); + } + } + + @Nested + @DisplayName("getStatutIcon") + class GetStatutIcon { + + @Test + @DisplayName("retourne pi-circle quand statut null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getStatutIcon()).isEqualTo("pi-circle"); + } + + @Test + @DisplayName("retourne l'icône pour chaque statut") + void testIcons() { + assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutIcon()).isEqualTo("pi-check"); + assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutIcon()).isEqualTo("pi-check"); + assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutIcon()).isEqualTo("pi-clock"); + assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutIcon()).isEqualTo("pi-credit-card"); + assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutIcon()).isEqualTo("pi-times"); + assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutIcon()).isEqualTo("pi-ban"); + } + + @Test + @DisplayName("retourne pi-circle pour défaut") + void testDefault() { + assertThat(AdhesionResponse.builder().statut("INCONNU").build().getStatutIcon()).isEqualTo("pi-circle"); + } + } + + @Nested + @DisplayName("getMethodePaiementLibelle") + class GetMethodePaiementLibelle { + + @Test + @DisplayName("retourne Non défini quand methodePaiement null") + void testNull() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getMethodePaiementLibelle()).isEqualTo("Non défini"); + } + + @Test + @DisplayName("retourne le libellé pour chaque méthode") + void testMethodes() { + assertThat(AdhesionResponse.builder().methodePaiement("ESPECES").build().getMethodePaiementLibelle()).isEqualTo("Espèces"); + assertThat(AdhesionResponse.builder().methodePaiement("VIREMENT").build().getMethodePaiementLibelle()).isEqualTo("Virement bancaire"); + assertThat(AdhesionResponse.builder().methodePaiement("CHEQUE").build().getMethodePaiementLibelle()).isEqualTo("Chèque"); + assertThat(AdhesionResponse.builder().methodePaiement("WAVE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Wave Money"); + assertThat(AdhesionResponse.builder().methodePaiement("ORANGE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Orange Money"); + assertThat(AdhesionResponse.builder().methodePaiement("FREE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Free Money"); + assertThat(AdhesionResponse.builder().methodePaiement("CARTE_BANCAIRE").build().getMethodePaiementLibelle()).isEqualTo("Carte bancaire"); + } + + @Test + @DisplayName("retourne methodePaiement tel quel pour valeur inconnue") + void testDefault() { + AdhesionResponse r = AdhesionResponse.builder().methodePaiement("AUTRE").build(); + assertThat(r.getMethodePaiementLibelle()).isEqualTo("AUTRE"); + } + } + + @Nested + @DisplayName("Dates formatées") + class DatesFormatees { + + @Test + @DisplayName("getDateDemandeFormatee") + void testDateDemande() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getDateDemandeFormatee()).isEmpty(); + r.setDateDemande(LocalDate.of(2026, 2, 15)); + assertThat(r.getDateDemandeFormatee()).isEqualTo("15/02/2026"); + } + + @Test + @DisplayName("getDateApprobationFormatee") + void testDateApprobation() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getDateApprobationFormatee()).isEmpty(); + r.setDateApprobation(LocalDate.of(2026, 2, 16)); + assertThat(r.getDateApprobationFormatee()).isEqualTo("16/02/2026"); + } + + @Test + @DisplayName("getDatePaiementFormatee") + void testDatePaiement() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getDatePaiementFormatee()).isEmpty(); + r.setDatePaiement(LocalDateTime.of(2026, 2, 16, 14, 30)); + assertThat(r.getDatePaiementFormatee()).isEqualTo("16/02/2026 14:30"); + } + } + + @Nested + @DisplayName("Montants formatés") + class MontantsFormates { + + @Test + @DisplayName("getFraisAdhesionFormatte") + void testFrais() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getFraisAdhesionFormatte()).isEqualTo("0 FCFA"); + r.setFraisAdhesion(BigDecimal.valueOf(50000)); + assertThat(r.getFraisAdhesionFormatte()).contains("50"); + assertThat(r.getFraisAdhesionFormatte()).endsWith(" FCFA"); + } + + @Test + @DisplayName("getMontantPayeFormatte") + void testMontantPaye() { + AdhesionResponse r = new AdhesionResponse(); + assertThat(r.getMontantPayeFormatte()).isEqualTo("0 FCFA"); + r.setMontantPaye(BigDecimal.valueOf(25000)); + assertThat(r.getMontantPayeFormatte()).contains("25"); + assertThat(r.getMontantPayeFormatte()).endsWith(" FCFA"); + } + + @Test + @DisplayName("getMontantRestantFormatte") + void testMontantRestant() { + AdhesionResponse r = AdhesionResponse.builder() + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.valueOf(30)).build(); + assertThat(r.getMontantRestantFormatte()).contains("70"); + assertThat(r.getMontantRestantFormatte()).endsWith(" FCFA"); + } + } + + @Nested + @DisplayName("Builder et getters") + class BuilderGetters { + + @Test + @DisplayName("tous les champs sont accessibles") + void testBuilder() { + LocalDate dateDemande = LocalDate.of(2026, 1, 10); + AdhesionResponse r = AdhesionResponse.builder() + .numeroReference("REF-001") + .membreId(UUID.randomUUID()) + .numeroMembre("M1") + .nomMembre("Dupont") + .emailMembre("d@test.com") + .organisationId(UUID.randomUUID()) + .nomOrganisation("Org") + .dateDemande(dateDemande) + .fraisAdhesion(BigDecimal.valueOf(100)) + .montantPaye(BigDecimal.ZERO) + .codeDevise("XOF") + .statut("EN_ATTENTE") + .build(); + r.setId(UUID.randomUUID()); + assertThat(r.getNumeroReference()).isEqualTo("REF-001"); + assertThat(r.getNomMembre()).isEqualTo("Dupont"); + assertThat(r.getDateDemande()).isEqualTo(dateDemande); + assertThat(r.getStatut()).isEqualTo("EN_ATTENTE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponseTest.java new file mode 100644 index 0000000..7b1b735 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/formuleabonnement/response/FormuleAbonnementResponseTest.java @@ -0,0 +1,331 @@ +package dev.lions.unionflow.server.api.dto.formuleabonnement.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.formuleabonnement.StatutFormule; +import dev.lions.unionflow.server.api.enums.formuleabonnement.TypeFormule; + +/** + * Tests unitaires pour FormuleAbonnementResponse. + * Couvre toutes les branches : isActive, isInactive, isArchivee, isValide, getEconomieAnnuelle, + * getPourcentageEconomieAnnuelle (y compris coutMensuelAnnuel <= 0), hasPeriodeEssai, hasFormation, + * isMiseEnAvant, getBadge (4 branches), getScoreFonctionnalites, getCssClass (type null + switch). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests FormuleAbonnementResponse") +class FormuleAbonnementResponseTest { + + @Nested + @DisplayName("isValide") + class IsValide { + + @Test + @DisplayName("false quand statut pas ACTIVE") + void testNonActive() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .statut(StatutFormule.INACTIVE) + .build(); + assertThat(r.isValide()).isFalse(); + } + + @Test + @DisplayName("false quand dateDebutValidite dans le futur") + void testDateDebutFuture() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .statut(StatutFormule.ACTIVE) + .dateDebutValidite(LocalDate.now().plusDays(1)) + .build(); + assertThat(r.isValide()).isFalse(); + } + + @Test + @DisplayName("false quand dateFinValidite dans le passé") + void testDateFinPassee() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .statut(StatutFormule.ACTIVE) + .dateFinValidite(LocalDate.now().minusDays(1)) + .build(); + assertThat(r.isValide()).isFalse(); + } + + @Test + @DisplayName("true quand ACTIVE et dates OK") + void testValide() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .statut(StatutFormule.ACTIVE) + .dateDebutValidite(LocalDate.now().minusDays(1)) + .dateFinValidite(LocalDate.now().plusDays(1)) + .build(); + assertThat(r.isValide()).isTrue(); + } + } + + @Nested + @DisplayName("getPourcentageEconomieAnnuelle") + class GetPourcentageEconomieAnnuelle { + + @Test + @DisplayName("retourne 0 quand prixMensuel null") + void testPrixMensuelNull() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().prixAnnuel(BigDecimal.TEN).build(); + assertThat(r.getPourcentageEconomieAnnuelle()).isEqualTo(0); + } + + @Test + @DisplayName("retourne 0 quand coutMensuelAnnuel <= 0") + void testCoutMensuelZero() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .prixMensuel(BigDecimal.ZERO) + .prixAnnuel(BigDecimal.ZERO) + .build(); + assertThat(r.getPourcentageEconomieAnnuelle()).isEqualTo(0); + } + + @Test + @DisplayName("calcule pourcentage quand économie positive") + void testPourcentagePositif() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .prixMensuel(BigDecimal.valueOf(10)) + .prixAnnuel(BigDecimal.valueOf(100)) + .build(); + assertThat(r.getPourcentageEconomieAnnuelle()).isGreaterThan(0); + } + } + + @Nested + @DisplayName("getBadge") + class GetBadge { + + @Test + @DisplayName("POPULAIRE quand populaire true") + void testPopulaire() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .populaire(true) + .recommandee(false) + .periodeEssaiJours(0) + .build(); + assertThat(r.getBadge()).isEqualTo("POPULAIRE"); + } + + @Test + @DisplayName("RECOMMANDÉE quand recommandee true et pas populaire") + void testRecommandee() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .populaire(false) + .recommandee(true) + .periodeEssaiJours(0) + .build(); + assertThat(r.getBadge()).isEqualTo("RECOMMANDÉE"); + } + + @Test + @DisplayName("ESSAI GRATUIT quand periodeEssaiJours > 0 et pas populaire/recommandee") + void testEssaiGratuit() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .populaire(false) + .recommandee(false) + .periodeEssaiJours(7) + .build(); + assertThat(r.getBadge()).isEqualTo("ESSAI GRATUIT"); + } + + @Test + @DisplayName("null quand aucun badge") + void testAucunBadge() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .populaire(false) + .recommandee(false) + .periodeEssaiJours(0) + .build(); + assertThat(r.getBadge()).isNull(); + } + } + + @Nested + @DisplayName("getScoreFonctionnalites") + class GetScoreFonctionnalites { + + @Test + @DisplayName("0 quand tous les booléens false") + void testTousFalse() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .supportTechnique(false) + .sauvegardeAutomatique(false) + .fonctionnalitesAvancees(false) + .apiAccess(false) + .rapportsPersonnalises(false) + .integrationsTierces(false) + .multiLangues(false) + .personnalisationInterface(false) + .build(); + assertThat(r.getScoreFonctionnalites()).isEqualTo(0); + } + + @Test + @DisplayName("0 quand toutes les fonctionnalités sont null (aucune config)") + void testToutesFonctionnalitesNull() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().build(); + assertThat(r.getScoreFonctionnalites()).isEqualTo(0); + } + + @Test + @DisplayName("score > 0 quand au moins un true") + void testAuMoinsUnTrue() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .supportTechnique(true) + .sauvegardeAutomatique(false) + .fonctionnalitesAvancees(false) + .apiAccess(false) + .rapportsPersonnalises(false) + .integrationsTierces(false) + .multiLangues(false) + .personnalisationInterface(false) + .build(); + assertThat(r.getScoreFonctionnalites()).isGreaterThan(0); + } + + @Test + @DisplayName("personnalisationInterface et multiLangues true augmentent le score") + void testPersonnalisationEtMultiLangues() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .supportTechnique(false) + .sauvegardeAutomatique(false) + .fonctionnalitesAvancees(false) + .apiAccess(false) + .rapportsPersonnalises(false) + .integrationsTierces(false) + .multiLangues(true) + .personnalisationInterface(true) + .build(); + assertThat(r.getScoreFonctionnalites()).isGreaterThan(0); + } + } + + @Nested + @DisplayName("getCssClass") + class GetCssClass { + + @Test + @DisplayName("formule-default quand type null") + void testTypeNull() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().type(null).build(); + assertThat(r.getCssClass()).isEqualTo("formule-default"); + } + + @Test + @DisplayName("formule-basic pour BASIC") + void testBasic() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().type(TypeFormule.BASIC).build(); + assertThat(r.getCssClass()).isEqualTo("formule-basic"); + } + + @Test + @DisplayName("formule-standard pour STANDARD") + void testStandard() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().type(TypeFormule.STANDARD).build(); + assertThat(r.getCssClass()).isEqualTo("formule-standard"); + } + + @Test + @DisplayName("formule-premium pour PREMIUM") + void testPremium() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().type(TypeFormule.PREMIUM).build(); + assertThat(r.getCssClass()).isEqualTo("formule-premium"); + } + + @Test + @DisplayName("formule-enterprise pour ENTERPRISE") + void testEnterprise() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().type(TypeFormule.ENTERPRISE).build(); + assertThat(r.getCssClass()).isEqualTo("formule-enterprise"); + } + } + + @Nested + @DisplayName("isActive, isInactive, isArchivee") + class Statut { + + @Test + @DisplayName("isActive true pour ACTIVE") + void testActive() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().statut(StatutFormule.ACTIVE).build(); + assertThat(r.isActive()).isTrue(); + } + + @Test + @DisplayName("isInactive true pour INACTIVE") + void testInactive() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().statut(StatutFormule.INACTIVE).build(); + assertThat(r.isInactive()).isTrue(); + } + + @Test + @DisplayName("isArchivee true pour ARCHIVEE") + void testArchivee() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().statut(StatutFormule.ARCHIVEE).build(); + assertThat(r.isArchivee()).isTrue(); + } + } + + @Nested + @DisplayName("hasPeriodeEssai, hasFormation, isMiseEnAvant") + class AutresBranches { + + @Test + @DisplayName("hasPeriodeEssai true quand periodeEssaiJours > 0") + void testHasPeriodeEssai() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().periodeEssaiJours(14).build(); + assertThat(r.hasPeriodeEssai()).isTrue(); + } + + @Test + @DisplayName("hasFormation true quand formationIncluse et heuresFormation > 0") + void testHasFormation() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .formationIncluse(true) + .heuresFormation(10) + .build(); + assertThat(r.hasFormation()).isTrue(); + } + + @Test + @DisplayName("hasFormation false quand formationIncluse false") + void testHasFormationFalse() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .formationIncluse(false) + .heuresFormation(10) + .build(); + assertThat(r.hasFormation()).isFalse(); + } + + @Test + @DisplayName("hasFormation false quand heuresFormation null ou 0") + void testHasFormationHeuresNullOuZero() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder() + .formationIncluse(true) + .heuresFormation(null) + .build(); + assertThat(r.hasFormation()).isFalse(); + r.setHeuresFormation(0); + assertThat(r.hasFormation()).isFalse(); + } + + @Test + @DisplayName("isMiseEnAvant true quand populaire ou recommandee") + void testMiseEnAvant() { + FormuleAbonnementResponse r = FormuleAbonnementResponse.builder().populaire(true).build(); + assertThat(r.isMiseEnAvant()).isTrue(); + r.setPopulaire(false); + r.setRecommandee(true); + assertThat(r.isMiseEnAvant()).isTrue(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteriaTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteriaTest.java new file mode 100644 index 0000000..1ad25d4 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchCriteriaTest.java @@ -0,0 +1,367 @@ +package dev.lions.unionflow.server.api.dto.membre; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +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; + +/** + * Tests unitaires pour MembreSearchCriteria. + * Couvre toutes les branches : hasAnyCriteria, isValid, sanitize, getDescription. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests MembreSearchCriteria") +class MembreSearchCriteriaTest { + + @Nested + @DisplayName("hasAnyCriteria") + class HasAnyCriteria { + + @Test + @DisplayName("false quand tous les champs null ou vides") + void testAucunCritere() { + MembreSearchCriteria c = new MembreSearchCriteria(); + assertThat(c.hasAnyCriteria()).isFalse(); + } + + @Test + @DisplayName("true quand query non vide") + void testQuery() { + MembreSearchCriteria c = MembreSearchCriteria.builder().query("x").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("false quand query vide après trim") + void testQueryVide() { + MembreSearchCriteria c = MembreSearchCriteria.builder().query(" ").build(); + assertThat(c.hasAnyCriteria()).isFalse(); + } + + @Test + @DisplayName("true quand nom défini") + void testNom() { + MembreSearchCriteria c = MembreSearchCriteria.builder().nom("Dupont").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand prenom défini") + void testPrenom() { + MembreSearchCriteria c = MembreSearchCriteria.builder().prenom("Marie").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand email défini") + void testEmail() { + MembreSearchCriteria c = MembreSearchCriteria.builder().email("@x.com").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand telephone défini") + void testTelephone() { + MembreSearchCriteria c = MembreSearchCriteria.builder().telephone("+221").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand organisationIds non vide") + void testOrganisationIds() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .organisationIds(List.of(UUID.randomUUID())) + .build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("false quand organisationIds vide") + void testOrganisationIdsVide() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .organisationIds(List.of()) + .build(); + assertThat(c.hasAnyCriteria()).isFalse(); + } + + @Test + @DisplayName("true quand roles non vide") + void testRoles() { + MembreSearchCriteria c = MembreSearchCriteria.builder().roles(List.of("ADMIN")).build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand statut défini") + void testStatut() { + MembreSearchCriteria c = MembreSearchCriteria.builder().statut("ACTIF").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand dateAdhesionMin défini") + void testDateAdhesionMin() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .dateAdhesionMin(LocalDate.of(2020, 1, 1)) + .build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand dateAdhesionMax défini") + void testDateAdhesionMax() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .dateAdhesionMax(LocalDate.of(2025, 12, 31)) + .build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand ageMin défini") + void testAgeMin() { + MembreSearchCriteria c = MembreSearchCriteria.builder().ageMin(18).build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand ageMax défini") + void testAgeMax() { + MembreSearchCriteria c = MembreSearchCriteria.builder().ageMax(65).build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand region défini") + void testRegion() { + MembreSearchCriteria c = MembreSearchCriteria.builder().region("Dakar").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand ville défini") + void testVille() { + MembreSearchCriteria c = MembreSearchCriteria.builder().ville("Dakar").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand profession défini") + void testProfession() { + MembreSearchCriteria c = MembreSearchCriteria.builder().profession("Ingénieur").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand nationalite défini") + void testNationalite() { + MembreSearchCriteria c = MembreSearchCriteria.builder().nationalite("Sénégalaise").build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand membreBureau défini") + void testMembreBureau() { + MembreSearchCriteria c = MembreSearchCriteria.builder().membreBureau(true).build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + + @Test + @DisplayName("true quand responsable défini") + void testResponsable() { + MembreSearchCriteria c = MembreSearchCriteria.builder().responsable(true).build(); + assertThat(c.hasAnyCriteria()).isTrue(); + } + } + + @Nested + @DisplayName("isValid") + class IsValid { + + @Test + @DisplayName("true quand pas de dates ni âges") + void testSansContraintes() { + MembreSearchCriteria c = MembreSearchCriteria.builder().query("x").build(); + assertThat(c.isValid()).isTrue(); + } + + @Test + @DisplayName("false quand dateAdhesionMin après dateAdhesionMax") + void testDatesIncoherentes() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .dateAdhesionMin(LocalDate.of(2025, 1, 1)) + .dateAdhesionMax(LocalDate.of(2020, 1, 1)) + .build(); + assertThat(c.isValid()).isFalse(); + } + + @Test + @DisplayName("true quand dateAdhesionMin avant dateAdhesionMax") + void testDatesCoherentes() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .dateAdhesionMin(LocalDate.of(2020, 1, 1)) + .dateAdhesionMax(LocalDate.of(2025, 1, 1)) + .build(); + assertThat(c.isValid()).isTrue(); + } + + @Test + @DisplayName("false quand ageMin > ageMax") + void testAgesIncoherents() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .ageMin(65) + .ageMax(18) + .build(); + assertThat(c.isValid()).isFalse(); + } + + @Test + @DisplayName("true quand ageMin <= ageMax") + void testAgesCoherents() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .ageMin(18) + .ageMax(65) + .build(); + assertThat(c.isValid()).isTrue(); + } + } + + @Nested + @DisplayName("sanitize") + class Sanitize { + + @Test + @DisplayName("trim et null si vide") + void testSanitize() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .query(" ab ") + .nom(" ") + .build(); + c.sanitize(); + assertThat(c.getQuery()).isEqualTo("ab"); + assertThat(c.getNom()).isNull(); + } + + @Test + @DisplayName("null reste null") + void testSanitizeNull() { + MembreSearchCriteria c = new MembreSearchCriteria(); + c.sanitize(); + assertThat(c.getQuery()).isNull(); + } + } + + @Nested + @DisplayName("getDescription") + class GetDescription { + + @Test + @DisplayName("vide quand aucun critère") + void testVide() { + MembreSearchCriteria c = new MembreSearchCriteria(); + assertThat(c.getDescription()).isEmpty(); + } + + @Test + @DisplayName("contient query") + void testAvecQuery() { + MembreSearchCriteria c = MembreSearchCriteria.builder().query("marie").build(); + assertThat(c.getDescription()).contains("marie"); + } + + @Test + @DisplayName("contient nom et prénom") + void testAvecNomPrenom() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .nom("Dupont") + .prenom("Marie") + .build(); + String d = c.getDescription(); + assertThat(d).contains("Dupont"); + assertThat(d).contains("Marie"); + } + + @Test + @DisplayName("contient statut") + void testAvecStatut() { + MembreSearchCriteria c = MembreSearchCriteria.builder().statut("ACTIF").build(); + assertThat(c.getDescription()).contains("ACTIF"); + } + + @Test + @DisplayName("contient organisationIds size") + void testAvecOrganisationIds() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .organisationIds(List.of(UUID.randomUUID(), UUID.randomUUID())) + .build(); + assertThat(c.getDescription()).contains("2"); + } + + @Test + @DisplayName("contient roles") + void testAvecRoles() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .roles(List.of("ADMIN", "USER")) + .build(); + assertThat(c.getDescription()).contains("ADMIN"); + assertThat(c.getDescription()).contains("USER"); + } + + @Test + @DisplayName("contient dates et âges") + void testAvecDatesAges() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .dateAdhesionMin(LocalDate.of(2020, 1, 1)) + .dateAdhesionMax(LocalDate.of(2025, 12, 31)) + .ageMin(18) + .ageMax(65) + .build(); + String d = c.getDescription(); + assertThat(d).contains("2020-01-01"); + assertThat(d).contains("2025-12-31"); + assertThat(d).contains("18"); + assertThat(d).contains("65"); + } + + @Test + @DisplayName("contient region, ville, profession, nationalite") + void testAvecLieu() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .region("Dakar") + .ville("Plateau") + .profession("Ingénieur") + .nationalite("Sénégalaise") + .build(); + String d = c.getDescription(); + assertThat(d).contains("Dakar"); + assertThat(d).contains("Plateau"); + assertThat(d).contains("Ingénieur"); + assertThat(d).contains("Sénégalaise"); + } + + @Test + @DisplayName("contient Membre bureau et Responsable") + void testAvecMembreBureauResponsable() { + MembreSearchCriteria c = MembreSearchCriteria.builder() + .membreBureau(true) + .responsable(true) + .build(); + String d = c.getDescription(); + assertThat(d).contains("Membre bureau"); + assertThat(d).contains("Responsable"); + } + + @Test + @DisplayName("membreBureau false pas affiché") + void testMembreBureauFalse() { + MembreSearchCriteria c = MembreSearchCriteria.builder().membreBureau(false).build(); + assertThat(c.getDescription()).doesNotContain("Membre bureau"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTOTest.java new file mode 100644 index 0000000..b088c83 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSearchResultDTOTest.java @@ -0,0 +1,243 @@ +package dev.lions.unionflow.server.api.dto.membre; + +import static org.assertj.core.api.Assertions.assertThat; + +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 dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse; + +/** + * Tests unitaires pour MembreSearchResultDTO. + * Couvre toutes les branches : calculatePaginationFlags, isEmpty, getResultDescription, + * getNextPageNumber, getPreviousPageNumber, empty (2 surcharges). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests MembreSearchResultDTO") +class MembreSearchResultDTOTest { + + private static MembreSummaryResponse unMembre() { + return new MembreSummaryResponse( + UUID.randomUUID(), null, "Prenom", "Nom", null, null, null, null, null, null, true, List.of(), null, null); + } + + @Nested + @DisplayName("calculatePaginationFlags") + class CalculatePaginationFlags { + + @Test + @DisplayName("isFirst true quand currentPage 0") + void testFirstPage() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setCurrentPage(0); + r.setTotalPages(5); + r.setMembres(List.of(unMembre())); + r.calculatePaginationFlags(); + assertThat(r.isFirst()).isTrue(); + assertThat(r.isHasPrevious()).isFalse(); + } + + @Test + @DisplayName("isLast true quand currentPage dernière") + void testLastPage() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setCurrentPage(4); + r.setTotalPages(5); + r.setMembres(List.of(unMembre())); + r.calculatePaginationFlags(); + assertThat(r.isLast()).isTrue(); + assertThat(r.isHasNext()).isFalse(); + } + + @Test + @DisplayName("numberOfElements 0 quand membres null") + void testMembresNull() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(null); + r.setCurrentPage(0); + r.setTotalPages(1); + r.calculatePaginationFlags(); + assertThat(r.getNumberOfElements()).isEqualTo(0); + } + + @Test + @DisplayName("numberOfElements = size quand membres non null") + void testMembresNonVide() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of(unMembre(), unMembre())); + r.setCurrentPage(0); + r.setTotalPages(1); + r.calculatePaginationFlags(); + assertThat(r.getNumberOfElements()).isEqualTo(2); + } + } + + @Nested + @DisplayName("isEmpty") + class IsEmpty { + + @Test + @DisplayName("true quand membres null") + void testMembresNull() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(null); + assertThat(r.isEmpty()).isTrue(); + } + + @Test + @DisplayName("true quand membres vide") + void testMembresVide() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of()); + assertThat(r.isEmpty()).isTrue(); + } + + @Test + @DisplayName("false quand membres non vide") + void testMembresNonVide() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of(unMembre())); + assertThat(r.isEmpty()).isFalse(); + } + } + + @Nested + @DisplayName("getResultDescription") + class GetResultDescription { + + @Test + @DisplayName("Aucun membre trouvé quand vide") + void testVide() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of()); + r.setTotalElements(0); + assertThat(r.getResultDescription()).isEqualTo("Aucun membre trouvé"); + } + + @Test + @DisplayName("1 membre trouvé quand totalElements 1") + void testUnSeul() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of(unMembre())); + r.setTotalElements(1); + r.setTotalPages(1); + r.setCurrentPage(0); + r.setPageSize(20); + r.setNumberOfElements(1); + assertThat(r.getResultDescription()).isEqualTo("1 membre trouvé"); + } + + @Test + @DisplayName("X membres trouvés quand une seule page") + void testUnePage() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of(unMembre(), unMembre())); + r.setTotalElements(2); + r.setTotalPages(1); + r.setCurrentPage(0); + r.setPageSize(20); + r.setNumberOfElements(2); + assertThat(r.getResultDescription()).isEqualTo("2 membres trouvés"); + } + + @Test + @DisplayName("Membres X-Y sur Z quand plusieurs pages") + void testPlusieursPages() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setMembres(List.of(unMembre(), unMembre())); + r.setTotalElements(50); + r.setTotalPages(5); + r.setCurrentPage(1); + r.setPageSize(20); + r.setNumberOfElements(2); + r.calculatePaginationFlags(); + String d = r.getResultDescription(); + assertThat(d).contains("21"); + assertThat(d).contains("22"); + assertThat(d).contains("50"); + assertThat(d).contains("2"); + assertThat(d).contains("5"); + } + } + + @Nested + @DisplayName("getNextPageNumber") + class GetNextPageNumber { + + @Test + @DisplayName("retourne currentPage+2 (1-based) quand hasNext true") + void testHasNext() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setHasNext(true); + r.setCurrentPage(0); + assertThat(r.getNextPageNumber()).isEqualTo(2); + } + + @Test + @DisplayName("retourne -1 quand hasNext false") + void testNoNext() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setHasNext(false); + assertThat(r.getNextPageNumber()).isEqualTo(-1); + } + } + + @Nested + @DisplayName("getPreviousPageNumber") + class GetPreviousPageNumber { + + @Test + @DisplayName("retourne currentPage (1-based) quand hasPrevious true") + void testHasPrevious() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setHasPrevious(true); + r.setCurrentPage(2); + assertThat(r.getPreviousPageNumber()).isEqualTo(2); + } + + @Test + @DisplayName("retourne -1 quand hasPrevious false") + void testNoPrevious() { + MembreSearchResultDTO r = new MembreSearchResultDTO(); + r.setHasPrevious(false); + assertThat(r.getPreviousPageNumber()).isEqualTo(-1); + } + } + + @Nested + @DisplayName("empty") + class Empty { + + @Test + @DisplayName("empty(criteria) utilise pageSize 20 et currentPage 0") + void testEmptyAvecCriteria() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("x").build(); + MembreSearchResultDTO r = MembreSearchResultDTO.empty(criteria); + assertThat(r.getMembres()).isEmpty(); + assertThat(r.getTotalElements()).isEqualTo(0); + assertThat(r.getPageSize()).isEqualTo(20); + assertThat(r.getCurrentPage()).isEqualTo(0); + assertThat(r.isFirst()).isTrue(); + assertThat(r.isLast()).isTrue(); + assertThat(r.getCriteria()).isEqualTo(criteria); + assertThat(r.getStatistics()).isNotNull(); + } + + @Test + @DisplayName("empty(criteria, pageSize, currentPage) remplit tous les champs") + void testEmptyAvecPageSize() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().nom("Dupont").build(); + MembreSearchResultDTO r = MembreSearchResultDTO.empty(criteria, 10, 0); + assertThat(r.getPageSize()).isEqualTo(10); + assertThat(r.getCurrentPage()).isEqualTo(0); + assertThat(r.getTotalPages()).isEqualTo(0); + assertThat(r.getNumberOfElements()).isEqualTo(0); + assertThat(r.getStatistics().getMembresActifs()).isEqualTo(0); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTOTest.java new file mode 100644 index 0000000..2b89556 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/notification/ActionNotificationDTOTest.java @@ -0,0 +1,382 @@ +package dev.lions.unionflow.server.api.dto.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour ActionNotificationDTO. + * Couvre incrementerExecutions (nombreExecutions null), getCouleurParDefaut (default), + * getIconeParDefaut (tous les cas), constructeurs, peutEtreExecutee, utilisateurAutorise. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests ActionNotificationDTO") +class ActionNotificationDTOTest { + + @Nested + @DisplayName("incrementerExecutions") + class IncrementerExecutions { + + @Test + @DisplayName("initialise à 0 puis incrémente quand nombreExecutions null") + void testQuandNull() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "confirm"); + action.setNombreExecutions(null); + action.incrementerExecutions(); + assertThat(action.getNombreExecutions()).isEqualTo(1); + } + + @Test + @DisplayName("incrémente quand nombreExecutions déjà défini") + void testQuandDefini() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "confirm"); + action.setNombreExecutions(2); + action.incrementerExecutions(); + assertThat(action.getNombreExecutions()).isEqualTo(3); + } + } + + @Nested + @DisplayName("getCouleurParDefaut") + class GetCouleurParDefaut { + + @Test + @DisplayName("retourne couleur si définie") + void testCouleurDefinie() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "confirm"); + action.setCouleur("#000000"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#000000"); + } + + @Test + @DisplayName("retourne vert pour confirm") + void testConfirm() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#4CAF50"); + } + + @Test + @DisplayName("retourne rouge pour cancel") + void testCancel() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "cancel"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#F44336"); + } + + @Test + @DisplayName("retourne bleu pour info") + void testInfo() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "info"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#2196F3"); + } + + @Test + @DisplayName("retourne orange pour warning") + void testWarning() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "warning"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#FF9800"); + } + + @Test + @DisplayName("retourne bleu pour url") + void testUrl() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "url"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#2196F3"); + } + + @Test + @DisplayName("retourne bleu pour route") + void testRoute() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "route"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#2196F3"); + } + + @Test + @DisplayName("retourne gris par défaut pour type inconnu") + void testDefault() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "unknown_type"); + assertThat(action.getCouleurParDefaut()).isEqualTo("#9E9E9E"); + } + } + + @Nested + @DisplayName("getIconeParDefaut") + class GetIconeParDefaut { + + @Test + @DisplayName("retourne icone si définie") + void testIconeDefinie() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "confirm"); + action.setIcone("custom"); + assertThat(action.getIconeParDefaut()).isEqualTo("custom"); + } + + @Test + @DisplayName("retourne check pour confirm") + void testConfirm() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + assertThat(action.getIconeParDefaut()).isEqualTo("check"); + } + + @Test + @DisplayName("retourne close pour cancel") + void testCancel() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "cancel"); + assertThat(action.getIconeParDefaut()).isEqualTo("close"); + } + + @Test + @DisplayName("retourne info pour info") + void testInfo() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "info"); + assertThat(action.getIconeParDefaut()).isEqualTo("info"); + } + + @Test + @DisplayName("retourne warning pour warning") + void testWarning() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "warning"); + assertThat(action.getIconeParDefaut()).isEqualTo("warning"); + } + + @Test + @DisplayName("retourne open_in_new pour url") + void testUrl() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "url"); + assertThat(action.getIconeParDefaut()).isEqualTo("open_in_new"); + } + + @Test + @DisplayName("retourne arrow_forward pour route") + void testRoute() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "route"); + assertThat(action.getIconeParDefaut()).isEqualTo("arrow_forward"); + } + + @Test + @DisplayName("retourne phone pour call") + void testCall() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "call"); + assertThat(action.getIconeParDefaut()).isEqualTo("phone"); + } + + @Test + @DisplayName("retourne message pour message") + void testMessage() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "message"); + assertThat(action.getIconeParDefaut()).isEqualTo("message"); + } + + @Test + @DisplayName("retourne email pour email") + void testEmail() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "email"); + assertThat(action.getIconeParDefaut()).isEqualTo("email"); + } + + @Test + @DisplayName("retourne share pour share") + void testShare() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "share"); + assertThat(action.getIconeParDefaut()).isEqualTo("share"); + } + + @Test + @DisplayName("retourne touch_app pour type inconnu") + void testDefault() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "Lib", "unknown"); + assertThat(action.getIconeParDefaut()).isEqualTo("touch_app"); + } + } + + @Nested + @DisplayName("Constructeurs") + class Constructeurs { + + @Test + @DisplayName("constructeur URL remplit url et icone") + void testConstructeurUrl() { + ActionNotificationDTO action = new ActionNotificationDTO("id", "Ouvrir", "http://url", "open"); + assertThat(action.getId()).isEqualTo("id"); + assertThat(action.getLibelle()).isEqualTo("Ouvrir"); + assertThat(action.getTypeAction()).isEqualTo("url"); + assertThat(action.getUrl()).isEqualTo("http://url"); + assertThat(action.getIcone()).isEqualTo("open"); + } + + @Test + @DisplayName("constructeur route remplit route, icone et parametres") + void testConstructeurRoute() { + ActionNotificationDTO action = new ActionNotificationDTO( + "id", "Aller", "/page", "arrow", Map.of("p", "v")); + assertThat(action.getTypeAction()).isEqualTo("route"); + assertThat(action.getRoute()).isEqualTo("/page"); + assertThat(action.getIcone()).isEqualTo("arrow"); + assertThat(action.getParametres()).containsEntry("p", "v"); + } + } + + @Nested + @DisplayName("peutEtreExecutee") + class PeutEtreExecutee { + + @Test + @DisplayName("true quand estActivee et nombreExecutions < maxExecutions") + void testPeutExecuter() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + assertThat(action.peutEtreExecutee()).isTrue(); + } + + @Test + @DisplayName("true quand peutEtreRepetee même si nombreExecutions >= maxExecutions") + void testRepetee() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setNombreExecutions(1); + action.setMaxExecutions(1); + action.setPeutEtreRepetee(true); + assertThat(action.peutEtreExecutee()).isTrue(); + } + + @Test + @DisplayName("false quand nombreExecutions >= maxExecutions et peutEtreRepetee false") + void testMaxAtteint() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setNombreExecutions(1); + action.setMaxExecutions(1); + action.setPeutEtreRepetee(false); + assertThat(action.peutEtreExecutee()).isFalse(); + } + + @Test + @DisplayName("false quand estActivee false") + void testDesactivee() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setEstActivee(false); + assertThat(action.peutEtreExecutee()).isFalse(); + } + } + + @Nested + @DisplayName("utilisateurAutorise") + class UtilisateurAutorise { + + @Test + @DisplayName("true quand pas de rôles ni permissions requis") + void testSansRestriction() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + assertThat(action.utilisateurAutorise(new String[] {}, new String[] {})).isTrue(); + } + + @Test + @DisplayName("true quand utilisateur a le rôle requis") + void testAvecRole() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setRolesAutorises(new String[] { "ADMIN" }); + assertThat(action.utilisateurAutorise(new String[] { "ADMIN" }, new String[] {})).isTrue(); + } + + @Test + @DisplayName("false quand utilisateur n'a pas le rôle requis") + void testSansRole() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setRolesAutorises(new String[] { "ADMIN" }); + assertThat(action.utilisateurAutorise(new String[] { "USER" }, new String[] {})).isFalse(); + } + + @Test + @DisplayName("true quand utilisateur a la permission requise") + void testAvecPermission() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setPermissionsRequises(new String[] { "write" }); + assertThat(action.utilisateurAutorise(new String[] {}, new String[] { "write" })).isTrue(); + } + + @Test + @DisplayName("false quand utilisateur n'a pas la permission requise") + void testSansPermission() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setPermissionsRequises(new String[] { "write" }); + assertThat(action.utilisateurAutorise(new String[] {}, new String[] { "read" })).isFalse(); + } + + @Test + @DisplayName("true quand rolesAutorises null") + void testRolesNull() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setRolesAutorises(null); + assertThat(action.utilisateurAutorise(new String[] {}, new String[] {})).isTrue(); + } + + @Test + @DisplayName("true quand permissionsRequises null") + void testPermissionsNull() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + action.setPermissionsRequises(null); + assertThat(action.utilisateurAutorise(new String[] {}, new String[] {})).isTrue(); + } + } + + @Nested + @DisplayName("isExpiree") + class IsExpiree { + + @Test + @DisplayName("retourne false (non implémenté)") + void testRetourneFalse() { + ActionNotificationDTO action = new ActionNotificationDTO("a", "L", "confirm"); + assertThat(action.isExpiree()).isFalse(); + } + } + + @Nested + @DisplayName("toString") + class ToString { + + @Test + @DisplayName("contient id, libelle et type") + void testToString() { + ActionNotificationDTO action = new ActionNotificationDTO("x", "Label", "confirm"); + String s = action.toString(); + assertThat(s).contains("x"); + assertThat(s).contains("Label"); + assertThat(s).contains("confirm"); + } + } + + @Nested + @DisplayName("Méthodes statiques") + class MethodesStatiques { + + @Test + @DisplayName("creerActionConfirmation") + void testCreerConfirmation() { + ActionNotificationDTO a = ActionNotificationDTO.creerActionConfirmation("c1", "OK"); + assertThat(a.getId()).isEqualTo("c1"); + assertThat(a.getLibelle()).isEqualTo("OK"); + assertThat(a.getTypeAction()).isEqualTo("confirm"); + assertThat(a.getCouleur()).isEqualTo("#4CAF50"); + assertThat(a.getIcone()).isEqualTo("check"); + } + + @Test + @DisplayName("creerActionAnnulation") + void testCreerAnnulation() { + ActionNotificationDTO a = ActionNotificationDTO.creerActionAnnulation("c2", "Annuler"); + assertThat(a.getTypeAction()).isEqualTo("cancel"); + assertThat(a.getEstDestructive()).isTrue(); + } + + @Test + @DisplayName("creerActionNavigation") + void testCreerNavigation() { + ActionNotificationDTO a = ActionNotificationDTO.creerActionNavigation("n1", "Voir", "/detail"); + assertThat(a.getRoute()).isEqualTo("/detail"); + assertThat(a.getTypeAction()).isEqualTo("route"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTOTest.java new file mode 100644 index 0000000..e978e7e --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/notification/PreferencesNotificationDTOTest.java @@ -0,0 +1,289 @@ +package dev.lions.unionflow.server.api.dto.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalTime; +import java.util.Set; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.notification.CanalNotification; +import dev.lions.unionflow.server.api.enums.notification.TypeNotification; + +/** + * Tests unitaires pour PreferencesNotificationDTO. + * Couvre constructeur(String), isTypeActive (toutes branches), isCanalActif (toutes branches), + * isEnModeSilencieux(LocalTime) (traverse minuit, plage normale, null), + * isExpediteurBloque, isExpediteurPrioritaire, toString. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests PreferencesNotificationDTO") +class PreferencesNotificationDTOTest { + + @Nested + @DisplayName("Constructeurs") + class Constructeurs { + + @Test + @DisplayName("constructeur par défaut initialise les champs") + void testConstructeurParDefaut() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + assertThat(dto.getNotificationsActivees()).isTrue(); + assertThat(dto.getPushActivees()).isTrue(); + assertThat(dto.getEmailActivees()).isTrue(); + assertThat(dto.getSmsActivees()).isFalse(); + assertThat(dto.getInAppActivees()).isTrue(); + assertThat(dto.getModeSilencieux()).isFalse(); + assertThat(dto.getFrequenceRegroupementMinutes()).isEqualTo(5); + assertThat(dto.getMaxNotificationsSimultanees()).isEqualTo(10); + assertThat(dto.getDureeAffichageSecondes()).isEqualTo(10); + assertThat(dto.getLangue()).isEqualTo("fr"); + } + + @Test + @DisplayName("constructeur avec utilisateurId appelle this() et set utilisateurId") + void testConstructeurAvecUtilisateur() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO("user-123"); + assertThat(dto.getUtilisateurId()).isEqualTo("user-123"); + assertThat(dto.getNotificationsActivees()).isTrue(); + } + } + + @Nested + @DisplayName("isTypeActive") + class IsTypeActive { + + @Test + @DisplayName("retourne false quand notificationsActivees false") + void testNotificationsDesactivees() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setNotificationsActivees(false); + assertThat(dto.isTypeActive(TypeNotification.EMAIL)).isFalse(); + } + + @Test + @DisplayName("retourne false quand type dans typesDesactivees") + void testTypeDesactive() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setTypesDesactivees(Set.of(TypeNotification.EMAIL)); + assertThat(dto.isTypeActive(TypeNotification.EMAIL)).isFalse(); + } + + @Test + @DisplayName("retourne true quand type dans typesActives") + void testTypeActif() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setTypesActives(Set.of(TypeNotification.EMAIL)); + assertThat(dto.isTypeActive(TypeNotification.EMAIL)).isTrue(); + } + + @Test + @DisplayName("retourne type.isActiveeParDefaut() quand typesActives null et type pas dans typesDesactivees") + void testDefautType() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setTypesActives(null); + dto.setTypesDesactivees(null); + TypeNotification type = TypeNotification.EMAIL; + assertThat(dto.isTypeActive(type)).isEqualTo(type.isActiveeParDefaut()); + } + + @Test + @DisplayName("retourne false quand type pas dans typesActives (typesActives non vide)") + void testTypePasDansActives() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setTypesActives(Set.of(TypeNotification.IN_APP)); + assertThat(dto.isTypeActive(TypeNotification.EMAIL)).isFalse(); + } + } + + @Nested + @DisplayName("isCanalActif") + class IsCanalActif { + + @Test + @DisplayName("retourne false quand notificationsActivees false") + void testNotificationsDesactivees() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setNotificationsActivees(false); + assertThat(dto.isCanalActif(CanalNotification.DEFAULT_CHANNEL)).isFalse(); + } + + @Test + @DisplayName("retourne false quand canal dans canauxDesactives") + void testCanalDesactive() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setCanauxDesactives(Set.of(CanalNotification.DEFAULT_CHANNEL)); + assertThat(dto.isCanalActif(CanalNotification.DEFAULT_CHANNEL)).isFalse(); + } + + @Test + @DisplayName("retourne true quand canal dans canauxActifs") + void testCanalActif() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setCanauxActifs(Set.of(CanalNotification.DEFAULT_CHANNEL)); + assertThat(dto.isCanalActif(CanalNotification.DEFAULT_CHANNEL)).isTrue(); + } + + @Test + @DisplayName("retourne true quand canauxActifs null (défaut)") + void testCanauxActifsNull() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setCanauxActifs(null); + dto.setCanauxDesactives(null); + assertThat(dto.isCanalActif(CanalNotification.DEFAULT_CHANNEL)).isTrue(); + } + + @Test + @DisplayName("retourne false quand canal pas dans canauxActifs (canauxActifs non vide)") + void testCanalPasDansActifs() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setCanauxActifs(Set.of(CanalNotification.REMINDER_CHANNEL)); + assertThat(dto.isCanalActif(CanalNotification.DEFAULT_CHANNEL)).isFalse(); + } + } + + @Nested + @DisplayName("isEnModeSilencieux (package)") + class IsEnModeSilencieux { + + @Test + @DisplayName("retourne false quand modeSilencieux false") + void testModeDesactive() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(false); + assertThat(dto.isEnModeSilencieux(LocalTime.NOON)).isFalse(); + } + + @Test + @DisplayName("retourne false quand heureDebut ou heureFin null") + void testHeuresNull() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(true); + dto.setHeureDebutSilencieux(null); + dto.setHeureFinSilencieux(LocalTime.of(18, 0)); + assertThat(dto.isEnModeSilencieux(LocalTime.NOON)).isFalse(); + dto.setHeureDebutSilencieux(LocalTime.of(22, 0)); + dto.setHeureFinSilencieux(null); + assertThat(dto.isEnModeSilencieux(LocalTime.of(23, 0))).isFalse(); + } + + @Test + @DisplayName("période normale (début < fin): true dans la plage") + void testPlageNormaleDansPlage() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(true); + dto.setHeureDebutSilencieux(LocalTime.of(9, 0)); + dto.setHeureFinSilencieux(LocalTime.of(12, 0)); + assertThat(dto.isEnModeSilencieux(LocalTime.of(10, 30))).isTrue(); + } + + @Test + @DisplayName("période normale: false en dehors de la plage") + void testPlageNormaleHorsPlage() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(true); + dto.setHeureDebutSilencieux(LocalTime.of(9, 0)); + dto.setHeureFinSilencieux(LocalTime.of(12, 0)); + assertThat(dto.isEnModeSilencieux(LocalTime.of(8, 0))).isFalse(); + assertThat(dto.isEnModeSilencieux(LocalTime.of(13, 0))).isFalse(); + assertThat(dto.isEnModeSilencieux(LocalTime.of(9, 0))).isFalse(); + assertThat(dto.isEnModeSilencieux(LocalTime.of(12, 0))).isFalse(); + } + + @Test + @DisplayName("période traverse minuit (début > fin): true après début ou avant fin") + void testTraverseMinuit() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(true); + dto.setHeureDebutSilencieux(LocalTime.of(22, 0)); + dto.setHeureFinSilencieux(LocalTime.of(7, 0)); + assertThat(dto.isEnModeSilencieux(LocalTime.of(23, 0))).isTrue(); + assertThat(dto.isEnModeSilencieux(LocalTime.of(2, 0))).isTrue(); + } + + @Test + @DisplayName("période traverse minuit: false entre fin et début") + void testTraverseMinuitHorsPlage() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setModeSilencieux(true); + dto.setHeureDebutSilencieux(LocalTime.of(22, 0)); + dto.setHeureFinSilencieux(LocalTime.of(7, 0)); + assertThat(dto.isEnModeSilencieux(LocalTime.of(10, 0))).isFalse(); + } + } + + @Nested + @DisplayName("isExpediteurBloque") + class IsExpediteurBloque { + + @Test + @DisplayName("retourne false quand expediteursBloqués null") + void testNull() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursBloques(null); + assertThat(dto.isExpediteurBloque("exp-1")).isFalse(); + } + + @Test + @DisplayName("retourne true quand expediteur dans la liste") + void testBloque() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursBloques(Set.of("exp-1", "exp-2")); + assertThat(dto.isExpediteurBloque("exp-1")).isTrue(); + } + + @Test + @DisplayName("retourne false quand expediteur pas dans la liste") + void testPasBloque() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursBloques(Set.of("exp-1")); + assertThat(dto.isExpediteurBloque("exp-2")).isFalse(); + } + } + + @Nested + @DisplayName("isExpediteurPrioritaire") + class IsExpediteurPrioritaire { + + @Test + @DisplayName("retourne false quand expediteursPrioritaires null") + void testNull() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursPrioritaires(null); + assertThat(dto.isExpediteurPrioritaire("exp-1")).isFalse(); + } + + @Test + @DisplayName("retourne true quand expediteur dans la liste") + void testPrioritaire() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursPrioritaires(Set.of("exp-1")); + assertThat(dto.isExpediteurPrioritaire("exp-1")).isTrue(); + } + + @Test + @DisplayName("retourne false quand expediteur pas dans la liste") + void testPasPrioritaire() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO(); + dto.setExpediteursPrioritaires(Set.of("exp-1")); + assertThat(dto.isExpediteurPrioritaire("exp-2")).isFalse(); + } + } + + @Nested + @DisplayName("toString") + class ToString { + + @Test + @DisplayName("contient utilisateurId et notificationsActivees") + void testToString() { + PreferencesNotificationDTO dto = new PreferencesNotificationDTO("u1"); + String s = dto.toString(); + assertThat(s).contains("u1"); + assertThat(s).contains("true"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponseTest.java new file mode 100644 index 0000000..e22abdb --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/response/OrganisationResponseTest.java @@ -0,0 +1,61 @@ +package dev.lions.unionflow.server.api.dto.organisation.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour OrganisationResponse. + * Vérifie l'alias numeroRegistre / numeroEnregistrement. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-05 + */ +@DisplayName("Tests OrganisationResponse") +class OrganisationResponseTest { + + @Test + @DisplayName("getNumeroRegistre retourne numeroEnregistrement") + void getNumeroRegistre_returnsNumeroEnregistrement() { + OrganisationResponse r = OrganisationResponse.builder() + .numeroEnregistrement("MUT-CI-2020-001") + .build(); + assertThat(r.getNumeroRegistre()).isEqualTo("MUT-CI-2020-001"); + } + + @Test + @DisplayName("getNumeroRegistre retourne null quand numeroEnregistrement est null") + void getNumeroRegistre_nullWhenNumeroEnregistrementNull() { + OrganisationResponse r = OrganisationResponse.builder().build(); + assertThat(r.getNumeroRegistre()).isNull(); + } + + @Test + @DisplayName("setNumeroRegistre met à jour numeroEnregistrement") + void setNumeroRegistre_updatesNumeroEnregistrement() { + OrganisationResponse r = OrganisationResponse.builder().build(); + r.setNumeroRegistre("ASSO-2021-042"); + assertThat(r.getNumeroEnregistrement()).isEqualTo("ASSO-2021-042"); + assertThat(r.getNumeroRegistre()).isEqualTo("ASSO-2021-042"); + } + + @Test + @DisplayName("getNomOrganisationParente retourne organisationParenteNom") + void getNomOrganisationParente_returnsOrganisationParenteNom() { + OrganisationResponse r = OrganisationResponse.builder() + .organisationParenteNom("Organisation Mère") + .build(); + assertThat(r.getNomOrganisationParente()).isEqualTo("Organisation Mère"); + } + + @Test + @DisplayName("setNomOrganisationParente met à jour organisationParenteNom") + void setNomOrganisationParente_updatesOrganisationParenteNom() { + OrganisationResponse r = OrganisationResponse.builder().build(); + r.setNomOrganisationParente("Parent Org"); + assertThat(r.getOrganisationParenteNom()).isEqualTo("Parent Org"); + assertThat(r.getNomOrganisationParente()).isEqualTo("Parent Org"); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTOTest.java new file mode 100644 index 0000000..12b97c6 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveBalanceDTOTest.java @@ -0,0 +1,199 @@ +package dev.lions.unionflow.server.api.dto.paiement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour WaveBalanceDTO. + * Couvre toutes les branches : calculerSoldeTotal (via setters), isWalletActif, + * isSoldeSuffisant, getSoldeDisponibleAujourdhui, mettreAJourApresTransaction. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests WaveBalanceDTO") +class WaveBalanceDTOTest { + + @Nested + @DisplayName("Constructeurs") + class Constructeurs { + + @Test + @DisplayName("constructeur par défaut initialise devise et statut") + void testConstructeurVide() { + WaveBalanceDTO dto = new WaveBalanceDTO(); + assertThat(dto.getDevise()).isEqualTo("XOF"); + assertThat(dto.getStatutWallet()).isEqualTo("ACTIVE"); + assertThat(dto.getSoldeEnAttente()).isEqualTo(BigDecimal.ZERO); + assertThat(dto.getNombreTransactionsAujourdhui()).isEqualTo(0); + } + + @Test + @DisplayName("constructeur avec wallet et solde calcule soldeTotal") + void testConstructeurAvecParams() { + WaveBalanceDTO dto = new WaveBalanceDTO("wallet-1", BigDecimal.valueOf(1000)); + assertThat(dto.getNumeroWallet()).isEqualTo("wallet-1"); + assertThat(dto.getSoldeDisponible()).isEqualByComparingTo(BigDecimal.valueOf(1000)); + assertThat(dto.getSoldeTotal()).isEqualByComparingTo(BigDecimal.valueOf(1000)); + } + } + + @Nested + @DisplayName("setSoldeDisponible / setSoldeEnAttente (calculerSoldeTotal)") + class CalculerSoldeTotal { + + @Test + @DisplayName("soldeTotal = disponible + enAttente quand les deux sont définis") + void testCalculSoldeTotal() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(100)); + dto.setSoldeEnAttente(BigDecimal.valueOf(50)); + assertThat(dto.getSoldeTotal()).isEqualByComparingTo(BigDecimal.valueOf(150)); + } + + @Test + @DisplayName("setSoldeDisponible recalcule soldeTotal") + void testSetSoldeDisponible() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(100)); + dto.setSoldeEnAttente(BigDecimal.valueOf(20)); + dto.setSoldeDisponible(BigDecimal.valueOf(200)); + assertThat(dto.getSoldeTotal()).isEqualByComparingTo(BigDecimal.valueOf(220)); + } + } + + @Nested + @DisplayName("isWalletActif") + class IsWalletActif { + + @Test + @DisplayName("true quand statut ACTIVE") + void testActif() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.ZERO); + dto.setStatutWallet("ACTIVE"); + assertThat(dto.isWalletActif()).isTrue(); + } + + @Test + @DisplayName("false quand statut INACTIVE") + void testInactif() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.ZERO); + dto.setStatutWallet("INACTIVE"); + assertThat(dto.isWalletActif()).isFalse(); + } + } + + @Nested + @DisplayName("isSoldeSuffisant") + class IsSoldeSuffisant { + + @Test + @DisplayName("false quand soldeDisponible null") + void testSoldeNull() { + WaveBalanceDTO dto = new WaveBalanceDTO(); + dto.setSoldeDisponible(null); + assertThat(dto.isSoldeSuffisant(BigDecimal.ONE)).isFalse(); + } + + @Test + @DisplayName("true quand solde >= montant") + void testSuffisant() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(100)); + assertThat(dto.isSoldeSuffisant(BigDecimal.valueOf(50))).isTrue(); + assertThat(dto.isSoldeSuffisant(BigDecimal.valueOf(100))).isTrue(); + } + + @Test + @DisplayName("false quand solde < montant") + void testInsuffisant() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(100)); + assertThat(dto.isSoldeSuffisant(BigDecimal.valueOf(101))).isFalse(); + } + } + + @Nested + @DisplayName("getSoldeDisponibleAujourdhui") + class GetSoldeDisponibleAujourdhui { + + @Test + @DisplayName("retourne soldeDisponible quand limiteQuotidienne null") + void testLimiteNull() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(500)); + dto.setLimiteQuotidienne(null); + dto.setMontantUtiliseAujourdhui(BigDecimal.ZERO); + assertThat(dto.getSoldeDisponibleAujourdhui()).isEqualByComparingTo(BigDecimal.valueOf(500)); + } + + @Test + @DisplayName("retourne soldeDisponible quand montantUtiliseAujourdhui null") + void testMontantUtiliseNull() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(500)); + dto.setLimiteQuotidienne(BigDecimal.valueOf(1000)); + dto.setMontantUtiliseAujourdhui(null); + assertThat(dto.getSoldeDisponibleAujourdhui()).isEqualByComparingTo(BigDecimal.valueOf(500)); + } + + @Test + @DisplayName("retourne le minimum entre solde et limite restante") + void testMinimum() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(500)); + dto.setLimiteQuotidienne(BigDecimal.valueOf(1000)); + dto.setMontantUtiliseAujourdhui(BigDecimal.valueOf(700)); + assertThat(dto.getSoldeDisponibleAujourdhui()).isEqualByComparingTo(BigDecimal.valueOf(300)); + } + } + + @Nested + @DisplayName("mettreAJourApresTransaction") + class MettreAJourApresTransaction { + + @Test + @DisplayName("initialise les champs null puis incrémente") + void testAvecChampsNull() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(1000)); + dto.setMontantUtiliseAujourdhui(null); + dto.setMontantUtiliseCeMois(null); + dto.setNombreTransactionsAujourdhui(null); + dto.setNombreTransactionsCeMois(null); + dto.mettreAJourApresTransaction(BigDecimal.valueOf(50)); + assertThat(dto.getMontantUtiliseAujourdhui()).isEqualByComparingTo(BigDecimal.valueOf(50)); + assertThat(dto.getMontantUtiliseCeMois()).isEqualByComparingTo(BigDecimal.valueOf(50)); + assertThat(dto.getNombreTransactionsAujourdhui()).isEqualTo(1); + assertThat(dto.getNombreTransactionsCeMois()).isEqualTo(1); + assertThat(dto.getDateDerniereMiseAJour()).isNotNull(); + } + + @Test + @DisplayName("ajoute au montant existant") + void testAvecValeursExistantes() { + WaveBalanceDTO dto = new WaveBalanceDTO("w", BigDecimal.valueOf(1000)); + dto.setMontantUtiliseAujourdhui(BigDecimal.valueOf(100)); + dto.setMontantUtiliseCeMois(BigDecimal.valueOf(200)); + dto.setNombreTransactionsAujourdhui(2); + dto.setNombreTransactionsCeMois(3); + dto.mettreAJourApresTransaction(BigDecimal.valueOf(25)); + assertThat(dto.getMontantUtiliseAujourdhui()).isEqualByComparingTo(BigDecimal.valueOf(125)); + assertThat(dto.getMontantUtiliseCeMois()).isEqualByComparingTo(BigDecimal.valueOf(225)); + assertThat(dto.getNombreTransactionsAujourdhui()).isEqualTo(3); + assertThat(dto.getNombreTransactionsCeMois()).isEqualTo(4); + } + } + + @Nested + @DisplayName("toString") + class ToString { + + @Test + @DisplayName("contient numeroWallet et solde") + void testToString() { + WaveBalanceDTO dto = new WaveBalanceDTO("wallet-1", BigDecimal.valueOf(500)); + String s = dto.toString(); + assertThat(s).contains("wallet-1"); + assertThat(s).contains("500"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTOTest.java new file mode 100644 index 0000000..16ef7da --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveCheckoutSessionDTOTest.java @@ -0,0 +1,54 @@ +package dev.lions.unionflow.server.api.dto.paiement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.paiement.StatutSession; + +/** + * Tests unitaires pour WaveCheckoutSessionDTO. + * Couvre constructeurs et getters pour 100% instructions. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests WaveCheckoutSessionDTO") +class WaveCheckoutSessionDTOTest { + + @Nested + @DisplayName("Constructeur par défaut") + class ConstructeurDefaut { + + @Test + @DisplayName("initialise devise, statut et flags") + void testConstructeurVide() { + WaveCheckoutSessionDTO dto = new WaveCheckoutSessionDTO(); + assertThat(dto.getDevise()).isEqualTo("XOF"); + assertThat(dto.getStatut()).isEqualTo(StatutSession.PENDING); + assertThat(dto.getNombreTentatives()).isEqualTo(0); + assertThat(dto.getWebhookRecu()).isFalse(); + } + } + + @Nested + @DisplayName("Constructeur avec URLs") + class ConstructeurAvecUrls { + + @Test + @DisplayName("remplit montant, successUrl, errorUrl") + void testConstructeurAvecUrls() { + WaveCheckoutSessionDTO dto = new WaveCheckoutSessionDTO( + BigDecimal.valueOf(5000), + "https://example.com/success", + "https://example.com/error"); + assertThat(dto.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(5000)); + assertThat(dto.getSuccessUrl()).isEqualTo("https://example.com/success"); + assertThat(dto.getErrorUrl()).isEqualTo("https://example.com/error"); + assertThat(dto.getStatut()).isEqualTo(StatutSession.PENDING); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTOTest.java new file mode 100644 index 0000000..a47b63c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/WaveWebhookDTOTest.java @@ -0,0 +1,215 @@ +package dev.lions.unionflow.server.api.dto.paiement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import dev.lions.unionflow.server.api.enums.paiement.StatutTraitement; +import dev.lions.unionflow.server.api.enums.paiement.TypeEvenement; + +/** + * Tests unitaires pour WaveWebhookDTO. + * Couvre toutes les branches : setTypeEvenement/setCodeEvenement, isEvenementCheckout, + * isEvenementPayout, marquerCommeTraite, marquerCommeEchec, demarrerTraitement. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests WaveWebhookDTO") +class WaveWebhookDTOTest { + + @Nested + @DisplayName("Constructeurs") + class Constructeurs { + + @Test + @DisplayName("constructeur par défaut initialise statut et date") + void testConstructeurVide() { + WaveWebhookDTO dto = new WaveWebhookDTO(); + assertThat(dto.getStatutTraitement()).isEqualTo(StatutTraitement.RECU); + assertThat(dto.getDateReception()).isNotNull(); + assertThat(dto.getNombreTentativesTraitement()).isEqualTo(0); + assertThat(dto.getTraitementAutomatique()).isTrue(); + } + + @Test + @DisplayName("constructeur avec paramètres remplit type et code") + void testConstructeurAvecParams() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + assertThat(dto.getWebhookId()).isEqualTo("wh-1"); + assertThat(dto.getTypeEvenement()).isEqualTo(TypeEvenement.CHECKOUT_COMPLETE); + assertThat(dto.getCodeEvenement()).isEqualTo("checkout.complete"); + assertThat(dto.getPayloadJson()).isEqualTo("{}"); + } + } + + @Nested + @DisplayName("setTypeEvenement") + class SetTypeEvenement { + + @Test + @DisplayName("met à jour codeEvenement quand type non null") + void testTypeNonNull() { + WaveWebhookDTO dto = new WaveWebhookDTO(); + dto.setTypeEvenement(TypeEvenement.PAYOUT_COMPLETE); + assertThat(dto.getTypeEvenement()).isEqualTo(TypeEvenement.PAYOUT_COMPLETE); + assertThat(dto.getCodeEvenement()).isEqualTo("payout.complete"); + } + + @Test + @DisplayName("ne modifie pas codeEvenement quand type null") + void testTypeNull() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.setCodeEvenement("custom.code"); + dto.setTypeEvenement(null); + assertThat(dto.getCodeEvenement()).isEqualTo("custom.code"); + } + } + + @Nested + @DisplayName("setCodeEvenement") + class SetCodeEvenement { + + @Test + @DisplayName("définit typeEvenement via fromCode") + void testFromCode() { + WaveWebhookDTO dto = new WaveWebhookDTO(); + dto.setCodeEvenement("checkout.cancelled"); + assertThat(dto.getTypeEvenement()).isEqualTo(TypeEvenement.CHECKOUT_CANCELLED); + assertThat(dto.getCodeEvenement()).isEqualTo("checkout.cancelled"); + } + + @Test + @DisplayName("code inconnu met typeEvenement à null") + void testCodeInconnu() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.setCodeEvenement("unknown.event"); + assertThat(dto.getTypeEvenement()).isNull(); + } + } + + @Nested + @DisplayName("isEvenementCheckout") + class IsEvenementCheckout { + + @Test + @DisplayName("true pour CHECKOUT_COMPLETE") + void testCheckoutComplete() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + assertThat(dto.isEvenementCheckout()).isTrue(); + } + + @Test + @DisplayName("true pour CHECKOUT_CANCELLED") + void testCheckoutCancelled() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_CANCELLED, "{}"); + assertThat(dto.isEvenementCheckout()).isTrue(); + } + + @Test + @DisplayName("true pour CHECKOUT_EXPIRED") + void testCheckoutExpired() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_EXPIRED, "{}"); + assertThat(dto.isEvenementCheckout()).isTrue(); + } + + @Test + @DisplayName("false pour PAYOUT_COMPLETE") + void testPayout() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.PAYOUT_COMPLETE, "{}"); + assertThat(dto.isEvenementCheckout()).isFalse(); + } + } + + @Nested + @DisplayName("isEvenementPayout") + class IsEvenementPayout { + + @Test + @DisplayName("true pour PAYOUT_COMPLETE") + void testPayoutComplete() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.PAYOUT_COMPLETE, "{}"); + assertThat(dto.isEvenementPayout()).isTrue(); + } + + @Test + @DisplayName("true pour PAYOUT_FAILED") + void testPayoutFailed() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.PAYOUT_FAILED, "{}"); + assertThat(dto.isEvenementPayout()).isTrue(); + } + + @Test + @DisplayName("false pour CHECKOUT_COMPLETE") + void testCheckout() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + assertThat(dto.isEvenementPayout()).isFalse(); + } + } + + @Nested + @DisplayName("marquerCommeTraite") + class MarquerCommeTraite { + + @Test + @DisplayName("passe statut à TRAITE et remplit dateTraitement") + void testMarquerCommeTraite() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.marquerCommeTraite(); + assertThat(dto.getStatutTraitement()).isEqualTo(StatutTraitement.TRAITE); + assertThat(dto.getDateTraitement()).isNotNull(); + } + } + + @Nested + @DisplayName("marquerCommeEchec") + class MarquerCommeEchec { + + @Test + @DisplayName("passe statut à ECHEC et incrémente tentatives") + void testMarquerCommeEchec() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.marquerCommeEchec("Erreur", "ERR_01"); + assertThat(dto.getStatutTraitement()).isEqualTo(StatutTraitement.ECHEC); + assertThat(dto.getMessageErreurTraitement()).isEqualTo("Erreur"); + assertThat(dto.getCodeErreurTraitement()).isEqualTo("ERR_01"); + assertThat(dto.getNombreTentativesTraitement()).isEqualTo(1); + } + } + + @Nested + @DisplayName("demarrerTraitement") + class DemarrerTraitement { + + @Test + @DisplayName("passe statut à EN_COURS et incrémente tentatives") + void testDemarrerTraitement() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.demarrerTraitement(); + assertThat(dto.getStatutTraitement()).isEqualTo(StatutTraitement.EN_COURS); + assertThat(dto.getNombreTentativesTraitement()).isEqualTo(1); + } + } + + @Nested + @DisplayName("toString") + class ToString { + + @Test + @DisplayName("contient webhookId et statut") + void testToString() { + WaveWebhookDTO dto = new WaveWebhookDTO("wh-1", TypeEvenement.CHECKOUT_COMPLETE, "{}"); + dto.setSessionCheckoutId("sess-1"); + dto.setMontantTransaction(BigDecimal.TEN); + String s = dto.toString(); + assertThat(s).contains("wh-1"); + assertThat(s).contains("RECU"); + assertThat(s).contains("sess-1"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTOTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTOTest.java new file mode 100644 index 0000000..1de701c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/CreneauDisponibiliteDTOTest.java @@ -0,0 +1,310 @@ +package dev.lions.unionflow.server.api.dto.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +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.EnumSource; + +/** + * Tests unitaires pour CreneauDisponibiliteDTO. + * Couvre isValide, getDureeMinutes, isDisponibleLe (tous types), contientHeure, getLibelle, + * et l'enum TypeCreneau. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests CreneauDisponibiliteDTO") +class CreneauDisponibiliteDTOTest { + + @Nested + @DisplayName("isValide") + class IsValide { + + @Test + @DisplayName("retourne false quand heureDebut est null") + void testHeureDebutNull() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureFin(LocalTime.of(18, 0)).build(); + assertThat(dto.isValide()).isFalse(); + } + + @Test + @DisplayName("retourne false quand heureFin est null") + void testHeureFinNull() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)).build(); + assertThat(dto.isValide()).isFalse(); + } + + @Test + @DisplayName("retourne false quand heureFin avant heureDebut") + void testHeureFinAvantDebut() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(18, 0)) + .heureFin(LocalTime.of(9, 0)).build(); + assertThat(dto.isValide()).isFalse(); + } + + @Test + @DisplayName("retourne false quand heureFin égale heureDebut") + void testHeuresEgales() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(9, 0)).build(); + assertThat(dto.isValide()).isFalse(); + } + + @Test + @DisplayName("retourne true quand heureFin après heureDebut") + void testValide() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)).build(); + assertThat(dto.isValide()).isTrue(); + } + } + + @Nested + @DisplayName("getDureeMinutes") + class GetDureeMinutes { + + @Test + @DisplayName("retourne 0 quand créneau invalide") + void testInvalide() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(18, 0)) + .heureFin(LocalTime.of(9, 0)).build(); + assertThat(dto.getDureeMinutes()).isEqualTo(0); + } + + @Test + @DisplayName("retourne la durée en minutes") + void testDuree() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 30)).build(); + assertThat(dto.getDureeMinutes()).isEqualTo(210); + } + } + + @Nested + @DisplayName("isDisponibleLe") + class IsDisponibleLe { + + @Test + @DisplayName("retourne false quand estActif est false") + void testNonActif() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .estActif(false) + .type(CreneauDisponibiliteDTO.TypeCreneau.RECURRENT) + .jourSemaine(DayOfWeek.MONDAY).build(); + assertThat(dto.isDisponibleLe(LocalDate.of(2026, 2, 2))).isFalse(); + } + + @Test + @DisplayName("PONCTUEL: true seulement si dateSpecifique égale à la date") + void testPonctuel() { + LocalDate date = LocalDate.of(2026, 2, 15); + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .estActif(true) + .type(CreneauDisponibiliteDTO.TypeCreneau.PONCTUEL) + .dateSpecifique(date).build(); + assertThat(dto.isDisponibleLe(date)).isTrue(); + assertThat(dto.isDisponibleLe(date.plusDays(1))).isFalse(); + dto.setDateSpecifique(null); + assertThat(dto.isDisponibleLe(date)).isFalse(); + } + + @Test + @DisplayName("RECURRENT: true si jourSemaine correspond") + void testRecurrent() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .estActif(true) + .type(CreneauDisponibiliteDTO.TypeCreneau.RECURRENT) + .jourSemaine(DayOfWeek.WEDNESDAY).build(); + LocalDate mercredi = LocalDate.of(2026, 2, 18); + assertThat(dto.isDisponibleLe(mercredi)).isTrue(); + assertThat(dto.isDisponibleLe(mercredi.plusDays(1))).isFalse(); + dto.setJourSemaine(null); + assertThat(dto.isDisponibleLe(mercredi)).isFalse(); + } + + @Test + @DisplayName("URGENCE et FLEXIBLE: toujours true si actif") + void testUrgenceFlexible() { + CreneauDisponibiliteDTO urg = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .estActif(true) + .type(CreneauDisponibiliteDTO.TypeCreneau.URGENCE).build(); + CreneauDisponibiliteDTO flex = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .estActif(true) + .type(CreneauDisponibiliteDTO.TypeCreneau.FLEXIBLE).build(); + LocalDate any = LocalDate.of(2026, 2, 20); + assertThat(urg.isDisponibleLe(any)).isTrue(); + assertThat(flex.isDisponibleLe(any)).isTrue(); + } + } + + @Nested + @DisplayName("contientHeure") + class ContientHeure { + + @Test + @DisplayName("retourne false quand créneau invalide") + void testInvalide() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(18, 0)) + .heureFin(LocalTime.of(9, 0)).build(); + assertThat(dto.contientHeure(LocalTime.of(12, 0))).isFalse(); + } + + @Test + @DisplayName("retourne true quand heure dans le créneau") + void testDedans() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)).build(); + assertThat(dto.contientHeure(LocalTime.of(10, 30))).isTrue(); + assertThat(dto.contientHeure(LocalTime.of(9, 0))).isTrue(); + assertThat(dto.contientHeure(LocalTime.of(12, 0))).isTrue(); + } + + @Test + @DisplayName("retourne false quand heure en dehors") + void testDehors() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)).build(); + assertThat(dto.contientHeure(LocalTime.of(8, 59))).isFalse(); + assertThat(dto.contientHeure(LocalTime.of(12, 1))).isFalse(); + } + } + + @Nested + @DisplayName("getLibelle") + class GetLibelle { + + @Test + @DisplayName("RECURRENT avec jourSemaine inclut le jour") + void testRecurrent() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.RECURRENT) + .jourSemaine(DayOfWeek.MONDAY).build(); + assertThat(dto.getLibelle()).startsWith("MONDAY "); + assertThat(dto.getLibelle()).endsWith("09:00 - 12:00"); + } + + @Test + @DisplayName("RECURRENT sans jourSemaine: seulement heures") + void testRecurrentSansJourSemaine() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.RECURRENT) + .jourSemaine(null).build(); + assertThat(dto.getLibelle()).isEqualTo("09:00 - 12:00"); + } + + @Test + @DisplayName("PONCTUEL avec dateSpecifique inclut la date") + void testPonctuel() { + LocalDate date = LocalDate.of(2026, 2, 15); + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.PONCTUEL) + .dateSpecifique(date).build(); + assertThat(dto.getLibelle()).startsWith("2026-02-15 "); + assertThat(dto.getLibelle()).endsWith("09:00 - 12:00"); + } + + @Test + @DisplayName("PONCTUEL sans dateSpecifique: seulement heures") + void testPonctuelSansDate() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.PONCTUEL) + .dateSpecifique(null).build(); + assertThat(dto.getLibelle()).isEqualTo("09:00 - 12:00"); + } + + @Test + @DisplayName("autre type: seulement heures (URGENCE)") + void testAutre() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(9, 0)) + .heureFin(LocalTime.of(12, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.URGENCE).build(); + assertThat(dto.getLibelle()).isEqualTo("09:00 - 12:00"); + } + + @Test + @DisplayName("FLEXIBLE: seulement heures (pas RECURRENT ni PONCTUEL)") + void testFlexible() { + CreneauDisponibiliteDTO dto = CreneauDisponibiliteDTO.builder() + .heureDebut(LocalTime.of(14, 0)) + .heureFin(LocalTime.of(17, 0)) + .type(CreneauDisponibiliteDTO.TypeCreneau.FLEXIBLE).build(); + assertThat(dto.getLibelle()).isEqualTo("14:00 - 17:00"); + } + } + + @Nested + @DisplayName("TypeCreneau enum") + class TypeCreneauEnum { + + @Test + @DisplayName("values contient RECURRENT, PONCTUEL, URGENCE, FLEXIBLE") + void testValues() { + CreneauDisponibiliteDTO.TypeCreneau[] values = CreneauDisponibiliteDTO.TypeCreneau.values(); + assertThat(values).hasSize(4); + assertThat(values).contains( + CreneauDisponibiliteDTO.TypeCreneau.RECURRENT, + CreneauDisponibiliteDTO.TypeCreneau.PONCTUEL, + CreneauDisponibiliteDTO.TypeCreneau.URGENCE, + CreneauDisponibiliteDTO.TypeCreneau.FLEXIBLE); + } + + @ParameterizedTest + @EnumSource(CreneauDisponibiliteDTO.TypeCreneau.class) + @DisplayName("getLibelle non vide pour chaque type") + void testGetLibelle(CreneauDisponibiliteDTO.TypeCreneau type) { + assertThat(type.getLibelle()).isNotBlank(); + } + + @Test + @DisplayName("valueOf pour chaque constante") + void testValueOf() { + assertThat(CreneauDisponibiliteDTO.TypeCreneau.valueOf("RECURRENT")).isEqualTo(CreneauDisponibiliteDTO.TypeCreneau.RECURRENT); + assertThat(CreneauDisponibiliteDTO.TypeCreneau.valueOf("PONCTUEL")).isEqualTo(CreneauDisponibiliteDTO.TypeCreneau.PONCTUEL); + assertThat(CreneauDisponibiliteDTO.TypeCreneau.valueOf("URGENCE")).isEqualTo(CreneauDisponibiliteDTO.TypeCreneau.URGENCE); + assertThat(CreneauDisponibiliteDTO.TypeCreneau.valueOf("FLEXIBLE")).isEqualTo(CreneauDisponibiliteDTO.TypeCreneau.FLEXIBLE); + } + + @Test + @DisplayName("valueOf INEXISTANT lance IllegalArgumentException") + void testValueOfInexistant() { + assertThat(org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, + () -> CreneauDisponibiliteDTO.TypeCreneau.valueOf("INEXISTANT"))).isNotNull(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponseTest.java new file mode 100644 index 0000000..d1b9865 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/CommentaireAideResponseTest.java @@ -0,0 +1,81 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour CommentaireAideResponse. + * Couvre builder, getters/setters et equals/hashCode (héritage BaseResponse). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests CommentaireAideResponse") +class CommentaireAideResponseTest { + + @Nested + @DisplayName("Builder et getters") + class BuilderEtGetters { + + @Test + @DisplayName("builder remplit tous les champs") + void testBuilder() { + UUID id = UUID.randomUUID(); + UUID auteurId = UUID.randomUUID(); + CommentaireAideResponse r = CommentaireAideResponse.builder() + .contenu("Contenu") + .typeCommentaire("public") + .auteurId(auteurId) + .auteurNom("Dupont") + .auteurRole("Membre") + .estPrive(false) + .estImportant(true) + .estModifie(false) + .nombreReactions(5) + .estResolu(false) + .build(); + r.setId(id); + assertThat(r.getId()).isEqualTo(id); + assertThat(r.getContenu()).isEqualTo("Contenu"); + assertThat(r.getTypeCommentaire()).isEqualTo("public"); + assertThat(r.getAuteurId()).isEqualTo(auteurId); + assertThat(r.getAuteurNom()).isEqualTo("Dupont"); + assertThat(r.getAuteurRole()).isEqualTo("Membre"); + assertThat(r.getEstPrive()).isFalse(); + assertThat(r.getEstImportant()).isTrue(); + assertThat(r.getEstModifie()).isFalse(); + assertThat(r.getNombreReactions()).isEqualTo(5); + assertThat(r.getEstResolu()).isFalse(); + } + + @Test + @DisplayName("constructeur par défaut puis setters") + void testSetters() { + CommentaireAideResponse r = new CommentaireAideResponse(); + r.setContenu("c"); + r.setCommentaireParentId(UUID.randomUUID()); + assertThat(r.getContenu()).isEqualTo("c"); + assertThat(r.getCommentaireParentId()).isNotNull(); + } + + @Test + @DisplayName("equals et hashCode avec même contenu et même id") + void testEqualsHashCode() { + UUID id = UUID.randomUUID(); + CommentaireAideResponse a = CommentaireAideResponse.builder().contenu("x").build(); + a.setId(id); + CommentaireAideResponse b = CommentaireAideResponse.builder().contenu("x").build(); + b.setId(id); + assertThat(a).isEqualTo(a); + assertThat(a.hashCode()).isEqualTo(a.hashCode()); + CommentaireAideResponse autreId = CommentaireAideResponse.builder().contenu("x").build(); + autreId.setId(UUID.randomUUID()); + assertThat(a).isNotEqualTo(autreId); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponseTest.java new file mode 100644 index 0000000..ef7c09a --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/DemandeAideResponseTest.java @@ -0,0 +1,404 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; + +/** + * Tests unitaires pour DemandeAideResponse. + * Couvre estModifiable, peutEtreAnnulee, estUrgente, estTerminee, estEnSucces, + * getPourcentageAvancement (tous statuts), getDelaiRestantHeures, estDelaiDepasse, + * getStatutLibelle, getPrioriteLibelle. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests DemandeAideResponse") +class DemandeAideResponseTest { + + @Nested + @DisplayName("estModifiable") + class EstModifiable { + + @Test + @DisplayName("retourne false quand statut null") + void testStatutNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.estModifiable()).isFalse(); + } + + @Test + @DisplayName("retourne true pour BROUILLON") + void testBrouillon() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.BROUILLON).build(); + assertThat(r.estModifiable()).isTrue(); + } + + @Test + @DisplayName("retourne true pour INFORMATIONS_REQUISES") + void testInformationsRequises() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.INFORMATIONS_REQUISES).build(); + assertThat(r.estModifiable()).isTrue(); + } + + @Test + @DisplayName("retourne false pour SOUMISE") + void testSoumise() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.SOUMISE).build(); + assertThat(r.estModifiable()).isFalse(); + } + } + + @Nested + @DisplayName("peutEtreAnnulee") + class PeutEtreAnnulee { + + @Test + @DisplayName("retourne false quand statut null") + void testStatutNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.peutEtreAnnulee()).isFalse(); + } + + @Test + @DisplayName("retourne true pour BROUILLON") + void testBrouillon() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.BROUILLON).build(); + assertThat(r.peutEtreAnnulee()).isTrue(); + } + + @Test + @DisplayName("retourne false pour VERSEE (final)") + void testFinal() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.VERSEE).build(); + assertThat(r.peutEtreAnnulee()).isFalse(); + } + + @Test + @DisplayName("retourne false pour ANNULEE") + void testAnnulee() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.ANNULEE).build(); + assertThat(r.peutEtreAnnulee()).isFalse(); + } + } + + @Nested + @DisplayName("estUrgente") + class EstUrgente { + + @Test + @DisplayName("retourne false quand priorite null") + void testPrioriteNull() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(null).build(); + assertThat(r.estUrgente()).isFalse(); + } + + @Test + @DisplayName("retourne true pour CRITIQUE") + void testCritique() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(PrioriteAide.CRITIQUE).build(); + assertThat(r.estUrgente()).isTrue(); + } + + @Test + @DisplayName("retourne true pour URGENTE") + void testUrgente() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(PrioriteAide.URGENTE).build(); + assertThat(r.estUrgente()).isTrue(); + } + + @Test + @DisplayName("retourne false pour NORMALE") + void testNormale() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(PrioriteAide.NORMALE).build(); + assertThat(r.estUrgente()).isFalse(); + } + } + + @Nested + @DisplayName("estTerminee") + class EstTerminee { + + @Test + @DisplayName("retourne false quand statut null") + void testStatutNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.estTerminee()).isFalse(); + } + + @Test + @DisplayName("retourne true pour VERSEE") + void testTerminee() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.VERSEE).build(); + assertThat(r.estTerminee()).isTrue(); + } + + @Test + @DisplayName("retourne false pour EN_ATTENTE") + void testEnCours() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.EN_ATTENTE).build(); + assertThat(r.estTerminee()).isFalse(); + } + } + + @Nested + @DisplayName("estEnSucces") + class EstEnSucces { + + @Test + @DisplayName("retourne false quand statut null") + void testStatutNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.estEnSucces()).isFalse(); + } + + @Test + @DisplayName("retourne true pour VERSEE") + void testVersee() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.VERSEE).build(); + assertThat(r.estEnSucces()).isTrue(); + } + + @Test + @DisplayName("retourne false pour REJETEE") + void testRejetee() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.REJETEE).build(); + assertThat(r.estEnSucces()).isFalse(); + } + } + + @Nested + @DisplayName("getPourcentageAvancement") + class GetPourcentageAvancement { + + @Test + @DisplayName("retourne 0 quand statut null") + void testStatutNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(0.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"BROUILLON"}) + @DisplayName("BROUILLON -> 5.0") + void testBrouillon(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(5.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"SOUMISE"}) + @DisplayName("SOUMISE -> 10.0") + void testSoumise(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(10.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"EN_ATTENTE"}) + @DisplayName("EN_ATTENTE -> 20.0") + void testEnAttente(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(20.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"EN_COURS_EVALUATION"}) + @DisplayName("EN_COURS_EVALUATION -> 40.0") + void testEnCoursEvaluation(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(40.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"INFORMATIONS_REQUISES"}) + @DisplayName("INFORMATIONS_REQUISES -> 35.0") + void testInformationsRequises(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(35.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"APPROUVEE", "APPROUVEE_PARTIELLEMENT"}) + @DisplayName("APPROUVEE/APPROUVEE_PARTIELLEMENT -> 60.0") + void testApprouvee(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(60.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"EN_COURS_TRAITEMENT"}) + @DisplayName("EN_COURS_TRAITEMENT -> 70.0") + void testEnCoursTraitement(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(70.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"EN_COURS_VERSEMENT"}) + @DisplayName("EN_COURS_VERSEMENT -> 85.0") + void testEnCoursVersement(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(85.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"VERSEE", "LIVREE", "TERMINEE"}) + @DisplayName("VERSEE/LIVREE/TERMINEE -> 100.0") + void testFinauxSucces(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(100.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"REJETEE", "ANNULEE", "EXPIREE"}) + @DisplayName("REJETEE/ANNULEE/EXPIREE -> 100.0") + void testFinauxEchec(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(100.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"SUSPENDUE"}) + @DisplayName("SUSPENDUE -> 50.0") + void testSuspendue(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(50.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"EN_SUIVI"}) + @DisplayName("EN_SUIVI -> 95.0") + void testEnSuivi(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(95.0); + } + + @ParameterizedTest + @EnumSource(value = StatutAide.class, names = {"CLOTUREE"}) + @DisplayName("CLOTUREE -> 100.0") + void testCloturee(StatutAide statut) { + DemandeAideResponse r = DemandeAideResponse.builder().statut(statut).build(); + assertThat(r.getPourcentageAvancement()).isEqualTo(100.0); + } + } + + @Nested + @DisplayName("getDelaiRestantHeures") + class GetDelaiRestantHeures { + + @Test + @DisplayName("retourne -1 quand dateLimiteTraitement null") + void testNull() { + DemandeAideResponse r = DemandeAideResponse.builder().dateLimiteTraitement(null).build(); + assertThat(r.getDelaiRestantHeures()).isEqualTo(-1); + } + + @Test + @DisplayName("retourne 0 quand date limite dépassée") + void testDepasse() { + LocalDateTime hier = LocalDateTime.now().minusHours(1); + DemandeAideResponse r = DemandeAideResponse.builder().dateLimiteTraitement(hier).build(); + assertThat(r.getDelaiRestantHeures()).isEqualTo(0); + } + + @Test + @DisplayName("retourne heures restantes quand date limite future") + void testRestant() { + LocalDateTime demain = LocalDateTime.now().plusHours(24); + DemandeAideResponse r = DemandeAideResponse.builder().dateLimiteTraitement(demain).build(); + assertThat(r.getDelaiRestantHeures()).isGreaterThanOrEqualTo(23); + } + } + + @Nested + @DisplayName("estDelaiDepasse") + class EstDelaiDepasse { + + @Test + @DisplayName("retourne true quand delai restant 0") + void testDepasse() { + LocalDateTime hier = LocalDateTime.now().minusHours(1); + DemandeAideResponse r = DemandeAideResponse.builder().dateLimiteTraitement(hier).build(); + assertThat(r.estDelaiDepasse()).isTrue(); + } + + @Test + @DisplayName("retourne false quand delai restant > 0") + void testNonDepasse() { + LocalDateTime demain = LocalDateTime.now().plusHours(24); + DemandeAideResponse r = DemandeAideResponse.builder().dateLimiteTraitement(demain).build(); + assertThat(r.estDelaiDepasse()).isFalse(); + } + } + + @Nested + @DisplayName("getStatutLibelle") + class GetStatutLibelle { + + @Test + @DisplayName("retourne Non défini quand statut null") + void testNull() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(null).build(); + assertThat(r.getStatutLibelle()).isEqualTo("Non défini"); + } + + @Test + @DisplayName("retourne libellé du statut") + void testLibelle() { + DemandeAideResponse r = DemandeAideResponse.builder().statut(StatutAide.BROUILLON).build(); + assertThat(r.getStatutLibelle()).isEqualTo("Brouillon"); + } + } + + @Nested + @DisplayName("getPrioriteLibelle") + class GetPrioriteLibelle { + + @Test + @DisplayName("retourne Normale quand priorite null") + void testNull() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(null).build(); + assertThat(r.getPrioriteLibelle()).isEqualTo("Normale"); + } + + @Test + @DisplayName("retourne libellé de la priorité") + void testLibelle() { + DemandeAideResponse r = DemandeAideResponse.builder().priorite(PrioriteAide.URGENTE).build(); + assertThat(r.getPrioriteLibelle()).isEqualTo("Urgente"); + } + } + + @Nested + @DisplayName("Builder et getters") + class BuilderEtGetters { + + @Test + @DisplayName("builder remplit les champs") + void testBuilder() { + UUID id = UUID.randomUUID(); + DemandeAideResponse r = DemandeAideResponse.builder() + .numeroReference("REF-001") + .titre("Titre") + .statut(StatutAide.SOUMISE) + .priorite(PrioriteAide.NORMALE) + .build(); + r.setId(id); + assertThat(r.getId()).isEqualTo(id); + assertThat(r.getNumeroReference()).isEqualTo("REF-001"); + assertThat(r.getTitre()).isEqualTo("Titre"); + assertThat(r.getStatut()).isEqualTo(StatutAide.SOUMISE); + assertThat(r.getPriorite()).isEqualTo(PrioriteAide.NORMALE); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponseTest.java new file mode 100644 index 0000000..c0cd4e7 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/EvaluationAideResponseTest.java @@ -0,0 +1,343 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +import static org.assertj.core.api.Assertions.assertThat; + +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; + +/** + * Tests unitaires pour EvaluationAideResponse. + * Couvre getNoteMoyenneDetaillees, isPositive, isNegative, getScoreQualite, + * isComplete, getNiveauSatisfaction (tous cas du switch). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests EvaluationAideResponse") +class EvaluationAideResponseTest { + + @Nested + @DisplayName("getNoteMoyenneDetaillees") + class GetNoteMoyenneDetaillees { + + @Test + @DisplayName("retourne noteGlobale quand notesDetaillees null") + void testNotesNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .notesDetaillees(null) + .build(); + assertThat(r.getNoteMoyenneDetaillees()).isEqualTo(4.0); + } + + @Test + @DisplayName("retourne noteGlobale quand notesDetaillees vide") + void testNotesVide() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(3.5) + .notesDetaillees(Map.of()) + .build(); + assertThat(r.getNoteMoyenneDetaillees()).isEqualTo(3.5); + } + + @Test + @DisplayName("retourne moyenne des notes détaillées") + void testMoyenne() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(5.0) + .notesDetaillees(Map.of("a", 3.0, "b", 5.0)) + .build(); + assertThat(r.getNoteMoyenneDetaillees()).isEqualTo(4.0); + } + + @Test + @DisplayName("retourne noteGlobale quand notesDetaillees non vide mais noteGlobale null (fallback 0)") + void testNoteGlobaleNullAvecNotes() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(null) + .notesDetaillees(Map.of("a", 2.0)) + .build(); + assertThat(r.getNoteMoyenneDetaillees()).isEqualTo(2.0); + } + } + + @Nested + @DisplayName("isPositive") + class IsPositive { + + @Test + @DisplayName("retourne false quand noteGlobale null") + void testNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(null).build(); + assertThat(r.isPositive()).isFalse(); + } + + @Test + @DisplayName("retourne true quand noteGlobale >= 4") + void testPositive() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(4.0).build(); + assertThat(r.isPositive()).isTrue(); + r.setNoteGlobale(5.0); + assertThat(r.isPositive()).isTrue(); + } + + @Test + @DisplayName("retourne false quand noteGlobale < 4") + void testNonPositive() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(3.9).build(); + assertThat(r.isPositive()).isFalse(); + } + } + + @Nested + @DisplayName("isNegative") + class IsNegative { + + @Test + @DisplayName("retourne false quand noteGlobale null") + void testNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(null).build(); + assertThat(r.isNegative()).isFalse(); + } + + @Test + @DisplayName("retourne true quand noteGlobale <= 2") + void testNegative() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(2.0).build(); + assertThat(r.isNegative()).isTrue(); + r.setNoteGlobale(1.0); + assertThat(r.isNegative()).isTrue(); + } + + @Test + @DisplayName("retourne false quand noteGlobale > 2") + void testNonNegative() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(2.1).build(); + assertThat(r.isNegative()).isFalse(); + } + } + + @Nested + @DisplayName("getScoreQualite") + class GetScoreQualite { + + @Test + @DisplayName("retourne 0 quand noteGlobale null et pas d'autres notes") + void testNoteGlobaleNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(null).build(); + assertThat(r.getScoreQualite()).isEqualTo(0.0); + } + + @Test + @DisplayName("inclut noteGlobale et bonus recommande + problemeResolu") + void testAvecRecommandeEtResolu() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .recommande(true) + .problemeResolu(true) + .build(); + double score = r.getScoreQualite(); + assertThat(score).isGreaterThanOrEqualTo(4.0); + assertThat(score).isLessThanOrEqualTo(5.0); + } + + @Test + @DisplayName("inclut notes détaillées (delai, communication, etc.)") + void testAvecNotesDetaillees() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .noteDelaiReponse(5.0) + .noteCommunication(4.0) + .noteProfessionnalisme(5.0) + .noteRespectEngagements(4.0) + .build(); + double score = r.getScoreQualite(); + assertThat(score).isGreaterThan(4.0); + assertThat(score).isLessThanOrEqualTo(5.0); + } + + @Test + @DisplayName("décrémente avec nombreSignalements") + void testAvecSignalements() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .nombreSignalements(2) + .build(); + double score = r.getScoreQualite(); + assertThat(score).isLessThan(4.0); + } + + @Test + @DisplayName("score borné entre 0 et 5") + void testBorne() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(5.0) + .noteDelaiReponse(5.0) + .noteCommunication(5.0) + .noteProfessionnalisme(5.0) + .noteRespectEngagements(5.0) + .recommande(true) + .problemeResolu(true) + .build(); + assertThat(r.getScoreQualite()).isLessThanOrEqualTo(5.0); + } + } + + @Nested + @DisplayName("isComplete") + class IsComplete { + + @Test + @DisplayName("retourne false quand noteGlobale null") + void testNoteNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .commentairePrincipal("x") + .recommande(true) + .aideUtile(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isFalse(); + } + + @Test + @DisplayName("retourne false quand commentairePrincipal null") + void testCommentaireNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .recommande(true) + .aideUtile(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isFalse(); + } + + @Test + @DisplayName("retourne false quand commentairePrincipal vide (trim)") + void testCommentaireVide() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .commentairePrincipal(" ") + .recommande(true) + .aideUtile(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isFalse(); + } + + @Test + @DisplayName("retourne false quand recommande null") + void testRecommandeNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .commentairePrincipal("OK") + .aideUtile(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isFalse(); + } + + @Test + @DisplayName("retourne false quand aideUtile null") + void testAideUtileNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .commentairePrincipal("OK") + .recommande(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isFalse(); + } + + @Test + @DisplayName("retourne true quand tous les champs requis remplis") + void testComplete() { + EvaluationAideResponse r = EvaluationAideResponse.builder() + .noteGlobale(4.0) + .commentairePrincipal("Très bien") + .recommande(true) + .aideUtile(true) + .problemeResolu(true) + .build(); + assertThat(r.isComplete()).isTrue(); + } + } + + @Nested + @DisplayName("getNiveauSatisfaction") + class GetNiveauSatisfaction { + + @Test + @DisplayName("retourne Non évalué quand noteGlobale null") + void testNull() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(null).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Non évalué"); + } + + @Test + @DisplayName("note 5 -> Excellent") + void test5() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(5.0).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Excellent"); + } + + @Test + @DisplayName("note 4 -> Très bien") + void test4() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(4.0).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Très bien"); + } + + @Test + @DisplayName("note 3 -> Bien") + void test3() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(3.0).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Bien"); + } + + @Test + @DisplayName("note 2 -> Passable") + void test2() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(2.0).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Passable"); + } + + @Test + @DisplayName("note 1 -> Insuffisant") + void test1() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(1.0).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Insuffisant"); + } + + @Test + @DisplayName("autre valeur -> Non évalué") + void testDefault() { + EvaluationAideResponse r = EvaluationAideResponse.builder().noteGlobale(0.5).build(); + assertThat(r.getNiveauSatisfaction()).isEqualTo("Non évalué"); + } + } + + @Nested + @DisplayName("Builder et getters") + class BuilderEtGetters { + + @Test + @DisplayName("builder remplit les champs") + void testBuilder() { + UUID id = UUID.randomUUID(); + EvaluationAideResponse r = EvaluationAideResponse.builder() + .demandeAideId(id) + .noteGlobale(4.5) + .commentairePrincipal("Bien") + .build(); + r.setId(id); + assertThat(r.getId()).isEqualTo(id); + assertThat(r.getDemandeAideId()).isEqualTo(id); + assertThat(r.getNoteGlobale()).isEqualTo(4.5); + assertThat(r.getCommentairePrincipal()).isEqualTo("Bien"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponseTest.java new file mode 100644 index 0000000..02e6dc6 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/solidarite/response/PropositionAideResponseTest.java @@ -0,0 +1,248 @@ +package dev.lions.unionflow.server.api.dto.solidarite.response; + +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; +import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition; + +/** + * Tests unitaires pour PropositionAideResponse. + * Couvre isActiveEtDisponible, isExpiree, peutAccepterBeneficiaires, + * getPourcentageCapaciteUtilisee, getPlacesRestantes. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-02-21 + */ +@DisplayName("Tests PropositionAideResponse") +class PropositionAideResponseTest { + + @Nested + @DisplayName("isExpiree") + class IsExpiree { + + @Test + @DisplayName("retourne false quand dateExpiration null") + void testNull() { + PropositionAideResponse r = PropositionAideResponse.builder().dateExpiration(null).build(); + assertThat(r.isExpiree()).isFalse(); + } + + @Test + @DisplayName("retourne true quand date expiration passée") + void testExpiree() { + LocalDateTime hier = LocalDateTime.now().minusHours(1); + PropositionAideResponse r = PropositionAideResponse.builder().dateExpiration(hier).build(); + assertThat(r.isExpiree()).isTrue(); + } + + @Test + @DisplayName("retourne false quand date expiration future") + void testNonExpiree() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder().dateExpiration(demain).build(); + assertThat(r.isExpiree()).isFalse(); + } + } + + @Nested + @DisplayName("isActiveEtDisponible") + class IsActiveEtDisponible { + + @Test + @DisplayName("retourne false quand statut pas ACTIVE") + void testStatutNonActive() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.BROUILLON) + .estDisponible(true) + .dateExpiration(demain) + .build(); + assertThat(r.isActiveEtDisponible()).isFalse(); + } + + @Test + @DisplayName("retourne false quand estDisponible false") + void testNonDisponible() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.ACTIVE) + .estDisponible(false) + .dateExpiration(demain) + .build(); + assertThat(r.isActiveEtDisponible()).isFalse(); + } + + @Test + @DisplayName("retourne false quand expirée") + void testExpiree() { + LocalDateTime hier = LocalDateTime.now().minusHours(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.ACTIVE) + .estDisponible(true) + .dateExpiration(hier) + .build(); + assertThat(r.isActiveEtDisponible()).isFalse(); + } + + @Test + @DisplayName("retourne true quand ACTIVE, disponible et non expirée") + void testActiveDisponible() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.ACTIVE) + .estDisponible(true) + .dateExpiration(demain) + .build(); + assertThat(r.isActiveEtDisponible()).isTrue(); + } + } + + @Nested + @DisplayName("peutAccepterBeneficiaires") + class PeutAccepterBeneficiaires { + + @Test + @DisplayName("retourne false quand pas active et disponible") + void testNonActive() { + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.SUSPENDUE) + .estDisponible(true) + .dateExpiration(LocalDateTime.now().plusDays(1)) + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(5) + .build(); + assertThat(r.peutAccepterBeneficiaires()).isFalse(); + } + + @Test + @DisplayName("retourne false quand capacité pleine") + void testCapacitePleine() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.ACTIVE) + .estDisponible(true) + .dateExpiration(demain) + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(10) + .build(); + assertThat(r.peutAccepterBeneficiaires()).isFalse(); + } + + @Test + @DisplayName("retourne true quand active, disponible et places restantes") + void testPeutAccepter() { + LocalDateTime demain = LocalDateTime.now().plusDays(1); + PropositionAideResponse r = PropositionAideResponse.builder() + .statut(StatutProposition.ACTIVE) + .estDisponible(true) + .dateExpiration(demain) + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(3) + .build(); + assertThat(r.peutAccepterBeneficiaires()).isTrue(); + } + } + + @Nested + @DisplayName("getPourcentageCapaciteUtilisee") + class GetPourcentageCapaciteUtilisee { + + @Test + @DisplayName("retourne 100 quand nombreMaxBeneficiaires null") + void testMaxNull() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(null) + .nombreBeneficiairesAides(5) + .build(); + assertThat(r.getPourcentageCapaciteUtilisee()).isEqualTo(100.0); + } + + @Test + @DisplayName("retourne 100 quand nombreMaxBeneficiaires 0") + void testMaxZero() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(0) + .nombreBeneficiairesAides(0) + .build(); + assertThat(r.getPourcentageCapaciteUtilisee()).isEqualTo(100.0); + } + + @Test + @DisplayName("retourne pourcentage calculé") + void testCalcul() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(3) + .build(); + assertThat(r.getPourcentageCapaciteUtilisee()).isEqualTo(30.0); + } + } + + @Nested + @DisplayName("getPlacesRestantes") + class GetPlacesRestantes { + + @Test + @DisplayName("retourne 0 quand nombreMaxBeneficiaires null") + void testMaxNull() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(null) + .build(); + assertThat(r.getPlacesRestantes()).isEqualTo(0); + } + + @Test + @DisplayName("retourne max quand nombreBeneficiairesAides null") + void testAidesNull() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(null) + .build(); + assertThat(r.getPlacesRestantes()).isEqualTo(10); + } + + @Test + @DisplayName("retourne différence positive") + void testDifference() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(4) + .build(); + assertThat(r.getPlacesRestantes()).isEqualTo(6); + } + + @Test + @DisplayName("retourne 0 quand plus de bénéficiaires que max (cap)") + void testCap() { + PropositionAideResponse r = PropositionAideResponse.builder() + .nombreMaxBeneficiaires(10) + .nombreBeneficiairesAides(15) + .build(); + assertThat(r.getPlacesRestantes()).isEqualTo(0); + } + } + + @Nested + @DisplayName("Builder et getters") + class BuilderEtGetters { + + @Test + @DisplayName("builder remplit les champs") + void testBuilder() { + PropositionAideResponse r = PropositionAideResponse.builder() + .numeroReference("PROP-001") + .titre("Titre") + .statut(StatutProposition.ACTIVE) + .estDisponible(true) + .build(); + assertThat(r.getNumeroReference()).isEqualTo("PROP-001"); + assertThat(r.getTitre()).isEqualTo("Titre"); + assertThat(r.getStatut()).isEqualTo(StatutProposition.ACTIVE); + assertThat(r.getEstDisponible()).isTrue(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponseTest.java new file mode 100644 index 0000000..ae8b6ee --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/system/response/SystemMetricsResponseTest.java @@ -0,0 +1,259 @@ +package dev.lions.unionflow.server.api.dto.system.response; + +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; + +/** + * Tests unitaires pour SystemMetricsResponse. + * Couvre formatBytes, formatUptime, et tous les getters/setters via builder. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-07 + */ +@DisplayName("Tests SystemMetricsResponse") +class SystemMetricsResponseTest { + + @Nested + @DisplayName("formatBytes") + class FormatBytes { + + @Test + @DisplayName("retourne bytes pour moins de 1024") + void testBytes() { + assertThat(SystemMetricsResponse.formatBytes(0)).isEqualTo("0 B"); + assertThat(SystemMetricsResponse.formatBytes(512)).isEqualTo("512 B"); + assertThat(SystemMetricsResponse.formatBytes(1023)).isEqualTo("1023 B"); + } + + @Test + @DisplayName("retourne KB pour 1024+") + void testKiloBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024)).isEqualTo("1.0 KB"); + assertThat(SystemMetricsResponse.formatBytes(2048)).isEqualTo("2.0 KB"); + assertThat(SystemMetricsResponse.formatBytes(1536)).isEqualTo("1.5 KB"); + } + + @Test + @DisplayName("retourne MB pour 1024*1024+") + void testMegaBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024 * 1024)).isEqualTo("1.0 MB"); + assertThat(SystemMetricsResponse.formatBytes(5 * 1024 * 1024)).isEqualTo("5.0 MB"); + } + + @Test + @DisplayName("retourne GB pour 1024*1024*1024+") + void testGigaBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024L * 1024 * 1024)).isEqualTo("1.0 GB"); + assertThat(SystemMetricsResponse.formatBytes(3L * 1024 * 1024 * 1024)).isEqualTo("3.0 GB"); + } + + @Test + @DisplayName("retourne TB pour 1024^4+") + void testTeraBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024L * 1024 * 1024 * 1024)).isEqualTo("1.0 TB"); + assertThat(SystemMetricsResponse.formatBytes(2L * 1024 * 1024 * 1024 * 1024)).isEqualTo("2.0 TB"); + } + + @Test + @DisplayName("retourne PB pour 1024^5+") + void testPetaBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024L * 1024 * 1024 * 1024 * 1024)) + .isEqualTo("1.0 PB"); + } + + @Test + @DisplayName("retourne EB pour 1024^6+") + void testExaBytes() { + assertThat(SystemMetricsResponse.formatBytes(1024L * 1024 * 1024 * 1024 * 1024 * 1024)) + .isEqualTo("1.0 EB"); + } + } + + @Nested + @DisplayName("formatUptime") + class FormatUptime { + + @Test + @DisplayName("retourne minutes seulement pour moins d'1h") + void testMinutes() { + assertThat(SystemMetricsResponse.formatUptime(0)).isEqualTo("0m"); + assertThat(SystemMetricsResponse.formatUptime(30 * 1000)).isEqualTo("0m"); + assertThat(SystemMetricsResponse.formatUptime(60 * 1000)).isEqualTo("1m"); + assertThat(SystemMetricsResponse.formatUptime(45 * 60 * 1000)).isEqualTo("45m"); + } + + @Test + @DisplayName("retourne heures et minutes pour moins de 24h") + void testHours() { + assertThat(SystemMetricsResponse.formatUptime(60 * 60 * 1000)).isEqualTo("1h 0m"); + assertThat(SystemMetricsResponse.formatUptime(90 * 60 * 1000)).isEqualTo("1h 30m"); + assertThat(SystemMetricsResponse.formatUptime(5 * 60 * 60 * 1000 + 15 * 60 * 1000)) + .isEqualTo("5h 15m"); + assertThat(SystemMetricsResponse.formatUptime(23 * 60 * 60 * 1000 + 59 * 60 * 1000)) + .isEqualTo("23h 59m"); + } + + @Test + @DisplayName("retourne jours, heures et minutes pour 24h+") + void testDays() { + assertThat(SystemMetricsResponse.formatUptime(24 * 60 * 60 * 1000L)).isEqualTo("1j 0h 0m"); + assertThat(SystemMetricsResponse.formatUptime(36 * 60 * 60 * 1000L)).isEqualTo("1j 12h 0m"); + assertThat(SystemMetricsResponse.formatUptime( + 5L * 24 * 60 * 60 * 1000 + 3 * 60 * 60 * 1000 + 30 * 60 * 1000)) + .isEqualTo("5j 3h 30m"); + } + } + + @Nested + @DisplayName("Builder et Getters") + class BuilderGetters { + + @Test + @DisplayName("tous les champs builder sont accessibles") + void testBuilderGetters() { + LocalDateTime now = LocalDateTime.now(); + SystemMetricsResponse dto = SystemMetricsResponse.builder() + .cpuUsagePercent(23.5) + .availableProcessors(8) + .systemLoadAverage(1.5) + .totalMemoryBytes(8589934592L) + .usedMemoryBytes(5726623744L) + .freeMemoryBytes(2863311248L) + .maxMemoryBytes(17179869184L) + .memoryUsagePercent(67.0) + .totalMemoryFormatted("8.0 GB") + .usedMemoryFormatted("5.4 GB") + .freeMemoryFormatted("2.6 GB") + .totalDiskBytes(500000000000L) + .usedDiskBytes(225000000000L) + .freeDiskBytes(275000000000L) + .diskUsagePercent(45.0) + .totalDiskFormatted("500 GB") + .usedDiskFormatted("225 GB") + .freeDiskFormatted("275 GB") + .activeUsersCount(1247) + .totalUsersCount(5000) + .activeSessionsCount(500) + .failedLoginAttempts24h(23) + .apiRequestsLastHour(45892L) + .apiRequestsToday(1234567L) + .averageResponseTimeMs(127.5) + .totalRequestsCount(9876543L) + .dbConnectionPoolSize(10) + .dbActiveConnections(5) + .dbIdleConnections(5) + .dbHealthy(true) + .criticalErrorsCount(3) + .warningsCount(27) + .infoLogsCount(1247) + .debugLogsCount(5892) + .totalLogsCount(7169L) + .networkBytesReceivedPerSec(12500000.0) + .networkBytesSentPerSec(8200000.0) + .networkInFormatted("12.5 MB/s") + .networkOutFormatted("8.2 MB/s") + .systemStatus("OPERATIONAL") + .uptimeMillis(123456789L) + .uptimeFormatted("1j 10h 17m") + .startTime(now) + .currentTime(now) + .javaVersion("17.0.8") + .quarkusVersion("3.15.1") + .applicationVersion("1.0.0") + .lastBackup(now) + .nextScheduledMaintenance(now.plusDays(7)) + .lastMaintenance(now.minusDays(1)) + .apiBaseUrl("http://localhost:8085") + .authServerUrl("http://localhost:8180/realms/unionflow") + .cdnUrl("https://cdn.example.com") + .totalCacheSizeBytes(471859200L) + .totalCacheSizeFormatted("450 MB") + .totalCacheEntries(1500) + .build(); + + assertThat(dto.getCpuUsagePercent()).isEqualTo(23.5); + assertThat(dto.getAvailableProcessors()).isEqualTo(8); + assertThat(dto.getSystemLoadAverage()).isEqualTo(1.5); + assertThat(dto.getTotalMemoryBytes()).isEqualTo(8589934592L); + assertThat(dto.getUsedMemoryBytes()).isEqualTo(5726623744L); + assertThat(dto.getFreeMemoryBytes()).isEqualTo(2863311248L); + assertThat(dto.getMaxMemoryBytes()).isEqualTo(17179869184L); + assertThat(dto.getMemoryUsagePercent()).isEqualTo(67.0); + assertThat(dto.getTotalMemoryFormatted()).isEqualTo("8.0 GB"); + assertThat(dto.getUsedMemoryFormatted()).isEqualTo("5.4 GB"); + assertThat(dto.getFreeMemoryFormatted()).isEqualTo("2.6 GB"); + assertThat(dto.getTotalDiskBytes()).isEqualTo(500000000000L); + assertThat(dto.getUsedDiskBytes()).isEqualTo(225000000000L); + assertThat(dto.getFreeDiskBytes()).isEqualTo(275000000000L); + assertThat(dto.getDiskUsagePercent()).isEqualTo(45.0); + assertThat(dto.getTotalDiskFormatted()).isEqualTo("500 GB"); + assertThat(dto.getUsedDiskFormatted()).isEqualTo("225 GB"); + assertThat(dto.getFreeDiskFormatted()).isEqualTo("275 GB"); + assertThat(dto.getActiveUsersCount()).isEqualTo(1247); + assertThat(dto.getTotalUsersCount()).isEqualTo(5000); + assertThat(dto.getActiveSessionsCount()).isEqualTo(500); + assertThat(dto.getFailedLoginAttempts24h()).isEqualTo(23); + assertThat(dto.getApiRequestsLastHour()).isEqualTo(45892L); + assertThat(dto.getApiRequestsToday()).isEqualTo(1234567L); + assertThat(dto.getAverageResponseTimeMs()).isEqualTo(127.5); + assertThat(dto.getTotalRequestsCount()).isEqualTo(9876543L); + assertThat(dto.getDbConnectionPoolSize()).isEqualTo(10); + assertThat(dto.getDbActiveConnections()).isEqualTo(5); + assertThat(dto.getDbIdleConnections()).isEqualTo(5); + assertThat(dto.getDbHealthy()).isTrue(); + assertThat(dto.getCriticalErrorsCount()).isEqualTo(3); + assertThat(dto.getWarningsCount()).isEqualTo(27); + assertThat(dto.getInfoLogsCount()).isEqualTo(1247); + assertThat(dto.getDebugLogsCount()).isEqualTo(5892); + assertThat(dto.getTotalLogsCount()).isEqualTo(7169L); + assertThat(dto.getNetworkBytesReceivedPerSec()).isEqualTo(12500000.0); + assertThat(dto.getNetworkBytesSentPerSec()).isEqualTo(8200000.0); + assertThat(dto.getNetworkInFormatted()).isEqualTo("12.5 MB/s"); + assertThat(dto.getNetworkOutFormatted()).isEqualTo("8.2 MB/s"); + assertThat(dto.getSystemStatus()).isEqualTo("OPERATIONAL"); + assertThat(dto.getUptimeMillis()).isEqualTo(123456789L); + assertThat(dto.getUptimeFormatted()).isEqualTo("1j 10h 17m"); + assertThat(dto.getStartTime()).isEqualTo(now); + assertThat(dto.getCurrentTime()).isEqualTo(now); + assertThat(dto.getJavaVersion()).isEqualTo("17.0.8"); + assertThat(dto.getQuarkusVersion()).isEqualTo("3.15.1"); + assertThat(dto.getApplicationVersion()).isEqualTo("1.0.0"); + assertThat(dto.getLastBackup()).isEqualTo(now); + assertThat(dto.getNextScheduledMaintenance()).isEqualTo(now.plusDays(7)); + assertThat(dto.getLastMaintenance()).isEqualTo(now.minusDays(1)); + assertThat(dto.getApiBaseUrl()).isEqualTo("http://localhost:8085"); + assertThat(dto.getAuthServerUrl()).isEqualTo("http://localhost:8180/realms/unionflow"); + assertThat(dto.getCdnUrl()).isEqualTo("https://cdn.example.com"); + assertThat(dto.getTotalCacheSizeBytes()).isEqualTo(471859200L); + assertThat(dto.getTotalCacheSizeFormatted()).isEqualTo("450 MB"); + assertThat(dto.getTotalCacheEntries()).isEqualTo(1500); + } + + @Test + @DisplayName("builder avec valeurs nulles") + void testBuilderNulls() { + SystemMetricsResponse dto = SystemMetricsResponse.builder().build(); + assertThat(dto.getCpuUsagePercent()).isNull(); + assertThat(dto.getTotalMemoryBytes()).isNull(); + assertThat(dto.getSystemStatus()).isNull(); + } + + @Test + @DisplayName("setters fonctionnent correctement") + void testSetters() { + SystemMetricsResponse dto = new SystemMetricsResponse(); + dto.setCpuUsagePercent(50.0); + dto.setSystemStatus("DEGRADED"); + dto.setActiveUsersCount(100); + + assertThat(dto.getCpuUsagePercent()).isEqualTo(50.0); + assertThat(dto.getSystemStatus()).isEqualTo("DEGRADED"); + assertThat(dto.getActiveUsersCount()).isEqualTo(100); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/EnumsRefactoringTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/EnumsRefactoringTest.java new file mode 100644 index 0000000..e7da2b3 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/EnumsRefactoringTest.java @@ -0,0 +1,341 @@ +package dev.lions.unionflow.server.api.enums; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; +import dev.lions.unionflow.server.api.enums.abonnement.StatutFormule; +import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule; +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.enums.finance.StatutCotisation; +import dev.lions.unionflow.server.api.enums.membre.StatutMembre; +import dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation; +import dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation; +import dev.lions.unionflow.server.api.enums.paiement.StatutSession; +import dev.lions.unionflow.server.api.enums.paiement.StatutTraitement; +import dev.lions.unionflow.server.api.enums.paiement.TypeEvenement; +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.StatutEvaluation; +import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import dev.lions.unionflow.server.api.enums.solidarite.TypeEvaluation; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests de validation de la refactorisation des énumérations UnionFlow Vérifie + * que toutes les enums + * sont correctement séparées et fonctionnelles + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-10 + */ +@DisplayName("Tests de Refactorisation des Énumérations") +class EnumsRefactoringTest { + + @Test + @DisplayName("Test de base - toutes les enums sont accessibles") + void testEnumsAccessibles() { + assertThat(StatutMembre.ACTIF).isNotNull(); + assertThat(TypeOrganisation.LIONS_CLUB).isNotNull(); + } + + @Nested + @DisplayName("Énumérations Organisation") + class OrganisationEnumsTest { + + @Test + @DisplayName("TypeOrganisation - Tous les types disponibles") + void testTypeOrganisationTousLesTypes() { + // Given & When & Then + assertThat(TypeOrganisation.LIONS_CLUB.getLibelle()).isEqualTo("Lions Club"); + assertThat(TypeOrganisation.ASSOCIATION.getLibelle()).isEqualTo("Association"); + assertThat(TypeOrganisation.FEDERATION.getLibelle()).isEqualTo("Fédération / Union d'associations"); + assertThat(TypeOrganisation.ONG.getLibelle()).isEqualTo("ONG / Association humanitaire"); + assertThat(TypeOrganisation.SYNDICAT.getLibelle()).isEqualTo("Syndicat non partisan"); + assertThat(TypeOrganisation.MUTUELLE_SANTE.getLibelle()).isEqualTo("Mutuelle de santé"); + assertThat(TypeOrganisation.MUTUELLE_EPARGNE_CREDIT.getLibelle()).isEqualTo("Mutuelle d'épargne et de crédit"); + assertThat(TypeOrganisation.TONTINE.getLibelle()).isEqualTo("Tontine / épargne rotative"); + } + + @Test + @DisplayName("StatutOrganisation - Tous les statuts disponibles") + void testStatutOrganisationTousLesStatuts() { + // Given & When & Then + assertThat(StatutOrganisation.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutOrganisation.INACTIVE.getLibelle()).isEqualTo("Inactive"); + assertThat(StatutOrganisation.SUSPENDUE.getLibelle()).isEqualTo("Suspendue"); + assertThat(StatutOrganisation.EN_CREATION.getLibelle()).isEqualTo("En Création"); + assertThat(StatutOrganisation.DISSOUTE.getLibelle()).isEqualTo("Dissoute"); + } + } + + @Nested + @DisplayName("Énumérations Membre") + class MembreEnumsTest { + + @Test + @DisplayName("StatutMembre - Tous les statuts disponibles") + void testStatutMembreTousLesStatuts() { + // Given & When & Then + assertThat(StatutMembre.ACTIF.getLibelle()).isEqualTo("Actif"); + assertThat(StatutMembre.INACTIF.getLibelle()).contains("Inactif"); + assertThat(StatutMembre.SUSPENDU.getLibelle()).contains("Suspendu"); + assertThat(StatutMembre.RADIE.getLibelle()).contains("Radié"); + assertThat(StatutMembre.DEMISSIONNAIRE.getLibelle()).isNotNull(); + assertThat(StatutMembre.HONORAIRE.getLibelle()).isNotNull(); + assertThat(StatutMembre.DECEDE.getLibelle()).isNotNull(); + } + } + + @Nested + @DisplayName("Énumérations Paiement") + class PaiementEnumsTest { + + @Test + @DisplayName("StatutSession - Tous les statuts disponibles") + void testStatutSessionTousLesStatuts() { + // Given & When & Then + assertThat(StatutSession.PENDING.getLibelle()).isEqualTo("En attente"); + assertThat(StatutSession.COMPLETED.getLibelle()).isEqualTo("Complétée"); + assertThat(StatutSession.CANCELLED.getLibelle()).isEqualTo("Annulée"); + assertThat(StatutSession.EXPIRED.getLibelle()).isEqualTo("Expirée"); + assertThat(StatutSession.FAILED.getLibelle()).isEqualTo("Échouée"); + } + + @Test + @DisplayName("TypeEvenement - Méthode fromCode") + void testTypeEvenementFromCode() { + // Given & When & Then + assertThat(TypeEvenement.fromCode("checkout.complete")) + .isEqualTo(TypeEvenement.CHECKOUT_COMPLETE); + assertThat(TypeEvenement.fromCode("payout.failed")).isEqualTo(TypeEvenement.PAYOUT_FAILED); + assertThat(TypeEvenement.fromCode("balance.updated")) + .isEqualTo(TypeEvenement.BALANCE_UPDATED); + assertThat(TypeEvenement.fromCode("code_inexistant")).isNull(); + } + + @Test + @DisplayName("StatutTraitement - Tous les statuts disponibles") + void testStatutTraitementTousLesStatuts() { + // Given & When & Then + assertThat(StatutTraitement.RECU.getLibelle()).isEqualTo("Reçu"); + assertThat(StatutTraitement.EN_COURS.getLibelle()).isEqualTo("En cours de traitement"); + assertThat(StatutTraitement.TRAITE.getLibelle()).isEqualTo("Traité avec succès"); + assertThat(StatutTraitement.ECHEC.getLibelle()).isEqualTo("Échec de traitement"); + assertThat(StatutTraitement.IGNORE.getLibelle()).isEqualTo("Ignoré"); + } + } + + @Nested + @DisplayName("Énumérations Abonnement") + class AbonnementEnumsTest { + + @Test + @DisplayName("TypeFormule - Tous les types disponibles") + void testTypeFormuleTousLesTypes() { + // Given & When & Then + assertThat(TypeFormule.STARTER.getLibelle()).contains("Starter"); + assertThat(TypeFormule.STANDARD.getLibelle()).contains("Standard"); + assertThat(TypeFormule.PREMIUM.getLibelle()).contains("Premium"); + assertThat(TypeFormule.CRYSTAL.getLibelle()).contains("Crystal"); + } + + @Test + @DisplayName("StatutFormule - Tous les statuts disponibles") + void testStatutFormuleTousLesStatuts() { + // Given & When & Then + assertThat(StatutFormule.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutFormule.INACTIVE.getLibelle()).isEqualTo("Inactive"); + assertThat(StatutFormule.ARCHIVEE.getLibelle()).isEqualTo("Archivée"); + assertThat(StatutFormule.BIENTOT_DISPONIBLE.getLibelle()).isEqualTo("Bientôt Disponible"); + } + + @Test + @DisplayName("StatutAbonnement - Tous les statuts disponibles") + void testStatutAbonnementTousLesStatuts() { + // Given & When & Then + assertThat(StatutAbonnement.ACTIF.getLibelle()).isEqualTo("Actif"); + assertThat(StatutAbonnement.SUSPENDU.getLibelle()).isEqualTo("Suspendu"); + assertThat(StatutAbonnement.EXPIRE.getLibelle()).isEqualTo("Expiré"); + assertThat(StatutAbonnement.ANNULE.getLibelle()).isEqualTo("Annulé"); + assertThat(StatutAbonnement.EN_ATTENTE_PAIEMENT.getLibelle()) + .isEqualTo("En attente de paiement"); + } + } + + @Nested + @DisplayName("Énumérations Événement") + class EvenementEnumsTest { + + @Test + @DisplayName("TypeEvenementMetier - Tous les types disponibles") + void testTypeEvenementMetierTousLesTypes() { + // Given & When & Then + assertThat(TypeEvenementMetier.ASSEMBLEE_GENERALE.getLibelle()) + .isEqualTo("Assemblée Générale"); + assertThat(TypeEvenementMetier.FORMATION.getLibelle()).isEqualTo("Formation"); + assertThat(TypeEvenementMetier.ACTIVITE_SOCIALE.getLibelle()).isEqualTo("Activité Sociale"); + assertThat(TypeEvenementMetier.ACTION_CARITATIVE.getLibelle()).isEqualTo("Action Caritative"); + assertThat(TypeEvenementMetier.REUNION_BUREAU.getLibelle()).isEqualTo("Réunion de Bureau"); + assertThat(TypeEvenementMetier.CONFERENCE.getLibelle()).isEqualTo("Conférence"); + assertThat(TypeEvenementMetier.ATELIER.getLibelle()).isEqualTo("Atelier"); + assertThat(TypeEvenementMetier.CEREMONIE.getLibelle()).isEqualTo("Cérémonie"); + assertThat(TypeEvenementMetier.AUTRE.getLibelle()).isEqualTo("Autre"); + } + + @Test + @DisplayName("StatutEvenement - Tous les statuts disponibles") + void testStatutEvenementTousLesStatuts() { + // Given & When & Then + assertThat(StatutEvenement.PLANIFIE.getLibelle()).isEqualTo("Planifié"); + assertThat(StatutEvenement.CONFIRME.getLibelle()).isEqualTo("Confirmé"); + assertThat(StatutEvenement.EN_COURS.getLibelle()).isEqualTo("En cours"); + assertThat(StatutEvenement.TERMINE.getLibelle()).isEqualTo("Terminé"); + assertThat(StatutEvenement.ANNULE.getLibelle()).isEqualTo("Annulé"); + assertThat(StatutEvenement.REPORTE.getLibelle()).isEqualTo("Reporté"); + } + + @Test + @DisplayName("PrioriteEvenement - Toutes les priorités disponibles") + void testPrioriteEvenementToutesLesPriorites() { + // Given & When & Then + assertThat(PrioriteEvenement.CRITIQUE.getLibelle()).isEqualTo("Critique"); + assertThat(PrioriteEvenement.HAUTE.getLibelle()).isEqualTo("Haute"); + assertThat(PrioriteEvenement.NORMALE.getLibelle()).isEqualTo("Normale"); + assertThat(PrioriteEvenement.BASSE.getLibelle()).isEqualTo("Basse"); + } + } + + @Nested + @DisplayName("Énumérations Finance") + class FinanceEnumsTest { + + @Test + @DisplayName("StatutCotisation - Tous les statuts disponibles") + void testStatutCotisationTousLesStatuts() { + // Given & When & Then + assertThat(StatutCotisation.EN_ATTENTE.getLibelle()).isEqualTo("En attente"); + assertThat(StatutCotisation.PAYEE.getLibelle()).isEqualTo("Payée"); + assertThat(StatutCotisation.PARTIELLEMENT_PAYEE.getLibelle()) + .isEqualTo("Partiellement payée"); + assertThat(StatutCotisation.EN_RETARD.getLibelle()).isEqualTo("En retard"); + assertThat(StatutCotisation.ANNULEE.getLibelle()).isEqualTo("Annulée"); + assertThat(StatutCotisation.REMBOURSEE.getLibelle()).isEqualTo("Remboursée"); + } + } + + @Nested + @DisplayName("Énumérations Solidarité") + class SolidariteEnumsTest { + + @Test + @DisplayName("TypeAide - Tous les types disponibles") + void testTypeAideTousLesTypes() { + // Given & When & Then + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getLibelle()) + .isEqualTo("Aide financière urgente"); + assertThat(TypeAide.AIDE_FRAIS_MEDICAUX.getLibelle()).isEqualTo("Aide frais médicaux"); + assertThat(TypeAide.AIDE_FRAIS_SCOLARITE.getLibelle()).isEqualTo("Aide frais de scolarité"); + assertThat(TypeAide.HEBERGEMENT_URGENCE.getLibelle()).isEqualTo("Hébergement d'urgence"); + assertThat(TypeAide.AIDE_ALIMENTAIRE.getLibelle()).isEqualTo("Aide alimentaire"); + assertThat(TypeAide.CONSEIL_JURIDIQUE.getLibelle()).isEqualTo("Conseil juridique"); + assertThat(TypeAide.AIDE_RECHERCHE_EMPLOI.getLibelle()).isEqualTo("Aide recherche d'emploi"); + assertThat(TypeAide.SOUTIEN_PSYCHOLOGIQUE.getLibelle()).isEqualTo("Soutien psychologique"); + assertThat(TypeAide.AUTRE.getLibelle()).isEqualTo("Autre"); + } + + @Test + @DisplayName("StatutAide - Tous les statuts disponibles") + void testStatutAideTousLesStatuts() { + // Given & When & Then + assertThat(StatutAide.EN_ATTENTE.getLibelle()).isEqualTo("En attente"); + assertThat(StatutAide.EN_COURS_EVALUATION.getLibelle()).isEqualTo("En cours d'évaluation"); + assertThat(StatutAide.APPROUVEE.getLibelle()).isEqualTo("Approuvée"); + assertThat(StatutAide.REJETEE.getLibelle()).isEqualTo("Rejetée"); + assertThat(StatutAide.EN_COURS_VERSEMENT.getLibelle()).isEqualTo("En cours de versement"); + assertThat(StatutAide.VERSEE.getLibelle()).isEqualTo("Versée"); + assertThat(StatutAide.ANNULEE.getLibelle()).isEqualTo("Annulée"); + assertThat(StatutAide.SUSPENDUE.getLibelle()).isEqualTo("Suspendue"); + } + + @Test + @DisplayName("PrioriteAide - Toutes les priorités disponibles") + void testPrioriteAideToutesLesPriorites() { + // Given & When & Then + assertThat(PrioriteAide.CRITIQUE.getLibelle()).isEqualTo("Critique"); + assertThat(PrioriteAide.URGENTE.getLibelle()).isEqualTo("Urgente"); + assertThat(PrioriteAide.ELEVEE.getLibelle()).isEqualTo("Élevée"); + assertThat(PrioriteAide.NORMALE.getLibelle()).isEqualTo("Normale"); + assertThat(PrioriteAide.FAIBLE.getLibelle()).isEqualTo("Faible"); + } + + @Test + @DisplayName("StatutProposition - Tous les statuts disponibles") + void testStatutPropositionTousLesStatuts() { + assertThat(StatutProposition.BROUILLON.getLibelle()).isEqualTo("Brouillon"); + assertThat(StatutProposition.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutProposition.SUSPENDUE.getLibelle()).isEqualTo("Suspendue"); + assertThat(StatutProposition.TERMINEE.getLibelle()).isEqualTo("Terminée"); + assertThat(StatutProposition.ANNULEE.getLibelle()).isEqualTo("Annulée"); + } + + @Test + @DisplayName("StatutEvaluation - Tous les statuts disponibles") + void testStatutEvaluationTousLesStatuts() { + assertThat(StatutEvaluation.BROUILLON.getLibelle()).isEqualTo("Brouillon"); + assertThat(StatutEvaluation.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutEvaluation.MASQUEE.getLibelle()).isEqualTo("Masquée"); + } + + @Test + @DisplayName("TypeEvaluation - Tous les types disponibles") + void testTypeEvaluationTousLesTypes() { + assertThat(TypeEvaluation.SATISFACTION_BENEFICIAIRE.getLibelle()).isEqualTo("Satisfaction du bénéficiaire"); + assertThat(TypeEvaluation.EVALUATION_IMPACT.getLibelle()).isEqualTo("Évaluation d'impact"); + } + } + + @Nested + @DisplayName("Tests de Couverture Complète") + class CouvertureCompleteTest { + + @Test + @DisplayName("Vérification que toutes les enums ont des valeurs") + void testToutesLesEnumsOntDesValeurs() { + // Given & When & Then - Organisation + assertThat(TypeOrganisation.values()).isNotEmpty(); + assertThat(StatutOrganisation.values()).isNotEmpty(); + + // Given & When & Then - Membre + assertThat(StatutMembre.values()).isNotEmpty(); + + // Given & When & Then - Paiement + assertThat(StatutSession.values()).isNotEmpty(); + assertThat(TypeEvenement.values()).isNotEmpty(); + assertThat(StatutTraitement.values()).isNotEmpty(); + + // Given & When & Then - Abonnement + assertThat(TypeFormule.values()).isNotEmpty(); + assertThat(StatutFormule.values()).isNotEmpty(); + assertThat(StatutAbonnement.values()).isNotEmpty(); + + // Given & When & Then - Événement + assertThat(TypeEvenementMetier.values()).isNotEmpty(); + + // Given & When & Then - Finance + assertThat(StatutCotisation.values()).isNotEmpty(); + + // Given & When & Then - Solidarité + assertThat(TypeAide.values()).isNotEmpty(); + assertThat(StatutAide.values()).isNotEmpty(); + assertThat(StatutProposition.values()).isNotEmpty(); + assertThat(StatutEvaluation.values()).isNotEmpty(); + assertThat(TypeEvaluation.values()).isNotEmpty(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnementTest.java new file mode 100644 index 0000000..ea0f4ba --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutAbonnementTest.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutAbonnement") +class StatutAbonnementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutAbonnement.ACTIF).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutAbonnement[] values = StatutAbonnement.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutAbonnement.ACTIF, + StatutAbonnement.SUSPENDU, + StatutAbonnement.EXPIRE, + StatutAbonnement.ANNULE, + StatutAbonnement.EN_ATTENTE_PAIEMENT); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(StatutAbonnement.valueOf("ACTIF")).isEqualTo(StatutAbonnement.ACTIF); + assertThat(StatutAbonnement.valueOf("SUSPENDU")).isEqualTo(StatutAbonnement.SUSPENDU); + assertThat(StatutAbonnement.valueOf("EXPIRE")).isEqualTo(StatutAbonnement.EXPIRE); + assertThat(StatutAbonnement.valueOf("ANNULE")).isEqualTo(StatutAbonnement.ANNULE); + assertThat(StatutAbonnement.valueOf("EN_ATTENTE_PAIEMENT")).isEqualTo(StatutAbonnement.EN_ATTENTE_PAIEMENT); + + assertThatThrownBy(() -> StatutAbonnement.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutAbonnement.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutAbonnement statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "ACTIF, Actif", + "SUSPENDU, Suspendu", + "EXPIRE, Expiré", + "ANNULE, Annulé", + "EN_ATTENTE_PAIEMENT, En attente de paiement" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutAbonnement statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormuleTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormuleTest.java new file mode 100644 index 0000000..6f77fdc --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutFormuleTest.java @@ -0,0 +1,70 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutFormule") +class StatutFormuleTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutFormule.ACTIVE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutFormule[] values = StatutFormule.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + StatutFormule.ACTIVE, + StatutFormule.INACTIVE, + StatutFormule.ARCHIVEE, + StatutFormule.BIENTOT_DISPONIBLE); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(StatutFormule.valueOf("ACTIVE")).isEqualTo(StatutFormule.ACTIVE); + assertThat(StatutFormule.valueOf("INACTIVE")).isEqualTo(StatutFormule.INACTIVE); + assertThat(StatutFormule.valueOf("ARCHIVEE")).isEqualTo(StatutFormule.ARCHIVEE); + assertThat(StatutFormule.valueOf("BIENTOT_DISPONIBLE")).isEqualTo(StatutFormule.BIENTOT_DISPONIBLE); + + assertThatThrownBy(() -> StatutFormule.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutFormule.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutFormule statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "ACTIVE, Active", + "INACTIVE, Inactive", + "ARCHIVEE, Archivée", + "BIENTOT_DISPONIBLE, Bientôt Disponible" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutFormule statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscriptionTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscriptionTest.java new file mode 100644 index 0000000..8d114e0 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutSouscriptionTest.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutSouscription") +class StatutSouscriptionTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutSouscription.ACTIVE).isNotNull(); + assertThat(StatutSouscription.RESILIEE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutSouscription[] values = StatutSouscription.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + StatutSouscription.ACTIVE, + StatutSouscription.EXPIREE, + StatutSouscription.SUSPENDUE, + StatutSouscription.RESILIEE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutSouscription.valueOf("ACTIVE")).isEqualTo(StatutSouscription.ACTIVE); + assertThat(StatutSouscription.valueOf("EXPIREE")).isEqualTo(StatutSouscription.EXPIREE); + assertThat(StatutSouscription.valueOf("SUSPENDUE")).isEqualTo(StatutSouscription.SUSPENDUE); + assertThat(StatutSouscription.valueOf("RESILIEE")).isEqualTo(StatutSouscription.RESILIEE); + + assertThatThrownBy(() -> StatutSouscription.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutSouscription.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutSouscription statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(StatutSouscription.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutSouscription.EXPIREE.getLibelle()).isEqualTo("Expirée"); + assertThat(StatutSouscription.SUSPENDUE.getLibelle()).contains("Suspendue"); + assertThat(StatutSouscription.RESILIEE.getLibelle()).isEqualTo("Résiliée"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutSouscription.ACTIVE.name()).isEqualTo("ACTIVE"); + assertThat(StatutSouscription.RESILIEE.name()).isEqualTo("RESILIEE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormuleTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormuleTest.java new file mode 100644 index 0000000..b2e3c8c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeFormuleTest.java @@ -0,0 +1,56 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeFormule") +class TypeFormuleTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeFormule.STARTER).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeFormule[] values = TypeFormule.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + TypeFormule.STARTER, + TypeFormule.STANDARD, + TypeFormule.PREMIUM, + TypeFormule.CRYSTAL); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(TypeFormule.valueOf("STARTER")).isEqualTo(TypeFormule.STARTER); + assertThat(TypeFormule.valueOf("STANDARD")).isEqualTo(TypeFormule.STANDARD); + assertThat(TypeFormule.valueOf("PREMIUM")).isEqualTo(TypeFormule.PREMIUM); + assertThat(TypeFormule.valueOf("CRYSTAL")).isEqualTo(TypeFormule.CRYSTAL); + + assertThatThrownBy(() -> TypeFormule.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeFormule.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeFormule type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnementTest.java new file mode 100644 index 0000000..21a3de9 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypePeriodeAbonnementTest.java @@ -0,0 +1,67 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypePeriodeAbonnement") +class TypePeriodeAbonnementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypePeriodeAbonnement.MENSUEL).isNotNull(); + assertThat(TypePeriodeAbonnement.ANNUEL).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypePeriodeAbonnement[] values = TypePeriodeAbonnement.values(); + assertThat(values).hasSize(2); + assertThat(values).containsExactly( + TypePeriodeAbonnement.MENSUEL, + TypePeriodeAbonnement.ANNUEL); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypePeriodeAbonnement.valueOf("MENSUEL")).isEqualTo(TypePeriodeAbonnement.MENSUEL); + assertThat(TypePeriodeAbonnement.valueOf("ANNUEL")).isEqualTo(TypePeriodeAbonnement.ANNUEL); + + assertThatThrownBy(() -> TypePeriodeAbonnement.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypePeriodeAbonnement.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypePeriodeAbonnement type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(TypePeriodeAbonnement.MENSUEL.getLibelle()).isEqualTo("Mensuel"); + assertThat(TypePeriodeAbonnement.ANNUEL.getLibelle()).isEqualTo("Annuel — 2 mois offerts"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(TypePeriodeAbonnement.MENSUEL.name()).isEqualTo("MENSUEL"); + assertThat(TypePeriodeAbonnement.ANNUEL.name()).isEqualTo("ANNUEL"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/analytics/FormatExportTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/FormatExportTest.java new file mode 100644 index 0000000..8522cd7 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/FormatExportTest.java @@ -0,0 +1,277 @@ +package dev.lions.unionflow.server.api.enums.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +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.EnumSource; + +@DisplayName("Tests pour FormatExport") +class FormatExportTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(FormatExport.PDF).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + FormatExport[] values = FormatExport.values(); + assertThat(values).hasSize(11); + } + + @ParameterizedTest + @EnumSource(FormatExport.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(FormatExport format) { + assertThat(format.getLibelle()).isNotNull().isNotEmpty(); + assertThat(format.getExtension()).isNotNull().isNotEmpty(); + assertThat(format.getMimeType()).isNotNull().isNotEmpty(); + assertThat(format.getDescription()).isNotNull().isNotEmpty(); + } + } + + @Nested + @DisplayName("Tests supporteGraphiques") + class SupporteGraphiquesTests { + + @ParameterizedTest + @CsvSource({ + "PDF, true", + "WORD, true", + "EXCEL, true", + "CSV, false", + "JSON, false", + "XML, false", + "PNG, true", + "JPEG, true", + "SVG, true" + }) + @DisplayName("supporteGraphiques - valeurs exactes") + void testSupporteGraphiques(FormatExport format, Boolean expected) { + assertThat(format.supporteGraphiques()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests supporteGrandesQuantitesDonnees") + class SupporteGrandesQuantitesDonneesTests { + + @ParameterizedTest + @CsvSource({ + "PDF, true", + "WORD, false", + "EXCEL, true", + "CSV, true", + "JSON, true", + "XML, false", + "PNG, false", + "JPEG, false" + }) + @DisplayName("supporteGrandesQuantitesDonnees - valeurs exactes") + void testSupporteGrandesQuantitesDonnees(FormatExport format, Boolean expected) { + assertThat(format.supporteGrandesQuantitesDonnees()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isFormatExecutif") + class IsFormatExecutifTests { + + @ParameterizedTest + @CsvSource({ + "PDF, true", + "POWERPOINT, true", + "WORD, true", + "EXCEL, false", + "CSV, false", + "JSON, false" + }) + @DisplayName("isFormatExecutif - formats exécutifs") + void testIsFormatExecutif(FormatExport format, Boolean expected) { + assertThat(format.isFormatExecutif()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isFormatAnalyse") + class IsFormatAnalyseTests { + + @ParameterizedTest + @CsvSource({ + "EXCEL, true", + "CSV, true", + "JSON, true", + "PDF, false", + "WORD, false", + "XML, false" + }) + @DisplayName("isFormatAnalyse - formats d'analyse") + void testIsFormatAnalyse(FormatExport format, Boolean expected) { + assertThat(format.isFormatAnalyse()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isFormatWeb") + class IsFormatWebTests { + + @ParameterizedTest + @CsvSource({ + "HTML, true", + "PNG, true", + "SVG, true", + "JSON, true", + "PDF, false", + "EXCEL, false", + "WORD, false" + }) + @DisplayName("isFormatWeb - formats web") + void testIsFormatWeb(FormatExport format, Boolean expected) { + assertThat(format.isFormatWeb()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getIcone") + class GetIconeTests { + + @ParameterizedTest + @CsvSource({ + "PDF, picture_as_pdf", + "WORD, description", + "EXCEL, table_chart", + "CSV, grid_on", + "JSON, code", + "XML, code", + "PNG, image", + "JPEG, image", + "SVG, vector_image", + "POWERPOINT, slideshow", + "HTML, web" + }) + @DisplayName("getIcone - toutes les icônes") + void testGetIcone(FormatExport format, String expectedIcone) { + assertThat(format.getIcone()).isEqualTo(expectedIcone); + } + } + + @Nested + @DisplayName("Tests getCouleur") + class GetCouleurTests { + + @ParameterizedTest + @EnumSource(FormatExport.class) + @DisplayName("getCouleur - toutes les couleurs sont valides") + void testGetCouleur(FormatExport format) { + assertThat(format.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}"); + } + } + + @Nested + @DisplayName("Tests genererNomFichier") + class GenererNomFichierTests { + + @Test + @DisplayName("genererNomFichier - ajoute l'extension") + void testGenererNomFichier() { + assertThat(FormatExport.PDF.genererNomFichier("rapport")).isEqualTo("rapport.pdf"); + assertThat(FormatExport.EXCEL.genererNomFichier("donnees")).isEqualTo("donnees.xlsx"); + assertThat(FormatExport.CSV.genererNomFichier("export")).isEqualTo("export.csv"); + } + } + + @Nested + @DisplayName("Tests getTailleMaximaleRecommandee") + class GetTailleMaximaleRecommandeeTests { + + @ParameterizedTest + @CsvSource({ + "PDF, 50", + "WORD, 50", + "POWERPOINT, 50", + "EXCEL, 100", + "CSV, 200", + "JSON, 200", + "XML, 200", + "PNG, 10", + "JPEG, 10", + "SVG, 5", + "HTML, 5" + }) + @DisplayName("getTailleMaximaleRecommandee - toutes les tailles") + void testGetTailleMaximaleRecommandee(FormatExport format, int expectedMB) { + assertThat(format.getTailleMaximaleRecommandee()).isEqualTo(expectedMB); + } + } + + @Nested + @DisplayName("Tests necessiteTraitementSpecial") + class NecessiteTraitementSpecialTests { + + @ParameterizedTest + @CsvSource({ + "PDF, true", + "EXCEL, true", + "POWERPOINT, true", + "WORD, true", + "CSV, false", + "JSON, false", + "PNG, false" + }) + @DisplayName("necessiteTraitementSpecial - formats spéciaux") + void testNecessiteTraitementSpecial(FormatExport format, Boolean expected) { + assertThat(format.necessiteTraitementSpecial()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getFormatsRecommandes") + class GetFormatsRecommandesTests { + + @Test + @DisplayName("getFormatsRecommandes - executif") + void testGetFormatsRecommandesExecutif() { + FormatExport[] formats = FormatExport.getFormatsRecommandes("executif"); + assertThat(formats).containsExactly(FormatExport.PDF, FormatExport.POWERPOINT, FormatExport.WORD); + } + + @Test + @DisplayName("getFormatsRecommandes - analytique") + void testGetFormatsRecommandesAnalytique() { + FormatExport[] formats = FormatExport.getFormatsRecommandes("analytique"); + assertThat(formats).containsExactly(FormatExport.EXCEL, FormatExport.CSV, FormatExport.JSON, FormatExport.PDF); + } + + @Test + @DisplayName("getFormatsRecommandes - technique") + void testGetFormatsRecommandesTechnique() { + FormatExport[] formats = FormatExport.getFormatsRecommandes("technique"); + assertThat(formats).containsExactly(FormatExport.JSON, FormatExport.XML, FormatExport.CSV, FormatExport.HTML); + } + + @Test + @DisplayName("getFormatsRecommandes - partage") + void testGetFormatsRecommandesPartage() { + FormatExport[] formats = FormatExport.getFormatsRecommandes("partage"); + assertThat(formats).containsExactly(FormatExport.PDF, FormatExport.PNG, FormatExport.HTML); + } + + @Test + @DisplayName("getFormatsRecommandes - type inconnu") + void testGetFormatsRecommandesInconnu() { + FormatExport[] formats = FormatExport.getFormatsRecommandes("inconnu"); + assertThat(formats).containsExactly(FormatExport.PDF, FormatExport.EXCEL, FormatExport.CSV); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyseTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyseTest.java new file mode 100644 index 0000000..7aef60c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/PeriodeAnalyseTest.java @@ -0,0 +1,481 @@ +package dev.lions.unionflow.server.api.enums.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +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.EnumSource; + +@DisplayName("Tests pour PeriodeAnalyse") +class PeriodeAnalyseTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(PeriodeAnalyse.AUJOURD_HUI).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + PeriodeAnalyse[] values = PeriodeAnalyse.values(); + assertThat(values).hasSize(16); + } + + @ParameterizedTest + @EnumSource(PeriodeAnalyse.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(PeriodeAnalyse periode) { + assertThat(periode.getLibelle()).isNotNull().isNotEmpty(); + assertThat(periode.getCode()).isNotNull().isNotEmpty(); + assertThat(periode.getDuree()).isGreaterThanOrEqualTo(0); + assertThat(periode.getUnite()).isNotNull(); + } + } + + @Nested + @DisplayName("Tests getDateDebut") + class GetDateDebutTests { + + @Test + @DisplayName("getDateDebut - AUJOURD_HUI") + void testGetDateDebutAujourdHui() { + LocalDateTime debut = PeriodeAnalyse.AUJOURD_HUI.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(debut.getHour()).isEqualTo(0); + assertThat(debut.getMinute()).isEqualTo(0); + } + + @Test + @DisplayName("getDateDebut - HIER") + void testGetDateDebutHier() { + LocalDateTime debut = PeriodeAnalyse.HIER.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.toLocalDate()).isEqualTo(LocalDateTime.now().minusDays(1).toLocalDate()); + } + + @Test + @DisplayName("getDateDebut - CETTE_SEMAINE") + void testGetDateDebutCetteSemaine() { + LocalDateTime debut = PeriodeAnalyse.CETTE_SEMAINE.getDateDebut(); + assertThat(debut).isNotNull(); + // Doit être le lundi de cette semaine + assertThat(debut.getDayOfWeek().getValue()).isEqualTo(1); // Lundi + } + + @Test + @DisplayName("getDateDebut - SEMAINE_DERNIERE") + void testGetDateDebutSemaineDerniere() { + LocalDateTime debut = PeriodeAnalyse.SEMAINE_DERNIERE.getDateDebut(); + assertThat(debut).isNotNull(); + // Doit être le lundi de la semaine dernière + assertThat(debut.getDayOfWeek().getValue()).isEqualTo(1); // Lundi + } + + @Test + @DisplayName("getDateDebut - CE_MOIS") + void testGetDateDebutCeMois() { + LocalDateTime debut = PeriodeAnalyse.CE_MOIS.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.getDayOfMonth()).isEqualTo(1); + assertThat(debut.getMonth()).isEqualTo(LocalDateTime.now().getMonth()); + } + + @Test + @DisplayName("getDateDebut - MOIS_DERNIER") + void testGetDateDebutMoisDernier() { + LocalDateTime debut = PeriodeAnalyse.MOIS_DERNIER.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.getDayOfMonth()).isEqualTo(1); + assertThat(debut.getMonth()).isEqualTo(LocalDateTime.now().minusMonths(1).getMonth()); + } + + @Test + @DisplayName("getDateDebut - CETTE_ANNEE") + void testGetDateDebutCetteAnnee() { + LocalDateTime debut = PeriodeAnalyse.CETTE_ANNEE.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.getDayOfYear()).isEqualTo(1); + assertThat(debut.getYear()).isEqualTo(LocalDateTime.now().getYear()); + } + + @Test + @DisplayName("getDateDebut - ANNEE_DERNIERE") + void testGetDateDebutAnneeDerniere() { + LocalDateTime debut = PeriodeAnalyse.ANNEE_DERNIERE.getDateDebut(); + assertThat(debut).isNotNull(); + assertThat(debut.getDayOfYear()).isEqualTo(1); + assertThat(debut.getYear()).isEqualTo(LocalDateTime.now().getYear() - 1); + } + + @Test + @DisplayName("getDateDebut - DEPUIS_CREATION") + void testGetDateDebutDepuisCreation() { + LocalDateTime debut = PeriodeAnalyse.DEPUIS_CREATION.getDateDebut(); + assertThat(debut).isEqualTo(LocalDateTime.of(2020, 1, 1, 0, 0)); + } + + @Test + @DisplayName("getDateDebut - PERIODE_PERSONNALISEE") + void testGetDateDebutPeriodePersonnalisee() { + LocalDateTime avant = LocalDateTime.now(); + LocalDateTime debut = PeriodeAnalyse.PERIODE_PERSONNALISEE.getDateDebut(); + LocalDateTime apres = LocalDateTime.now(); + assertThat(debut).isBetween(avant.minusSeconds(1), apres.plusSeconds(1)); + } + + @ParameterizedTest + @CsvSource({ + "TROIS_DERNIERS_MOIS", + "SIX_DERNIERS_MOIS", + "SEPT_DERNIERS_JOURS", + "TRENTE_DERNIERS_JOURS", + "QUATRE_VINGT_DIX_DERNIERS_JOURS", + "DEUX_DERNIERES_ANNEES" + }) + @DisplayName("getDateDebut - périodes avec default") + void testGetDateDebutDefault(PeriodeAnalyse periode) { + LocalDateTime debut = periode.getDateDebut(); + assertThat(debut).isNotNull(); + // Vérifie que la date est dans le passé + assertThat(debut).isBefore(LocalDateTime.now()); + } + } + + @Nested + @DisplayName("Tests getDateFin") + class GetDateFinTests { + + @Test + @DisplayName("getDateFin - AUJOURD_HUI") + void testGetDateFinAujourdHui() { + LocalDateTime fin = PeriodeAnalyse.AUJOURD_HUI.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(fin.getHour()).isEqualTo(23); + assertThat(fin.getMinute()).isEqualTo(59); + } + + @Test + @DisplayName("getDateFin - HIER") + void testGetDateFinHier() { + LocalDateTime fin = PeriodeAnalyse.HIER.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().minusDays(1).toLocalDate()); + assertThat(fin.getHour()).isEqualTo(23); + } + + @Test + @DisplayName("getDateFin - CETTE_SEMAINE") + void testGetDateFinCetteSemaine() { + LocalDateTime fin = PeriodeAnalyse.CETTE_SEMAINE.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + } + + @Test + @DisplayName("getDateFin - SEMAINE_DERNIERE") + void testGetDateFinSemaineDerniere() { + LocalDateTime fin = PeriodeAnalyse.SEMAINE_DERNIERE.getDateFin(); + assertThat(fin).isNotNull(); + // Doit être le dimanche de la semaine dernière + assertThat(fin.getDayOfWeek().getValue()).isEqualTo(7); // Dimanche + } + + @Test + @DisplayName("getDateFin - CE_MOIS") + void testGetDateFinCeMois() { + LocalDateTime fin = PeriodeAnalyse.CE_MOIS.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + } + + @Test + @DisplayName("getDateFin - MOIS_DERNIER") + void testGetDateFinMoisDernier() { + LocalDateTime fin = PeriodeAnalyse.MOIS_DERNIER.getDateFin(); + assertThat(fin).isNotNull(); + // Doit être le dernier jour du mois dernier + assertThat(fin.getMonth()).isEqualTo(LocalDateTime.now().minusMonths(1).getMonth()); + } + + @Test + @DisplayName("getDateFin - CETTE_ANNEE") + void testGetDateFinCetteAnnee() { + LocalDateTime fin = PeriodeAnalyse.CETTE_ANNEE.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + } + + @Test + @DisplayName("getDateFin - ANNEE_DERNIERE") + void testGetDateFinAnneeDerniere() { + LocalDateTime fin = PeriodeAnalyse.ANNEE_DERNIERE.getDateFin(); + assertThat(fin).isNotNull(); + // Doit être le dernier jour de l'année dernière + assertThat(fin.getYear()).isEqualTo(LocalDateTime.now().getYear() - 1); + assertThat(fin.getMonthValue()).isEqualTo(12); + assertThat(fin.getDayOfMonth()).isEqualTo(31); + } + + @Test + @DisplayName("getDateFin - DEPUIS_CREATION") + void testGetDateFinDepuisCreation() { + LocalDateTime avant = LocalDateTime.now(); + LocalDateTime fin = PeriodeAnalyse.DEPUIS_CREATION.getDateFin(); + LocalDateTime apres = LocalDateTime.now(); + assertThat(fin).isBetween(avant.minusSeconds(1), apres.plusSeconds(1)); + } + + @Test + @DisplayName("getDateFin - PERIODE_PERSONNALISEE") + void testGetDateFinPeriodePersonnalisee() { + LocalDateTime avant = LocalDateTime.now(); + LocalDateTime fin = PeriodeAnalyse.PERIODE_PERSONNALISEE.getDateFin(); + LocalDateTime apres = LocalDateTime.now(); + assertThat(fin).isBetween(avant.minusSeconds(1), apres.plusSeconds(1)); + } + + @ParameterizedTest + @CsvSource({ + "TROIS_DERNIERS_MOIS", + "SIX_DERNIERS_MOIS", + "SEPT_DERNIERS_JOURS", + "TRENTE_DERNIERS_JOURS", + "QUATRE_VINGT_DIX_DERNIERS_JOURS", + "DEUX_DERNIERES_ANNEES" + }) + @DisplayName("getDateFin - périodes avec default") + void testGetDateFinDefault(PeriodeAnalyse periode) { + LocalDateTime fin = periode.getDateFin(); + assertThat(fin).isNotNull(); + assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(fin.getHour()).isEqualTo(23); + assertThat(fin.getMinute()).isEqualTo(59); + } + } + + @Nested + @DisplayName("Tests isPeriodeCourte") + class IsPeriodeCourteTests { + + @ParameterizedTest + @CsvSource({ + "AUJOURD_HUI, true", + "HIER, true", + "CETTE_SEMAINE, true", + "SEMAINE_DERNIERE, true", + "SEPT_DERNIERS_JOURS, true", + "CE_MOIS, false", + "MOIS_DERNIER, false", + "CETTE_ANNEE, false", + "DEPUIS_CREATION, false" + }) + @DisplayName("isPeriodeCourte - toutes les périodes") + void testIsPeriodeCourte(PeriodeAnalyse periode, boolean expected) { + assertThat(periode.isPeriodeCourte()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isPeriodeLongue") + class IsPeriodeLongueTests { + + @ParameterizedTest + @CsvSource({ + "CETTE_ANNEE, true", + "ANNEE_DERNIERE, true", + "DEUX_DERNIERES_ANNEES, true", + "DEPUIS_CREATION, true", + "AUJOURD_HUI, false", + "CE_MOIS, false", + "CETTE_SEMAINE, false" + }) + @DisplayName("isPeriodeLongue - toutes les périodes") + void testIsPeriodeLongue(PeriodeAnalyse periode, boolean expected) { + assertThat(periode.isPeriodeLongue()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isPersonnalisable") + class IsPersonnalisableTests { + + @Test + @DisplayName("isPersonnalisable - PERIODE_PERSONNALISEE") + void testIsPersonnalisableTrue() { + assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.isPersonnalisable()).isTrue(); + } + + @ParameterizedTest + @CsvSource({ + "AUJOURD_HUI", + "CE_MOIS", + "CETTE_ANNEE", + "DEPUIS_CREATION" + }) + @DisplayName("isPersonnalisable - autres périodes") + void testIsPersonnalisableFalse(PeriodeAnalyse periode) { + assertThat(periode.isPersonnalisable()).isFalse(); + } + } + + @Nested + @DisplayName("Tests getIntervalleRegroupement") + class GetIntervalleRegroupementTests { + + @ParameterizedTest + @CsvSource({ + "AUJOURD_HUI, heure", + "HIER, heure", + "CETTE_SEMAINE, jour", + "SEMAINE_DERNIERE, jour", + "SEPT_DERNIERS_JOURS, jour", + "CE_MOIS, jour", + "MOIS_DERNIER, jour", + "TRENTE_DERNIERS_JOURS, jour", + "TROIS_DERNIERS_MOIS, semaine", + "SIX_DERNIERS_MOIS, semaine", + "QUATRE_VINGT_DIX_DERNIERS_JOURS, semaine", + "CETTE_ANNEE, mois", + "ANNEE_DERNIERE, mois", + "DEUX_DERNIERES_ANNEES, mois", + "DEPUIS_CREATION, annee", + "PERIODE_PERSONNALISEE, jour" + }) + @DisplayName("getIntervalleRegroupement - toutes les périodes") + void testGetIntervalleRegroupement(PeriodeAnalyse periode, String expected) { + assertThat(periode.getIntervalleRegroupement()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getFormatDate") + class GetFormatDateTests { + + @ParameterizedTest + @CsvSource({ + "AUJOURD_HUI, HH:mm", + "HIER, HH:mm", + "CETTE_SEMAINE, dd/MM", + "SEMAINE_DERNIERE, dd/MM", + "SEPT_DERNIERS_JOURS, dd/MM", + "CE_MOIS, dd/MM", + "MOIS_DERNIER, dd/MM", + "TRENTE_DERNIERS_JOURS, dd/MM", + "TROIS_DERNIERS_MOIS, dd/MM", + "SIX_DERNIERS_MOIS, dd/MM", + "QUATRE_VINGT_DIX_DERNIERS_JOURS, dd/MM", + "CETTE_ANNEE, MM/yyyy", + "ANNEE_DERNIERE, MM/yyyy", + "DEUX_DERNIERES_ANNEES, yyyy", + "DEPUIS_CREATION, yyyy", + "PERIODE_PERSONNALISEE, dd/MM/yyyy" + }) + @DisplayName("getFormatDate - toutes les périodes") + void testGetFormatDate(PeriodeAnalyse periode, String expected) { + assertThat(periode.getFormatDate()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests valeurs spécifiques") + class ValeursSpecifiquesTests { + + @Test + @DisplayName("Test duree et unite pour toutes les périodes") + void testDureeEtUnite() { + assertThat(PeriodeAnalyse.AUJOURD_HUI.getDuree()).isEqualTo(1); + assertThat(PeriodeAnalyse.AUJOURD_HUI.getUnite()).isEqualTo(ChronoUnit.DAYS); + + assertThat(PeriodeAnalyse.CE_MOIS.getDuree()).isEqualTo(1); + assertThat(PeriodeAnalyse.CE_MOIS.getUnite()).isEqualTo(ChronoUnit.MONTHS); + + assertThat(PeriodeAnalyse.CETTE_ANNEE.getDuree()).isEqualTo(1); + assertThat(PeriodeAnalyse.CETTE_ANNEE.getUnite()).isEqualTo(ChronoUnit.YEARS); + + assertThat(PeriodeAnalyse.DEPUIS_CREATION.getDuree()).isEqualTo(0); + assertThat(PeriodeAnalyse.DEPUIS_CREATION.getUnite()).isEqualTo(ChronoUnit.FOREVER); + + assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getDuree()).isEqualTo(0); + assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getUnite()).isEqualTo(ChronoUnit.DAYS); + } + + @Test + @DisplayName("Test code pour toutes les périodes") + void testCode() { + assertThat(PeriodeAnalyse.AUJOURD_HUI.getCode()).isEqualTo("today"); + assertThat(PeriodeAnalyse.HIER.getCode()).isEqualTo("yesterday"); + assertThat(PeriodeAnalyse.CE_MOIS.getCode()).isEqualTo("this_month"); + assertThat(PeriodeAnalyse.DEPUIS_CREATION.getCode()).isEqualTo("since_creation"); + assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getCode()).isEqualTo("custom"); + } + + @Test + @DisplayName("Test toutes les périodes - couverture complète") + void testToutesLesPeriodes() { + // Test toutes les périodes pour s'assurer qu'elles sont toutes couvertes + for (PeriodeAnalyse periode : PeriodeAnalyse.values()) { + assertThat(periode.getDateDebut()).isNotNull(); + assertThat(periode.getDateFin()).isNotNull(); + assertThat(periode.getIntervalleRegroupement()).isNotNull().isNotEmpty(); + assertThat(periode.getFormatDate()).isNotNull().isNotEmpty(); + } + } + + @Test + @DisplayName("Test getDateDebut - cas default avec différentes unités") + void testGetDateDebutDefault() { + // Test que le default fonctionne pour toutes les périodes qui l'utilisent + LocalDateTime debutTroisMois = PeriodeAnalyse.TROIS_DERNIERS_MOIS.getDateDebut(); + LocalDateTime debutSixMois = PeriodeAnalyse.SIX_DERNIERS_MOIS.getDateDebut(); + LocalDateTime debutSeptJours = PeriodeAnalyse.SEPT_DERNIERS_JOURS.getDateDebut(); + LocalDateTime debutTrenteJours = PeriodeAnalyse.TRENTE_DERNIERS_JOURS.getDateDebut(); + LocalDateTime debutQuatreVingtDixJours = PeriodeAnalyse.QUATRE_VINGT_DIX_DERNIERS_JOURS.getDateDebut(); + LocalDateTime debutDeuxAnnees = PeriodeAnalyse.DEUX_DERNIERES_ANNEES.getDateDebut(); + + assertThat(debutTroisMois).isBefore(LocalDateTime.now()); + assertThat(debutSixMois).isBefore(LocalDateTime.now()); + assertThat(debutSeptJours).isBefore(LocalDateTime.now()); + assertThat(debutTrenteJours).isBefore(LocalDateTime.now()); + assertThat(debutQuatreVingtDixJours).isBefore(LocalDateTime.now()); + assertThat(debutDeuxAnnees).isBefore(LocalDateTime.now()); + + // Vérifie que les dates sont cohérentes avec les durées + // Plus la durée est grande, plus la date de début est dans le passé + assertThat(debutSixMois).isBefore(debutTroisMois); + assertThat(debutTrenteJours).isBefore(debutSeptJours); + } + + @Test + @DisplayName("Test getDateFin - cas default") + void testGetDateFinDefault() { + // Test que le default fonctionne pour toutes les périodes qui l'utilisent + LocalDateTime finTroisMois = PeriodeAnalyse.TROIS_DERNIERS_MOIS.getDateFin(); + LocalDateTime finSixMois = PeriodeAnalyse.SIX_DERNIERS_MOIS.getDateFin(); + LocalDateTime finSeptJours = PeriodeAnalyse.SEPT_DERNIERS_JOURS.getDateFin(); + LocalDateTime finTrenteJours = PeriodeAnalyse.TRENTE_DERNIERS_JOURS.getDateFin(); + LocalDateTime finQuatreVingtDixJours = PeriodeAnalyse.QUATRE_VINGT_DIX_DERNIERS_JOURS.getDateFin(); + LocalDateTime finDeuxAnnees = PeriodeAnalyse.DEUX_DERNIERES_ANNEES.getDateFin(); + + assertThat(finTroisMois.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(finTroisMois.getHour()).isEqualTo(23); + assertThat(finTroisMois.getMinute()).isEqualTo(59); + assertThat(finSixMois.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(finSeptJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(finTrenteJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(finQuatreVingtDixJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + assertThat(finDeuxAnnees.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate()); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetriqueTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetriqueTest.java new file mode 100644 index 0000000..a97c05f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/analytics/TypeMetriqueTest.java @@ -0,0 +1,246 @@ +package dev.lions.unionflow.server.api.enums.analytics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Field; +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.EnumSource; + +@DisplayName("Tests pour TypeMetrique") +class TypeMetriqueTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeMetrique.NOMBRE_MEMBRES_ACTIFS).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeMetrique[] values = TypeMetrique.values(); + assertThat(values).hasSize(35); + } + + @ParameterizedTest + @EnumSource(TypeMetrique.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(TypeMetrique metrique) { + assertThat(metrique.getLibelle()).isNotNull().isNotEmpty(); + assertThat(metrique.getCategorie()).isNotNull().isNotEmpty(); + assertThat(metrique.getTypeValeur()).isNotNull().isNotEmpty(); + } + } + + @Nested + @DisplayName("Tests isFinanciere") + class IsFinanciereTests { + + @ParameterizedTest + @CsvSource({ + "TOTAL_COTISATIONS_COLLECTEES, true", + "COTISATIONS_EN_ATTENTE, true", + "TAUX_RECOUVREMENT_COTISATIONS, true", + "NOMBRE_MEMBRES_ACTIFS, false", + "NOMBRE_EVENEMENTS_ORGANISES, false" + }) + @DisplayName("isFinanciere - métriques financières") + void testIsFinanciere(TypeMetrique metrique, Boolean expected) { + assertThat(metrique.isFinanciere()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isPourcentage") + class IsPourcentageTests { + + @ParameterizedTest + @CsvSource({ + "TAUX_CROISSANCE_MEMBRES, true", + "TAUX_RECOUVREMENT_COTISATIONS, true", + "TAUX_PARTICIPATION_EVENEMENTS, true", + "NOMBRE_MEMBRES_ACTIFS, false", + "TOTAL_COTISATIONS_COLLECTEES, false" + }) + @DisplayName("isPourcentage - métriques en pourcentage") + void testIsPourcentage(TypeMetrique metrique, Boolean expected) { + assertThat(metrique.isPourcentage()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isCompteur") + class IsCompteurTests { + + @ParameterizedTest + @CsvSource({ + "NOMBRE_MEMBRES_ACTIFS, true", + "NOMBRE_EVENEMENTS_ORGANISES, true", + "NOMBRE_DEMANDES_AIDE, true", + "TAUX_CROISSANCE_MEMBRES, false", + "TOTAL_COTISATIONS_COLLECTEES, false" + }) + @DisplayName("isCompteur - métriques compteurs") + void testIsCompteur(TypeMetrique metrique, Boolean expected) { + assertThat(metrique.isCompteur()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getUnite") + class GetUniteTests { + + @ParameterizedTest + @CsvSource({ + "TAUX_CROISSANCE_MEMBRES, %", + "TOTAL_COTISATIONS_COLLECTEES, XOF", + "DELAI_TRAITEMENT_DEMANDES, jours", + "UTILISATION_STOCKAGE, MB", + "FREQUENCE_UTILISATION_APP, /jour", + "NPS_SATISFACTION, /10", + "NOMBRE_MEMBRES_ACTIFS," + }) + @DisplayName("getUnite - toutes les unités") + void testGetUnite(TypeMetrique metrique, String expected) { + if (expected == null || expected.isEmpty()) { + assertThat(metrique.getUnite()).isEmpty(); + } else { + assertThat(metrique.getUnite()).isEqualTo(expected); + } + } + } + + @Nested + @DisplayName("Tests getIcone") + class GetIconeTests { + + @ParameterizedTest + @CsvSource({ + "NOMBRE_MEMBRES_ACTIFS, people", + "TOTAL_COTISATIONS_COLLECTEES, attach_money", + "NOMBRE_EVENEMENTS_ORGANISES, event", + "NOMBRE_DEMANDES_AIDE, favorite", + "TAUX_CONNEXION_MOBILE, trending_up", + "NOMBRE_ORGANISATIONS_ACTIVES, business", + "TEMPS_REPONSE_API, settings" + }) + @DisplayName("getIcone - toutes les icônes") + void testGetIcone(TypeMetrique metrique, String expected) { + assertThat(metrique.getIcone()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getCouleur") + class GetCouleurTests { + + @ParameterizedTest + @CsvSource({ + "NOMBRE_MEMBRES_ACTIFS, #2196F3", + "TOTAL_COTISATIONS_COLLECTEES, #4CAF50", + "NOMBRE_EVENEMENTS_ORGANISES, #FF9800", + "NOMBRE_DEMANDES_AIDE, #E91E63", + "TAUX_CONNEXION_MOBILE, #9C27B0", + "NOMBRE_ORGANISATIONS_ACTIVES, #607D8B", + "TEMPS_REPONSE_API, #795548" + }) + @DisplayName("getCouleur - toutes les couleurs") + void testGetCouleur(TypeMetrique metrique, String expected) { + assertThat(metrique.getCouleur()).isEqualTo(expected); + } + + @Test + @DisplayName("getUnite - cas default (typeValeur non reconnu)") + void testGetUniteDefault() { + // Test les métriques avec typeValeur qui tombent dans le default (count, average, distribution, trend) + assertThat(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite()).isEmpty(); // count -> default + assertThat(TypeMetrique.NOMBRE_MEMBRES_INACTIFS.getUnite()).isEmpty(); // count -> default + assertThat(TypeMetrique.MOYENNE_AGE_MEMBRES.getUnite()).isEmpty(); // average -> default + assertThat(TypeMetrique.MOYENNE_COTISATION_MEMBRE.getUnite()).isEmpty(); // average -> default + assertThat(TypeMetrique.MOYENNE_PARTICIPANTS_EVENEMENT.getUnite()).isEmpty(); // average -> default + assertThat(TypeMetrique.MOYENNE_MEMBRES_PAR_ORGANISATION.getUnite()).isEmpty(); // average -> default + assertThat(TypeMetrique.REPARTITION_GENRE_MEMBRES.getUnite()).isEmpty(); // distribution -> default + assertThat(TypeMetrique.EVOLUTION_REVENUS_MENSUELLE.getUnite()).isEmpty(); // trend -> default + + // Test aussi les autres métriques avec count + assertThat(TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES.getUnite()).isEmpty(); + assertThat(TypeMetrique.EVENEMENTS_ANNULES.getUnite()).isEmpty(); + assertThat(TypeMetrique.NOMBRE_DEMANDES_AIDE.getUnite()).isEmpty(); + assertThat(TypeMetrique.ACTIONS_UTILISATEUR_JOUR.getUnite()).isEmpty(); + assertThat(TypeMetrique.NOMBRE_ORGANISATIONS_ACTIVES.getUnite()).isEmpty(); + assertThat(TypeMetrique.ORGANISATIONS_PREMIUM.getUnite()).isEmpty(); + assertThat(TypeMetrique.NOMBRE_ERREURS_SYSTEME.getUnite()).isEmpty(); + } + + @Test + @DisplayName("Test toutes les métriques - couverture complète") + void testToutesLesMetriques() { + // Test toutes les métriques pour s'assurer qu'elles sont toutes couvertes + for (TypeMetrique metrique : TypeMetrique.values()) { + assertThat(metrique.getLibelle()).isNotNull().isNotEmpty(); + assertThat(metrique.getCategorie()).isNotNull().isNotEmpty(); + assertThat(metrique.getTypeValeur()).isNotNull().isNotEmpty(); + assertThat(metrique.getUnite()).isNotNull(); + assertThat(metrique.getIcone()).isNotNull().isNotEmpty(); + assertThat(metrique.getCouleur()).isNotNull().isNotEmpty(); + } + } + + @Test + @DisplayName("getIcone - cas default (catégorie non reconnue)") + void testGetIconeDefault() throws Exception { + // Utiliser la réflexion pour modifier temporairement la catégorie + // et tester le cas default + TypeMetrique metrique = TypeMetrique.NOMBRE_MEMBRES_ACTIFS; + String categorieOriginale = metrique.getCategorie(); + + try { + // Modifier la catégorie via réflexion pour une catégorie non reconnue + Field categorieField = TypeMetrique.class.getDeclaredField("categorie"); + categorieField.setAccessible(true); + categorieField.set(metrique, "categorie_inconnue"); + + // Tester que le default retourne "analytics" + assertThat(metrique.getIcone()).isEqualTo("analytics"); + } finally { + // Restaurer la valeur originale + Field categorieField = TypeMetrique.class.getDeclaredField("categorie"); + categorieField.setAccessible(true); + categorieField.set(metrique, categorieOriginale); + } + } + + @Test + @DisplayName("getCouleur - cas default (catégorie non reconnue)") + void testGetCouleurDefault() throws Exception { + // Utiliser la réflexion pour modifier temporairement la catégorie + // et tester le cas default + TypeMetrique metrique = TypeMetrique.NOMBRE_MEMBRES_ACTIFS; + String categorieOriginale = metrique.getCategorie(); + + try { + // Modifier la catégorie via réflexion pour une catégorie non reconnue + Field categorieField = TypeMetrique.class.getDeclaredField("categorie"); + categorieField.setAccessible(true); + categorieField.set(metrique, "categorie_inconnue"); + + // Tester que le default retourne "#757575" + assertThat(metrique.getCouleur()).isEqualTo("#757575"); + } finally { + // Restaurer la valeur originale + Field categorieField = TypeMetrique.class.getDeclaredField("categorie"); + categorieField.setAccessible(true); + categorieField.set(metrique, categorieOriginale); + } + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/audit/PorteeAuditTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/audit/PorteeAuditTest.java new file mode 100644 index 0000000..7b00a39 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/audit/PorteeAuditTest.java @@ -0,0 +1,67 @@ +package dev.lions.unionflow.server.api.enums.audit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour PorteeAudit") +class PorteeAuditTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(PorteeAudit.ORGANISATION).isNotNull(); + assertThat(PorteeAudit.PLATEFORME).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + PorteeAudit[] values = PorteeAudit.values(); + assertThat(values).hasSize(2); + assertThat(values).containsExactly( + PorteeAudit.ORGANISATION, + PorteeAudit.PLATEFORME); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(PorteeAudit.valueOf("ORGANISATION")).isEqualTo(PorteeAudit.ORGANISATION); + assertThat(PorteeAudit.valueOf("PLATEFORME")).isEqualTo(PorteeAudit.PLATEFORME); + + assertThatThrownBy(() -> PorteeAudit.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(PorteeAudit.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(PorteeAudit portee) { + assertThat(portee.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle contient libellé attendu") + void testGetLibelleContenu() { + assertThat(PorteeAudit.ORGANISATION.getLibelle()).contains("organisation"); + assertThat(PorteeAudit.PLATEFORME.getLibelle()).contains("Super Admin"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(PorteeAudit.ORGANISATION.name()).isEqualTo("ORGANISATION"); + assertThat(PorteeAudit.PLATEFORME.name()).isEqualTo("PLATEFORME"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptableTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptableTest.java new file mode 100644 index 0000000..286b3ed --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeCompteComptableTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.comptabilite; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeCompteComptable. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeCompteComptable") +class TypeCompteComptableTest { + + @Test + @DisplayName("Tous les types de compte comptable doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeCompteComptable type : TypeCompteComptable.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeCompteComptable.values().length).isGreaterThan(0); + for (TypeCompteComptable type : TypeCompteComptable.values()) { + assertThat(type).isNotNull(); + assertThat(type.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptableTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptableTest.java new file mode 100644 index 0000000..a7d9dc6 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/comptabilite/TypeJournalComptableTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.comptabilite; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeJournalComptable. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeJournalComptable") +class TypeJournalComptableTest { + + @Test + @DisplayName("Tous les types de journal comptable doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeJournalComptable type : TypeJournalComptable.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeJournalComptable.values().length).isGreaterThan(0); + for (TypeJournalComptable type : TypeJournalComptable.values()) { + assertThat(type).isNotNull(); + assertThat(type.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/document/TypeDocumentTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/document/TypeDocumentTest.java new file mode 100644 index 0000000..0622c6b --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/document/TypeDocumentTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.document; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeDocument. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeDocument") +class TypeDocumentTest { + + @Test + @DisplayName("Tous les types de document doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeDocument type : TypeDocument.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeDocument.values().length).isGreaterThan(0); + for (TypeDocument type : TypeDocument.values()) { + assertThat(type).isNotNull(); + assertThat(type.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenementTest.java new file mode 100644 index 0000000..7c6646d --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/PrioriteEvenementTest.java @@ -0,0 +1,207 @@ +package dev.lions.unionflow.server.api.enums.evenement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +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.EnumSource; + +@DisplayName("Tests pour PrioriteEvenement") +class PrioriteEvenementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(PrioriteEvenement.CRITIQUE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + PrioriteEvenement[] values = PrioriteEvenement.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + PrioriteEvenement.CRITIQUE, + PrioriteEvenement.HAUTE, + PrioriteEvenement.NORMALE, + PrioriteEvenement.BASSE); + } + + @ParameterizedTest + @EnumSource(PrioriteEvenement.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(PrioriteEvenement priorite) { + assertThat(priorite.getLibelle()).isNotNull().isNotEmpty(); + assertThat(priorite.getCode()).isNotNull().isNotEmpty(); + assertThat(priorite.getNiveau()).isBetween(1, 4); + assertThat(priorite.getDescription()).isNotNull().isNotEmpty(); + assertThat(priorite.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}"); + assertThat(priorite.getIcone()).isNotNull().isNotEmpty(); + } + } + + @Nested + @DisplayName("Tests isElevee") + class IsEleveeTests { + + @ParameterizedTest + @CsvSource({ + "CRITIQUE, true", + "HAUTE, true", + "NORMALE, false", + "BASSE, false" + }) + @DisplayName("isElevee - priorités élevées") + void testIsElevee(PrioriteEvenement priorite, Boolean expected) { + assertThat(priorite.isElevee()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isUrgente") + class IsUrgenteTests { + + @ParameterizedTest + @CsvSource({ + "CRITIQUE, true", + "HAUTE, true", + "NORMALE, false", + "BASSE, false" + }) + @DisplayName("isUrgente - priorités urgentes") + void testIsUrgente(PrioriteEvenement priorite, Boolean expected) { + assertThat(priorite.isUrgente()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isSuperieurA") + class IsSuperieurATests { + + @Test + @DisplayName("isSuperieurA - CRITIQUE supérieur à HAUTE") + void testIsSuperieurACritiqueVersHaute() { + assertThat(PrioriteEvenement.CRITIQUE.isSuperieurA(PrioriteEvenement.HAUTE)).isTrue(); + } + + @Test + @DisplayName("isSuperieurA - HAUTE supérieur à NORMALE") + void testIsSuperieurAHauteVersNormale() { + assertThat(PrioriteEvenement.HAUTE.isSuperieurA(PrioriteEvenement.NORMALE)).isTrue(); + } + + @Test + @DisplayName("isSuperieurA - NORMALE supérieur à BASSE") + void testIsSuperieurANormaleVersBasse() { + assertThat(PrioriteEvenement.NORMALE.isSuperieurA(PrioriteEvenement.BASSE)).isTrue(); + } + + @Test + @DisplayName("isSuperieurA - BASSE n'est pas supérieur à CRITIQUE") + void testIsSuperieurABasseVersCritique() { + assertThat(PrioriteEvenement.BASSE.isSuperieurA(PrioriteEvenement.CRITIQUE)).isFalse(); + } + + @Test + @DisplayName("isSuperieurA - même priorité retourne false") + void testIsSuperieurAMemePriorite() { + assertThat(PrioriteEvenement.CRITIQUE.isSuperieurA(PrioriteEvenement.CRITIQUE)).isFalse(); + assertThat(PrioriteEvenement.HAUTE.isSuperieurA(PrioriteEvenement.HAUTE)).isFalse(); + assertThat(PrioriteEvenement.NORMALE.isSuperieurA(PrioriteEvenement.NORMALE)).isFalse(); + assertThat(PrioriteEvenement.BASSE.isSuperieurA(PrioriteEvenement.BASSE)).isFalse(); + } + + @ParameterizedTest + @CsvSource({ + "CRITIQUE, HAUTE, true", + "CRITIQUE, NORMALE, true", + "CRITIQUE, BASSE, true", + "HAUTE, NORMALE, true", + "HAUTE, BASSE, true", + "NORMALE, BASSE, true", + "HAUTE, CRITIQUE, false", + "NORMALE, CRITIQUE, false", + "NORMALE, HAUTE, false", + "BASSE, CRITIQUE, false", + "BASSE, HAUTE, false", + "BASSE, NORMALE, false" + }) + @DisplayName("isSuperieurA - toutes les combinaisons") + void testIsSuperieurAToutesCombinaisons( + PrioriteEvenement priorite1, PrioriteEvenement priorite2, boolean expected) { + assertThat(priorite1.isSuperieurA(priorite2)).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isNotificationImmediate et isEscaladeAutomatique") + class IsNotificationImmediateEtEscaladeTests { + + @ParameterizedTest + @CsvSource({ + "CRITIQUE, true, true", + "HAUTE, true, false", + "NORMALE, false, false", + "BASSE, false, false" + }) + @DisplayName("isNotificationImmediate et isEscaladeAutomatique - toutes les priorités") + void testIsNotificationImmediateEtEscalade( + PrioriteEvenement priorite, boolean notificationImmediate, boolean escaladeAutomatique) { + assertThat(priorite.isNotificationImmediate()).isEqualTo(notificationImmediate); + assertThat(priorite.isEscaladeAutomatique()).isEqualTo(escaladeAutomatique); + } + } + + @Nested + @DisplayName("Tests méthodes statiques") + class MethodesStatiquesTests { + + @Test + @DisplayName("getPrioritesElevees - retourne les priorités élevées") + void testGetPrioritesElevees() { + List priorites = PrioriteEvenement.getPrioritesElevees(); + assertThat(priorites).hasSize(2); + assertThat(priorites).contains(PrioriteEvenement.CRITIQUE, PrioriteEvenement.HAUTE); + } + + @Test + @DisplayName("getPrioritesUrgentes - retourne les priorités urgentes") + void testGetPrioritesUrgentes() { + List priorites = PrioriteEvenement.getPrioritesUrgentes(); + assertThat(priorites).hasSize(2); + assertThat(priorites).contains(PrioriteEvenement.CRITIQUE, PrioriteEvenement.HAUTE); + } + + @ParameterizedTest + @CsvSource({ + "ASSEMBLEE_GENERALE, HAUTE", + "REUNION_BUREAU, HAUTE", + "ACTION_CARITATIVE, NORMALE", + "FORMATION, NORMALE", + "CONFERENCE, NORMALE", + "CEREMONIE, NORMALE", + "AUTRE, NORMALE", + "ACTIVITE_SOCIALE, BASSE", + "ATELIER, BASSE" + }) + @DisplayName("determinerPriorite - tous les types d'événements") + void testDeterminerPrioriteTousLesTypes( + TypeEvenementMetier type, PrioriteEvenement expected) { + assertThat(PrioriteEvenement.determinerPriorite(type)).isEqualTo(expected); + } + + @Test + @DisplayName("getDefaut - retourne NORMALE") + void testGetDefaut() { + assertThat(PrioriteEvenement.getDefaut()).isEqualTo(PrioriteEvenement.NORMALE); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenementTest.java new file mode 100644 index 0000000..af525b9 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/StatutEvenementTest.java @@ -0,0 +1,419 @@ +package dev.lions.unionflow.server.api.enums.evenement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +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.EnumSource; + +@DisplayName("Tests pour StatutEvenement") +class StatutEvenementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutEvenement.PLANIFIE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutEvenement[] values = StatutEvenement.values(); + assertThat(values).hasSize(6); + assertThat(values).containsExactly( + StatutEvenement.PLANIFIE, + StatutEvenement.CONFIRME, + StatutEvenement.EN_COURS, + StatutEvenement.TERMINE, + StatutEvenement.ANNULE, + StatutEvenement.REPORTE); + } + + @ParameterizedTest + @EnumSource(StatutEvenement.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(StatutEvenement statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + assertThat(statut.getCode()).isNotNull().isNotEmpty(); + assertThat(statut.getDescription()).isNotNull().isNotEmpty(); + assertThat(statut.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}"); + assertThat(statut.getIcone()).isNotNull().isNotEmpty(); + } + } + + @Nested + @DisplayName("Tests permetModification") + class PermetModificationTests { + + @ParameterizedTest + @CsvSource({ + "PLANIFIE, true", + "CONFIRME, true", + "REPORTE, true", + "EN_COURS, false", + "TERMINE, false", + "ANNULE, false" + }) + @DisplayName("permetModification - statuts modifiables") + void testPermetModification(StatutEvenement statut, Boolean expected) { + assertThat(statut.permetModification()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests permetAnnulation") + class PermetAnnulationTests { + + @ParameterizedTest + @CsvSource({ + "PLANIFIE, true", + "CONFIRME, true", + "EN_COURS, true", + "REPORTE, true", + "TERMINE, false", + "ANNULE, false" + }) + @DisplayName("permetAnnulation - statuts annulables") + void testPermetAnnulation(StatutEvenement statut, Boolean expected) { + assertThat(statut.permetAnnulation()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isEnCours") + class IsEnCoursTests { + + @ParameterizedTest + @CsvSource({ + "EN_COURS, true", + "PLANIFIE, false", + "TERMINE, false" + }) + @DisplayName("isEnCours - statut en cours") + void testIsEnCours(StatutEvenement statut, Boolean expected) { + assertThat(statut.isEnCours()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isSucces") + class IsSuccesTests { + + @ParameterizedTest + @CsvSource({ + "TERMINE, true", + "PLANIFIE, false", + "ANNULE, false" + }) + @DisplayName("isSucces - statut de succès") + void testIsSucces(StatutEvenement statut, Boolean expected) { + assertThat(statut.isSucces()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests peutTransitionnerVers") + class PeutTransitionnerVersTests { + + @Test + @DisplayName("peutTransitionnerVers - même statut retourne false") + void testPeutTransitionnerVersMemeStatut() { + assertThat(StatutEvenement.PLANIFIE.peutTransitionnerVers(StatutEvenement.PLANIFIE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - statut final vers autre statut (sauf REPORTE)") + void testPeutTransitionnerVersStatutFinal() { + assertThat(StatutEvenement.TERMINE.peutTransitionnerVers(StatutEvenement.PLANIFIE)) + .isFalse(); + assertThat(StatutEvenement.ANNULE.peutTransitionnerVers(StatutEvenement.PLANIFIE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - statut final vers REPORTE") + void testPeutTransitionnerVersStatutFinalVersReporte() { + // estFinal && nouveauStatut != REPORTE retourne false + // Mais TERMINE.estFinal = true, donc la condition estFinal && nouveauStatut != REPORTE + // est true && true = true, donc retourne false + assertThat(StatutEvenement.TERMINE.peutTransitionnerVers(StatutEvenement.REPORTE)) + .isFalse(); + assertThat(StatutEvenement.ANNULE.peutTransitionnerVers(StatutEvenement.REPORTE)) + .isFalse(); + + // Test avec nouveauStatut == REPORTE : estFinal && nouveauStatut != REPORTE + // = true && false = false, donc on continue vers le switch + // Mais TERMINE et ANNULE ne sont pas dans le switch, donc default -> false + // Donc le résultat est toujours false + // Mais testons aussi avec un statut non-final vers REPORTE pour couvrir la branche + assertThat(StatutEvenement.PLANIFIE.peutTransitionnerVers(StatutEvenement.REPORTE)) + .isTrue(); + } + + @Test + @DisplayName("peutTransitionnerVers - REPORTE vers CONFIRME (non géré dans switch)") + void testPeutTransitionnerVersReporteVersConfirme() { + // REPORTE -> CONFIRME n'est pas dans le switch, donc retourne false (default) + assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.CONFIRME)) + .isFalse(); + assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.EN_COURS)) + .isFalse(); + assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.TERMINE)) + .isFalse(); + } + + @ParameterizedTest + @CsvSource({ + "PLANIFIE, CONFIRME, true", + "PLANIFIE, ANNULE, true", + "PLANIFIE, REPORTE, true", + "PLANIFIE, EN_COURS, false", + "PLANIFIE, TERMINE, false", + "CONFIRME, EN_COURS, true", + "CONFIRME, ANNULE, true", + "CONFIRME, REPORTE, true", + "CONFIRME, PLANIFIE, false", + "CONFIRME, TERMINE, false", + "EN_COURS, TERMINE, true", + "EN_COURS, ANNULE, true", + "EN_COURS, PLANIFIE, false", + "EN_COURS, CONFIRME, false", + "REPORTE, PLANIFIE, true", + "REPORTE, ANNULE, true", + "REPORTE, CONFIRME, false", + "REPORTE, EN_COURS, false", + "TERMINE, ANNULE, false", + "ANNULE, TERMINE, false" + }) + @DisplayName("peutTransitionnerVers - toutes les transitions") + void testPeutTransitionnerVersToutesTransitions( + StatutEvenement source, StatutEvenement cible, boolean expected) { + assertThat(source.peutTransitionnerVers(cible)).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getNiveauPriorite") + class GetNiveauPrioriteTests { + + @ParameterizedTest + @CsvSource({ + "EN_COURS, 1", + "CONFIRME, 2", + "PLANIFIE, 3", + "REPORTE, 4", + "TERMINE, 5", + "ANNULE, 6" + }) + @DisplayName("getNiveauPriorite - tous les niveaux") + void testGetNiveauPriorite(StatutEvenement statut, int expected) { + assertThat(statut.getNiveauPriorite()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests méthodes statiques") + class MethodesStatiquesTests { + + @Test + @DisplayName("getStatutsFinaux - retourne les statuts finaux") + void testGetStatutsFinaux() { + List statuts = StatutEvenement.getStatutsFinaux(); + assertThat(statuts).isNotEmpty(); + assertThat(statuts).contains(StatutEvenement.TERMINE, StatutEvenement.ANNULE); + } + + @Test + @DisplayName("getStatutsEchec - retourne les statuts d'échec") + void testGetStatutsEchec() { + List statuts = StatutEvenement.getStatutsEchec(); + assertThat(statuts).isNotEmpty(); + assertThat(statuts).contains(StatutEvenement.ANNULE); + } + + @Test + @DisplayName("getStatutsActifs - retourne les statuts actifs") + void testGetStatutsActifs() { + StatutEvenement[] statuts = StatutEvenement.getStatutsActifs(); + assertThat(statuts).hasSize(4); + assertThat(statuts).contains( + StatutEvenement.PLANIFIE, + StatutEvenement.CONFIRME, + StatutEvenement.EN_COURS, + StatutEvenement.REPORTE); + } + + @Test + @DisplayName("fromCode - trouve le statut par code") + void testFromCode() { + assertThat(StatutEvenement.fromCode("planned")).isEqualTo(StatutEvenement.PLANIFIE); + assertThat(StatutEvenement.fromCode("completed")).isEqualTo(StatutEvenement.TERMINE); + } + + @Test + @DisplayName("fromCode - code inexistant retourne null") + void testFromCodeInexistant() { + assertThat(StatutEvenement.fromCode("inexistant")).isNull(); + } + + @Test + @DisplayName("fromCode - code null retourne null") + void testFromCodeNull() { + assertThat(StatutEvenement.fromCode(null)).isNull(); + } + + @Test + @DisplayName("fromCode - code vide retourne null") + void testFromCodeVide() { + assertThat(StatutEvenement.fromCode("")).isNull(); + } + + @Test + @DisplayName("fromCode - code avec whitespace retourne null") + void testFromCodeWhitespace() { + assertThat(StatutEvenement.fromCode(" ")).isNull(); + } + + @Test + @DisplayName("fromCode - tous les codes") + void testFromCodeTousLesCodes() { + assertThat(StatutEvenement.fromCode("planned")).isEqualTo(StatutEvenement.PLANIFIE); + assertThat(StatutEvenement.fromCode("confirmed")).isEqualTo(StatutEvenement.CONFIRME); + assertThat(StatutEvenement.fromCode("ongoing")).isEqualTo(StatutEvenement.EN_COURS); + assertThat(StatutEvenement.fromCode("completed")).isEqualTo(StatutEvenement.TERMINE); + assertThat(StatutEvenement.fromCode("cancelled")).isEqualTo(StatutEvenement.ANNULE); + assertThat(StatutEvenement.fromCode("postponed")).isEqualTo(StatutEvenement.REPORTE); + } + + @Test + @DisplayName("fromLibelle - trouve le statut par libellé") + void testFromLibelle() { + assertThat(StatutEvenement.fromLibelle("Planifié")).isEqualTo(StatutEvenement.PLANIFIE); + assertThat(StatutEvenement.fromLibelle("Terminé")).isEqualTo(StatutEvenement.TERMINE); + } + + @Test + @DisplayName("fromLibelle - libellé inexistant retourne null") + void testFromLibelleInexistant() { + assertThat(StatutEvenement.fromLibelle("Inexistant")).isNull(); + } + + @Test + @DisplayName("fromLibelle - libellé null retourne null") + void testFromLibelleNull() { + assertThat(StatutEvenement.fromLibelle(null)).isNull(); + } + + @Test + @DisplayName("fromLibelle - libellé vide retourne null") + void testFromLibelleVide() { + assertThat(StatutEvenement.fromLibelle("")).isNull(); + } + + @Test + @DisplayName("fromLibelle - libellé avec whitespace retourne null") + void testFromLibelleWhitespace() { + assertThat(StatutEvenement.fromLibelle(" ")).isNull(); + } + + @Test + @DisplayName("fromLibelle - tous les libellés") + void testFromLibelleTousLesLibelles() { + assertThat(StatutEvenement.fromLibelle("Planifié")).isEqualTo(StatutEvenement.PLANIFIE); + assertThat(StatutEvenement.fromLibelle("Confirmé")).isEqualTo(StatutEvenement.CONFIRME); + assertThat(StatutEvenement.fromLibelle("En cours")).isEqualTo(StatutEvenement.EN_COURS); + assertThat(StatutEvenement.fromLibelle("Terminé")).isEqualTo(StatutEvenement.TERMINE); + assertThat(StatutEvenement.fromLibelle("Annulé")).isEqualTo(StatutEvenement.ANNULE); + assertThat(StatutEvenement.fromLibelle("Reporté")).isEqualTo(StatutEvenement.REPORTE); + } + } + + @Nested + @DisplayName("Tests getTransitionsPossibles") + class GetTransitionsPossiblesTests { + + @Test + @DisplayName("getTransitionsPossibles - PLANIFIE") + void testGetTransitionsPossiblesPlanifie() { + StatutEvenement[] transitions = StatutEvenement.PLANIFIE.getTransitionsPossibles(); + assertThat(transitions).containsExactly( + StatutEvenement.CONFIRME, + StatutEvenement.ANNULE, + StatutEvenement.REPORTE); + } + + @Test + @DisplayName("getTransitionsPossibles - CONFIRME") + void testGetTransitionsPossiblesConfirme() { + StatutEvenement[] transitions = StatutEvenement.CONFIRME.getTransitionsPossibles(); + assertThat(transitions).containsExactly( + StatutEvenement.EN_COURS, + StatutEvenement.ANNULE, + StatutEvenement.REPORTE); + } + + @Test + @DisplayName("getTransitionsPossibles - EN_COURS") + void testGetTransitionsPossiblesEnCours() { + StatutEvenement[] transitions = StatutEvenement.EN_COURS.getTransitionsPossibles(); + assertThat(transitions).containsExactly( + StatutEvenement.TERMINE, + StatutEvenement.ANNULE); + } + + @Test + @DisplayName("getTransitionsPossibles - REPORTE") + void testGetTransitionsPossiblesReporte() { + StatutEvenement[] transitions = StatutEvenement.REPORTE.getTransitionsPossibles(); + // Note: selon le code, REPORTE peut transitionner vers PLANIFIE, CONFIRME ou ANNULE + // Mais peutTransitionnerVers ne permet que PLANIFIE ou ANNULE + assertThat(transitions).containsExactly( + StatutEvenement.PLANIFIE, + StatutEvenement.CONFIRME, + StatutEvenement.ANNULE); + } + + @Test + @DisplayName("getTransitionsPossibles - TERMINE") + void testGetTransitionsPossiblesTermine() { + StatutEvenement[] transitions = StatutEvenement.TERMINE.getTransitionsPossibles(); + assertThat(transitions).isEmpty(); + } + + @Test + @DisplayName("getTransitionsPossibles - ANNULE") + void testGetTransitionsPossiblesAnnule() { + StatutEvenement[] transitions = StatutEvenement.ANNULE.getTransitionsPossibles(); + assertThat(transitions).isEmpty(); + } + } + + @Nested + @DisplayName("Tests isEstFinal et isEstEchec") + class IsEstFinalEtEchecTests { + + @ParameterizedTest + @CsvSource({ + "PLANIFIE, false, false", + "CONFIRME, false, false", + "EN_COURS, false, false", + "TERMINE, true, false", + "ANNULE, true, true", + "REPORTE, false, false" + }) + @DisplayName("isEstFinal et isEstEchec - tous les statuts") + void testIsEstFinalEtEchec(StatutEvenement statut, boolean estFinal, boolean estEchec) { + assertThat(statut.isEstFinal()).isEqualTo(estFinal); + assertThat(statut.isEstEchec()).isEqualTo(estEchec); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetierTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetierTest.java new file mode 100644 index 0000000..ee3cab5 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/evenement/TypeEvenementMetierTest.java @@ -0,0 +1,68 @@ +package dev.lions.unionflow.server.api.enums.evenement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeEvenementMetier") +class TypeEvenementMetierTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeEvenementMetier.ASSEMBLEE_GENERALE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeEvenementMetier[] values = TypeEvenementMetier.values(); + assertThat(values).hasSize(9); + assertThat(values).containsExactly( + TypeEvenementMetier.ASSEMBLEE_GENERALE, + TypeEvenementMetier.FORMATION, + TypeEvenementMetier.ACTIVITE_SOCIALE, + TypeEvenementMetier.ACTION_CARITATIVE, + TypeEvenementMetier.REUNION_BUREAU, + TypeEvenementMetier.CONFERENCE, + TypeEvenementMetier.ATELIER, + TypeEvenementMetier.CEREMONIE, + TypeEvenementMetier.AUTRE); + } + + @ParameterizedTest + @EnumSource(TypeEvenementMetier.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeEvenementMetier type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "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" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(TypeEvenementMetier type, String expectedLibelle) { + assertThat(type.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisationTest.java new file mode 100644 index 0000000..8894a96 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/finance/StatutCotisationTest.java @@ -0,0 +1,76 @@ +package dev.lions.unionflow.server.api.enums.finance; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutCotisation") +class StatutCotisationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutCotisation.EN_ATTENTE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutCotisation[] values = StatutCotisation.values(); + assertThat(values).hasSize(6); + assertThat(values).containsExactly( + StatutCotisation.EN_ATTENTE, + StatutCotisation.PAYEE, + StatutCotisation.PARTIELLEMENT_PAYEE, + StatutCotisation.EN_RETARD, + StatutCotisation.ANNULEE, + StatutCotisation.REMBOURSEE); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(StatutCotisation.valueOf("EN_ATTENTE")).isEqualTo(StatutCotisation.EN_ATTENTE); + assertThat(StatutCotisation.valueOf("PAYEE")).isEqualTo(StatutCotisation.PAYEE); + assertThat(StatutCotisation.valueOf("PARTIELLEMENT_PAYEE")).isEqualTo(StatutCotisation.PARTIELLEMENT_PAYEE); + assertThat(StatutCotisation.valueOf("EN_RETARD")).isEqualTo(StatutCotisation.EN_RETARD); + assertThat(StatutCotisation.valueOf("ANNULEE")).isEqualTo(StatutCotisation.ANNULEE); + assertThat(StatutCotisation.valueOf("REMBOURSEE")).isEqualTo(StatutCotisation.REMBOURSEE); + + assertThatThrownBy(() -> StatutCotisation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutCotisation.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutCotisation statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "EN_ATTENTE, En attente", + "PAYEE, Payée", + "PARTIELLEMENT_PAYEE, Partiellement payée", + "EN_RETARD, En retard", + "ANNULEE, Annulée", + "REMBOURSEE, Remboursée" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutCotisation statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/membre/LienParenteTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/membre/LienParenteTest.java new file mode 100644 index 0000000..f1f4490 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/membre/LienParenteTest.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.api.enums.membre; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour LienParente") +class LienParenteTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(LienParente.CONJOINT).isNotNull(); + assertThat(LienParente.ENFANT).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + LienParente[] values = LienParente.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + LienParente.CONJOINT, + LienParente.ENFANT, + LienParente.PARENT, + LienParente.AUTRE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(LienParente.valueOf("CONJOINT")).isEqualTo(LienParente.CONJOINT); + assertThat(LienParente.valueOf("ENFANT")).isEqualTo(LienParente.ENFANT); + assertThat(LienParente.valueOf("PARENT")).isEqualTo(LienParente.PARENT); + assertThat(LienParente.valueOf("AUTRE")).isEqualTo(LienParente.AUTRE); + + assertThatThrownBy(() -> LienParente.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(LienParente.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(LienParente lien) { + assertThat(lien.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(LienParente.CONJOINT.getLibelle()).isEqualTo("Conjoint(e)"); + assertThat(LienParente.ENFANT.getLibelle()).isEqualTo("Enfant"); + assertThat(LienParente.PARENT.getLibelle()).isEqualTo("Parent"); + assertThat(LienParente.AUTRE.getLibelle()).isEqualTo("Autre"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(LienParente.CONJOINT.name()).isEqualTo("CONJOINT"); + assertThat(LienParente.AUTRE.name()).isEqualTo("AUTRE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateurTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateurTest.java new file mode 100644 index 0000000..135f3a1 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutCompteUtilisateurTest.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.api.enums.membre; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutCompteUtilisateur") +class StatutCompteUtilisateurTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutCompteUtilisateur.ACTIF).isNotNull(); + assertThat(StatutCompteUtilisateur.EN_ATTENTE_VALIDATION).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutCompteUtilisateur[] values = StatutCompteUtilisateur.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + StatutCompteUtilisateur.EN_ATTENTE_VALIDATION, + StatutCompteUtilisateur.ACTIF, + StatutCompteUtilisateur.SUSPENDU, + StatutCompteUtilisateur.DESACTIVE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutCompteUtilisateur.valueOf("EN_ATTENTE_VALIDATION")).isEqualTo(StatutCompteUtilisateur.EN_ATTENTE_VALIDATION); + assertThat(StatutCompteUtilisateur.valueOf("ACTIF")).isEqualTo(StatutCompteUtilisateur.ACTIF); + assertThat(StatutCompteUtilisateur.valueOf("SUSPENDU")).isEqualTo(StatutCompteUtilisateur.SUSPENDU); + assertThat(StatutCompteUtilisateur.valueOf("DESACTIVE")).isEqualTo(StatutCompteUtilisateur.DESACTIVE); + + assertThatThrownBy(() -> StatutCompteUtilisateur.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutCompteUtilisateur.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutCompteUtilisateur statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle contient libellé attendu") + void testGetLibelleContenu() { + assertThat(StatutCompteUtilisateur.EN_ATTENTE_VALIDATION.getLibelle()).contains("En attente"); + assertThat(StatutCompteUtilisateur.ACTIF.getLibelle()).contains("Actif"); + assertThat(StatutCompteUtilisateur.SUSPENDU.getLibelle()).contains("Suspendu"); + assertThat(StatutCompteUtilisateur.DESACTIVE.getLibelle()).contains("Désactivé"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutCompteUtilisateur.ACTIF.name()).isEqualTo("ACTIF"); + assertThat(StatutCompteUtilisateur.EN_ATTENTE_VALIDATION.name()).isEqualTo("EN_ATTENTE_VALIDATION"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutMembreTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutMembreTest.java new file mode 100644 index 0000000..cf066e3 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/membre/StatutMembreTest.java @@ -0,0 +1,84 @@ +package dev.lions.unionflow.server.api.enums.membre; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutMembre") +class StatutMembreTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutMembre.ACTIF).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutMembre[] values = StatutMembre.values(); + assertThat(values).hasSize(8); + assertThat(values).contains( + StatutMembre.EN_ATTENTE_VALIDATION, + StatutMembre.ACTIF, + StatutMembre.INACTIF, + StatutMembre.SUSPENDU, + StatutMembre.DEMISSIONNAIRE, + StatutMembre.RADIE, + StatutMembre.HONORAIRE, + StatutMembre.DECEDE); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(StatutMembre.valueOf("ACTIF")).isEqualTo(StatutMembre.ACTIF); + assertThat(StatutMembre.valueOf("INACTIF")).isEqualTo(StatutMembre.INACTIF); + assertThat(StatutMembre.valueOf("SUSPENDU")).isEqualTo(StatutMembre.SUSPENDU); + assertThat(StatutMembre.valueOf("RADIE")).isEqualTo(StatutMembre.RADIE); + + assertThatThrownBy(() -> StatutMembre.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutMembre.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutMembre statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(StatutMembre.ACTIF.getLibelle()).isEqualTo("Actif"); + assertThat(StatutMembre.INACTIF.getLibelle()).contains("Inactif"); + assertThat(StatutMembre.SUSPENDU.getLibelle()).contains("Suspendu"); + assertThat(StatutMembre.RADIE.getLibelle()).contains("Radié"); + } + + @Test + @DisplayName("Test ordinal et name") + void testOrdinalEtName() { + assertThat(StatutMembre.EN_ATTENTE_VALIDATION.name()).isEqualTo("EN_ATTENTE_VALIDATION"); + assertThat(StatutMembre.ACTIF.name()).isEqualTo("ACTIF"); + assertThat(StatutMembre.INACTIF.name()).isEqualTo("INACTIF"); + assertThat(StatutMembre.SUSPENDU.name()).isEqualTo("SUSPENDU"); + assertThat(StatutMembre.RADIE.name()).isEqualTo("RADIE"); + assertThat(StatutMembre.DEMISSIONNAIRE.name()).isEqualTo("DEMISSIONNAIRE"); + assertThat(StatutMembre.HONORAIRE.name()).isEqualTo("HONORAIRE"); + assertThat(StatutMembre.DECEDE.name()).isEqualTo("DECEDE"); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/module/TypeModuleTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/module/TypeModuleTest.java new file mode 100644 index 0000000..35c8d9b --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/module/TypeModuleTest.java @@ -0,0 +1,84 @@ +package dev.lions.unionflow.server.api.enums.module; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeModule") +class TypeModuleTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeModule.COTISATIONS).isNotNull(); + assertThat(TypeModule.CULTES_RELIGIEUX).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeModule[] values = TypeModule.values(); + assertThat(values).hasSize(16); + assertThat(values).contains( + TypeModule.COTISATIONS, + TypeModule.EVENEMENTS, + TypeModule.SOLIDARITE, + TypeModule.COMPTABILITE, + TypeModule.DOCUMENTS, + TypeModule.NOTIFICATIONS, + TypeModule.CREDIT_EPARGNE, + TypeModule.AYANTS_DROIT, + TypeModule.TONTINE, + TypeModule.ONG_PROJETS, + TypeModule.COOP_AGRICOLE, + TypeModule.VOTE_INTERNE, + TypeModule.COLLECTE_FONDS, + TypeModule.REGISTRE_PROFESSIONNEL, + TypeModule.CULTES_RELIGIEUX, + TypeModule.GOUVERNANCE_MULTI); + } + + @Test + @DisplayName("Test valueOf - constantes principales") + void testValueOf() { + assertThat(TypeModule.valueOf("COTISATIONS")).isEqualTo(TypeModule.COTISATIONS); + assertThat(TypeModule.valueOf("SOLIDARITE")).isEqualTo(TypeModule.SOLIDARITE); + assertThat(TypeModule.valueOf("CULTES_RELIGIEUX")).isEqualTo(TypeModule.CULTES_RELIGIEUX); + assertThat(TypeModule.valueOf("GOUVERNANCE_MULTI")).isEqualTo(TypeModule.GOUVERNANCE_MULTI); + + assertThatThrownBy(() -> TypeModule.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeModule.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeModule type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes - échantillon") + void testGetLibelleValeursExactes() { + assertThat(TypeModule.COTISATIONS.getLibelle()).isEqualTo("Gestion des cotisations"); + assertThat(TypeModule.CULTES_RELIGIEUX.getLibelle()).isEqualTo("Gestion cultes et dîmes"); + assertThat(TypeModule.SOLIDARITE.getLibelle()).isEqualTo("Fonds de solidarité"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(TypeModule.COTISATIONS.name()).isEqualTo("COTISATIONS"); + assertThat(TypeModule.CULTES_RELIGIEUX.name()).isEqualTo("CULTES_RELIGIEUX"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/notification/CanalNotificationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/notification/CanalNotificationTest.java new file mode 100644 index 0000000..d437a1e --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/notification/CanalNotificationTest.java @@ -0,0 +1,424 @@ +package dev.lions.unionflow.server.api.enums.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +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.EnumSource; + +@DisplayName("Tests pour CanalNotification") +class CanalNotificationTest { + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + CanalNotification[] values = CanalNotification.values(); + assertThat(values).hasSize(16); + } + + @ParameterizedTest + @EnumSource(CanalNotification.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(CanalNotification canal) { + assertThat(canal.getId()).isNotNull().isNotEmpty(); + assertThat(canal.getNom()).isNotNull().isNotEmpty(); + assertThat(canal.getDescription()).isNotNull().isNotEmpty(); + assertThat(canal.getImportance()).isBetween(1, 5); + assertThat(canal.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}"); + assertThat(canal.getTypeDefaut()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, true", + "ERROR_CHANNEL, true", + "WARNING_CHANNEL, true", + "IMPORTANT_CHANNEL, true", + "REMINDER_CHANNEL, true", + "SUCCESS_CHANNEL, false", + "CELEBRATION_CHANNEL, false", + "DEFAULT_CHANNEL, false" + }) + @DisplayName("isSonActive - canaux avec son activé") + void testIsSonActive(CanalNotification canal, Boolean expected) { + assertThat(canal.isSonActive()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, true", + "ERROR_CHANNEL, true", + "WARNING_CHANNEL, true", + "IMPORTANT_CHANNEL, true", + "REMINDER_CHANNEL, true", + "SUCCESS_CHANNEL, false", + "CELEBRATION_CHANNEL, false", + "DEFAULT_CHANNEL, false" + }) + @DisplayName("isVibrationActive - canaux avec vibration activée") + void testIsVibrationActive(CanalNotification canal, Boolean expected) { + assertThat(canal.isVibrationActive()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, true", + "ERROR_CHANNEL, false", + "WARNING_CHANNEL, false", + "IMPORTANT_CHANNEL, false", + "REMINDER_CHANNEL, false", + "SUCCESS_CHANNEL, false", + "CELEBRATION_CHANNEL, false", + "DEFAULT_CHANNEL, false" + }) + @DisplayName("isLumiereLED - canaux avec lumière LED activée") + void testIsLumiereLED(CanalNotification canal, Boolean expected) { + assertThat(canal.isLumiereLED()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isCritique") + class IsCritiqueTests { + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, true", + "ERROR_CHANNEL, true", + "WARNING_CHANNEL, true", + "IMPORTANT_CHANNEL, true", + "REMINDER_CHANNEL, false", + "SUCCESS_CHANNEL, false", + "DEFAULT_CHANNEL, false" + }) + @DisplayName("isCritique - importance >= 4") + void testIsCritique(CanalNotification canal, Boolean expected) { + assertThat(canal.isCritique()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isSilencieux") + class IsSilencieuxTests { + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, false", + "EVENTS_CHANNEL, false", + "SUCCESS_CHANNEL, true", + "CELEBRATION_CHANNEL, true", + "DEFAULT_CHANNEL, true" + }) + @DisplayName("isSilencieux - pas de son ni vibration") + void testIsSilencieux(CanalNotification canal, Boolean expected) { + assertThat(canal.isSilencieux()).isEqualTo(expected); + } + + @Test + @DisplayName("isSilencieux true quand son et vibration désactivés") + void testIsSilencieuxTrue() { + assertThat(CanalNotification.SUCCESS_CHANNEL.isSilencieux()).isTrue(); + assertThat(CanalNotification.DEFAULT_CHANNEL.isSilencieux()).isTrue(); + } + + @Test + @DisplayName("isSilencieux false quand son ou vibration activé") + void testIsSilencieuxFalse() { + assertThat(CanalNotification.URGENT_CHANNEL.isSilencieux()).isFalse(); + assertThat(CanalNotification.REMINDER_CHANNEL.isSilencieux()).isFalse(); + } + } + + @Nested + @DisplayName("Tests getImportanceAndroid") + class GetImportanceAndroidTests { + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, 5, IMPORTANCE_MAX", + "ERROR_CHANNEL, 4, IMPORTANCE_HIGH", + "WARNING_CHANNEL, 4, IMPORTANCE_HIGH", + "IMPORTANT_CHANNEL, 4, IMPORTANCE_HIGH", + "REMINDER_CHANNEL, 3, IMPORTANCE_DEFAULT", + "SUCCESS_CHANNEL, 2, IMPORTANCE_LOW", + "CELEBRATION_CHANNEL, 2, IMPORTANCE_LOW", + "DEFAULT_CHANNEL, 2, IMPORTANCE_LOW" + }) + @DisplayName("getImportanceAndroid - mapping correct") + void testGetImportanceAndroid(CanalNotification canal, int importance, String expected) { + assertThat(canal.getImportance()).isEqualTo(importance); + assertThat(canal.getImportanceAndroid()).isEqualTo(expected); + } + + @Test + @DisplayName("getImportanceAndroid - importance 1 = IMPORTANCE_MIN") + void testGetImportanceAndroidMin() { + // Tester avec la réflexion pour créer un canal avec importance 1 + try { + java.lang.reflect.Field importanceField = CanalNotification.class.getDeclaredField("importance"); + importanceField.setAccessible(true); + int importanceOriginale = (int) importanceField.get(CanalNotification.DEFAULT_CHANNEL); + + // Modifier temporairement l'importance à 1 + importanceField.setInt(CanalNotification.DEFAULT_CHANNEL, 1); + + // Maintenant importance = 1 devrait retourner IMPORTANCE_MIN + assertThat(CanalNotification.DEFAULT_CHANNEL.getImportanceAndroid()).isEqualTo("IMPORTANCE_MIN"); + + // Restaurer l'importance originale + importanceField.setInt(CanalNotification.DEFAULT_CHANNEL, importanceOriginale); + } catch (Exception e) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible"); + } + } + + @Test + @DisplayName("getImportanceAndroid - importance invalide = IMPORTANCE_DEFAULT") + void testGetImportanceAndroidDefault() { + // Tester avec la réflexion pour créer un canal avec importance invalide + try { + java.lang.reflect.Field importanceField = CanalNotification.class.getDeclaredField("importance"); + importanceField.setAccessible(true); + int importanceOriginale = (int) importanceField.get(CanalNotification.DEFAULT_CHANNEL); + + // Modifier temporairement l'importance à 99 (invalide) + importanceField.setInt(CanalNotification.DEFAULT_CHANNEL, 99); + + // Maintenant importance = 99 devrait retourner IMPORTANCE_DEFAULT (default) + assertThat(CanalNotification.DEFAULT_CHANNEL.getImportanceAndroid()).isEqualTo("IMPORTANCE_DEFAULT"); + + // Restaurer l'importance originale + importanceField.setInt(CanalNotification.DEFAULT_CHANNEL, importanceOriginale); + } catch (Exception e) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible"); + } + } + } + + @Nested + @DisplayName("Tests getPrioriteIOS") + class GetPrioriteIOSTests { + + @Test + @DisplayName("getPrioriteIOS - importance >= 4 = high") + void testGetPrioriteIOSHigh() { + CanalNotification canal = CanalNotification.URGENT_CHANNEL; + assertThat(canal.getPrioriteIOS()).isEqualTo("high"); + } + + @Test + @DisplayName("getPrioriteIOS - importance < 4 = low") + void testGetPrioriteIOSLow() { + CanalNotification canal = CanalNotification.DEFAULT_CHANNEL; + assertThat(canal.getPrioriteIOS()).isEqualTo("low"); + } + } + + @Nested + @DisplayName("Tests getSonDefaut") + class GetSonDefautTests { + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, urgent_sound.mp3", + "ERROR_CHANNEL, error_sound.mp3", + "WARNING_CHANNEL, warning_sound.mp3", + "IMPORTANT_CHANNEL, important_sound.mp3", + "REMINDER_CHANNEL, reminder_sound.mp3", + "SUCCESS_CHANNEL, success_sound.mp3", + "CELEBRATION_CHANNEL, celebration_sound.mp3", + "DEFAULT_CHANNEL, default" + }) + @DisplayName("getSonDefaut - tous les canaux") + void testGetSonDefaut(CanalNotification canal, String expected) { + assertThat(canal.getSonDefaut()).isEqualTo(expected); + } + + @Test + @DisplayName("getSonDefaut - canaux catégoriels (default)") + void testGetSonDefautCanauxCategories() { + assertThat(CanalNotification.EVENTS_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.PAYMENTS_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.SOLIDARITY_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.MEMBERS_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.ORGANIZATION_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.SYSTEM_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.MESSAGES_CHANNEL.getSonDefaut()).isEqualTo("default"); + assertThat(CanalNotification.LOCATION_CHANNEL.getSonDefaut()).isEqualTo("default"); + } + } + + @Nested + @DisplayName("Tests getPatternVibration") + class GetPatternVibrationTests { + + @Test + @DisplayName("getPatternVibration - URGENT_CHANNEL") + void testGetPatternVibrationUrgent() { + long[] pattern = CanalNotification.URGENT_CHANNEL.getPatternVibration(); + assertThat(pattern).isNotEmpty(); + assertThat(pattern[0]).isEqualTo(0); + assertThat(pattern).hasSize(6); // Triple vibration + } + + @Test + @DisplayName("getPatternVibration - ERROR_CHANNEL") + void testGetPatternVibrationError() { + long[] pattern = CanalNotification.ERROR_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(4); // Double vibration longue + assertThat(pattern[0]).isEqualTo(0); + } + + @Test + @DisplayName("getPatternVibration - WARNING_CHANNEL") + void testGetPatternVibrationWarning() { + long[] pattern = CanalNotification.WARNING_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(4); // Double vibration courte + assertThat(pattern[0]).isEqualTo(0); + } + + @Test + @DisplayName("getPatternVibration - IMPORTANT_CHANNEL") + void testGetPatternVibrationImportant() { + long[] pattern = CanalNotification.IMPORTANT_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(4); // Vibration distinctive + assertThat(pattern[0]).isEqualTo(0); + } + + @Test + @DisplayName("getPatternVibration - REMINDER_CHANNEL") + void testGetPatternVibrationReminder() { + long[] pattern = CanalNotification.REMINDER_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(4); // Vibration douce + assertThat(pattern[0]).isEqualTo(0); + } + + @Test + @DisplayName("getPatternVibration - autres canaux (default)") + void testGetPatternVibrationDefault() { + long[] pattern = CanalNotification.SUCCESS_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(2); // Vibration simple + assertThat(pattern[0]).isEqualTo(0); + + pattern = CanalNotification.DEFAULT_CHANNEL.getPatternVibration(); + assertThat(pattern).hasSize(2); // Vibration simple + assertThat(pattern[0]).isEqualTo(0); + } + + @ParameterizedTest + @EnumSource(CanalNotification.class) + @DisplayName("getPatternVibration - tous les canaux retournent un pattern valide") + void testGetPatternVibrationTousCanaux(CanalNotification canal) { + long[] pattern = canal.getPatternVibration(); + assertThat(pattern).isNotNull(); + assertThat(pattern.length).isGreaterThan(0); + assertThat(pattern[0]).isEqualTo(0); + } + } + + @Nested + @DisplayName("Tests peutEtreDesactive") + class PeutEtreDesactiveTests { + + @Test + @DisplayName("peutEtreDesactive - URGENT_CHANNEL ne peut pas être désactivé") + void testPeutEtreDesactiveUrgent() { + assertThat(CanalNotification.URGENT_CHANNEL.peutEtreDesactive()).isFalse(); + } + + @Test + @DisplayName("peutEtreDesactive - ERROR_CHANNEL ne peut pas être désactivé") + void testPeutEtreDesactiveError() { + assertThat(CanalNotification.ERROR_CHANNEL.peutEtreDesactive()).isFalse(); + } + + @Test + @DisplayName("peutEtreDesactive - autres canaux peuvent être désactivés") + void testPeutEtreDesactiveAutres() { + assertThat(CanalNotification.DEFAULT_CHANNEL.peutEtreDesactive()).isTrue(); + assertThat(CanalNotification.SUCCESS_CHANNEL.peutEtreDesactive()).isTrue(); + } + } + + @Nested + @DisplayName("Tests getDureeVieMs") + class GetDureeVieMsTests { + + @ParameterizedTest + @CsvSource({ + "URGENT_CHANNEL, 3600000", + "ERROR_CHANNEL, 86400000", + "WARNING_CHANNEL, 172800000", + "IMPORTANT_CHANNEL, 259200000", + "REMINDER_CHANNEL, 86400000", + "SUCCESS_CHANNEL, 172800000", + "CELEBRATION_CHANNEL, 259200000", + "DEFAULT_CHANNEL, 604800000" + }) + @DisplayName("getDureeVieMs - toutes les durées") + void testGetDureeVieMs(CanalNotification canal, long expectedMs) { + assertThat(canal.getDureeVieMs()).isEqualTo(expectedMs); + } + + @Test + @DisplayName("getDureeVieMs - canaux catégoriels (default = 1 semaine)") + void testGetDureeVieMsCanauxCategories() { + assertThat(CanalNotification.EVENTS_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.PAYMENTS_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.SOLIDARITY_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.MEMBERS_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.ORGANIZATION_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.SYSTEM_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.MESSAGES_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + assertThat(CanalNotification.LOCATION_CHANNEL.getDureeVieMs()).isEqualTo(604800000L); + } + } + + @Nested + @DisplayName("Tests méthodes statiques") + class MethodesStatiquesTests { + + @Test + @DisplayName("parId - trouve le canal") + void testParId() { + assertThat(CanalNotification.parId("urgent")).isEqualTo(CanalNotification.URGENT_CHANNEL); + assertThat(CanalNotification.parId("default")).isEqualTo(CanalNotification.DEFAULT_CHANNEL); + } + + @Test + @DisplayName("parId - id inexistant retourne DEFAULT_CHANNEL") + void testParIdInexistant() { + assertThat(CanalNotification.parId("inexistant")).isEqualTo(CanalNotification.DEFAULT_CHANNEL); + } + + @Test + @DisplayName("getCanauxCritiques - retourne les canaux critiques") + void testGetCanauxCritiques() { + CanalNotification[] canaux = CanalNotification.getCanauxCritiques(); + assertThat(canaux).hasSize(4); + assertThat(canaux).containsExactly( + CanalNotification.URGENT_CHANNEL, + CanalNotification.ERROR_CHANNEL, + CanalNotification.WARNING_CHANNEL, + CanalNotification.IMPORTANT_CHANNEL); + } + + @Test + @DisplayName("getCanauxCategories - retourne les canaux catégoriels") + void testGetCanauxCategories() { + CanalNotification[] canaux = CanalNotification.getCanauxCategories(); + assertThat(canaux).hasSize(8); + assertThat(canaux).contains( + CanalNotification.EVENTS_CHANNEL, + CanalNotification.PAYMENTS_CHANNEL, + CanalNotification.SOLIDARITY_CHANNEL); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotificationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotificationTest.java new file mode 100644 index 0000000..cdd3450 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotificationTest.java @@ -0,0 +1,62 @@ +package dev.lions.unionflow.server.api.enums.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour PrioriteNotification. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests PrioriteNotification") +class PrioriteNotificationTest { + + @Test + @DisplayName("Toutes les priorités doivent avoir un libellé") + void testToutesLesPrioritesOntLibelle() { + for (PrioriteNotification priorite : PrioriteNotification.values()) { + assertThat(priorite.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("CRITIQUE doit avoir le libellé correct") + void testCritique() { + assertThat(PrioriteNotification.CRITIQUE.getLibelle()).isEqualTo("Critique"); + } + + @Test + @DisplayName("HAUTE doit avoir le libellé correct") + void testHaute() { + assertThat(PrioriteNotification.HAUTE.getLibelle()).isEqualTo("Haute"); + } + + @Test + @DisplayName("NORMALE doit avoir le libellé correct") + void testNormale() { + assertThat(PrioriteNotification.NORMALE.getLibelle()).isEqualTo("Normale"); + } + + @Test + @DisplayName("BASSE doit avoir le libellé correct") + void testBasse() { + assertThat(PrioriteNotification.BASSE.getLibelle()).isEqualTo("Basse"); + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(PrioriteNotification.values()).hasSize(4); + assertThat(PrioriteNotification.values()) + .containsExactlyInAnyOrder( + PrioriteNotification.CRITIQUE, + PrioriteNotification.HAUTE, + PrioriteNotification.NORMALE, + PrioriteNotification.BASSE); + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/notification/StatutNotificationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/notification/StatutNotificationTest.java new file mode 100644 index 0000000..0574938 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/notification/StatutNotificationTest.java @@ -0,0 +1,394 @@ +package dev.lions.unionflow.server.api.enums.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +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.EnumSource; + +@DisplayName("Tests pour StatutNotification") +class StatutNotificationTest { + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutNotification[] values = StatutNotification.values(); + assertThat(values).hasSize(23); + } + + @ParameterizedTest + @EnumSource(StatutNotification.class) + @DisplayName("Test getters de base pour toutes les valeurs") + void testGettersBase(StatutNotification statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + assertThat(statut.getCode()).isNotNull().isNotEmpty(); + assertThat(statut.getDescription()).isNotNull().isNotEmpty(); + assertThat(statut.getIcone()).isNotNull().isNotEmpty(); + assertThat(statut.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}"); + } + + @ParameterizedTest + @CsvSource({ + "ENVOYEE, true", + "RECUE, true", + "AFFICHEE, true", + "OUVERTE, true", + "LUE, true", + "ACTION_EXECUTEE, true", + "BROUILLON, false", + "PROGRAMMEE, false", + "EN_ATTENTE, false" + }) + @DisplayName("isVisibleUtilisateur - statuts visibles") + void testIsVisibleUtilisateur(StatutNotification statut, Boolean expected) { + assertThat(statut.isVisibleUtilisateur()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({ + "ECHEC_ENVOI, true", + "PARTIELLEMENT_ENVOYEE, true", + "ANNULEE, true", + "ERREUR_TECHNIQUE, true", + "DESTINATAIRE_INVALIDE, true", + "TOKEN_INVALIDE, true", + "QUOTA_DEPASSE, true", + "ENVOYEE, false", + "RECUE, false", + "BROUILLON, false" + }) + @DisplayName("isNecessiteAttention - statuts nécessitant attention") + void testIsNecessiteAttention(StatutNotification statut, Boolean expected) { + assertThat(statut.isNecessiteAttention()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isSucces") + class IsSuccesTests { + + @ParameterizedTest + @CsvSource({ + "ENVOYEE, true", + "RECUE, true", + "AFFICHEE, true", + "OUVERTE, true", + "LUE, true", + "ACTION_EXECUTEE, true", + "BROUILLON, false", + "ECHEC_ENVOI, false" + }) + @DisplayName("isSucces - statuts de succès") + void testIsSucces(StatutNotification statut, Boolean expected) { + assertThat(statut.isSucces()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isErreur") + class IsErreurTests { + + @ParameterizedTest + @CsvSource({ + "ECHEC_ENVOI, true", + "ERREUR_TECHNIQUE, true", + "DESTINATAIRE_INVALIDE, true", + "TOKEN_INVALIDE, true", + "QUOTA_DEPASSE, true", + "ENVOYEE, false", + "RECUE, false" + }) + @DisplayName("isErreur - statuts d'erreur") + void testIsErreur(StatutNotification statut, Boolean expected) { + assertThat(statut.isErreur()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isEnCours") + class IsEnCoursTests { + + @ParameterizedTest + @CsvSource({ + "PROGRAMMEE, true", + "EN_ATTENTE, true", + "EN_COURS_ENVOI, true", + "ENVOYEE, false", + "RECUE, false" + }) + @DisplayName("isEnCours - statuts en cours") + void testIsEnCours(StatutNotification statut, Boolean expected) { + assertThat(statut.isEnCours()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests isFinal") + class IsFinalTests { + + @ParameterizedTest + @CsvSource({ + "SUPPRIMEE, true", + "ARCHIVEE, true", + "EXPIREE, true", + "ANNULEE, true", + "ERREUR_TECHNIQUE, true", + "ENVOYEE, false", + "RECUE, false" + }) + @DisplayName("isFinal - statuts finaux") + void testIsFinal(StatutNotification statut, Boolean expected) { + assertThat(statut.isFinal()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests permetModification") + class PermetModificationTests { + + @ParameterizedTest + @CsvSource({ + "BROUILLON, true", + "PROGRAMMEE, true", + "EN_ATTENTE, false", + "ENVOYEE, false" + }) + @DisplayName("permetModification - statuts modifiables") + void testPermetModification(StatutNotification statut, Boolean expected) { + assertThat(statut.permetModification()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests permetAnnulation") + class PermetAnnulationTests { + + @ParameterizedTest + @CsvSource({ + "PROGRAMMEE, true", + "EN_ATTENTE, true", + "BROUILLON, false", + "ENVOYEE, false" + }) + @DisplayName("permetAnnulation - statuts annulables") + void testPermetAnnulation(StatutNotification statut, Boolean expected) { + assertThat(statut.permetAnnulation()).isEqualTo(expected); + } + } + + @Nested + @DisplayName("Tests getPrioriteAffichage") + class GetPrioriteAffichageTests { + + @Test + @DisplayName("getPrioriteAffichage - erreur = priorité 1") + void testGetPrioriteAffichageErreur() { + assertThat(StatutNotification.ERREUR_TECHNIQUE.getPrioriteAffichage()).isEqualTo(1); + assertThat(StatutNotification.ECHEC_ENVOI.getPrioriteAffichage()).isEqualTo(1); + assertThat(StatutNotification.DESTINATAIRE_INVALIDE.getPrioriteAffichage()).isEqualTo(1); + } + + @Test + @DisplayName("getPrioriteAffichage - nécessite attention = priorité 2") + void testGetPrioriteAffichageNecessiteAttention() { + // PARTIELLEMENT_ENVOYEE nécessite attention mais n'est pas une erreur + assertThat(StatutNotification.PARTIELLEMENT_ENVOYEE.getPrioriteAffichage()).isEqualTo(2); + assertThat(StatutNotification.ANNULEE.getPrioriteAffichage()).isEqualTo(2); + } + + @Test + @DisplayName("getPrioriteAffichage - en cours = priorité 3") + void testGetPrioriteAffichageEnCours() { + assertThat(StatutNotification.EN_COURS_ENVOI.getPrioriteAffichage()).isEqualTo(3); + assertThat(StatutNotification.PROGRAMMEE.getPrioriteAffichage()).isEqualTo(3); + assertThat(StatutNotification.EN_ATTENTE.getPrioriteAffichage()).isEqualTo(3); + } + + @Test + @DisplayName("getPrioriteAffichage - succès = priorité 4") + void testGetPrioriteAffichageSucces() { + assertThat(StatutNotification.ENVOYEE.getPrioriteAffichage()).isEqualTo(4); + assertThat(StatutNotification.RECUE.getPrioriteAffichage()).isEqualTo(4); + assertThat(StatutNotification.AFFICHEE.getPrioriteAffichage()).isEqualTo(4); + assertThat(StatutNotification.OUVERTE.getPrioriteAffichage()).isEqualTo(4); + assertThat(StatutNotification.LUE.getPrioriteAffichage()).isEqualTo(4); + assertThat(StatutNotification.ACTION_EXECUTEE.getPrioriteAffichage()).isEqualTo(4); + } + + @Test + @DisplayName("getPrioriteAffichage - autres statuts = priorité 5") + void testGetPrioriteAffichageAutres() { + assertThat(StatutNotification.BROUILLON.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.SUPPRIMEE.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.ARCHIVEE.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.EXPIREE.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.IGNOREE.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.NON_LUE.getPrioriteAffichage()).isEqualTo(5); + assertThat(StatutNotification.MARQUEE_IMPORTANTE.getPrioriteAffichage()).isEqualTo(5); + } + } + + @Nested + @DisplayName("Tests getStatutsSuivantsPossibles") + class GetStatutsSuivantsPossiblesTests { + + @Test + @DisplayName("getStatutsSuivantsPossibles - BROUILLON") + void testGetStatutsSuivantsPossiblesBrouillon() { + StatutNotification[] suivants = StatutNotification.BROUILLON.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.PROGRAMMEE, + StatutNotification.EN_ATTENTE, + StatutNotification.ANNULEE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - PROGRAMMEE") + void testGetStatutsSuivantsPossiblesProgrammee() { + StatutNotification[] suivants = StatutNotification.PROGRAMMEE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.EN_ATTENTE, + StatutNotification.EN_COURS_ENVOI, + StatutNotification.ANNULEE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - EN_ATTENTE") + void testGetStatutsSuivantsPossiblesEnAttente() { + StatutNotification[] suivants = StatutNotification.EN_ATTENTE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.EN_COURS_ENVOI, + StatutNotification.ECHEC_ENVOI, + StatutNotification.ANNULEE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - EN_COURS_ENVOI") + void testGetStatutsSuivantsPossiblesEnCoursEnvoi() { + StatutNotification[] suivants = StatutNotification.EN_COURS_ENVOI.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.ENVOYEE, + StatutNotification.PARTIELLEMENT_ENVOYEE, + StatutNotification.ECHEC_ENVOI); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - ENVOYEE") + void testGetStatutsSuivantsPossiblesEnvoyee() { + StatutNotification[] suivants = StatutNotification.ENVOYEE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.RECUE, + StatutNotification.ECHEC_ENVOI); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - RECUE") + void testGetStatutsSuivantsPossiblesRecue() { + StatutNotification[] suivants = StatutNotification.RECUE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.AFFICHEE, + StatutNotification.IGNOREE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - AFFICHEE") + void testGetStatutsSuivantsPossiblesAffichee() { + StatutNotification[] suivants = StatutNotification.AFFICHEE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.OUVERTE, + StatutNotification.LUE, + StatutNotification.NON_LUE, + StatutNotification.IGNOREE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - OUVERTE") + void testGetStatutsSuivantsPossiblesOuverte() { + StatutNotification[] suivants = StatutNotification.OUVERTE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.LUE, + StatutNotification.ACTION_EXECUTEE, + StatutNotification.MARQUEE_IMPORTANTE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - NON_LUE") + void testGetStatutsSuivantsPossiblesNonLue() { + StatutNotification[] suivants = StatutNotification.NON_LUE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.LUE, + StatutNotification.OUVERTE, + StatutNotification.SUPPRIMEE, + StatutNotification.ARCHIVEE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - LUE") + void testGetStatutsSuivantsPossiblesLue() { + StatutNotification[] suivants = StatutNotification.LUE.getStatutsSuivantsPossibles(); + assertThat(suivants).containsExactlyInAnyOrder( + StatutNotification.ACTION_EXECUTEE, + StatutNotification.MARQUEE_IMPORTANTE, + StatutNotification.SUPPRIMEE, + StatutNotification.ARCHIVEE); + } + + @Test + @DisplayName("getStatutsSuivantsPossibles - statuts non couverts (default)") + void testGetStatutsSuivantsPossiblesDefault() { + // Les statuts non couverts par le switch retournent un tableau vide + StatutNotification[] suivants = StatutNotification.SUPPRIMEE.getStatutsSuivantsPossibles(); + assertThat(suivants).isEmpty(); + + suivants = StatutNotification.ARCHIVEE.getStatutsSuivantsPossibles(); + assertThat(suivants).isEmpty(); + + suivants = StatutNotification.EXPIREE.getStatutsSuivantsPossibles(); + assertThat(suivants).isEmpty(); + } + } + + @Nested + @DisplayName("Tests méthodes statiques") + class MethodesStatiquesTests { + + @Test + @DisplayName("parCode - trouve le statut par code") + void testParCode() { + assertThat(StatutNotification.parCode("sent")).isEqualTo(StatutNotification.ENVOYEE); + assertThat(StatutNotification.parCode("read")).isEqualTo(StatutNotification.LUE); + } + + @Test + @DisplayName("parCode - code inexistant retourne null") + void testParCodeInexistant() { + assertThat(StatutNotification.parCode("inexistant")).isNull(); + assertThat(StatutNotification.parCode("")).isNull(); + assertThat(StatutNotification.parCode(null)).isNull(); + } + + @Test + @DisplayName("getStatutsVisibles - retourne les statuts visibles") + void testGetStatutsVisibles() { + StatutNotification[] statuts = StatutNotification.getStatutsVisibles(); + assertThat(statuts).isNotEmpty(); + assertThat(statuts).allMatch(StatutNotification::isVisibleUtilisateur); + } + + @Test + @DisplayName("getStatutsErreur - retourne les statuts d'erreur") + void testGetStatutsErreur() { + StatutNotification[] statuts = StatutNotification.getStatutsErreur(); + assertThat(statuts).isNotEmpty(); + assertThat(statuts).allMatch(StatutNotification::isErreur); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/notification/TypeNotificationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/notification/TypeNotificationTest.java new file mode 100644 index 0000000..377f919 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/notification/TypeNotificationTest.java @@ -0,0 +1,79 @@ +package dev.lions.unionflow.server.api.enums.notification; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeNotification. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeNotification") +class TypeNotificationTest { + + @Test + @DisplayName("Tous les types de notification doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeNotification type : TypeNotification.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("EMAIL doit avoir le libellé correct") + void testEmail() { + assertThat(TypeNotification.EMAIL.getLibelle()).isEqualTo("Email"); + } + + @Test + @DisplayName("SMS doit avoir le libellé correct") + void testSMS() { + assertThat(TypeNotification.SMS.getLibelle()).isEqualTo("SMS"); + } + + @Test + @DisplayName("PUSH doit avoir le libellé correct") + void testPush() { + assertThat(TypeNotification.PUSH.getLibelle()).isEqualTo("Push Notification"); + } + + @Test + @DisplayName("IN_APP doit avoir le libellé correct") + void testInApp() { + assertThat(TypeNotification.IN_APP.getLibelle()).isEqualTo("Notification In-App"); + } + + @Test + @DisplayName("SYSTEME doit avoir le libellé correct") + void testSysteme() { + assertThat(TypeNotification.SYSTEME.getLibelle()).isEqualTo("Notification Système"); + } + + @Test + @DisplayName("isActiveeParDefaut doit retourner true pour tous sauf SYSTEME") + void testIsActiveeParDefaut() { + assertThat(TypeNotification.EMAIL.isActiveeParDefaut()).isTrue(); + assertThat(TypeNotification.SMS.isActiveeParDefaut()).isTrue(); + assertThat(TypeNotification.PUSH.isActiveeParDefaut()).isTrue(); + assertThat(TypeNotification.IN_APP.isActiveeParDefaut()).isTrue(); + assertThat(TypeNotification.SYSTEME.isActiveeParDefaut()).isFalse(); + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeNotification.values()).hasSize(5); + assertThat(TypeNotification.values()) + .containsExactlyInAnyOrder( + TypeNotification.EMAIL, + TypeNotification.SMS, + TypeNotification.PUSH, + TypeNotification.IN_APP, + TypeNotification.SYSTEME); + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisationTest.java new file mode 100644 index 0000000..869ebfd --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/organisation/StatutOrganisationTest.java @@ -0,0 +1,89 @@ +package dev.lions.unionflow.server.api.enums.organisation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutOrganisation") +class StatutOrganisationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutOrganisation.ACTIVE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutOrganisation[] values = StatutOrganisation.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutOrganisation.ACTIVE, + StatutOrganisation.INACTIVE, + StatutOrganisation.SUSPENDUE, + StatutOrganisation.EN_CREATION, + StatutOrganisation.DISSOUTE); + } + + @Test + @DisplayName("Test valueOf") + void testValueOf() { + assertThat(StatutOrganisation.valueOf("ACTIVE")).isEqualTo(StatutOrganisation.ACTIVE); + assertThat(StatutOrganisation.valueOf("INACTIVE")).isEqualTo(StatutOrganisation.INACTIVE); + assertThat(StatutOrganisation.valueOf("SUSPENDUE")).isEqualTo(StatutOrganisation.SUSPENDUE); + assertThat(StatutOrganisation.valueOf("EN_CREATION")).isEqualTo(StatutOrganisation.EN_CREATION); + assertThat(StatutOrganisation.valueOf("DISSOUTE")).isEqualTo(StatutOrganisation.DISSOUTE); + + assertThatThrownBy(() -> StatutOrganisation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutOrganisation.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutOrganisation statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "ACTIVE, Active", + "INACTIVE, Inactive", + "SUSPENDUE, Suspendue", + "EN_CREATION, En Création", + "DISSOUTE, Dissoute" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutOrganisation statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + + @Test + @DisplayName("Test ordinal et name") + void testOrdinalEtName() { + assertThat(StatutOrganisation.ACTIVE.ordinal()).isEqualTo(0); + assertThat(StatutOrganisation.INACTIVE.ordinal()).isEqualTo(1); + assertThat(StatutOrganisation.SUSPENDUE.ordinal()).isEqualTo(2); + assertThat(StatutOrganisation.EN_CREATION.ordinal()).isEqualTo(3); + assertThat(StatutOrganisation.DISSOUTE.ordinal()).isEqualTo(4); + + assertThat(StatutOrganisation.ACTIVE.name()).isEqualTo("ACTIVE"); + assertThat(StatutOrganisation.INACTIVE.name()).isEqualTo("INACTIVE"); + assertThat(StatutOrganisation.SUSPENDUE.name()).isEqualTo("SUSPENDUE"); + assertThat(StatutOrganisation.EN_CREATION.name()).isEqualTo("EN_CREATION"); + assertThat(StatutOrganisation.DISSOUTE.name()).isEqualTo("DISSOUTE"); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisationTest.java new file mode 100644 index 0000000..ab3c57c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/organisation/TypeOrganisationTest.java @@ -0,0 +1,104 @@ +package dev.lions.unionflow.server.api.enums.organisation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeOrganisation") +class TypeOrganisationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeOrganisation.LIONS_CLUB).isNotNull(); + assertThat(TypeOrganisation.ORGANISATION_RELIGIEUSE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum - couverture exhaustive") + void testToutesValeurs() { + TypeOrganisation[] values = TypeOrganisation.values(); + assertThat(values).hasSize(14); + assertThat(values).containsExactly( + TypeOrganisation.ASSOCIATION, + TypeOrganisation.MUTUELLE_EPARGNE_CREDIT, + TypeOrganisation.MUTUELLE_SANTE, + TypeOrganisation.TONTINE, + TypeOrganisation.ONG, + TypeOrganisation.COOPERATIVE_AGRICOLE, + TypeOrganisation.ASSOCIATION_PROFESSIONNELLE, + TypeOrganisation.ASSOCIATION_COMMUNAUTAIRE, + TypeOrganisation.ORGANISATION_RELIGIEUSE, + TypeOrganisation.FEDERATION, + TypeOrganisation.SYNDICAT, + TypeOrganisation.LIONS_CLUB, + TypeOrganisation.ROTARY_CLUB, + TypeOrganisation.AUTRE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypeOrganisation.valueOf("ASSOCIATION")).isEqualTo(TypeOrganisation.ASSOCIATION); + assertThat(TypeOrganisation.valueOf("MUTUELLE_EPARGNE_CREDIT")).isEqualTo(TypeOrganisation.MUTUELLE_EPARGNE_CREDIT); + assertThat(TypeOrganisation.valueOf("MUTUELLE_SANTE")).isEqualTo(TypeOrganisation.MUTUELLE_SANTE); + assertThat(TypeOrganisation.valueOf("TONTINE")).isEqualTo(TypeOrganisation.TONTINE); + assertThat(TypeOrganisation.valueOf("ONG")).isEqualTo(TypeOrganisation.ONG); + assertThat(TypeOrganisation.valueOf("COOPERATIVE_AGRICOLE")).isEqualTo(TypeOrganisation.COOPERATIVE_AGRICOLE); + assertThat(TypeOrganisation.valueOf("ASSOCIATION_PROFESSIONNELLE")).isEqualTo(TypeOrganisation.ASSOCIATION_PROFESSIONNELLE); + assertThat(TypeOrganisation.valueOf("ASSOCIATION_COMMUNAUTAIRE")).isEqualTo(TypeOrganisation.ASSOCIATION_COMMUNAUTAIRE); + assertThat(TypeOrganisation.valueOf("ORGANISATION_RELIGIEUSE")).isEqualTo(TypeOrganisation.ORGANISATION_RELIGIEUSE); + assertThat(TypeOrganisation.valueOf("FEDERATION")).isEqualTo(TypeOrganisation.FEDERATION); + assertThat(TypeOrganisation.valueOf("SYNDICAT")).isEqualTo(TypeOrganisation.SYNDICAT); + assertThat(TypeOrganisation.valueOf("LIONS_CLUB")).isEqualTo(TypeOrganisation.LIONS_CLUB); + assertThat(TypeOrganisation.valueOf("ROTARY_CLUB")).isEqualTo(TypeOrganisation.ROTARY_CLUB); + assertThat(TypeOrganisation.valueOf("AUTRE")).isEqualTo(TypeOrganisation.AUTRE); + + assertThatThrownBy(() -> TypeOrganisation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeOrganisation.class) + @DisplayName("Test getLibelle pour toutes les valeurs - non null et non vide") + void testGetLibelle(TypeOrganisation type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "LIONS_CLUB, Lions Club", + "ASSOCIATION, Association", + "ONG, ONG / Association humanitaire", + "ORGANISATION_RELIGIEUSE, Organisation religieuse", + "FEDERATION, Fédération / Union d'associations", + "AUTRE, Autre" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(String name, String expectedLibelle) { + assertThat(TypeOrganisation.valueOf(name).getLibelle()).isEqualTo(expectedLibelle); + } + + @Test + @DisplayName("Test name() pour toutes les constantes") + void testOrdinalEtName() { + assertThat(TypeOrganisation.ASSOCIATION.name()).isEqualTo("ASSOCIATION"); + assertThat(TypeOrganisation.LIONS_CLUB.name()).isEqualTo("LIONS_CLUB"); + assertThat(TypeOrganisation.ORGANISATION_RELIGIEUSE.name()).isEqualTo("ORGANISATION_RELIGIEUSE"); + assertThat(TypeOrganisation.MUTUELLE_SANTE.name()).isEqualTo("MUTUELLE_SANTE"); + assertThat(TypeOrganisation.ONG.name()).isEqualTo("ONG"); + assertThat(TypeOrganisation.AUTRE.name()).isEqualTo("AUTRE"); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiementTest.java new file mode 100644 index 0000000..e47155a --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/MethodePaiementTest.java @@ -0,0 +1,55 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour MethodePaiement. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests MethodePaiement") +class MethodePaiementTest { + + @Test + @DisplayName("Toutes les méthodes de paiement doivent avoir un libellé") + void testToutesLesMethodesOntLibelle() { + for (MethodePaiement methode : MethodePaiement.values()) { + assertThat(methode.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(MethodePaiement.values().length).isGreaterThan(0); + for (MethodePaiement methode : MethodePaiement.values()) { + assertThat(methode).isNotNull(); + assertThat(methode.name()).isNotBlank(); + } + } + + @Test + @DisplayName("isMobileMoney - méthodes mobile money retournent true") + void testIsMobileMoneyTrue() { + assertThat(MethodePaiement.WAVE_MOBILE_MONEY.isMobileMoney()).isTrue(); + assertThat(MethodePaiement.ORANGE_MONEY.isMobileMoney()).isTrue(); + assertThat(MethodePaiement.MTN_MOBILE_MONEY.isMobileMoney()).isTrue(); + assertThat(MethodePaiement.MOOV_MONEY.isMobileMoney()).isTrue(); + } + + @Test + @DisplayName("isMobileMoney - autres méthodes retournent false") + void testIsMobileMoneyFalse() { + assertThat(MethodePaiement.VIREMENT_BANCAIRE.isMobileMoney()).isFalse(); + assertThat(MethodePaiement.CARTE_BANCAIRE.isMobileMoney()).isFalse(); + assertThat(MethodePaiement.ESPECES.isMobileMoney()).isFalse(); + assertThat(MethodePaiement.CHEQUE.isMobileMoney()).isFalse(); + assertThat(MethodePaiement.AUTRE.isMobileMoney()).isFalse(); + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiementTest.java new file mode 100644 index 0000000..4d19e7a --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutIntentionPaiementTest.java @@ -0,0 +1,74 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutIntentionPaiement") +class StatutIntentionPaiementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutIntentionPaiement.INITIEE).isNotNull(); + assertThat(StatutIntentionPaiement.COMPLETEE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutIntentionPaiement[] values = StatutIntentionPaiement.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutIntentionPaiement.INITIEE, + StatutIntentionPaiement.EN_COURS, + StatutIntentionPaiement.COMPLETEE, + StatutIntentionPaiement.EXPIREE, + StatutIntentionPaiement.ECHOUEE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutIntentionPaiement.valueOf("INITIEE")).isEqualTo(StatutIntentionPaiement.INITIEE); + assertThat(StatutIntentionPaiement.valueOf("EN_COURS")).isEqualTo(StatutIntentionPaiement.EN_COURS); + assertThat(StatutIntentionPaiement.valueOf("COMPLETEE")).isEqualTo(StatutIntentionPaiement.COMPLETEE); + assertThat(StatutIntentionPaiement.valueOf("EXPIREE")).isEqualTo(StatutIntentionPaiement.EXPIREE); + assertThat(StatutIntentionPaiement.valueOf("ECHOUEE")).isEqualTo(StatutIntentionPaiement.ECHOUEE); + + assertThatThrownBy(() -> StatutIntentionPaiement.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutIntentionPaiement.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutIntentionPaiement statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle contient libellé attendu") + void testGetLibelleContenu() { + assertThat(StatutIntentionPaiement.INITIEE.getLibelle()).contains("Initiée"); + assertThat(StatutIntentionPaiement.COMPLETEE.getLibelle()).contains("Complétée"); + assertThat(StatutIntentionPaiement.ECHOUEE.getLibelle()).contains("Échouée"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutIntentionPaiement.INITIEE.name()).isEqualTo("INITIEE"); + assertThat(StatutIntentionPaiement.EN_COURS.name()).isEqualTo("EN_COURS"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiementTest.java new file mode 100644 index 0000000..e748c23 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutPaiementTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour StatutPaiement. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests StatutPaiement") +class StatutPaiementTest { + + @Test + @DisplayName("Tous les statuts de paiement doivent avoir un libellé") + void testTousLesStatutsOntLibelle() { + for (StatutPaiement statut : StatutPaiement.values()) { + assertThat(statut.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(StatutPaiement.values().length).isGreaterThan(0); + for (StatutPaiement statut : StatutPaiement.values()) { + assertThat(statut).isNotNull(); + assertThat(statut.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutSessionTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutSessionTest.java new file mode 100644 index 0000000..12ed4d7 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutSessionTest.java @@ -0,0 +1,54 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutSession") +class StatutSessionTest { + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutSession[] values = StatutSession.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutSession.PENDING, + StatutSession.COMPLETED, + StatutSession.CANCELLED, + StatutSession.EXPIRED, + StatutSession.FAILED); + } + + @ParameterizedTest + @EnumSource(StatutSession.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutSession statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "PENDING, En attente", + "COMPLETED, Complétée", + "CANCELLED, Annulée", + "EXPIRED, Expirée", + "FAILED, Échouée" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutSession statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitementTest.java new file mode 100644 index 0000000..c660be8 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/StatutTraitementTest.java @@ -0,0 +1,54 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutTraitement") +class StatutTraitementTest { + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutTraitement[] values = StatutTraitement.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutTraitement.RECU, + StatutTraitement.EN_COURS, + StatutTraitement.TRAITE, + StatutTraitement.ECHEC, + StatutTraitement.IGNORE); + } + + @ParameterizedTest + @EnumSource(StatutTraitement.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutTraitement statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "RECU, Reçu", + "EN_COURS, En cours de traitement", + "TRAITE, Traité avec succès", + "ECHEC, Échec de traitement", + "IGNORE, Ignoré" + }) + @DisplayName("Test getLibelle avec valeurs exactes") + void testGetLibelleValeursExactes(StatutTraitement statut, String expectedLibelle) { + assertThat(statut.getLibelle()).isEqualTo(expectedLibelle); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenementTest.java new file mode 100644 index 0000000..2907cd9 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeEvenementTest.java @@ -0,0 +1,93 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeEvenement") +class TypeEvenementTest { + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeEvenement[] values = TypeEvenement.values(); + assertThat(values).hasSize(8); + assertThat(values).containsExactly( + TypeEvenement.CHECKOUT_COMPLETE, + TypeEvenement.CHECKOUT_CANCELLED, + TypeEvenement.CHECKOUT_EXPIRED, + TypeEvenement.PAYOUT_COMPLETE, + TypeEvenement.PAYOUT_FAILED, + TypeEvenement.BALANCE_UPDATED, + TypeEvenement.TRANSACTION_CREATED, + TypeEvenement.TRANSACTION_UPDATED); + } + + @ParameterizedTest + @EnumSource(TypeEvenement.class) + @DisplayName("Test getCodeWave pour toutes les valeurs") + void testGetCodeWave(TypeEvenement type) { + assertThat(type.getCodeWave()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "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" + }) + @DisplayName("Test getCodeWave avec valeurs exactes") + void testGetCodeWaveValeursExactes(TypeEvenement type, String expectedCode) { + assertThat(type.getCodeWave()).isEqualTo(expectedCode); + } + } + + @Nested + @DisplayName("Tests fromCode") + class FromCodeTests { + + @ParameterizedTest + @CsvSource({ + "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" + }) + @DisplayName("fromCode - trouve le bon type") + void testFromCode(String code, TypeEvenement expected) { + assertThat(TypeEvenement.fromCode(code)).isEqualTo(expected); + } + + @Test + @DisplayName("fromCode - code inexistant") + void testFromCodeInexistant() { + assertThat(TypeEvenement.fromCode("inexistant")).isNull(); + } + + @Test + @DisplayName("fromCode - code null") + void testFromCodeNull() { + assertThat(TypeEvenement.fromCode(null)).isNull(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiementTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiementTest.java new file mode 100644 index 0000000..4cb588b --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/paiement/TypeObjetIntentionPaiementTest.java @@ -0,0 +1,82 @@ +package dev.lions.unionflow.server.api.enums.paiement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeObjetIntentionPaiement") +class TypeObjetIntentionPaiementTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeObjetIntentionPaiement.COTISATION).isNotNull(); + assertThat(TypeObjetIntentionPaiement.ADHESION).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeObjetIntentionPaiement[] values = TypeObjetIntentionPaiement.values(); + assertThat(values).hasSize(7); + assertThat(values).containsExactly( + TypeObjetIntentionPaiement.COTISATION, + TypeObjetIntentionPaiement.ADHESION, + TypeObjetIntentionPaiement.EVENEMENT, + TypeObjetIntentionPaiement.ABONNEMENT_UNIONFLOW, + TypeObjetIntentionPaiement.DEPOT_EPARGNE, + TypeObjetIntentionPaiement.RETRAIT_EPARGNE, + TypeObjetIntentionPaiement.CREDIT_REMBOURSEMENT); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypeObjetIntentionPaiement.valueOf("COTISATION")).isEqualTo(TypeObjetIntentionPaiement.COTISATION); + assertThat(TypeObjetIntentionPaiement.valueOf("ADHESION")).isEqualTo(TypeObjetIntentionPaiement.ADHESION); + assertThat(TypeObjetIntentionPaiement.valueOf("EVENEMENT")).isEqualTo(TypeObjetIntentionPaiement.EVENEMENT); + assertThat(TypeObjetIntentionPaiement.valueOf("ABONNEMENT_UNIONFLOW")).isEqualTo(TypeObjetIntentionPaiement.ABONNEMENT_UNIONFLOW); + assertThat(TypeObjetIntentionPaiement.valueOf("DEPOT_EPARGNE")).isEqualTo(TypeObjetIntentionPaiement.DEPOT_EPARGNE); + assertThat(TypeObjetIntentionPaiement.valueOf("RETRAIT_EPARGNE")).isEqualTo(TypeObjetIntentionPaiement.RETRAIT_EPARGNE); + assertThat(TypeObjetIntentionPaiement.valueOf("CREDIT_REMBOURSEMENT")).isEqualTo(TypeObjetIntentionPaiement.CREDIT_REMBOURSEMENT); + + assertThatThrownBy(() -> TypeObjetIntentionPaiement.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeObjetIntentionPaiement.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeObjetIntentionPaiement type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(TypeObjetIntentionPaiement.COTISATION.getLibelle()).isEqualTo("Cotisation membre"); + assertThat(TypeObjetIntentionPaiement.ADHESION.getLibelle()).isEqualTo("Frais d'adhésion"); + assertThat(TypeObjetIntentionPaiement.EVENEMENT.getLibelle()).isEqualTo("Participation événement"); + assertThat(TypeObjetIntentionPaiement.ABONNEMENT_UNIONFLOW.getLibelle()).isEqualTo("Abonnement forfait UnionFlow"); + assertThat(TypeObjetIntentionPaiement.DEPOT_EPARGNE.getLibelle()).isEqualTo("Dépôt compte épargne"); + assertThat(TypeObjetIntentionPaiement.RETRAIT_EPARGNE.getLibelle()).isEqualTo("Retrait compte épargne"); + assertThat(TypeObjetIntentionPaiement.CREDIT_REMBOURSEMENT.getLibelle()).isEqualTo("Remboursement crédit"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(TypeObjetIntentionPaiement.COTISATION.name()).isEqualTo("COTISATION"); + assertThat(TypeObjetIntentionPaiement.ABONNEMENT_UNIONFLOW.name()).isEqualTo("ABONNEMENT_UNIONFLOW"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAideTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAideTest.java new file mode 100644 index 0000000..910ad8d --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/PrioriteAideTest.java @@ -0,0 +1,375 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour PrioriteAide. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests PrioriteAide") +class PrioriteAideTest { + + @Test + @DisplayName("Toutes les priorités doivent avoir un libellé") + void testToutesLesPrioritesOntLibelle() { + for (PrioriteAide priorite : PrioriteAide.values()) { + assertThat(priorite.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les priorités doivent avoir un code") + void testToutesLesPrioritesOntCode() { + for (PrioriteAide priorite : PrioriteAide.values()) { + assertThat(priorite.getCode()).isNotNull().isNotBlank(); + } + } + + @Nested + @DisplayName("Tests isUrgente") + class IsUrgenteTests { + + @Test + @DisplayName("isUrgente doit retourner true pour CRITIQUE et URGENTE") + void testIsUrgente() { + assertThat(PrioriteAide.CRITIQUE.isUrgente()).isTrue(); + assertThat(PrioriteAide.URGENTE.isUrgente()).isTrue(); + + assertThat(PrioriteAide.ELEVEE.isUrgente()).isFalse(); + assertThat(PrioriteAide.NORMALE.isUrgente()).isFalse(); + assertThat(PrioriteAide.FAIBLE.isUrgente()).isFalse(); + } + } + + @Nested + @DisplayName("Tests necessiteTraitementImmediat") + class NecessiteTraitementImmediatTests { + + @Test + @DisplayName("necessiteTraitementImmediat doit retourner true pour niveau <= 2") + void testNecessiteTraitementImmediat() { + assertThat(PrioriteAide.CRITIQUE.necessiteTraitementImmediat()).isTrue(); + assertThat(PrioriteAide.URGENTE.necessiteTraitementImmediat()).isTrue(); + + assertThat(PrioriteAide.ELEVEE.necessiteTraitementImmediat()).isFalse(); + assertThat(PrioriteAide.NORMALE.necessiteTraitementImmediat()).isFalse(); + assertThat(PrioriteAide.FAIBLE.necessiteTraitementImmediat()).isFalse(); + } + } + + @Nested + @DisplayName("Tests getDateLimiteTraitement") + class GetDateLimiteTraitementTests { + + @Test + @DisplayName("getDateLimiteTraitement doit retourner une date future") + void testGetDateLimiteTraitement() { + LocalDateTime maintenant = LocalDateTime.now(); + LocalDateTime dateLimite = PrioriteAide.CRITIQUE.getDateLimiteTraitement(); + + assertThat(dateLimite).isAfter(maintenant); + assertThat(dateLimite).isBefore(maintenant.plusHours(25)); // CRITIQUE = 24h + } + } + + @Nested + @DisplayName("Tests getPrioriteEscalade") + class GetPrioriteEscaladeTests { + + @Test + @DisplayName("getPrioriteEscalade doit retourner la priorité suivante") + void testGetPrioriteEscalade() { + assertThat(PrioriteAide.FAIBLE.getPrioriteEscalade()).isEqualTo(PrioriteAide.NORMALE); + assertThat(PrioriteAide.NORMALE.getPrioriteEscalade()).isEqualTo(PrioriteAide.ELEVEE); + assertThat(PrioriteAide.ELEVEE.getPrioriteEscalade()).isEqualTo(PrioriteAide.URGENTE); + assertThat(PrioriteAide.URGENTE.getPrioriteEscalade()).isEqualTo(PrioriteAide.CRITIQUE); + assertThat(PrioriteAide.CRITIQUE.getPrioriteEscalade()).isEqualTo(PrioriteAide.CRITIQUE); + } + } + + @Nested + @DisplayName("Tests determinerPriorite") + class DeterminerPrioriteTests { + + @Test + @DisplayName("determinerPriorite doit retourner CRITIQUE pour AIDE_FINANCIERE_URGENTE") + void testDeterminerPrioriteCritique() { + assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FINANCIERE_URGENTE)) + .isEqualTo(PrioriteAide.CRITIQUE); + assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FRAIS_MEDICAUX)) + .isEqualTo(PrioriteAide.CRITIQUE); + } + + @Test + @DisplayName("determinerPriorite doit retourner URGENTE pour HEBERGEMENT_URGENCE et AIDE_ALIMENTAIRE") + void testDeterminerPrioriteUrgente() { + assertThat(PrioriteAide.determinerPriorite(TypeAide.HEBERGEMENT_URGENCE)) + .isEqualTo(PrioriteAide.URGENTE); + assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_ALIMENTAIRE)) + .isEqualTo(PrioriteAide.URGENTE); + } + + @Test + @DisplayName("determinerPriorite doit retourner ELEVEE pour types urgents non spécifiques") + void testDeterminerPrioriteElevee() { + assertThat(PrioriteAide.determinerPriorite(TypeAide.PRET_SANS_INTERET)) + .isEqualTo(PrioriteAide.ELEVEE); + } + + @Test + @DisplayName("determinerPriorite retourne ELEVEE (default switch) pour type urgent non listé (reflection)") + void testDeterminerPrioriteUrgentDefault() throws Exception { + java.lang.reflect.Field prioriteField = TypeAide.class.getDeclaredField("priorite"); + prioriteField.setAccessible(true); + String backupPriorite = (String) prioriteField.get(TypeAide.AIDE_VESTIMENTAIRE); + try { + prioriteField.set(TypeAide.AIDE_VESTIMENTAIRE, "urgent"); + assertThat(TypeAide.AIDE_VESTIMENTAIRE.isUrgent()).isTrue(); + assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_VESTIMENTAIRE)) + .isEqualTo(PrioriteAide.ELEVEE); + } finally { + prioriteField.set(TypeAide.AIDE_VESTIMENTAIRE, backupPriorite); + } + } + + @Test + @DisplayName("determinerPriorite doit retourner ELEVEE pour types avec priorite important") + void testDeterminerPrioriteImportant() { + assertThat(PrioriteAide.determinerPriorite(TypeAide.PRET_SANS_INTERET)) + .isEqualTo(PrioriteAide.ELEVEE); + assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FRAIS_SCOLARITE)) + .isEqualTo(PrioriteAide.ELEVEE); + } + + @Test + @DisplayName("determinerPriorite doit retourner NORMALE par défaut") + void testDeterminerPrioriteNormale() { + assertThat(PrioriteAide.determinerPriorite(TypeAide.DON_MATERIEL)) + .isEqualTo(PrioriteAide.NORMALE); + assertThat(PrioriteAide.determinerPriorite(TypeAide.TRANSPORT)) + .isEqualTo(PrioriteAide.NORMALE); + } + } + + @Nested + @DisplayName("Tests getPrioritesUrgentes") + class GetPrioritesUrgentesTests { + + @Test + @DisplayName("getPrioritesUrgentes doit retourner CRITIQUE et URGENTE") + void testGetPrioritesUrgentes() { + List prioritésUrgentes = PrioriteAide.getPrioritesUrgentes(); + + assertThat(prioritésUrgentes).hasSize(2); + assertThat(prioritésUrgentes).contains(PrioriteAide.CRITIQUE); + assertThat(prioritésUrgentes).contains(PrioriteAide.URGENTE); + } + } + + @Nested + @DisplayName("Tests getParNiveauCroissant") + class GetParNiveauCroissantTests { + + @Test + @DisplayName("getParNiveauCroissant doit retourner les priorités triées par niveau croissant") + void testGetParNiveauCroissant() { + List priorités = PrioriteAide.getParNiveauCroissant(); + + assertThat(priorités).hasSize(5); + assertThat(priorités.get(0)).isEqualTo(PrioriteAide.CRITIQUE); + assertThat(priorités.get(1)).isEqualTo(PrioriteAide.URGENTE); + assertThat(priorités.get(2)).isEqualTo(PrioriteAide.ELEVEE); + assertThat(priorités.get(3)).isEqualTo(PrioriteAide.NORMALE); + assertThat(priorités.get(4)).isEqualTo(PrioriteAide.FAIBLE); + } + } + + @Nested + @DisplayName("Tests getParNiveauDecroissant") + class GetParNiveauDecroissantTests { + + @Test + @DisplayName("getParNiveauDecroissant doit retourner les priorités triées par niveau décroissant") + void testGetParNiveauDecroissant() { + List priorités = PrioriteAide.getParNiveauDecroissant(); + + assertThat(priorités).hasSize(5); + assertThat(priorités.get(0)).isEqualTo(PrioriteAide.FAIBLE); + assertThat(priorités.get(1)).isEqualTo(PrioriteAide.NORMALE); + assertThat(priorités.get(2)).isEqualTo(PrioriteAide.ELEVEE); + assertThat(priorités.get(3)).isEqualTo(PrioriteAide.URGENTE); + assertThat(priorités.get(4)).isEqualTo(PrioriteAide.CRITIQUE); + } + } + + @Nested + @DisplayName("Tests parCode") + class ParCodeTests { + + @Test + @DisplayName("parCode doit retourner la priorité correspondante") + void testParCode() { + assertThat(PrioriteAide.parCode("critical")).isEqualTo(PrioriteAide.CRITIQUE); + assertThat(PrioriteAide.parCode("urgent")).isEqualTo(PrioriteAide.URGENTE); + assertThat(PrioriteAide.parCode("high")).isEqualTo(PrioriteAide.ELEVEE); + assertThat(PrioriteAide.parCode("normal")).isEqualTo(PrioriteAide.NORMALE); + assertThat(PrioriteAide.parCode("low")).isEqualTo(PrioriteAide.FAIBLE); + } + + @Test + @DisplayName("parCode doit retourner NORMALE pour un code inconnu") + void testParCodeInconnu() { + assertThat(PrioriteAide.parCode("unknown")).isEqualTo(PrioriteAide.NORMALE); + assertThat(PrioriteAide.parCode("")).isEqualTo(PrioriteAide.NORMALE); + } + } + + @Nested + @DisplayName("Tests getScorePriorite") + class GetScorePrioriteTests { + + @Test + @DisplayName("getScorePriorite doit calculer correctement") + void testGetScorePriorite() { + // CRITIQUE: niveau=1, notificationImmediate=true, escaladeAutomatique=true + // Score = 1 - 0.5 - 0.3 = 0.2 + double scoreCritique = PrioriteAide.CRITIQUE.getScorePriorite(); + assertThat(scoreCritique).isLessThan(1.0); + + // URGENTE: niveau=2, notificationImmediate=true, escaladeAutomatique=false + // Score = 2 - 0.5 = 1.5 + double scoreUrgente = PrioriteAide.URGENTE.getScorePriorite(); + assertThat(scoreUrgente).isLessThan(2.0); + + // FAIBLE: niveau=5, notificationImmediate=false, escaladeAutomatique=false, + // delaiTraitementHeures=720 > 168 + // Score = 5 + 0.2 = 5.2 + double scoreFaible = PrioriteAide.FAIBLE.getScorePriorite(); + assertThat(scoreFaible).isGreaterThan(5.0); + } + } + + @Nested + @DisplayName("Tests isDelaiDepasse") + class IsDelaiDepasseTests { + + @Test + @DisplayName("isDelaiDepasse doit retourner false si le délai n'est pas dépassé") + void testIsDelaiDepasseNonDepasse() { + LocalDateTime dateCreation = LocalDateTime.now().minusHours(10); + assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation)).isFalse(); + } + + @Test + @DisplayName("isDelaiDepasse doit retourner true si le délai est dépassé") + void testIsDelaiDepasseDepasse() { + LocalDateTime dateCreation = LocalDateTime.now().minusHours(25); + assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation)).isTrue(); + } + + @Test + @DisplayName("isDelaiDepasse avec date de référence doit fonctionner correctement") + void testIsDelaiDepasseAvecDateReference() { + LocalDateTime dateCreation = LocalDateTime.now().minusHours(10); + LocalDateTime maintenant = LocalDateTime.now().plusHours(20); + assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isTrue(); + + maintenant = LocalDateTime.now().plusHours(10); + assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isFalse(); + } + + @Test + @DisplayName("isDelaiDepasse doit retourner false si exactement à la limite") + void testIsDelaiDepasseALaLimite() { + // Pour être exactement à la limite dans isDelaiDepasse(dateCreation, + // maintenant), + // il faut passer maintenant = dateCreation.plusHours(delai) + LocalDateTime dateCreation = LocalDateTime.now().minusHours(24); + LocalDateTime maintenant = dateCreation.plusHours(24); // Ex: CRITIQUE + + assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isFalse(); + } + } + + @Nested + @DisplayName("Tests getPourcentageTempsEcoule") + class GetPourcentageTempsEcouleTests { + + @Test + @DisplayName("getPourcentageTempsEcoule doit calculer correctement") + void testGetPourcentageTempsEcoule() { + LocalDateTime dateCreation = LocalDateTime.now().minusHours(12); + // CRITIQUE = 24h, donc 12h = 50% + double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation); + assertThat(pourcentage).isBetween(45.0, 55.0); + } + + @Test + @DisplayName("getPourcentageTempsEcoule doit retourner 100 si délai dépassé") + void testGetPourcentageTempsEcouleDepasse() { + LocalDateTime dateCreation = LocalDateTime.now().minusHours(30); + double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation); + assertThat(pourcentage).isEqualTo(100.0); + } + + @Test + @DisplayName("getPourcentageTempsEcoule doit gérer dureeTotal <= 0") + void testGetPourcentageTempsEcouleDureeZero() { + double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(LocalDateTime.now()); + assertThat(pourcentage).isGreaterThanOrEqualTo(0.0); + assertThat(pourcentage).isLessThanOrEqualTo(100.0); + } + + @Test + @DisplayName("getPourcentageTempsEcoule retourne 100 quand dureeTotal <= 0 (reflection)") + void testGetPourcentageTempsEcouleDureeTotalZero() throws Exception { + java.lang.reflect.Field f = PrioriteAide.class.getDeclaredField("delaiTraitementHeures"); + f.setAccessible(true); + int backup = (int) f.get(PrioriteAide.CRITIQUE); + try { + f.setInt(PrioriteAide.CRITIQUE, 0); + LocalDateTime dateCreation = LocalDateTime.now(); + assertThat(PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation)).isEqualTo(100.0); + } finally { + f.setInt(PrioriteAide.CRITIQUE, backup); + } + } + } + + @Nested + @DisplayName("Tests getMessageAlerte") + class GetMessageAlerteTests { + + @Test + @DisplayName("getMessageAlerte doit retourner le bon message selon le pourcentage") + void testGetMessageAlerte() { + // Pourcentage >= 100 + LocalDateTime dateCreation = LocalDateTime.now().minusHours(30); + assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation)) + .isEqualTo("Délai de traitement dépassé !"); + + // Pourcentage >= 80 + dateCreation = LocalDateTime.now().minusHours(20); + assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation)) + .isEqualTo("Délai de traitement bientôt dépassé"); + + // Pourcentage >= 60 + dateCreation = LocalDateTime.now().minusHours(15); + assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation)) + .isEqualTo("Plus de la moitié du délai écoulé"); + + // Pourcentage < 60 + dateCreation = LocalDateTime.now().minusHours(5); + assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation)).isNull(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAideTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAideTest.java new file mode 100644 index 0000000..cbd119f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutAideTest.java @@ -0,0 +1,341 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour StatutAide. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests StatutAide") +class StatutAideTest { + + @Test + @DisplayName("Tous les statuts doivent avoir un libellé") + void testTousLesStatutsOntLibelle() { + for (StatutAide statut : StatutAide.values()) { + assertThat(statut.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Tous les statuts doivent avoir un code") + void testTousLesStatutsOntCode() { + for (StatutAide statut : StatutAide.values()) { + assertThat(statut.getCode()).isNotNull().isNotBlank(); + } + } + + @Nested + @DisplayName("Tests isSucces") + class IsSuccesTests { + + @Test + @DisplayName("isSucces doit retourner true pour VERSEE, LIVREE, TERMINEE") + void testIsSucces() { + assertThat(StatutAide.VERSEE.isSucces()).isTrue(); + assertThat(StatutAide.LIVREE.isSucces()).isTrue(); + assertThat(StatutAide.TERMINEE.isSucces()).isTrue(); + + assertThat(StatutAide.APPROUVEE.isSucces()).isFalse(); + assertThat(StatutAide.REJETEE.isSucces()).isFalse(); + assertThat(StatutAide.EN_ATTENTE.isSucces()).isFalse(); + } + } + + @Nested + @DisplayName("Tests isEnCours") + class IsEnCoursTests { + + @Test + @DisplayName("isEnCours doit retourner true pour les statuts en cours") + void testIsEnCours() { + assertThat(StatutAide.EN_COURS_EVALUATION.isEnCours()).isTrue(); + assertThat(StatutAide.EN_COURS_TRAITEMENT.isEnCours()).isTrue(); + assertThat(StatutAide.EN_COURS_VERSEMENT.isEnCours()).isTrue(); + + assertThat(StatutAide.EN_ATTENTE.isEnCours()).isFalse(); + assertThat(StatutAide.APPROUVEE.isEnCours()).isFalse(); + assertThat(StatutAide.TERMINEE.isEnCours()).isFalse(); + } + } + + @Nested + @DisplayName("Tests permetModification") + class PermetModificationTests { + + @Test + @DisplayName("permetModification doit retourner true pour BROUILLON et INFORMATIONS_REQUISES") + void testPermetModification() { + assertThat(StatutAide.BROUILLON.permetModification()).isTrue(); + assertThat(StatutAide.INFORMATIONS_REQUISES.permetModification()).isTrue(); + + assertThat(StatutAide.EN_ATTENTE.permetModification()).isFalse(); + assertThat(StatutAide.APPROUVEE.permetModification()).isFalse(); + assertThat(StatutAide.TERMINEE.permetModification()).isFalse(); + } + } + + @Nested + @DisplayName("Tests permetAnnulation") + class PermetAnnulationTests { + + @Test + @DisplayName("permetAnnulation doit retourner false pour les statuts finaux et ANNULEE") + void testPermetAnnulation() { + assertThat(StatutAide.ANNULEE.permetAnnulation()).isFalse(); + assertThat(StatutAide.TERMINEE.permetAnnulation()).isFalse(); + assertThat(StatutAide.VERSEE.permetAnnulation()).isFalse(); + assertThat(StatutAide.REJETEE.permetAnnulation()).isFalse(); + assertThat(StatutAide.EXPIREE.permetAnnulation()).isFalse(); + assertThat(StatutAide.CLOTUREE.permetAnnulation()).isFalse(); + + assertThat(StatutAide.BROUILLON.permetAnnulation()).isTrue(); + assertThat(StatutAide.EN_ATTENTE.permetAnnulation()).isTrue(); + assertThat(StatutAide.EN_COURS_EVALUATION.permetAnnulation()).isTrue(); + assertThat(StatutAide.INFORMATIONS_REQUISES.permetAnnulation()).isTrue(); + assertThat(StatutAide.SUSPENDUE.permetAnnulation()).isTrue(); + } + } + + @Nested + @DisplayName("Tests getStatutsFinaux") + class GetStatutsFinauxTests { + + @Test + @DisplayName("getStatutsFinaux doit retourner tous les statuts finaux") + void testGetStatutsFinaux() { + List statutsFinaux = StatutAide.getStatutsFinaux(); + + assertThat(statutsFinaux).isNotEmpty(); + assertThat(statutsFinaux).contains(StatutAide.APPROUVEE); + assertThat(statutsFinaux).contains(StatutAide.APPROUVEE_PARTIELLEMENT); + assertThat(statutsFinaux).contains(StatutAide.REJETEE); + assertThat(statutsFinaux).contains(StatutAide.VERSEE); + assertThat(statutsFinaux).contains(StatutAide.LIVREE); + assertThat(statutsFinaux).contains(StatutAide.TERMINEE); + assertThat(statutsFinaux).contains(StatutAide.ANNULEE); + assertThat(statutsFinaux).contains(StatutAide.EXPIREE); + assertThat(statutsFinaux).contains(StatutAide.CLOTUREE); + + assertThat(statutsFinaux).doesNotContain(StatutAide.BROUILLON); + assertThat(statutsFinaux).doesNotContain(StatutAide.EN_ATTENTE); + } + } + + @Nested + @DisplayName("Tests getStatutsEchec") + class GetStatutsEchecTests { + + @Test + @DisplayName("getStatutsEchec doit retourner tous les statuts d'échec") + void testGetStatutsEchec() { + List statutsEchec = StatutAide.getStatutsEchec(); + + assertThat(statutsEchec).isNotEmpty(); + assertThat(statutsEchec).contains(StatutAide.REJETEE); + assertThat(statutsEchec).contains(StatutAide.ANNULEE); + assertThat(statutsEchec).contains(StatutAide.EXPIREE); + + assertThat(statutsEchec).doesNotContain(StatutAide.VERSEE); + assertThat(statutsEchec).doesNotContain(StatutAide.TERMINEE); + } + } + + @Nested + @DisplayName("Tests getStatutsSucces") + class GetStatutsSuccesTests { + + @Test + @DisplayName("getStatutsSucces doit retourner tous les statuts de succès") + void testGetStatutsSucces() { + List statutsSucces = StatutAide.getStatutsSucces(); + + assertThat(statutsSucces).isNotEmpty(); + assertThat(statutsSucces).contains(StatutAide.VERSEE); + assertThat(statutsSucces).contains(StatutAide.LIVREE); + assertThat(statutsSucces).contains(StatutAide.TERMINEE); + + assertThat(statutsSucces).doesNotContain(StatutAide.REJETEE); + assertThat(statutsSucces).doesNotContain(StatutAide.APPROUVEE); + } + } + + @Nested + @DisplayName("Tests getStatutsEnCours") + class GetStatutsEnCoursTests { + + @Test + @DisplayName("getStatutsEnCours doit retourner tous les statuts en cours") + void testGetStatutsEnCours() { + List statutsEnCours = StatutAide.getStatutsEnCours(); + + assertThat(statutsEnCours).isNotEmpty(); + assertThat(statutsEnCours).contains(StatutAide.EN_COURS_EVALUATION); + assertThat(statutsEnCours).contains(StatutAide.EN_COURS_TRAITEMENT); + assertThat(statutsEnCours).contains(StatutAide.EN_COURS_VERSEMENT); + + assertThat(statutsEnCours).doesNotContain(StatutAide.EN_ATTENTE); + assertThat(statutsEnCours).doesNotContain(StatutAide.APPROUVEE); + } + } + + @Nested + @DisplayName("Tests peutTransitionnerVers") + class PeutTransitionnerVersTests { + + @Test + @DisplayName("peutTransitionnerVers doit retourner false si même statut") + void testMemeStatut() { + assertThat(StatutAide.BROUILLON.peutTransitionnerVers(StatutAide.BROUILLON)).isFalse(); + assertThat(StatutAide.EN_ATTENTE.peutTransitionnerVers(StatutAide.EN_ATTENTE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - BROUILLON peut aller vers SOUMISE ou ANNULEE") + void testBrouillon() { + assertThat(StatutAide.BROUILLON.peutTransitionnerVers(StatutAide.SOUMISE)).isTrue(); + assertThat(StatutAide.BROUILLON.peutTransitionnerVers(StatutAide.ANNULEE)).isTrue(); + assertThat(StatutAide.BROUILLON.peutTransitionnerVers(StatutAide.EN_ATTENTE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - SOUMISE peut aller vers EN_ATTENTE ou ANNULEE") + void testSoumise() { + assertThat(StatutAide.SOUMISE.peutTransitionnerVers(StatutAide.EN_ATTENTE)).isTrue(); + assertThat(StatutAide.SOUMISE.peutTransitionnerVers(StatutAide.ANNULEE)).isTrue(); + assertThat(StatutAide.SOUMISE.peutTransitionnerVers(StatutAide.APPROUVEE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - EN_ATTENTE peut aller vers EN_COURS_EVALUATION ou ANNULEE") + void testEnAttente() { + assertThat(StatutAide.EN_ATTENTE.peutTransitionnerVers(StatutAide.EN_COURS_EVALUATION)) + .isTrue(); + assertThat(StatutAide.EN_ATTENTE.peutTransitionnerVers(StatutAide.ANNULEE)).isTrue(); + assertThat(StatutAide.EN_ATTENTE.peutTransitionnerVers(StatutAide.APPROUVEE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - EN_COURS_EVALUATION peut aller vers plusieurs statuts") + void testEnCoursEvaluation() { + assertThat(StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.APPROUVEE)) + .isTrue(); + assertThat( + StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.APPROUVEE_PARTIELLEMENT)) + .isTrue(); + assertThat(StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.REJETEE)).isTrue(); + assertThat( + StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.INFORMATIONS_REQUISES)) + .isTrue(); + assertThat(StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.SUSPENDUE)) + .isTrue(); + assertThat(StatutAide.EN_COURS_EVALUATION.peutTransitionnerVers(StatutAide.TERMINEE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - INFORMATIONS_REQUISES peut aller vers EN_COURS_EVALUATION ou ANNULEE") + void testInformationsRequises() { + assertThat( + StatutAide.INFORMATIONS_REQUISES.peutTransitionnerVers(StatutAide.EN_COURS_EVALUATION)) + .isTrue(); + assertThat(StatutAide.INFORMATIONS_REQUISES.peutTransitionnerVers(StatutAide.ANNULEE)) + .isTrue(); + assertThat(StatutAide.INFORMATIONS_REQUISES.peutTransitionnerVers(StatutAide.APPROUVEE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - APPROUVEE peut aller vers EN_COURS_TRAITEMENT ou SUSPENDUE") + void testApprouvee() { + assertThat(StatutAide.APPROUVEE.peutTransitionnerVers(StatutAide.EN_COURS_TRAITEMENT)) + .isTrue(); + assertThat(StatutAide.APPROUVEE.peutTransitionnerVers(StatutAide.SUSPENDUE)).isTrue(); + assertThat(StatutAide.APPROUVEE.peutTransitionnerVers(StatutAide.VERSEE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - EN_COURS_TRAITEMENT peut aller vers plusieurs statuts") + void testEnCoursTraitement() { + assertThat(StatutAide.EN_COURS_TRAITEMENT.peutTransitionnerVers(StatutAide.EN_COURS_VERSEMENT)) + .isTrue(); + assertThat(StatutAide.EN_COURS_TRAITEMENT.peutTransitionnerVers(StatutAide.LIVREE)).isTrue(); + assertThat(StatutAide.EN_COURS_TRAITEMENT.peutTransitionnerVers(StatutAide.TERMINEE)) + .isTrue(); + assertThat(StatutAide.EN_COURS_TRAITEMENT.peutTransitionnerVers(StatutAide.SUSPENDUE)) + .isTrue(); + assertThat(StatutAide.EN_COURS_TRAITEMENT.peutTransitionnerVers(StatutAide.APPROUVEE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - EN_COURS_VERSEMENT peut aller vers VERSEE ou SUSPENDUE") + void testEnCoursVersement() { + assertThat(StatutAide.EN_COURS_VERSEMENT.peutTransitionnerVers(StatutAide.VERSEE)).isTrue(); + assertThat(StatutAide.EN_COURS_VERSEMENT.peutTransitionnerVers(StatutAide.SUSPENDUE)) + .isTrue(); + assertThat(StatutAide.EN_COURS_VERSEMENT.peutTransitionnerVers(StatutAide.TERMINEE)) + .isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - SUSPENDUE peut aller vers EN_COURS_EVALUATION ou ANNULEE") + void testSuspendue() { + assertThat(StatutAide.SUSPENDUE.peutTransitionnerVers(StatutAide.EN_COURS_EVALUATION)) + .isTrue(); + assertThat(StatutAide.SUSPENDUE.peutTransitionnerVers(StatutAide.ANNULEE)).isTrue(); + assertThat(StatutAide.SUSPENDUE.peutTransitionnerVers(StatutAide.APPROUVEE)).isFalse(); + } + + @Test + @DisplayName("peutTransitionnerVers - Les statuts finaux ne peuvent pas transitionner (sauf APPROUVEE)") + void testStatutsFinaux() { + // Les statuts finaux (sauf APPROUVEE et APPROUVEE_PARTIELLEMENT) ne peuvent pas transitionner + // car le switch retourne false par défaut + assertThat(StatutAide.TERMINEE.peutTransitionnerVers(StatutAide.EN_SUIVI)).isFalse(); + assertThat(StatutAide.VERSEE.peutTransitionnerVers(StatutAide.EN_SUIVI)).isFalse(); + assertThat(StatutAide.TERMINEE.peutTransitionnerVers(StatutAide.APPROUVEE)).isFalse(); + assertThat(StatutAide.VERSEE.peutTransitionnerVers(StatutAide.APPROUVEE)).isFalse(); + assertThat(StatutAide.REJETEE.peutTransitionnerVers(StatutAide.EN_SUIVI)).isFalse(); + assertThat(StatutAide.ANNULEE.peutTransitionnerVers(StatutAide.EN_SUIVI)).isFalse(); + + // APPROUVEE et APPROUVEE_PARTIELLEMENT peuvent transitionner vers EN_COURS_TRAITEMENT ou SUSPENDUE + assertThat(StatutAide.APPROUVEE.peutTransitionnerVers(StatutAide.EN_COURS_TRAITEMENT)) + .isTrue(); + assertThat(StatutAide.APPROUVEE_PARTIELLEMENT.peutTransitionnerVers(StatutAide.EN_COURS_TRAITEMENT)) + .isTrue(); + assertThat(StatutAide.APPROUVEE.peutTransitionnerVers(StatutAide.SUSPENDUE)).isTrue(); + } + } + + @Nested + @DisplayName("Tests getNiveauPriorite") + class GetNiveauPrioriteTests { + + @Test + @DisplayName("getNiveauPriorite doit retourner le bon niveau") + void testGetNiveauPriorite() { + assertThat(StatutAide.INFORMATIONS_REQUISES.getNiveauPriorite()).isEqualTo(1); + assertThat(StatutAide.EN_COURS_EVALUATION.getNiveauPriorite()).isEqualTo(2); + assertThat(StatutAide.EN_COURS_TRAITEMENT.getNiveauPriorite()).isEqualTo(2); + assertThat(StatutAide.EN_COURS_VERSEMENT.getNiveauPriorite()).isEqualTo(2); + assertThat(StatutAide.APPROUVEE.getNiveauPriorite()).isEqualTo(3); + assertThat(StatutAide.APPROUVEE_PARTIELLEMENT.getNiveauPriorite()).isEqualTo(3); + assertThat(StatutAide.EN_ATTENTE.getNiveauPriorite()).isEqualTo(4); + assertThat(StatutAide.SOUMISE.getNiveauPriorite()).isEqualTo(4); + assertThat(StatutAide.SUSPENDUE.getNiveauPriorite()).isEqualTo(5); + assertThat(StatutAide.BROUILLON.getNiveauPriorite()).isEqualTo(6); + assertThat(StatutAide.EN_SUIVI.getNiveauPriorite()).isEqualTo(7); + assertThat(StatutAide.TERMINEE.getNiveauPriorite()).isEqualTo(8); + assertThat(StatutAide.VERSEE.getNiveauPriorite()).isEqualTo(8); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluationTest.java new file mode 100644 index 0000000..c713849 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutEvaluationTest.java @@ -0,0 +1,76 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutEvaluation") +class StatutEvaluationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutEvaluation.ACTIVE).isNotNull(); + assertThat(StatutEvaluation.BROUILLON).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutEvaluation[] values = StatutEvaluation.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutEvaluation.BROUILLON, + StatutEvaluation.ACTIVE, + StatutEvaluation.MASQUEE, + StatutEvaluation.SIGNALEE, + StatutEvaluation.SUPPRIMEE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutEvaluation.valueOf("BROUILLON")).isEqualTo(StatutEvaluation.BROUILLON); + assertThat(StatutEvaluation.valueOf("ACTIVE")).isEqualTo(StatutEvaluation.ACTIVE); + assertThat(StatutEvaluation.valueOf("MASQUEE")).isEqualTo(StatutEvaluation.MASQUEE); + assertThat(StatutEvaluation.valueOf("SIGNALEE")).isEqualTo(StatutEvaluation.SIGNALEE); + assertThat(StatutEvaluation.valueOf("SUPPRIMEE")).isEqualTo(StatutEvaluation.SUPPRIMEE); + + assertThatThrownBy(() -> StatutEvaluation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutEvaluation.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutEvaluation statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(StatutEvaluation.BROUILLON.getLibelle()).isEqualTo("Brouillon"); + assertThat(StatutEvaluation.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutEvaluation.MASQUEE.getLibelle()).isEqualTo("Masquée"); + assertThat(StatutEvaluation.SIGNALEE.getLibelle()).isEqualTo("Signalée"); + assertThat(StatutEvaluation.SUPPRIMEE.getLibelle()).isEqualTo("Supprimée"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutEvaluation.ACTIVE.name()).isEqualTo("ACTIVE"); + assertThat(StatutEvaluation.BROUILLON.name()).isEqualTo("BROUILLON"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutPropositionTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutPropositionTest.java new file mode 100644 index 0000000..94d9cce --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutPropositionTest.java @@ -0,0 +1,77 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutProposition") +class StatutPropositionTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutProposition.ACTIVE).isNotNull(); + assertThat(StatutProposition.BROUILLON).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutProposition[] values = StatutProposition.values(); + assertThat(values).hasSize(6); + assertThat(values).containsExactly( + StatutProposition.BROUILLON, + StatutProposition.ACTIVE, + StatutProposition.SUSPENDUE, + StatutProposition.EXPIREE, + StatutProposition.TERMINEE, + StatutProposition.ANNULEE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutProposition.valueOf("BROUILLON")).isEqualTo(StatutProposition.BROUILLON); + assertThat(StatutProposition.valueOf("ACTIVE")).isEqualTo(StatutProposition.ACTIVE); + assertThat(StatutProposition.valueOf("SUSPENDUE")).isEqualTo(StatutProposition.SUSPENDUE); + assertThat(StatutProposition.valueOf("EXPIREE")).isEqualTo(StatutProposition.EXPIREE); + assertThat(StatutProposition.valueOf("TERMINEE")).isEqualTo(StatutProposition.TERMINEE); + assertThat(StatutProposition.valueOf("ANNULEE")).isEqualTo(StatutProposition.ANNULEE); + + assertThatThrownBy(() -> StatutProposition.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutProposition.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutProposition statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(StatutProposition.BROUILLON.getLibelle()).isEqualTo("Brouillon"); + assertThat(StatutProposition.ACTIVE.getLibelle()).isEqualTo("Active"); + assertThat(StatutProposition.TERMINEE.getLibelle()).isEqualTo("Terminée"); + assertThat(StatutProposition.ANNULEE.getLibelle()).isEqualTo("Annulée"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutProposition.ACTIVE.name()).isEqualTo("ACTIVE"); + assertThat(StatutProposition.EXPIREE.name()).isEqualTo("EXPIREE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtapeTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtapeTest.java new file mode 100644 index 0000000..807cedd --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/StatutValidationEtapeTest.java @@ -0,0 +1,76 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour StatutValidationEtape") +class StatutValidationEtapeTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutValidationEtape.EN_ATTENTE).isNotNull(); + assertThat(StatutValidationEtape.APPROUVEE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutValidationEtape[] values = StatutValidationEtape.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutValidationEtape.EN_ATTENTE, + StatutValidationEtape.APPROUVEE, + StatutValidationEtape.REJETEE, + StatutValidationEtape.DELEGUEE, + StatutValidationEtape.EXPIREE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutValidationEtape.valueOf("EN_ATTENTE")).isEqualTo(StatutValidationEtape.EN_ATTENTE); + assertThat(StatutValidationEtape.valueOf("APPROUVEE")).isEqualTo(StatutValidationEtape.APPROUVEE); + assertThat(StatutValidationEtape.valueOf("REJETEE")).isEqualTo(StatutValidationEtape.REJETEE); + assertThat(StatutValidationEtape.valueOf("DELEGUEE")).isEqualTo(StatutValidationEtape.DELEGUEE); + assertThat(StatutValidationEtape.valueOf("EXPIREE")).isEqualTo(StatutValidationEtape.EXPIREE); + + assertThatThrownBy(() -> StatutValidationEtape.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutValidationEtape.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(StatutValidationEtape statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(StatutValidationEtape.EN_ATTENTE.getLibelle()).isEqualTo("En attente du valideur"); + assertThat(StatutValidationEtape.APPROUVEE.getLibelle()).isEqualTo("Approuvée"); + assertThat(StatutValidationEtape.REJETEE.getLibelle()).isEqualTo("Rejetée"); + assertThat(StatutValidationEtape.DELEGUEE.getLibelle()).contains("Déléguée"); + assertThat(StatutValidationEtape.EXPIREE.getLibelle()).contains("Expirée"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(StatutValidationEtape.EN_ATTENTE.name()).isEqualTo("EN_ATTENTE"); + assertThat(StatutValidationEtape.APPROUVEE.name()).isEqualTo("APPROUVEE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAideTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAideTest.java new file mode 100644 index 0000000..3068255 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeAideTest.java @@ -0,0 +1,407 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeAide. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeAide") +class TypeAideTest { + + @Test + @DisplayName("Tous les types d'aide doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeAide type : TypeAide.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Tous les types d'aide doivent avoir une catégorie") + void testTousLesTypesOntCategorie() { + for (TypeAide type : TypeAide.values()) { + assertThat(type.getCategorie()).isNotNull().isNotBlank(); + } + } + + @Nested + @DisplayName("Tests isUrgent") + class IsUrgentTests { + + @Test + @DisplayName("isUrgent doit retourner true pour les types avec priorite urgent") + void testIsUrgent() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isUrgent()).isTrue(); + assertThat(TypeAide.AIDE_FRAIS_MEDICAUX.isUrgent()).isTrue(); + assertThat(TypeAide.HEBERGEMENT_URGENCE.isUrgent()).isTrue(); + assertThat(TypeAide.AIDE_ALIMENTAIRE.isUrgent()).isTrue(); + + assertThat(TypeAide.DON_MATERIEL.isUrgent()).isFalse(); + assertThat(TypeAide.TRANSPORT.isUrgent()).isFalse(); + assertThat(TypeAide.AIDE_COTISATION.isUrgent()).isFalse(); + } + } + + @Nested + @DisplayName("Tests isFinancier") + class IsFinancierTests { + + @Test + @DisplayName("isFinancier doit retourner true pour les types financiers") + void testIsFinancier() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isFinancier()).isTrue(); + assertThat(TypeAide.PRET_SANS_INTERET.isFinancier()).isTrue(); + assertThat(TypeAide.AIDE_COTISATION.isFinancier()).isTrue(); + assertThat(TypeAide.AIDE_FRAIS_MEDICAUX.isFinancier()).isTrue(); + assertThat(TypeAide.AIDE_FRAIS_SCOLARITE.isFinancier()).isTrue(); + + assertThat(TypeAide.DON_MATERIEL.isFinancier()).isFalse(); + assertThat(TypeAide.TRANSPORT.isFinancier()).isFalse(); + } + } + + @Nested + @DisplayName("Tests isMateriel") + class IsMaterielTests { + + @Test + @DisplayName("isMateriel doit retourner true pour les types matériels") + void testIsMateriel() { + assertThat(TypeAide.DON_MATERIEL.isMateriel()).isTrue(); + assertThat(TypeAide.PRET_MATERIEL.isMateriel()).isTrue(); + assertThat(TypeAide.AIDE_DEMENAGEMENT.isMateriel()).isTrue(); + assertThat(TypeAide.AIDE_TRAVAUX.isMateriel()).isTrue(); + + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMateriel()).isFalse(); + assertThat(TypeAide.TRANSPORT.isMateriel()).isFalse(); + } + } + + @Nested + @DisplayName("Tests isMontantValide") + class IsMontantValideTests { + + @Test + @DisplayName("isMontantValide doit retourner true si necessiteMontant est false") + void testIsMontantValideNonNecessaire() { + assertThat(TypeAide.DON_MATERIEL.isMontantValide(null)).isTrue(); + assertThat(TypeAide.DON_MATERIEL.isMontantValide(1000.0)).isTrue(); + } + + @Test + @DisplayName("isMontantValide doit retourner true si montant est null et necessiteMontant est true") + void testIsMontantValideNull() { + // Si necessiteMontant est true mais montant est null, retourne true + // (la validation du null se fait ailleurs) + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(null)).isTrue(); + } + + @Test + @DisplayName("isMontantValide doit valider la fourchette") + void testIsMontantValideFourchette() { + // AIDE_FINANCIERE_URGENTE: montantMin=5000, montantMax=50000 + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(10000.0)).isTrue(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(5000.0)).isTrue(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(50000.0)).isTrue(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(4000.0)).isFalse(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(51000.0)).isFalse(); + } + + @Test + @DisplayName("isMontantValide doit gérer montantMin null") + void testIsMontantValideMontantMinNull() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(1000.0)).isFalse(); + } + + @Test + @DisplayName("isMontantValide skip montantMin quand null (reflection)") + void testIsMontantValideMontantMinNullSkip() throws Exception { + java.lang.reflect.Field minF = TypeAide.class.getDeclaredField("montantMin"); + minF.setAccessible(true); + Object backup = minF.get(TypeAide.AIDE_FINANCIERE_URGENTE); + try { + minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(60000.0)).isFalse(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(25000.0)).isTrue(); + } finally { + minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup); + } + } + + @Test + @DisplayName("isMontantValide skip montantMax quand null (reflection)") + void testIsMontantValideMontantMaxNullSkip() throws Exception { + java.lang.reflect.Field maxF = TypeAide.class.getDeclaredField("montantMax"); + maxF.setAccessible(true); + Object backup = maxF.get(TypeAide.AIDE_FINANCIERE_URGENTE); + try { + maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(10000.0)).isTrue(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(4000.0)).isFalse(); + } finally { + maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup); + } + } + } + + @Nested + @DisplayName("Tests getNiveauPriorite") + class GetNiveauPrioriteTests { + + @Test + @DisplayName("getNiveauPriorite doit retourner le bon niveau") + void testGetNiveauPriorite() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getNiveauPriorite()).isEqualTo(1); + assertThat(TypeAide.PRET_SANS_INTERET.getNiveauPriorite()).isEqualTo(2); + assertThat(TypeAide.AIDE_COTISATION.getNiveauPriorite()).isEqualTo(3); + assertThat(TypeAide.DON_MATERIEL.getNiveauPriorite()).isEqualTo(3); + } + } + + @Nested + @DisplayName("Tests getDateLimiteReponse") + class GetDateLimiteReponseTests { + + @Test + @DisplayName("getDateLimiteReponse doit retourner une date future") + void testGetDateLimiteReponse() { + var maintenant = java.time.LocalDateTime.now(); + var dateLimite = TypeAide.AIDE_FINANCIERE_URGENTE.getDateLimiteReponse(); + + assertThat(dateLimite).isAfter(maintenant); + assertThat(dateLimite).isBefore(maintenant.plusDays(8)); // 7 jours + } + } + + @Nested + @DisplayName("Tests getParCategorie") + class GetParCategorieTests { + + @Test + @DisplayName("getParCategorie doit retourner les types de la catégorie") + void testGetParCategorie() { + List financiers = TypeAide.getParCategorie("financiere"); + assertThat(financiers).isNotEmpty(); + assertThat(financiers).contains(TypeAide.AIDE_FINANCIERE_URGENTE); + assertThat(financiers).contains(TypeAide.PRET_SANS_INTERET); + assertThat(financiers).doesNotContain(TypeAide.DON_MATERIEL); + + List materiels = TypeAide.getParCategorie("materielle"); + assertThat(materiels).isNotEmpty(); + assertThat(materiels).contains(TypeAide.DON_MATERIEL); + assertThat(materiels).doesNotContain(TypeAide.AIDE_FINANCIERE_URGENTE); + } + } + + @Nested + @DisplayName("Tests getUrgents") + class GetUrgentsTests { + + @Test + @DisplayName("getUrgents doit retourner tous les types urgents") + void testGetUrgents() { + List urgents = TypeAide.getUrgents(); + + assertThat(urgents).isNotEmpty(); + assertThat(urgents).contains(TypeAide.AIDE_FINANCIERE_URGENTE); + assertThat(urgents).contains(TypeAide.AIDE_FRAIS_MEDICAUX); + assertThat(urgents).contains(TypeAide.HEBERGEMENT_URGENCE); + assertThat(urgents).contains(TypeAide.AIDE_ALIMENTAIRE); + assertThat(urgents).doesNotContain(TypeAide.DON_MATERIEL); + } + } + + @Nested + @DisplayName("Tests getFinanciers") + class GetFinanciersTests { + + @Test + @DisplayName("getFinanciers doit retourner tous les types financiers") + void testGetFinanciers() { + List financiers = TypeAide.getFinanciers(); + + assertThat(financiers).isNotEmpty(); + assertThat(financiers).contains(TypeAide.AIDE_FINANCIERE_URGENTE); + assertThat(financiers).contains(TypeAide.PRET_SANS_INTERET); + assertThat(financiers).contains(TypeAide.AIDE_COTISATION); + assertThat(financiers).contains(TypeAide.AIDE_FRAIS_MEDICAUX); + assertThat(financiers).contains(TypeAide.AIDE_FRAIS_SCOLARITE); + assertThat(financiers).doesNotContain(TypeAide.DON_MATERIEL); + } + } + + @Nested + @DisplayName("Tests getCategories") + class GetCategoriesTests { + + @Test + @DisplayName("getCategories doit retourner toutes les catégories uniques") + void testGetCategories() { + Set categories = TypeAide.getCategories(); + + assertThat(categories).isNotEmpty(); + assertThat(categories).contains("financiere"); + assertThat(categories).contains("materielle"); + assertThat(categories).contains("professionnelle"); + assertThat(categories).contains("sociale"); + assertThat(categories).contains("urgence"); + assertThat(categories).contains("specialisee"); + assertThat(categories).contains("autre"); + } + } + + @Nested + @DisplayName("Tests getLibelleCategorie") + class GetLibelleCategorieTests { + + @Test + @DisplayName("getLibelleCategorie doit retourner le bon libellé") + void testGetLibelleCategorie() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getLibelleCategorie()) + .isEqualTo("Aide financière"); + assertThat(TypeAide.DON_MATERIEL.getLibelleCategorie()).isEqualTo("Aide matérielle"); + assertThat(TypeAide.AIDE_RECHERCHE_EMPLOI.getLibelleCategorie()) + .isEqualTo("Aide professionnelle"); + assertThat(TypeAide.GARDE_ENFANTS.getLibelleCategorie()).isEqualTo("Aide sociale"); + assertThat(TypeAide.HEBERGEMENT_URGENCE.getLibelleCategorie()).isEqualTo("Aide d'urgence"); + assertThat(TypeAide.SOUTIEN_PSYCHOLOGIQUE.getLibelleCategorie()) + .isEqualTo("Aide spécialisée"); + assertThat(TypeAide.AUTRE.getLibelleCategorie()).isEqualTo("Autre"); + } + + @Test + @DisplayName("getLibelleCategorie retourne default (catégorie brute) pour catégorie inconnue") + void testGetLibelleCategorieInconnue() throws Exception { + java.lang.reflect.Field f = TypeAide.class.getDeclaredField("categorie"); + f.setAccessible(true); + String backup = TypeAide.AUTRE.getCategorie(); + try { + f.set(TypeAide.AUTRE, "inconnue"); + assertThat(TypeAide.AUTRE.getLibelleCategorie()).isEqualTo("inconnue"); + } finally { + f.set(TypeAide.AUTRE, backup); + } + } + } + + @Nested + @DisplayName("getNiveauPriorite default") + class GetNiveauPrioriteDefault { + + @Test + @DisplayName("getNiveauPriorite retourne 3 (default) pour priorite inconnue") + void testDefault() throws Exception { + java.lang.reflect.Field f = TypeAide.class.getDeclaredField("priorite"); + f.setAccessible(true); + String backup = TypeAide.AUTRE.getPriorite(); + try { + f.set(TypeAide.AUTRE, "inconnu"); + assertThat(TypeAide.AUTRE.getNiveauPriorite()).isEqualTo(3); + } finally { + f.set(TypeAide.AUTRE, backup); + } + } + } + + @Nested + @DisplayName("Tests getUniteMontant") + class GetUniteMontantTests { + + @Test + @DisplayName("getUniteMontant doit retourner FCFA si necessiteMontant est true") + void testGetUniteMontant() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getUniteMontant()).isEqualTo("FCFA"); + assertThat(TypeAide.PRET_SANS_INTERET.getUniteMontant()).isEqualTo("FCFA"); + assertThat(TypeAide.DON_MATERIEL.getUniteMontant()).isNull(); + assertThat(TypeAide.TRANSPORT.getUniteMontant()).isNull(); + } + } + + @Nested + @DisplayName("Tests getMessageValidationMontant") + class GetMessageValidationMontantTests { + + @Test + @DisplayName("getMessageValidationMontant doit retourner null si necessiteMontant est false") + void testGetMessageValidationMontantNonNecessaire() { + assertThat(TypeAide.DON_MATERIEL.getMessageValidationMontant(null)).isNull(); + assertThat(TypeAide.DON_MATERIEL.getMessageValidationMontant(1000.0)).isNull(); + } + + @Test + @DisplayName("getMessageValidationMontant doit retourner message si montant est null") + void testGetMessageValidationMontantNull() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(null)) + .isEqualTo("Le montant est obligatoire"); + } + + @Test + @DisplayName("getMessageValidationMontant doit retourner message si montant < montantMin") + void testGetMessageValidationMontantTropBas() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(4000.0)) + .contains("minimum"); + } + + @Test + @DisplayName("getMessageValidationMontant doit retourner message si montant > montantMax") + void testGetMessageValidationMontantTropHaut() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(51000.0)) + .contains("maximum"); + } + + @Test + @DisplayName("getMessageValidationMontant doit retourner null si montant est valide") + void testGetMessageValidationMontantValide() { + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(10000.0)) + .isNull(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(5000.0)) + .isNull(); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(50000.0)) + .isNull(); + } + + @Test + @DisplayName("getMessageValidationMontant skip montantMin quand null (reflection)") + void testGetMessageValidationMontantMontantMinNull() throws Exception { + java.lang.reflect.Field minF = TypeAide.class.getDeclaredField("montantMin"); + minF.setAccessible(true); + Object backup = minF.get(TypeAide.AIDE_FINANCIERE_URGENTE); + try { + minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(60000.0)) + .contains("maximum"); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(25000.0)) + .isNull(); + } finally { + minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup); + } + } + + @Test + @DisplayName("getMessageValidationMontant skip montantMax quand null (reflection)") + void testGetMessageValidationMontantMontantMaxNull() throws Exception { + java.lang.reflect.Field maxF = TypeAide.class.getDeclaredField("montantMax"); + maxF.setAccessible(true); + Object backup = maxF.get(TypeAide.AIDE_FINANCIERE_URGENTE); + try { + maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(4000.0)) + .contains("minimum"); + assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(10000.0)) + .isNull(); + } finally { + maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup); + } + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluationTest.java new file mode 100644 index 0000000..a9215f3 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeEvaluationTest.java @@ -0,0 +1,76 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeEvaluation") +class TypeEvaluationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeEvaluation.SATISFACTION_BENEFICIAIRE).isNotNull(); + assertThat(TypeEvaluation.EVALUATION_IMPACT).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeEvaluation[] values = TypeEvaluation.values(); + assertThat(values).hasSize(6); + assertThat(values).containsExactly( + TypeEvaluation.SATISFACTION_BENEFICIAIRE, + TypeEvaluation.EVALUATION_PROPOSANT, + TypeEvaluation.EVALUATION_PROCESSUS, + TypeEvaluation.SUIVI_POST_AIDE, + TypeEvaluation.EVALUATION_IMPACT, + TypeEvaluation.RETOUR_EXPERIENCE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypeEvaluation.valueOf("SATISFACTION_BENEFICIAIRE")).isEqualTo(TypeEvaluation.SATISFACTION_BENEFICIAIRE); + assertThat(TypeEvaluation.valueOf("EVALUATION_PROPOSANT")).isEqualTo(TypeEvaluation.EVALUATION_PROPOSANT); + assertThat(TypeEvaluation.valueOf("EVALUATION_PROCESSUS")).isEqualTo(TypeEvaluation.EVALUATION_PROCESSUS); + assertThat(TypeEvaluation.valueOf("SUIVI_POST_AIDE")).isEqualTo(TypeEvaluation.SUIVI_POST_AIDE); + assertThat(TypeEvaluation.valueOf("EVALUATION_IMPACT")).isEqualTo(TypeEvaluation.EVALUATION_IMPACT); + assertThat(TypeEvaluation.valueOf("RETOUR_EXPERIENCE")).isEqualTo(TypeEvaluation.RETOUR_EXPERIENCE); + + assertThatThrownBy(() -> TypeEvaluation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeEvaluation.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeEvaluation type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(TypeEvaluation.SATISFACTION_BENEFICIAIRE.getLibelle()).isEqualTo("Satisfaction du bénéficiaire"); + assertThat(TypeEvaluation.EVALUATION_IMPACT.getLibelle()).isEqualTo("Évaluation d'impact"); + assertThat(TypeEvaluation.RETOUR_EXPERIENCE.getLibelle()).isEqualTo("Retour d'expérience"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(TypeEvaluation.SATISFACTION_BENEFICIAIRE.name()).isEqualTo("SATISFACTION_BENEFICIAIRE"); + assertThat(TypeEvaluation.EVALUATION_IMPACT.name()).isEqualTo("EVALUATION_IMPACT"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflowTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflowTest.java new file mode 100644 index 0000000..a482b3e --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/solidarite/TypeWorkflowTest.java @@ -0,0 +1,70 @@ +package dev.lions.unionflow.server.api.enums.solidarite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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.EnumSource; + +@DisplayName("Tests pour TypeWorkflow") +class TypeWorkflowTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeWorkflow.DEMANDE_AIDE).isNotNull(); + assertThat(TypeWorkflow.ADHESION).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeWorkflow[] values = TypeWorkflow.values(); + assertThat(values).hasSize(3); + assertThat(values).containsExactly( + TypeWorkflow.DEMANDE_AIDE, + TypeWorkflow.ADHESION, + TypeWorkflow.AUTRE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypeWorkflow.valueOf("DEMANDE_AIDE")).isEqualTo(TypeWorkflow.DEMANDE_AIDE); + assertThat(TypeWorkflow.valueOf("ADHESION")).isEqualTo(TypeWorkflow.ADHESION); + assertThat(TypeWorkflow.valueOf("AUTRE")).isEqualTo(TypeWorkflow.AUTRE); + + assertThatThrownBy(() -> TypeWorkflow.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeWorkflow.class) + @DisplayName("Test getLibelle pour toutes les valeurs") + void testGetLibelle(TypeWorkflow type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test getLibelle valeurs exactes") + void testGetLibelleValeursExactes() { + assertThat(TypeWorkflow.DEMANDE_AIDE.getLibelle()).isEqualTo("Workflow demande d'aide solidarité"); + assertThat(TypeWorkflow.ADHESION.getLibelle()).isEqualTo("Workflow validation adhésion"); + assertThat(TypeWorkflow.AUTRE.getLibelle()).isEqualTo("Workflow personnalisé"); + } + + @Test + @DisplayName("Test name()") + void testName() { + assertThat(TypeWorkflow.DEMANDE_AIDE.name()).isEqualTo("DEMANDE_AIDE"); + assertThat(TypeWorkflow.AUTRE.name()).isEqualTo("AUTRE"); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWaveTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWaveTest.java new file mode 100644 index 0000000..67b5cba --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutCompteWaveTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.wave; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour StatutCompteWave. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests StatutCompteWave") +class StatutCompteWaveTest { + + @Test + @DisplayName("Tous les statuts de compte Wave doivent avoir un libellé") + void testTousLesStatutsOntLibelle() { + for (StatutCompteWave statut : StatutCompteWave.values()) { + assertThat(statut.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(StatutCompteWave.values().length).isGreaterThan(0); + for (StatutCompteWave statut : StatutCompteWave.values()) { + assertThat(statut).isNotNull(); + assertThat(statut.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWaveTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWaveTest.java new file mode 100644 index 0000000..c519a12 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutTransactionWaveTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.wave; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour StatutTransactionWave. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests StatutTransactionWave") +class StatutTransactionWaveTest { + + @Test + @DisplayName("Tous les statuts de transaction Wave doivent avoir un libellé") + void testTousLesStatutsOntLibelle() { + for (StatutTransactionWave statut : StatutTransactionWave.values()) { + assertThat(statut.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(StatutTransactionWave.values().length).isGreaterThan(0); + for (StatutTransactionWave statut : StatutTransactionWave.values()) { + assertThat(statut).isNotNull(); + assertThat(statut.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhookTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhookTest.java new file mode 100644 index 0000000..23e44b7 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/wave/StatutWebhookTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.wave; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour StatutWebhook. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests StatutWebhook") +class StatutWebhookTest { + + @Test + @DisplayName("Tous les statuts de webhook doivent avoir un libellé") + void testTousLesStatutsOntLibelle() { + for (StatutWebhook statut : StatutWebhook.values()) { + assertThat(statut.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(StatutWebhook.values().length).isGreaterThan(0); + for (StatutWebhook statut : StatutWebhook.values()) { + assertThat(statut).isNotNull(); + assertThat(statut.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhookTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhookTest.java new file mode 100644 index 0000000..4096931 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeEvenementWebhookTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.wave; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeEvenementWebhook. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeEvenementWebhook") +class TypeEvenementWebhookTest { + + @Test + @DisplayName("Tous les types d'événement webhook doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeEvenementWebhook type : TypeEvenementWebhook.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeEvenementWebhook.values().length).isGreaterThan(0); + for (TypeEvenementWebhook type : TypeEvenementWebhook.values()) { + assertThat(type).isNotNull(); + assertThat(type.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWaveTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWaveTest.java new file mode 100644 index 0000000..ab1d4ea --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/wave/TypeTransactionWaveTest.java @@ -0,0 +1,36 @@ +package dev.lions.unionflow.server.api.enums.wave; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour TypeTransactionWave. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests TypeTransactionWave") +class TypeTransactionWaveTest { + + @Test + @DisplayName("Tous les types de transaction Wave doivent avoir un libellé") + void testTousLesTypesOntLibelle() { + for (TypeTransactionWave type : TypeTransactionWave.values()) { + assertThat(type.getLibelle()).isNotNull().isNotBlank(); + } + } + + @Test + @DisplayName("Toutes les valeurs de l'enum doivent être présentes") + void testToutesLesValeurs() { + assertThat(TypeTransactionWave.values().length).isGreaterThan(0); + for (TypeTransactionWave type : TypeTransactionWave.values()) { + assertThat(type).isNotNull(); + assertThat(type.name()).isNotBlank(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/service/dashboard/DashboardServiceTest.java b/src/test/java/dev/lions/unionflow/server/api/service/dashboard/DashboardServiceTest.java new file mode 100644 index 0000000..1c2bd77 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/service/dashboard/DashboardServiceTest.java @@ -0,0 +1,91 @@ +package dev.lions.unionflow.server.api.service.dashboard; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataResponse; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsResponse; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityResponse; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventResponse; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour DashboardService. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-12-09 + */ +@DisplayName("Tests DashboardService") +class DashboardServiceTest { + + /** + * Implémentation de test pour DashboardService. + */ + static class TestDashboardService implements DashboardService { + + @Override + public DashboardDataResponse getDashboardData(String organizationId, String userId) { + DashboardDataResponse data = new DashboardDataResponse(); + data.setStats(new DashboardStatsResponse()); + return data; + } + + @Override + public DashboardStatsResponse getDashboardStats(String organizationId, String userId) { + return new DashboardStatsResponse(); + } + + @Override + public List getRecentActivities( + String organizationId, String userId, int limit) { + return List.of(); + } + + @Override + public List getUpcomingEvents(String organizationId, String userId, int limit) { + return List.of(); + } + } + + @Test + @DisplayName("L'interface peut être implémentée") + void testInterfacePeutEtreImplementee() { + DashboardService service = new TestDashboardService(); + assertThat(service).isNotNull(); + } + + @Test + @DisplayName("getDashboardData retourne un DashboardDataResponse") + void testGetDashboardData() { + DashboardService service = new TestDashboardService(); + DashboardDataResponse result = service.getDashboardData("org1", "user1"); + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("getDashboardStats retourne un DashboardStatsResponse") + void testGetDashboardStats() { + DashboardService service = new TestDashboardService(); + DashboardStatsResponse result = service.getDashboardStats("org1", "user1"); + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("getRecentActivities retourne une liste") + void testGetRecentActivities() { + DashboardService service = new TestDashboardService(); + List result = service.getRecentActivities("org1", "user1", 10); + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("getUpcomingEvents retourne une liste") + void testGetUpcomingEvents() { + DashboardService service = new TestDashboardService(); + List result = service.getUpcomingEvents("org1", "user1", 10); + assertThat(result).isNotNull(); + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/api/validation/ValidationConstantsTest.java b/src/test/java/dev/lions/unionflow/server/api/validation/ValidationConstantsTest.java new file mode 100644 index 0000000..43364cd --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/validation/ValidationConstantsTest.java @@ -0,0 +1,207 @@ +package dev.lions.unionflow.server.api.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Constructor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests unitaires pour ValidationConstants - Couverture 100% + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@DisplayName("Tests ValidationConstants") +class ValidationConstantsTest { + + @Test + @DisplayName("Test constructeur privé") + void testConstructeurPrive() throws Exception { + Constructor constructor = ValidationConstants.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + // Le constructeur doit être accessible et créer une instance + ValidationConstants instance = constructor.newInstance(); + assertThat(instance).isNotNull(); + } + + @Nested + @DisplayName("Tests des constantes de taille") + class TestsConstantesTaille { + + @Test + @DisplayName("Test constantes titre") + void testConstantesTitre() { + assertThat(ValidationConstants.TITRE_MIN_LENGTH).isEqualTo(5); + assertThat(ValidationConstants.TITRE_MAX_LENGTH).isEqualTo(100); + assertThat(ValidationConstants.TITRE_SIZE_MESSAGE).contains("5").contains("100").contains("titre"); + } + + @Test + @DisplayName("Test constantes nom organisation") + void testConstantesNomOrganisation() { + assertThat(ValidationConstants.NOM_ORGANISATION_MIN_LENGTH).isEqualTo(2); + assertThat(ValidationConstants.NOM_ORGANISATION_MAX_LENGTH).isEqualTo(200); + assertThat(ValidationConstants.NOM_ORGANISATION_SIZE_MESSAGE) + .contains("2") + .contains("200") + .contains("nom"); + } + + @Test + @DisplayName("Test constantes description") + void testConstantesDescription() { + assertThat(ValidationConstants.DESCRIPTION_MIN_LENGTH).isEqualTo(20); + assertThat(ValidationConstants.DESCRIPTION_MAX_LENGTH).isEqualTo(2000); + assertThat(ValidationConstants.DESCRIPTION_SIZE_MESSAGE) + .contains("20") + .contains("2000") + .contains("description"); + } + + @Test + @DisplayName("Test constantes description courte") + void testConstantesDescriptionCourte() { + assertThat(ValidationConstants.DESCRIPTION_COURTE_MAX_LENGTH).isEqualTo(1000); + assertThat(ValidationConstants.DESCRIPTION_COURTE_SIZE_MESSAGE) + .contains("1000") + .contains("description"); + } + + @Test + @DisplayName("Test constantes justification") + void testConstantesJustification() { + assertThat(ValidationConstants.JUSTIFICATION_MAX_LENGTH).isEqualTo(1000); + assertThat(ValidationConstants.JUSTIFICATION_SIZE_MESSAGE) + .contains("1000") + .contains("justification"); + } + + @Test + @DisplayName("Test constantes commentaires") + void testConstantesCommentaires() { + assertThat(ValidationConstants.COMMENTAIRES_MAX_LENGTH).isEqualTo(1000); + assertThat(ValidationConstants.COMMENTAIRES_SIZE_MESSAGE) + .contains("1000") + .contains("commentaires"); + } + + @Test + @DisplayName("Test constantes raison rejet") + void testConstantesRaisonRejet() { + assertThat(ValidationConstants.RAISON_REJET_MAX_LENGTH).isEqualTo(500); + assertThat(ValidationConstants.RAISON_REJET_SIZE_MESSAGE).contains("500").contains("rejet"); + } + + @Test + @DisplayName("Test constantes email") + void testConstantesEmail() { + assertThat(ValidationConstants.EMAIL_MAX_LENGTH).isEqualTo(100); + assertThat(ValidationConstants.EMAIL_SIZE_MESSAGE).contains("100").contains("email"); + } + + @Test + @DisplayName("Test constantes nom et prénom") + void testConstantesNomPrenom() { + assertThat(ValidationConstants.NOM_PRENOM_MIN_LENGTH).isEqualTo(2); + assertThat(ValidationConstants.NOM_PRENOM_MAX_LENGTH).isEqualTo(50); + assertThat(ValidationConstants.NOM_SIZE_MESSAGE).contains("2").contains("50").contains("nom"); + assertThat(ValidationConstants.PRENOM_SIZE_MESSAGE).contains("2").contains("50").contains("prénom"); + } + } + + @Nested + @DisplayName("Tests des patterns de validation") + class TestsPatternsValidation { + + @Test + @DisplayName("Test patterns téléphone") + void testPatternsTelephone() { + assertThat(ValidationConstants.TELEPHONE_PATTERN).isNotNull(); + assertThat(ValidationConstants.TELEPHONE_MESSAGE).contains("téléphone"); + } + + @Test + @DisplayName("Test patterns devise") + void testPatternsDevise() { + assertThat(ValidationConstants.DEVISE_PATTERN).isNotNull(); + assertThat(ValidationConstants.DEVISE_MESSAGE).contains("devise"); + } + + @Test + @DisplayName("Test patterns référence aide") + void testPatternsReferenceAide() { + assertThat(ValidationConstants.REFERENCE_AIDE_PATTERN).isNotNull(); + assertThat(ValidationConstants.REFERENCE_AIDE_MESSAGE).contains("référence"); + } + + @Test + @DisplayName("Test patterns numéro membre") + void testPatternsNumeroMembre() { + assertThat(ValidationConstants.NUMERO_MEMBRE_PATTERN).isNotNull(); + assertThat(ValidationConstants.NUMERO_MEMBRE_MESSAGE).contains("numéro"); + } + + @Test + @DisplayName("Test patterns couleur hexadécimale") + void testPatternsCouleurHex() { + assertThat(ValidationConstants.COULEUR_HEX_PATTERN).isNotNull(); + assertThat(ValidationConstants.COULEUR_HEX_MESSAGE).contains("couleur"); + } + } + + @Nested + @DisplayName("Tests des messages obligatoires") + class TestsMessagesObligatoires { + + @Test + @DisplayName("Test message obligatoire") + void testMessageObligatoire() { + assertThat(ValidationConstants.OBLIGATOIRE_MESSAGE).contains("obligatoire"); + } + + @Test + @DisplayName("Test message email format") + void testMessageEmailFormat() { + assertThat(ValidationConstants.EMAIL_FORMAT_MESSAGE).contains("email"); + } + + @Test + @DisplayName("Test messages de date") + void testMessagesDate() { + assertThat(ValidationConstants.DATE_PASSEE_MESSAGE).contains("passé"); + assertThat(ValidationConstants.DATE_FUTURE_MESSAGE).contains("futur"); + } + } + + @Nested + @DisplayName("Tests des constantes numériques") + class TestsConstantesNumeriques { + + @Test + @DisplayName("Test constantes montant") + void testConstantesMontant() { + assertThat(ValidationConstants.MONTANT_MIN_VALUE).isEqualTo("0.0"); + assertThat(ValidationConstants.MONTANT_INTEGER_DIGITS).isEqualTo(10); + assertThat(ValidationConstants.MONTANT_FRACTION_DIGITS).isEqualTo(2); + assertThat(ValidationConstants.MONTANT_DIGITS_MESSAGE).contains("10").contains("2"); + assertThat(ValidationConstants.MONTANT_POSITIF_MESSAGE).contains("positif"); + } + } + + @Test + @DisplayName("Test toutes les constantes sont non nulles") + void testToutesConstantesNonNulles() { + // Vérification que toutes les constantes String sont non nulles + assertThat(ValidationConstants.TITRE_SIZE_MESSAGE).isNotNull(); + assertThat(ValidationConstants.NOM_ORGANISATION_SIZE_MESSAGE).isNotNull(); + assertThat(ValidationConstants.DESCRIPTION_SIZE_MESSAGE).isNotNull(); + assertThat(ValidationConstants.TELEPHONE_PATTERN).isNotNull(); + assertThat(ValidationConstants.DEVISE_PATTERN).isNotNull(); + assertThat(ValidationConstants.OBLIGATOIRE_MESSAGE).isNotNull(); + assertThat(ValidationConstants.EMAIL_FORMAT_MESSAGE).isNotNull(); + } +}