From 5fa4711a8ff312297cd3a62ddeabb4b43920fcff Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:51:10 +0000 Subject: [PATCH] =?UTF-8?q?feat(api):=20DTOs=20v3.0=20=E2=80=94=20membre?= =?UTF-8?q?=20computed=20fields,=20CreateMembreRequest=20builder,=20souscr?= =?UTF-8?q?iption=20DTOs,=20RBAC=20enums=20enrichis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MembreResponse: ajout getInitiales(), getDateAdhesionFormatee(), getStatut(), getSeverity() computed - CotisationResponse: alias getMethodePaiementLibelle() - CreateMembreRequest: builder pattern, organisationId UUID - Nouveaux DTOs souscription: FormuleAbonnementResponse, SouscriptionDemandeRequest, SouscriptionStatutResponse - Nouveaux enums: PlageMembres, StatutValidationSouscription, TypeOrganisationFacturation - StatutMembre enrichi (RADIE, ARCHIVE, INVITE, EN_ATTENTE_VALIDATION) - TypeOrganisation, TypeFormule, StatutSouscription mis à jour --- .../server/api/dto/common/ErrorResponse.java | 21 ++ .../response/CotisationResponse.java | 26 ++ .../evenement/response/EvenementResponse.java | 73 +++++ .../membre/request/CreateMembreRequest.java | 2 +- .../membre/request/UpdateMembreRequest.java | 2 +- .../dto/membre/response/MembreResponse.java | 68 ++++- .../response/MembreSummaryResponse.java | 110 +++++++- .../request/CreateOrganisationRequest.java | 9 +- .../request/UpdateOrganisationRequest.java | 9 +- .../InitierPaiementEnLigneRequest.java | 2 +- .../response/IntentionStatutResponse.java | 24 ++ .../FormuleAbonnementResponse.java | 128 +++++++++ .../SouscriptionDemandeRequest.java | 79 ++++++ .../SouscriptionStatutResponse.java | 155 ++++++++++ .../api/enums/abonnement/PlageMembres.java | 64 +++++ .../enums/abonnement/StatutSouscription.java | 1 + .../StatutValidationSouscription.java | 43 +++ .../api/enums/abonnement/TypeFormule.java | 20 +- .../TypeOrganisationFacturation.java | 51 ++++ .../abonnement/TypePeriodeAbonnement.java | 46 ++- .../server/api/enums/membre/StatutMembre.java | 4 +- .../enums/organisation/TypeOrganisation.java | 13 +- .../unionflow/server/api/TestDataFactory.java | 2 +- .../response/CotisationResponseTest.java | 96 +++++++ .../response/EvenementResponseTest.java | 255 +++++++++++++++++ .../dto/membre/MembreSearchResultDTOTest.java | 2 +- .../dto/membre/MembreSummaryResponseTest.java | 266 ++++++++++++++++++ .../request/CreateMembreRequestTest.java | 2 +- .../CreateOrganisationRequestTest.java | 67 +++++ .../UpdateOrganisationRequestTest.java | 51 ++++ .../InitierPaiementEnLigneRequestTest.java | 4 +- .../response/IntentionStatutResponseTest.java | 112 ++++++++ .../souscription/SouscriptionDtosTest.java | 172 +++++++++++ .../api/enums/EnumsRefactoringTest.java | 3 +- .../enums/abonnement/PlageMembresTest.java | 125 ++++++++ .../abonnement/StatutSouscriptionTest.java | 4 +- .../StatutValidationSouscriptionTest.java | 89 ++++++ .../api/enums/abonnement/TypeFormuleTest.java | 12 +- .../TypeOrganisationFacturationTest.java | 124 ++++++++ .../abonnement/TypePeriodeAbonnementTest.java | 30 +- .../api/enums/membre/StatutMembreTest.java | 6 +- .../organisation/TypeOrganisationTest.java | 16 -- 42 files changed, 2330 insertions(+), 58 deletions(-) create mode 100644 src/main/java/dev/lions/unionflow/server/api/dto/common/ErrorResponse.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponse.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/dto/souscription/FormuleAbonnementResponse.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDemandeRequest.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionStatutResponse.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembres.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscription.java create mode 100644 src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturation.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSummaryResponseTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponseTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDtosTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembresTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscriptionTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturationTest.java diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/common/ErrorResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/common/ErrorResponse.java new file mode 100644 index 0000000..6ea5870 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/common/ErrorResponse.java @@ -0,0 +1,21 @@ +package dev.lions.unionflow.server.api.dto.common; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * DTO d'erreur unifié retourné par tous les endpoints REST. + * Remplace les ErrorResponse locales dupliquées dans chaque Resource. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ErrorResponse(String message, String error) { + + /** Constructeur pratique avec message uniquement (cas le plus courant). */ + public static ErrorResponse of(String message) { + return new ErrorResponse(message, null); + } + + /** Constructeur pratique avec les deux champs (compatibilité avec l'ancien format). */ + public static ErrorResponse ofError(String error) { + return new ErrorResponse(null, error); + } +} 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 index 4c45b0b..4e55c55 100644 --- 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 @@ -96,6 +96,32 @@ public class CotisationResponse extends BaseResponse { private Long joursRetard; private Boolean enRetard; + // === MÉTHODES DE FORMATAGE === + + public String getMontantDuFormatte() { + if (montantDu == null) return "0 FCFA"; + return String.format(java.util.Locale.US, "%,.0f %s", montantDu, codeDevise != null ? codeDevise : "FCFA"); + } + + public String getMontantPayeFormatte() { + if (montantPaye == null) return "0 FCFA"; + return String.format(java.util.Locale.US, "%,.0f %s", montantPaye, codeDevise != null ? codeDevise : "FCFA"); + } + + public String getMontantRestantFormatte() { + if (montantRestant == null) return "0 FCFA"; + return String.format(java.util.Locale.US, "%,.0f %s", montantRestant, codeDevise != null ? codeDevise : "FCFA"); + } + + public boolean isMontantRestantPositif() { + return montantRestant != null && montantRestant.signum() > 0; + } + + /** Alias de {@link #modePaiementLibelle} pour #{cotisation.methodePaiementLibelle}. */ + public String getMethodePaiementLibelle() { + return modePaiementLibelle; + } + // Informations de paiement private String methodePaiement; // WAVE_MONEY, VIREMENT, ESPECES, CARTE, MOBILE_MONEY private String referencePaiement; // Référence externe du paiement 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 index 14ef9c9..c2cddb8 100644 --- 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 @@ -5,6 +5,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; 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; @@ -151,10 +152,82 @@ public class EvenementResponse extends BaseResponse { return typeEvenement != null ? typeEvenement.getLibelle() : "Non défini"; } + public String getTypeEvenementIcon() { + if (typeEvenement == null) return "pi pi-calendar"; + return switch (typeEvenement) { + case ASSEMBLEE_GENERALE -> "pi pi-building"; + case FORMATION -> "pi pi-book"; + case REUNION_BUREAU -> "pi pi-users"; + case CONFERENCE -> "pi pi-microphone"; + case ATELIER -> "pi pi-wrench"; + case CEREMONIE -> "pi pi-flag"; + case ACTIVITE_SOCIALE, ACTION_CARITATIVE, AUTRE -> "pi pi-calendar"; + }; + } + + public String getTypeEvenementSeverity() { + if (typeEvenement == null) return "secondary"; + return switch (typeEvenement) { + case ASSEMBLEE_GENERALE -> "warning"; + case FORMATION -> "info"; + case ACTIVITE_SOCIALE, ACTION_CARITATIVE, REUNION_BUREAU, CONFERENCE, ATELIER, CEREMONIE, AUTRE -> "secondary"; + }; + } + public String getStatutLibelle() { return statut != null ? statut.getLibelle() : "Non défini"; } + public String getStatutSeverity() { + if (statut == null) return "secondary"; + return switch (statut) { + case PLANIFIE -> "info"; + case EN_COURS -> "success"; + case TERMINE, CONFIRME -> "secondary"; + case ANNULE -> "danger"; + case REPORTE -> "warning"; + }; + } + + public String getDateDebutFormatee() { + return dateDebut != null ? dateDebut.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) : "—"; + } + + public String getHeureDebutFormatee() { + return heureDebut != null ? heureDebut.format(DateTimeFormatter.ofPattern("HH:mm")) : "—"; + } + + public String getHeureFinFormatee() { + return heureFin != null ? heureFin.format(DateTimeFormatter.ofPattern("HH:mm")) : "—"; + } + + public String getStatutIcon() { + if (statut == null) return "pi pi-question"; + return switch (statut) { + case PLANIFIE -> "pi pi-clock"; + case CONFIRME -> "pi pi-check-circle"; + case EN_COURS -> "pi pi-play"; + case TERMINE -> "pi pi-check"; + case ANNULE -> "pi pi-times"; + case REPORTE -> "pi pi-refresh"; + }; + } + + public String getPrioriteSeverity() { + if (priorite == null) return "secondary"; + return switch (priorite) { + case CRITIQUE -> "danger"; + case HAUTE -> "warning"; + case NORMALE -> "info"; + case BASSE -> "secondary"; + }; + } + + public String getBudgetFormate() { + if (budget == null) return "—"; + return String.format(java.util.Locale.US, "%,.0f %s", budget, codeDevise != null ? codeDevise : "FCFA"); + } + public String getPrioriteLibelle() { return priorite != null ? priorite.getLibelle() : "Normale"; } 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 index 9482909..1a57671 100644 --- 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 @@ -40,7 +40,7 @@ public record CreateMembreRequest( @NotBlank @Size(max = 100) String nom, @NotBlank @Email @Size(max = 255) String email, @Size(max = 20) String telephone, - @Size(max = 13) String telephoneWave, + @Size(max = 20) String telephoneWave, @NotNull LocalDate dateNaissance, @Size(max = 100) String profession, @Size(max = 500) String photoUrl, 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 index 8035c99..59f2c70 100644 --- 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 @@ -14,7 +14,7 @@ public record UpdateMembreRequest( @NotBlank @Size(max = 100) String nom, @NotBlank @Email @Size(max = 255) String email, @Size(max = 20) String telephone, - @Size(max = 13) String telephoneWave, + @Size(max = 20) String telephoneWave, @NotNull LocalDate dateNaissance, @Size(max = 100) String profession, @Size(max = 500) String photoUrl, 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 index a55a99f..ae38058 100644 --- 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 @@ -2,6 +2,7 @@ package dev.lions.unionflow.server.api.dto.membre.response; import dev.lions.unionflow.server.api.dto.base.BaseResponse; import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.UUID; import java.util.List; import lombok.AllArgsConstructor; @@ -65,6 +66,71 @@ public class MembreResponse extends BaseResponse { // ── Adhésion (contexte organisation) ─────── private UUID organisationId; - private String associationNom; + private String organisationNom; private LocalDate dateAdhesion; + + // ── Adresse principale ───────────────────── + private String adresse; + private String ville; + private String codePostal; + + // ── Notes / biographie ───────────────────── + private String notes; + + // ── Statistiques ─────────────────────────── + private int nombreEvenementsParticipes; + + // ── Provisionnement (retourné une seule fois à la création) ──── + /** Mot de passe temporaire généré lors du provisionnement Keycloak. + * Null pour toutes les autres opérations. L'utilisateur devra le changer à la première connexion. */ + private String motDePasseTemporaire; + + // ── Méthodes calculées pour la compatibilité JSF EL ──────────── + + /** Initiales (première lettre prénom + première lettre nom) pour les avatars JSF. */ + public String getInitiales() { + String p = (prenom != null && !prenom.isEmpty()) ? prenom.substring(0, 1).toUpperCase() : ""; + String n = (nom != null && !nom.isEmpty()) ? nom.substring(0, 1).toUpperCase() : ""; + return p + n; + } + + /** Date d'adhésion formatée "dd/MM/yyyy" pour #{membre.dateAdhesionFormatee}. */ + public String getDateAdhesionFormatee() { + if (dateAdhesion == null) return null; + return dateAdhesion.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + + /** Alias de statutCompte pour #{membre.statut}. */ + public String getStatut() { + return statutCompte; + } + + /** Alias de statutCompteSeverity pour #{membre.statutSeverity}. */ + public String getStatutSeverity() { + return statutCompteSeverity; + } + + /** Libellé du rôle principal calculé depuis les rôles. */ + public String getTypeMembre() { + if (roles == null || roles.isEmpty()) return "Membre"; + if (roles.contains("PRESIDENT")) return "Président"; + if (roles.contains("VICE_PRESIDENT")) return "Vice-Président"; + if (roles.contains("SECRETAIRE")) return "Secrétaire"; + if (roles.contains("TRESORIER")) return "Trésorier"; + if (roles.contains("ADMIN_ORGANISATION")) return "Administrateur"; + if (roles.contains("MODERATEUR")) return "Modérateur"; + return "Membre"; + } + + /** Severity PrimeUI calculée depuis les rôles. */ + public String getTypeSeverity() { + if (roles == null || roles.isEmpty()) return "secondary"; + if (roles.contains("PRESIDENT")) return "primary"; + if (roles.contains("VICE_PRESIDENT")) return "primary"; + if (roles.contains("SECRETAIRE")) return "info"; + if (roles.contains("TRESORIER")) return "warning"; + if (roles.contains("ADMIN_ORGANISATION")) return "danger"; + if (roles.contains("MODERATEUR")) return "warning"; + return "secondary"; + } } 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 index 8a68bbc..1afc5df 100644 --- 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 @@ -1,10 +1,14 @@ package dev.lions.unionflow.server.api.dto.membre.response; -import java.util.UUID; +import java.time.LocalDate; import java.util.List; +import java.util.UUID; /** * DTO de réponse résumé pour Membre (listes et optimisations). + * + *

Ce record expose des méthodes calculées pour la compatibilité JSF EL : + * {@link #nomComplet()}, {@link #statut()}, {@link #typeMembre()}, etc. */ public record MembreSummaryResponse( UUID id, @@ -20,5 +24,107 @@ public record MembreSummaryResponse( Boolean actif, List roles, UUID organisationId, - String associationNom) { + String organisationNom, + LocalDate dateAdhesion) { + + // ── Getters JavaBean pour compatibilité JSF EL (EL cherche getXxx()) ────── + + public UUID getId() { return id; } + public String getNumeroMembre() { return numeroMembre; } + public String getPrenom() { return prenom; } + public String getNom() { return nom; } + public String getEmail() { return email; } + public String getTelephone() { return telephone; } + public String getProfession() { return profession; } + public String getStatutCompte() { return statutCompte; } + public String getStatutCompteLibelle() { return statutCompteLibelle; } + public String getStatutCompteSeverity() { return statutCompteSeverity; } + public Boolean getActif() { return actif; } + public boolean isActif() { return Boolean.TRUE.equals(actif); } + public List getRoles() { return roles; } + public UUID getOrganisationId() { return organisationId; } + public String getOrganisationNom() { return organisationNom; } + public LocalDate getDateAdhesion() { return dateAdhesion; } + + // Getters JavaBean pour les propriétés calculées + public String getNomComplet() { return nomComplet(); } + public String getStatut() { return statut(); } + public String getStatutLibelle() { return statutLibelle(); } + public String getStatutSeverity() { return statutSeverity(); } + public String getStatutIcon() { return statutIcon(); } + public String getTypeMembre() { return typeMembre(); } + public String getTypeSeverity() { return typeSeverity(); } + public String getTypeIcon() { return typeIcon(); } + + // ── Propriétés calculées pour la compatibilité JSF EL ────────────────── + + /** Prénom + Nom concaténés. */ + public String nomComplet() { + String p = prenom != null ? prenom.trim() : ""; + String n = nom != null ? nom.trim() : ""; + return (p + " " + n).trim(); + } + + /** Alias de {@link #statutCompte()} pour #{membre.statut}. */ + public String statut() { + return statutCompte; + } + + /** Alias de {@link #statutCompteLibelle()} pour #{membre.statutLibelle}. */ + public String statutLibelle() { + return statutCompteLibelle; + } + + /** Alias de {@link #statutCompteSeverity()} pour #{membre.statutSeverity}. */ + public String statutSeverity() { + return statutCompteSeverity; + } + + /** Icône PrimeIcons calculée depuis {@link #statutCompte()}. */ + public String statutIcon() { + if (statutCompte == null) return "pi pi-question-circle"; + return switch (statutCompte) { + case "ACTIF" -> "pi pi-check-circle"; + case "INACTIF" -> "pi pi-pause-circle"; + case "SUSPENDU" -> "pi pi-ban"; + case "RADIE" -> "pi pi-times-circle"; + default -> "pi pi-question-circle"; + }; + } + + /** Libellé du rôle principal calculé depuis {@link #roles()}. */ + public String typeMembre() { + if (roles == null || roles.isEmpty()) return "Membre"; + if (roles.contains("PRESIDENT")) return "Président"; + if (roles.contains("VICE_PRESIDENT")) return "Vice-Président"; + if (roles.contains("SECRETAIRE")) return "Secrétaire"; + if (roles.contains("TRESORIER")) return "Trésorier"; + if (roles.contains("ADMIN_ORGANISATION")) return "Administrateur"; + if (roles.contains("MODERATEUR")) return "Modérateur"; + return "Membre"; + } + + /** Severity PrimeUI calculée depuis {@link #roles()}. */ + public String typeSeverity() { + if (roles == null || roles.isEmpty()) return "secondary"; + if (roles.contains("PRESIDENT")) return "primary"; + if (roles.contains("VICE_PRESIDENT")) return "primary"; + if (roles.contains("SECRETAIRE")) return "info"; + if (roles.contains("TRESORIER")) return "warning"; + if (roles.contains("ADMIN_ORGANISATION")) return "danger"; + if (roles.contains("MODERATEUR")) return "warning"; + return "secondary"; + } + + /** Icône PrimeIcons calculée depuis {@link #roles()}. */ + public String typeIcon() { + if (roles == null || roles.isEmpty()) return "pi pi-user"; + if (roles.contains("PRESIDENT")) return "pi pi-star"; + if (roles.contains("VICE_PRESIDENT")) return "pi pi-star"; + if (roles.contains("SECRETAIRE")) return "pi pi-file-edit"; + if (roles.contains("TRESORIER")) return "pi pi-wallet"; + if (roles.contains("ADMIN_ORGANISATION")) return "pi pi-shield"; + if (roles.contains("MODERATEUR")) return "pi pi-wrench"; + return "pi pi-user"; + } } 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 index 3cc4dc9..a15745e 100644 --- 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 @@ -70,5 +70,12 @@ public record CreateOrganisationRequest( @Size(max = 1000) String partenaires, @Size(max = 1000) String notes, BigDecimal latitude, - BigDecimal longitude) { + BigDecimal longitude, + @Size(max = 500) String adresse, + @Size(max = 100) String ville, + @Size(max = 100) String region, + @Size(max = 100) String pays, + @Size(max = 20) String codePostal, + Boolean organisationPublique, + Boolean accepteNouveauxMembres) { } 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 index 39ad6ca..feda188 100644 --- 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 @@ -44,5 +44,12 @@ public record UpdateOrganisationRequest( @Size(max = 1000) String partenaires, @Size(max = 1000) String notes, BigDecimal latitude, - BigDecimal longitude) { + BigDecimal longitude, + @Size(max = 500) String adresse, + @Size(max = 100) String ville, + @Size(max = 100) String region, + @Size(max = 100) String pays, + @Size(max = 20) String codePostal, + Boolean organisationPublique, + Boolean accepteNouveauxMembres) { } 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 index 487ac54..42e9d47 100644 --- 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 @@ -29,7 +29,7 @@ public record InitierPaiementEnLigneRequest( 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") + /** Optionnel sur le web (QR code sans restriction de payeur). Obligatoire sur mobile. */ @Pattern(regexp = "^\\d{9,15}$", message = "Numéro de téléphone invalide (9-15 chiffres)") String numeroTelephone, diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponse.java new file mode 100644 index 0000000..91c3c2e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponse.java @@ -0,0 +1,24 @@ +package dev.lions.unionflow.server.api.dto.paiement.response; + +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Builder; + +/** + * Réponse du polling de statut d'une IntentionPaiement Wave. + * Utilisée par le web pour savoir si le paiement est confirmé. + */ +@Builder +public record IntentionStatutResponse( + UUID intentionId, + /** INITIEE | EN_COURS | COMPLETEE | EXPIREE | ECHOUEE */ + String statut, + /** URL à encoder en QR code (wave_launch_url Wave Checkout) */ + String waveLaunchUrl, + String waveCheckoutSessionId, + /** ID de transaction Wave (TCN...) — disponible quand COMPLETEE */ + String waveTransactionId, + BigDecimal montant, + String referenceCotisation, + String message +) {} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/souscription/FormuleAbonnementResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/FormuleAbonnementResponse.java new file mode 100644 index 0000000..b59c787 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/FormuleAbonnementResponse.java @@ -0,0 +1,128 @@ +package dev.lions.unionflow.server.api.dto.souscription; + +import java.math.BigDecimal; + +/** + * Représentation publique d'une formule d'abonnement UnionFlow. + * + *

Retournée par le endpoint {@code GET /api/souscriptions/formules} (PermitAll). + * Les prix affichés sont les prix de base avant application des coefficients + * de type d'organisation et de période. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-30 + */ +public class FormuleAbonnementResponse { + + /** Code de la formule : BASIC, STANDARD, PREMIUM */ + private String code; + + /** Libellé court de la formule */ + private String libelle; + + /** Description marketing de la formule */ + private String description; + + /** Code de la plage de membres : PETITE, MOYENNE, GRANDE, TRES_GRANDE */ + private String plage; + + /** Libellé de la plage (ex: "Petite structure (1–100 membres)") */ + private String plageLibelle; + + /** Nombre minimum de membres pour cette plage */ + private int minMembres; + + /** + * Nombre maximum de membres pour cette plage. + * -1 signifie illimité (TRES_GRANDE). + */ + private int maxMembres; + + /** Prix mensuel de base en XOF (sans remise de période ni coefficient) */ + private BigDecimal prixMensuel; + + /** Prix annuel équivalent (prixMensuel × 12 × coefficient annuel) */ + private BigDecimal prixAnnuel; + + /** Ordre d'affichage dans le catalogue */ + private int ordreAffichage; + + // ── Champs Option C ─────────────────────────────────────────────────────── + + /** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */ + private String planCommercial; + + /** Niveau de reporting (BASIQUE, STANDARD, AVANCE) */ + private String niveauReporting; + + /** Accès à l'API REST inclus */ + private boolean apiAccess; + + /** Module fédération multi-org inclus (ENTERPRISE) */ + private boolean federationAccess; + + /** Support prioritaire inclus */ + private boolean supportPrioritaire; + + /** SLA garanti (ex: "99.0%", "99.9%") */ + private String slaGaranti; + + /** Nombre maximum d'administrateurs (-1 = illimité) */ + private int maxAdmins; + + public FormuleAbonnementResponse() {} + + // ─── Getters & Setters ─────────────────────────────────────────────────────── + + public String getCode() { return code; } + public void setCode(String code) { this.code = code; } + + 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 getPlage() { return plage; } + public void setPlage(String plage) { this.plage = plage; } + + public String getPlageLibelle() { return plageLibelle; } + public void setPlageLibelle(String plageLibelle) { this.plageLibelle = plageLibelle; } + + public int getMinMembres() { return minMembres; } + public void setMinMembres(int minMembres) { this.minMembres = minMembres; } + + public int getMaxMembres() { return maxMembres; } + public void setMaxMembres(int maxMembres) { this.maxMembres = maxMembres; } + + public BigDecimal getPrixMensuel() { return prixMensuel; } + public void setPrixMensuel(BigDecimal prixMensuel) { this.prixMensuel = prixMensuel; } + + public BigDecimal getPrixAnnuel() { return prixAnnuel; } + public void setPrixAnnuel(BigDecimal prixAnnuel) { this.prixAnnuel = prixAnnuel; } + + public int getOrdreAffichage() { return ordreAffichage; } + public void setOrdreAffichage(int ordreAffichage) { this.ordreAffichage = ordreAffichage; } + + public String getPlanCommercial() { return planCommercial; } + public void setPlanCommercial(String planCommercial) { this.planCommercial = planCommercial; } + + public String getNiveauReporting() { return niveauReporting; } + public void setNiveauReporting(String niveauReporting) { this.niveauReporting = niveauReporting; } + + public boolean isApiAccess() { return apiAccess; } + public void setApiAccess(boolean apiAccess) { this.apiAccess = apiAccess; } + + public boolean isFederationAccess() { return federationAccess; } + public void setFederationAccess(boolean federationAccess) { this.federationAccess = federationAccess; } + + public boolean isSupportPrioritaire() { return supportPrioritaire; } + public void setSupportPrioritaire(boolean supportPrioritaire) { this.supportPrioritaire = supportPrioritaire; } + + public String getSlaGaranti() { return slaGaranti; } + public void setSlaGaranti(String slaGaranti) { this.slaGaranti = slaGaranti; } + + public int getMaxAdmins() { return maxAdmins; } + public void setMaxAdmins(int maxAdmins) { this.maxAdmins = maxAdmins; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDemandeRequest.java b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDemandeRequest.java new file mode 100644 index 0000000..2c80df3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDemandeRequest.java @@ -0,0 +1,79 @@ +package dev.lions.unionflow.server.api.dto.souscription; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * Requête de création d'une demande de souscription UnionFlow. + * + *

Envoyée par l'ADMIN_ORGANISATION lors de l'onboarding pour sélectionner + * la formule, la plage de membres, la période et le type de son organisation. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-30 + */ +public class SouscriptionDemandeRequest { + + /** Niveau de formule : BASIC, STANDARD, PREMIUM */ + @NotBlank(message = "Le type de formule est obligatoire") + private String typeFormule; + + /** Tranche de membres : PETITE, MOYENNE, GRANDE, TRES_GRANDE */ + @NotBlank(message = "La plage de membres est obligatoire") + private String plageMembres; + + /** Périodicité de facturation : MENSUEL, TRIMESTRIEL, SEMESTRIEL, ANNUEL */ + @NotBlank(message = "Le type de période est obligatoire") + private String typePeriode; + + /** Type d'organisation pour le calcul du coefficient : ASSOCIATION, MUTUELLE, COOPERATIVE, FEDERATION + * Optionnel — si absent, le service le dérive depuis l'entité Organisation. */ + private String typeOrganisation; + + /** UUID de l'organisation concernée */ + @NotNull(message = "L'identifiant de l'organisation est obligatoire") + private String organisationId; + + public SouscriptionDemandeRequest() {} + + public String getTypeFormule() { + return typeFormule; + } + + public void setTypeFormule(String typeFormule) { + this.typeFormule = typeFormule; + } + + public String getPlageMembres() { + return plageMembres; + } + + public void setPlageMembres(String plageMembres) { + this.plageMembres = plageMembres; + } + + public String getTypePeriode() { + return typePeriode; + } + + public void setTypePeriode(String typePeriode) { + this.typePeriode = typePeriode; + } + + public String getTypeOrganisation() { + return typeOrganisation; + } + + public void setTypeOrganisation(String typeOrganisation) { + this.typeOrganisation = typeOrganisation; + } + + public String getOrganisationId() { + return organisationId; + } + + public void setOrganisationId(String organisationId) { + this.organisationId = organisationId; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionStatutResponse.java b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionStatutResponse.java new file mode 100644 index 0000000..d91a9e2 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionStatutResponse.java @@ -0,0 +1,155 @@ +package dev.lions.unionflow.server.api.dto.souscription; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * Réponse décrivant l'état courant d'une souscription UnionFlow. + * + *

Retournée par les endpoints de création de demande, de consultation + * et d'initiation du paiement Wave. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-30 + */ +public class SouscriptionStatutResponse { + + private String souscriptionId; + private String statutValidation; + private String statutLibelle; + private String typeFormule; + private String plageMembres; + private String plageLibelle; + private String typePeriode; + private String typeOrganisation; + private BigDecimal montantTotal; + private BigDecimal montantMensuelBase; + private BigDecimal coefficientApplique; + private String waveSessionId; + /** URL Wave à ouvrir dans le navigateur/WebView pour initier le paiement. */ + private String waveLaunchUrl; + private LocalDate dateDebut; + private LocalDate dateFin; + private LocalDate dateValidation; + private String commentaireRejet; + private String organisationId; + private String organisationNom; + + // ── Quota & features (Option C) ───────────────────────────────────────── + private Integer quotaMax; + private Integer quotaUtilise; + private Integer quotaRestant; + private boolean quotaDepasse; + private String planCommercial; + private boolean apiAccess; + private boolean federationAccess; + private boolean supportPrioritaire; + private String slaGaranti; + private Integer maxAdmins; + private String niveauReporting; + /** Nombre de jours avant expiration (négatif = déjà expiré) */ + private long joursAvantExpiration; + /** Statut de la souscription : ACTIVE, EXPIREE, SUSPENDUE, EN_ATTENTE, RESILIEE */ + private String statut; + + public SouscriptionStatutResponse() {} + + // ─── Getters & Setters ─────────────────────────────────────────────────────── + + public String getSouscriptionId() { return souscriptionId; } + public void setSouscriptionId(String souscriptionId) { this.souscriptionId = souscriptionId; } + + public String getStatutValidation() { return statutValidation; } + public void setStatutValidation(String statutValidation) { this.statutValidation = statutValidation; } + + public String getStatutLibelle() { return statutLibelle; } + public void setStatutLibelle(String statutLibelle) { this.statutLibelle = statutLibelle; } + + public String getTypeFormule() { return typeFormule; } + public void setTypeFormule(String typeFormule) { this.typeFormule = typeFormule; } + + public String getPlageMembres() { return plageMembres; } + public void setPlageMembres(String plageMembres) { this.plageMembres = plageMembres; } + + public String getPlageLibelle() { return plageLibelle; } + public void setPlageLibelle(String plageLibelle) { this.plageLibelle = plageLibelle; } + + public String getTypePeriode() { return typePeriode; } + public void setTypePeriode(String typePeriode) { this.typePeriode = typePeriode; } + + public String getTypeOrganisation() { return typeOrganisation; } + public void setTypeOrganisation(String typeOrganisation) { this.typeOrganisation = typeOrganisation; } + + public BigDecimal getMontantTotal() { return montantTotal; } + public void setMontantTotal(BigDecimal montantTotal) { this.montantTotal = montantTotal; } + + public BigDecimal getMontantMensuelBase() { return montantMensuelBase; } + public void setMontantMensuelBase(BigDecimal montantMensuelBase) { this.montantMensuelBase = montantMensuelBase; } + + public BigDecimal getCoefficientApplique() { return coefficientApplique; } + public void setCoefficientApplique(BigDecimal coefficientApplique) { this.coefficientApplique = coefficientApplique; } + + public String getWaveSessionId() { return waveSessionId; } + public void setWaveSessionId(String waveSessionId) { this.waveSessionId = waveSessionId; } + + public String getWaveLaunchUrl() { return waveLaunchUrl; } + public void setWaveLaunchUrl(String waveLaunchUrl) { this.waveLaunchUrl = waveLaunchUrl; } + + public LocalDate getDateDebut() { return dateDebut; } + public void setDateDebut(LocalDate dateDebut) { this.dateDebut = dateDebut; } + + public LocalDate getDateFin() { return dateFin; } + public void setDateFin(LocalDate dateFin) { this.dateFin = dateFin; } + + public LocalDate getDateValidation() { return dateValidation; } + public void setDateValidation(LocalDate dateValidation) { this.dateValidation = dateValidation; } + + public String getCommentaireRejet() { return commentaireRejet; } + public void setCommentaireRejet(String commentaireRejet) { this.commentaireRejet = commentaireRejet; } + + public String getOrganisationId() { return organisationId; } + public void setOrganisationId(String organisationId) { this.organisationId = organisationId; } + + public String getOrganisationNom() { return organisationNom; } + public void setOrganisationNom(String organisationNom) { this.organisationNom = organisationNom; } + + public Integer getQuotaMax() { return quotaMax; } + public void setQuotaMax(Integer quotaMax) { this.quotaMax = quotaMax; } + + public Integer getQuotaUtilise() { return quotaUtilise; } + public void setQuotaUtilise(Integer quotaUtilise) { this.quotaUtilise = quotaUtilise; } + + public Integer getQuotaRestant() { return quotaRestant; } + public void setQuotaRestant(Integer quotaRestant) { this.quotaRestant = quotaRestant; } + + public boolean isQuotaDepasse() { return quotaDepasse; } + public void setQuotaDepasse(boolean quotaDepasse) { this.quotaDepasse = quotaDepasse; } + + public String getPlanCommercial() { return planCommercial; } + public void setPlanCommercial(String planCommercial) { this.planCommercial = planCommercial; } + + public boolean isApiAccess() { return apiAccess; } + public void setApiAccess(boolean apiAccess) { this.apiAccess = apiAccess; } + + public boolean isFederationAccess() { return federationAccess; } + public void setFederationAccess(boolean federationAccess) { this.federationAccess = federationAccess; } + + public boolean isSupportPrioritaire() { return supportPrioritaire; } + public void setSupportPrioritaire(boolean supportPrioritaire) { this.supportPrioritaire = supportPrioritaire; } + + public String getSlaGaranti() { return slaGaranti; } + public void setSlaGaranti(String slaGaranti) { this.slaGaranti = slaGaranti; } + + public Integer getMaxAdmins() { return maxAdmins; } + public void setMaxAdmins(Integer maxAdmins) { this.maxAdmins = maxAdmins; } + + public String getNiveauReporting() { return niveauReporting; } + public void setNiveauReporting(String niveauReporting) { this.niveauReporting = niveauReporting; } + + public long getJoursAvantExpiration() { return joursAvantExpiration; } + public void setJoursAvantExpiration(long joursAvantExpiration) { this.joursAvantExpiration = joursAvantExpiration; } + + public String getStatut() { return statut; } + public void setStatut(String statut) { this.statut = statut; } +} diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembres.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembres.java new file mode 100644 index 0000000..c95bdbe --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembres.java @@ -0,0 +1,64 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +/** + * Tranches de taille d'organisation pour le calcul tarifaire UnionFlow. + * + *

Chaque plage définit un intervalle [min, max] de membres. + * La plage TRES_GRANDE est sans limite supérieure (Integer.MAX_VALUE). + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-30 + */ +public enum PlageMembres { + + PETITE("Petite structure (1–100 membres)", 1, 100), + MOYENNE("Moyenne structure (101–500 membres)", 101, 500), + GRANDE("Grande structure (501–2 000 membres)", 501, 2000), + TRES_GRANDE("Très grande structure (2 000+ membres)", 2001, Integer.MAX_VALUE); + + private final String libelle; + private final int min; + private final int max; + + PlageMembres(String libelle, int min, int max) { + this.libelle = libelle; + this.min = min; + this.max = max; + } + + /** + * Détermine la plage tarifaire correspondant à un nombre de membres donné. + * + * @param nombre nombre de membres de l'organisation + * @return la plage correspondante (TRES_GRANDE si aucune ne correspond) + */ + public static PlageMembres fromNombreMembres(int nombre) { + for (PlageMembres p : values()) { + if (nombre >= p.min && nombre <= p.max) { + return p; + } + } + return TRES_GRANDE; + } + + public String getLibelle() { + return libelle; + } + + public int getMin() { + return min; + } + + /** + * Retourne le max de membres pour la plage. + * Retourne -1 pour TRES_GRANDE (illimité) afin de faciliter la sérialisation JSON. + */ + public int getMaxAffichage() { + return this == TRES_GRANDE ? -1 : max; + } + + public int getMax() { + return max; + } +} 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 index c2a1976..cb9ba96 100644 --- 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 @@ -1,6 +1,7 @@ package dev.lions.unionflow.server.api.enums.abonnement; public enum StatutSouscription { + EN_ATTENTE("En attente de validation"), ACTIVE("Active"), EXPIREE("Expirée"), SUSPENDUE("Suspendue — quota dépassé ou impayé"), diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscription.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscription.java new file mode 100644 index 0000000..02e0b85 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscription.java @@ -0,0 +1,43 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +/** + * Cycle de vie de validation d'une souscription UnionFlow. + * + *

Transitions valides : + *

+ *   EN_ATTENTE_PAIEMENT → PAIEMENT_INITIE → PAIEMENT_CONFIRME → VALIDEE
+ *                                                              ↘ REJETEE
+ * 
+ * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-30 + */ +public enum StatutValidationSouscription { + + EN_ATTENTE_PAIEMENT("En attente de paiement — souscription créée"), + PAIEMENT_INITIE("Paiement initié — session Wave ouverte"), + PAIEMENT_CONFIRME("Paiement confirmé — en attente de validation SuperAdmin"), + VALIDEE("Validée — compte activé"), + REJETEE("Rejetée — souscription refusée"); + + private final String libelle; + + StatutValidationSouscription(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } + + /** Indique si la souscription est dans un état terminal (non modifiable). */ + public boolean isTerminal() { + return this == VALIDEE || this == REJETEE; + } + + /** Indique si un paiement Wave peut être initié depuis cet état. */ + public boolean peutInitierPaiement() { + return this == EN_ATTENTE_PAIEMENT; + } +} 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 index 0810e4d..6e9fb77 100644 --- 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 @@ -1,17 +1,23 @@ package dev.lions.unionflow.server.api.enums.abonnement; /** - * Énumération des types de formules d'abonnement UnionFlow + * Niveaux de formule d'abonnement UnionFlow. + * + *

Trois niveaux disponibles, chacun applicable à toutes les plages de taille + * ({@link PlageMembres}). Les prix de base sont définis dans la matrice tarifaire + * et varient selon la plage et le type d'organisation. + * + *

Migration depuis l'ancien enum : STARTER→BASIC, CRYSTAL supprimé. * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-10 + * @version 2.0 + * @since 2026-03-30 */ 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"); + + BASIC("Basic"), + STANDARD("Standard"), + PREMIUM("Premium"); private final String libelle; diff --git a/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturation.java b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturation.java new file mode 100644 index 0000000..623cce0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturation.java @@ -0,0 +1,51 @@ +package dev.lions.unionflow.server.api.enums.abonnement; + +import java.math.BigDecimal; + +/** + * Catégorie tarifaire d'une organisation pour le calcul du prix de souscription. + * + *

Distinct de {@code TypeOrganisation} (catégorisation métier). + * Chaque valeur porte deux coefficients multiplicateurs : + *

+ * + *

Exemple : FEDERATION paye le tarif normal (×1.0) en BASIC/STANDARD + * mais un coefficient ×1.5 en PREMIUM (fonctionnalités multi-entités). + */ +public enum TypeOrganisationFacturation { + + ASSOCIATION("Association / ONG locale", new BigDecimal("1.0"), new BigDecimal("1.0")), + MUTUELLE("Mutuelle (santé, fonctionnaires, scolaires)", new BigDecimal("1.2"), new BigDecimal("1.2")), + COOPERATIVE("Coopérative / Microfinance", new BigDecimal("1.3"), new BigDecimal("1.3")), + FEDERATION("Fédération / Grande ONG", new BigDecimal("1.0"), new BigDecimal("1.5")); + + private final String libelle; + private final BigDecimal coefficientBase; + private final BigDecimal coefficientPremium; + + TypeOrganisationFacturation(String libelle, BigDecimal coefficientBase, BigDecimal coefficientPremium) { + this.libelle = libelle; + this.coefficientBase = coefficientBase; + this.coefficientPremium = coefficientPremium; + } + + /** + * Retourne le coefficient tarifaire approprié selon le code de formule. + * + * @param typeFormule code de la formule (BASIC, STANDARD, PREMIUM) + * @return coefficient multiplicateur du prix mensuel de base + */ + public BigDecimal getCoefficient(String typeFormule) { + if ("PREMIUM".equals(typeFormule) && this == FEDERATION) { + return coefficientPremium; + } + return coefficientBase; + } + + public String getLibelle() { return libelle; } + public BigDecimal getCoefficientBase() { return coefficientBase; } + public BigDecimal getCoefficientPremium() { return coefficientPremium; } +} 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 index 3258154..f683b1b 100644 --- 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 @@ -1,12 +1,50 @@ package dev.lions.unionflow.server.api.enums.abonnement; +import java.math.BigDecimal; + +/** + * Périodes d'abonnement UnionFlow avec remises associées. + * + *

Le {@code coefficient} est multiplié par le prix mensuel de base, + * puis par le nombre de mois pour obtenir le montant total. + * + *

+ * + * @author UnionFlow Team + * @version 2.0 + * @since 2026-03-30 + */ public enum TypePeriodeAbonnement { - MENSUEL("Mensuel"), - ANNUEL("Annuel — 2 mois offerts"); + + MENSUEL("Mensuel", 1, new BigDecimal("1.00")), + TRIMESTRIEL("Trimestriel — 5% de remise", 3, new BigDecimal("0.95")), + SEMESTRIEL("Semestriel — 10% de remise", 6, new BigDecimal("0.90")), + ANNUEL("Annuel — 20% de remise (2 mois offerts)", 12, new BigDecimal("0.80")); private final String libelle; + private final int nombreMois; + private final BigDecimal coefficient; - TypePeriodeAbonnement(String libelle) { this.libelle = libelle; } + TypePeriodeAbonnement(String libelle, int nombreMois, BigDecimal coefficient) { + this.libelle = libelle; + this.nombreMois = nombreMois; + this.coefficient = coefficient; + } - public String getLibelle() { return libelle; } + public String getLibelle() { + return libelle; + } + + public int getNombreMois() { + return nombreMois; + } + + public BigDecimal getCoefficient() { + return coefficient; + } } 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 index a4ca328..955f50f 100644 --- 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 @@ -8,6 +8,7 @@ package dev.lions.unionflow.server.api.enums.membre; * @since 2025-01-10 */ public enum StatutMembre { + INVITE("Invité — en attente d'acceptation"), EN_ATTENTE_VALIDATION("En attente de validation"), ACTIF("Actif"), INACTIF("Inactif — cotisations en retard"), @@ -15,7 +16,8 @@ public enum StatutMembre { DEMISSIONNAIRE("Démissionnaire"), RADIE("Radié — exclusion définitive"), HONORAIRE("Honoraire — sans cotisation obligatoire"), - DECEDE("Décédé — archivage / gestion ayants droit"); + DECEDE("Décédé — archivage / gestion ayants droit"), + ARCHIVE("Archivé — données conservées, accès révoqué"); private final String 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 index 363ea7c..751d2be 100644 --- 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 @@ -1,21 +1,26 @@ package dev.lions.unionflow.server.api.enums.organisation; /** - * Énumération des types d'organisations supportés par UnionFlow + * Types d'organisations supportées par UnionFlow. + * + *

Utilisé pour la catégorisation métier des organisations + * (Lions Club, Mutuelle santé, ONG, etc.). Ne pas confondre avec + * {@link TypeOrganisationFacturation} qui porte les coefficients tarifaires. * * @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"), + COOPERATIVE_AGRICOLE("Coopérative agricole"), + ASSOCIATION_PROFESSIONNELLE("Association professionnelle"), + ASSOCIATION_COMMUNAUTAIRE("Association communautaire"), ORGANISATION_RELIGIEUSE("Organisation religieuse"), FEDERATION("Fédération / Union d'associations"), SYNDICAT("Syndicat non partisan"), diff --git a/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java b/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java index 2ef9c6a..3433bd2 100644 --- a/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java +++ b/src/test/java/dev/lions/unionflow/server/api/TestDataFactory.java @@ -40,7 +40,7 @@ public final class TestDataFactory { String email) { return new MembreSummaryResponse( UUID.randomUUID(), numero, prenom, nom, email, "0102030405", "Profession", "ACTIF", "Actif", "success", true, - List.of("MEMBRE"), null, null); + List.of("MEMBRE"), null, null, null); } public static CreateMembreRequest createCreateMembreRequest(int age) { 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 index 953a1c9..7d8c441 100644 --- 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 @@ -132,6 +132,102 @@ class CotisationResponseTest { } } + @Nested + @DisplayName("Méthodes de formatage des montants") + class MethodesFormatageMontants { + + @Test + @DisplayName("getMontantDuFormatte retourne 0 FCFA quand montantDu null") + void testMontantDuNull() { + CotisationResponse r = CotisationResponse.builder().build(); + assertThat(r.getMontantDuFormatte()).isEqualTo("0 FCFA"); + } + + @Test + @DisplayName("getMontantDuFormatte formate avec devise par défaut FCFA") + void testMontantDuSansDevise() { + CotisationResponse r = CotisationResponse.builder().montantDu(BigDecimal.valueOf(5000)).build(); + assertThat(r.getMontantDuFormatte()).isEqualTo("5,000 FCFA"); + } + + @Test + @DisplayName("getMontantDuFormatte formate avec codeDevise fourni") + void testMontantDuAvecDevise() { + CotisationResponse r = CotisationResponse.builder().montantDu(BigDecimal.valueOf(5000)).codeDevise("XOF").build(); + assertThat(r.getMontantDuFormatte()).isEqualTo("5,000 XOF"); + } + + @Test + @DisplayName("getMontantPayeFormatte retourne 0 FCFA quand montantPaye null") + void testMontantPayeNull() { + CotisationResponse r = CotisationResponse.builder().build(); + assertThat(r.getMontantPayeFormatte()).isEqualTo("0 FCFA"); + } + + @Test + @DisplayName("getMontantPayeFormatte formate avec devise par défaut FCFA") + void testMontantPayeSansDevise() { + CotisationResponse r = CotisationResponse.builder().montantPaye(BigDecimal.valueOf(2000)).build(); + assertThat(r.getMontantPayeFormatte()).isEqualTo("2,000 FCFA"); + } + + @Test + @DisplayName("getMontantPayeFormatte formate avec codeDevise fourni") + void testMontantPayeAvecDevise() { + CotisationResponse r = CotisationResponse.builder().montantPaye(BigDecimal.valueOf(2000)).codeDevise("EUR").build(); + assertThat(r.getMontantPayeFormatte()).isEqualTo("2,000 EUR"); + } + + @Test + @DisplayName("getMontantRestantFormatte retourne 0 FCFA quand montantRestant null") + void testMontantRestantNull() { + CotisationResponse r = CotisationResponse.builder().build(); + assertThat(r.getMontantRestantFormatte()).isEqualTo("0 FCFA"); + } + + @Test + @DisplayName("getMontantRestantFormatte formate avec devise par défaut FCFA") + void testMontantRestantSansDevise() { + CotisationResponse r = CotisationResponse.builder().montantRestant(BigDecimal.valueOf(3000)).build(); + assertThat(r.getMontantRestantFormatte()).isEqualTo("3,000 FCFA"); + } + + @Test + @DisplayName("getMontantRestantFormatte formate avec codeDevise fourni") + void testMontantRestantAvecDevise() { + CotisationResponse r = CotisationResponse.builder().montantRestant(BigDecimal.valueOf(3000)).codeDevise("XOF").build(); + assertThat(r.getMontantRestantFormatte()).isEqualTo("3,000 XOF"); + } + + @Test + @DisplayName("isMontantRestantPositif retourne false quand montantRestant null") + void testMontantRestantPositifNull() { + CotisationResponse r = CotisationResponse.builder().build(); + assertThat(r.isMontantRestantPositif()).isFalse(); + } + + @Test + @DisplayName("isMontantRestantPositif retourne false quand montantRestant = 0") + void testMontantRestantPositifZero() { + CotisationResponse r = CotisationResponse.builder().montantRestant(BigDecimal.ZERO).build(); + assertThat(r.isMontantRestantPositif()).isFalse(); + } + + @Test + @DisplayName("isMontantRestantPositif retourne false quand montantRestant négatif") + void testMontantRestantPositifNegatif() { + CotisationResponse r = CotisationResponse.builder().montantRestant(BigDecimal.valueOf(-100)).build(); + assertThat(r.isMontantRestantPositif()).isFalse(); + } + + @Test + @DisplayName("isMontantRestantPositif retourne true quand montantRestant > 0") + void testMontantRestantPositifVrai() { + CotisationResponse r = CotisationResponse.builder().montantRestant(BigDecimal.valueOf(500)).build(); + assertThat(r.isMontantRestantPositif()).isTrue(); + } + } + @Nested @DisplayName("Builder complet") class BuilderComplet { 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 index 0a890c8..9264da4 100644 --- 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 @@ -492,4 +492,259 @@ class EvenementResponseTest { assertThat(r.estComplet()).isFalse(); } } + + @Nested + @DisplayName("getTypeEvenementIcon") + class GetTypeEvenementIcon { + + @Test + @DisplayName("retourne icône par défaut quand type null") + void testNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-calendar"); + } + + @Test + @DisplayName("ASSEMBLEE_GENERALE → pi pi-building") + void testAssembleeGenerale() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.ASSEMBLEE_GENERALE).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-building"); + } + + @Test + @DisplayName("FORMATION → pi pi-book") + void testFormation() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.FORMATION).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-book"); + } + + @Test + @DisplayName("REUNION_BUREAU → pi pi-users") + void testReunionBureau() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.REUNION_BUREAU).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-users"); + } + + @Test + @DisplayName("CONFERENCE → pi pi-microphone") + void testConference() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.CONFERENCE).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-microphone"); + } + + @Test + @DisplayName("ATELIER → pi pi-wrench") + void testAtelier() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.ATELIER).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-wrench"); + } + + @Test + @DisplayName("CEREMONIE → pi pi-flag") + void testCeremonie() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.CEREMONIE).build(); + assertThat(r.getTypeEvenementIcon()).isEqualTo("pi pi-flag"); + } + + @Test + @DisplayName("ACTIVITE_SOCIALE, ACTION_CARITATIVE, AUTRE → pi pi-calendar") + void testAutres() { + assertThat(EvenementResponse.builder().typeEvenement(TypeEvenementMetier.ACTIVITE_SOCIALE).build().getTypeEvenementIcon()).isEqualTo("pi pi-calendar"); + assertThat(EvenementResponse.builder().typeEvenement(TypeEvenementMetier.ACTION_CARITATIVE).build().getTypeEvenementIcon()).isEqualTo("pi pi-calendar"); + assertThat(EvenementResponse.builder().typeEvenement(TypeEvenementMetier.AUTRE).build().getTypeEvenementIcon()).isEqualTo("pi pi-calendar"); + } + } + + @Nested + @DisplayName("getTypeEvenementSeverity") + class GetTypeEvenementSeverity { + + @Test + @DisplayName("retourne secondary quand type null") + void testNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getTypeEvenementSeverity()).isEqualTo("secondary"); + } + + @Test + @DisplayName("ASSEMBLEE_GENERALE → warning") + void testAssembleeGenerale() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.ASSEMBLEE_GENERALE).build(); + assertThat(r.getTypeEvenementSeverity()).isEqualTo("warning"); + } + + @Test + @DisplayName("FORMATION → info") + void testFormation() { + EvenementResponse r = EvenementResponse.builder().typeEvenement(TypeEvenementMetier.FORMATION).build(); + assertThat(r.getTypeEvenementSeverity()).isEqualTo("info"); + } + + @Test + @DisplayName("autres types → secondary") + void testAutres() { + for (TypeEvenementMetier t : new TypeEvenementMetier[]{ + TypeEvenementMetier.ACTIVITE_SOCIALE, TypeEvenementMetier.ACTION_CARITATIVE, + TypeEvenementMetier.REUNION_BUREAU, TypeEvenementMetier.CONFERENCE, + TypeEvenementMetier.ATELIER, TypeEvenementMetier.CEREMONIE, TypeEvenementMetier.AUTRE}) { + assertThat(EvenementResponse.builder().typeEvenement(t).build().getTypeEvenementSeverity()) + .as("Type: %s", t).isEqualTo("secondary"); + } + } + } + + @Nested + @DisplayName("getStatutSeverity") + class GetStatutSeverity { + + @Test + @DisplayName("retourne secondary quand statut null") + void testNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getStatutSeverity()).isEqualTo("secondary"); + } + + @Test + @DisplayName("PLANIFIE → info") + void testPlanifie() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.PLANIFIE).build().getStatutSeverity()).isEqualTo("info"); + } + + @Test + @DisplayName("EN_COURS → success") + void testEnCours() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.EN_COURS).build().getStatutSeverity()).isEqualTo("success"); + } + + @Test + @DisplayName("TERMINE et CONFIRME → secondary") + void testTermineConfirme() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.TERMINE).build().getStatutSeverity()).isEqualTo("secondary"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.CONFIRME).build().getStatutSeverity()).isEqualTo("secondary"); + } + + @Test + @DisplayName("ANNULE → danger") + void testAnnule() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.ANNULE).build().getStatutSeverity()).isEqualTo("danger"); + } + + @Test + @DisplayName("REPORTE → warning") + void testReporte() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.REPORTE).build().getStatutSeverity()).isEqualTo("warning"); + } + } + + @Nested + @DisplayName("getStatutIcon") + class GetStatutIcon { + + @Test + @DisplayName("retourne pi pi-question quand statut null") + void testNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getStatutIcon()).isEqualTo("pi pi-question"); + } + + @Test + @DisplayName("tous les statuts retournent la bonne icône") + void testTousStatuts() { + assertThat(EvenementResponse.builder().statut(StatutEvenement.PLANIFIE).build().getStatutIcon()).isEqualTo("pi pi-clock"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.CONFIRME).build().getStatutIcon()).isEqualTo("pi pi-check-circle"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.EN_COURS).build().getStatutIcon()).isEqualTo("pi pi-play"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.TERMINE).build().getStatutIcon()).isEqualTo("pi pi-check"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.ANNULE).build().getStatutIcon()).isEqualTo("pi pi-times"); + assertThat(EvenementResponse.builder().statut(StatutEvenement.REPORTE).build().getStatutIcon()).isEqualTo("pi pi-refresh"); + } + } + + @Nested + @DisplayName("getPrioriteSeverity") + class GetPrioriteSeverity { + + @Test + @DisplayName("retourne secondary quand priorite null") + void testNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getPrioriteSeverity()).isEqualTo("secondary"); + } + + @Test + @DisplayName("toutes les priorités retournent la bonne sévérité") + void testToutesPriorites() { + assertThat(EvenementResponse.builder().priorite(PrioriteEvenement.CRITIQUE).build().getPrioriteSeverity()).isEqualTo("danger"); + assertThat(EvenementResponse.builder().priorite(PrioriteEvenement.HAUTE).build().getPrioriteSeverity()).isEqualTo("warning"); + assertThat(EvenementResponse.builder().priorite(PrioriteEvenement.NORMALE).build().getPrioriteSeverity()).isEqualTo("info"); + assertThat(EvenementResponse.builder().priorite(PrioriteEvenement.BASSE).build().getPrioriteSeverity()).isEqualTo("secondary"); + } + } + + @Nested + @DisplayName("getDateDebutFormatee, getHeureDebutFormatee, getHeureFinFormatee, getBudgetFormate") + class MethodesFormatage { + + @Test + @DisplayName("getDateDebutFormatee retourne — quand null") + void testDateDebutNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getDateDebutFormatee()).isEqualTo("—"); + } + + @Test + @DisplayName("getDateDebutFormatee formate en dd/MM/yyyy") + void testDateDebut() { + EvenementResponse r = EvenementResponse.builder().dateDebut(java.time.LocalDate.of(2026, 4, 7)).build(); + assertThat(r.getDateDebutFormatee()).isEqualTo("07/04/2026"); + } + + @Test + @DisplayName("getHeureDebutFormatee retourne — quand null") + void testHeureDebutNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getHeureDebutFormatee()).isEqualTo("—"); + } + + @Test + @DisplayName("getHeureDebutFormatee formate en HH:mm") + void testHeureDebut() { + EvenementResponse r = EvenementResponse.builder().heureDebut(java.time.LocalTime.of(9, 30)).build(); + assertThat(r.getHeureDebutFormatee()).isEqualTo("09:30"); + } + + @Test + @DisplayName("getHeureFinFormatee retourne — quand null") + void testHeureFinNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getHeureFinFormatee()).isEqualTo("—"); + } + + @Test + @DisplayName("getHeureFinFormatee formate en HH:mm") + void testHeureFin() { + EvenementResponse r = EvenementResponse.builder().heureFin(java.time.LocalTime.of(18, 0)).build(); + assertThat(r.getHeureFinFormatee()).isEqualTo("18:00"); + } + + @Test + @DisplayName("getBudgetFormate retourne — quand budget null") + void testBudgetNull() { + EvenementResponse r = EvenementResponse.builder().build(); + assertThat(r.getBudgetFormate()).isEqualTo("—"); + } + + @Test + @DisplayName("getBudgetFormate formate avec devise par défaut FCFA") + void testBudgetSansDevise() { + EvenementResponse r = EvenementResponse.builder().budget(java.math.BigDecimal.valueOf(50000)).build(); + assertThat(r.getBudgetFormate()).isEqualTo("50,000 FCFA"); + } + + @Test + @DisplayName("getBudgetFormate formate avec codeDevise fourni") + void testBudgetAvecDevise() { + EvenementResponse r = EvenementResponse.builder().budget(java.math.BigDecimal.valueOf(50000)).codeDevise("EUR").build(); + assertThat(r.getBudgetFormate()).isEqualTo("50,000 EUR"); + } + } } 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 index b088c83..4c6a98d 100644 --- 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 @@ -23,7 +23,7 @@ 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); + UUID.randomUUID(), null, "Prenom", "Nom", null, null, null, null, null, null, true, List.of(), null, null, null); } @Nested diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSummaryResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSummaryResponseTest.java new file mode 100644 index 0000000..14d3696 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/membre/MembreSummaryResponseTest.java @@ -0,0 +1,266 @@ +package dev.lions.unionflow.server.api.dto.membre; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse; +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; + +@DisplayName("Tests MembreSummaryResponse — méthodes calculées") +class MembreSummaryResponseTest { + + private static MembreSummaryResponse make(String statut, List roles) { + return new MembreSummaryResponse( + UUID.randomUUID(), "UF-001", "Jean", "Dupont", "jean@test.com", + "0100000000", "Dev", statut, statut + "_libelle", statut + "_severity", + "ACTIF".equals(statut), roles, null, null, null); + } + + // ── nomComplet ───────────────────────────────────────────────────────── + + @Nested + @DisplayName("nomComplet()") + class NomComplet { + + @Test + @DisplayName("prenom + nom concaténés") + void nominal() { + MembreSummaryResponse r = make("ACTIF", List.of()); + assertThat(r.nomComplet()).isEqualTo("Jean Dupont"); + } + + @Test + @DisplayName("prenom null → uniquement le nom") + void prenomNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, "Dupont", null, + null, null, null, null, null, false, List.of(), null, null, null); + assertThat(r.nomComplet()).isEqualTo("Dupont"); + } + + @Test + @DisplayName("nom null → uniquement le prenom") + void nomNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, "Jean", null, null, + null, null, null, null, null, false, List.of(), null, null, null); + assertThat(r.nomComplet()).isEqualTo("Jean"); + } + + @Test + @DisplayName("prenom et nom null → chaîne vide") + void tousNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, false, List.of(), null, null, null); + assertThat(r.nomComplet()).isEmpty(); + } + } + + // ── Alias statut ─────────────────────────────────────────────────────── + + @Nested + @DisplayName("aliases statut*()") + class AliasStatut { + + @Test + @DisplayName("statut() retourne statutCompte") + void statut() { + MembreSummaryResponse r = make("ACTIF", List.of()); + assertThat(r.statut()).isEqualTo(r.statutCompte()); + } + + @Test + @DisplayName("statutLibelle() retourne statutCompteLibelle") + void statutLibelle() { + MembreSummaryResponse r = make("ACTIF", List.of()); + assertThat(r.statutLibelle()).isEqualTo(r.statutCompteLibelle()); + } + + @Test + @DisplayName("statutSeverity() retourne statutCompteSeverity") + void statutSeverity() { + MembreSummaryResponse r = make("ACTIF", List.of()); + assertThat(r.statutSeverity()).isEqualTo(r.statutCompteSeverity()); + } + } + + // ── statutIcon ───────────────────────────────────────────────────────── + + @Nested + @DisplayName("statutIcon()") + class StatutIcon { + + @Test void actif() { assertThat(make("ACTIF", List.of()).statutIcon()).isEqualTo("pi pi-check-circle"); } + @Test void inactif() { assertThat(make("INACTIF", List.of()).statutIcon()).isEqualTo("pi pi-pause-circle"); } + @Test void suspendu() { assertThat(make("SUSPENDU", List.of()).statutIcon()).isEqualTo("pi pi-ban"); } + @Test void radie() { assertThat(make("RADIE", List.of()).statutIcon()).isEqualTo("pi pi-times-circle"); } + + @Test + @DisplayName("statut inconnu → pi pi-question-circle") + void inconnu() { + assertThat(make("AUTRE", List.of()).statutIcon()).isEqualTo("pi pi-question-circle"); + } + + @Test + @DisplayName("statut null → pi pi-question-circle") + void statutNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, false, List.of(), null, null, null); + assertThat(r.statutIcon()).isEqualTo("pi pi-question-circle"); + } + } + + // ── typeMembre ───────────────────────────────────────────────────────── + + @Nested + @DisplayName("typeMembre()") + class TypeMembre { + + @Test void president() { assertThat(make("ACTIF", List.of("PRESIDENT")).typeMembre()).isEqualTo("Président"); } + @Test void vicePresident() { assertThat(make("ACTIF", List.of("VICE_PRESIDENT")).typeMembre()).isEqualTo("Vice-Président"); } + @Test void secretaire() { assertThat(make("ACTIF", List.of("SECRETAIRE")).typeMembre()).isEqualTo("Secrétaire"); } + @Test void tresorier() { assertThat(make("ACTIF", List.of("TRESORIER")).typeMembre()).isEqualTo("Trésorier"); } + @Test void adminOrg() { assertThat(make("ACTIF", List.of("ADMIN_ORGANISATION")).typeMembre()).isEqualTo("Administrateur"); } + @Test void moderateur() { assertThat(make("ACTIF", List.of("MODERATEUR")).typeMembre()).isEqualTo("Modérateur"); } + @Test void membreActif() { assertThat(make("ACTIF", List.of("MEMBRE_ACTIF")).typeMembre()).isEqualTo("Membre"); } + @Test void rolesVide() { assertThat(make("ACTIF", List.of()).typeMembre()).isEqualTo("Membre"); } + @Test void rolesNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, false, null, null, null, null); + assertThat(r.typeMembre()).isEqualTo("Membre"); + } + } + + // ── typeSeverity ─────────────────────────────────────────────────────── + + @Nested + @DisplayName("typeSeverity()") + class TypeSeverity { + + @Test void president() { assertThat(make("ACTIF", List.of("PRESIDENT")).typeSeverity()).isEqualTo("primary"); } + @Test void vicePresident() { assertThat(make("ACTIF", List.of("VICE_PRESIDENT")).typeSeverity()).isEqualTo("primary"); } + @Test void secretaire() { assertThat(make("ACTIF", List.of("SECRETAIRE")).typeSeverity()).isEqualTo("info"); } + @Test void tresorier() { assertThat(make("ACTIF", List.of("TRESORIER")).typeSeverity()).isEqualTo("warning"); } + @Test void adminOrg() { assertThat(make("ACTIF", List.of("ADMIN_ORGANISATION")).typeSeverity()).isEqualTo("danger"); } + @Test void moderateur() { assertThat(make("ACTIF", List.of("MODERATEUR")).typeSeverity()).isEqualTo("warning"); } + @Test void roleDefault() { assertThat(make("ACTIF", List.of("MEMBRE_ACTIF")).typeSeverity()).isEqualTo("secondary"); } + @Test void rolesVide() { assertThat(make("ACTIF", List.of()).typeSeverity()).isEqualTo("secondary"); } + @Test void rolesNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, false, null, null, null, null); + assertThat(r.typeSeverity()).isEqualTo("secondary"); + } + } + + // ── typeIcon ─────────────────────────────────────────────────────────── + + @Nested + @DisplayName("typeIcon()") + class TypeIcon { + + @Test void president() { assertThat(make("ACTIF", List.of("PRESIDENT")).typeIcon()).isEqualTo("pi pi-star"); } + @Test void vicePresident() { assertThat(make("ACTIF", List.of("VICE_PRESIDENT")).typeIcon()).isEqualTo("pi pi-star"); } + @Test void secretaire() { assertThat(make("ACTIF", List.of("SECRETAIRE")).typeIcon()).isEqualTo("pi pi-file-edit"); } + @Test void tresorier() { assertThat(make("ACTIF", List.of("TRESORIER")).typeIcon()).isEqualTo("pi pi-wallet"); } + @Test void adminOrg() { assertThat(make("ACTIF", List.of("ADMIN_ORGANISATION")).typeIcon()).isEqualTo("pi pi-shield"); } + @Test void moderateur() { assertThat(make("ACTIF", List.of("MODERATEUR")).typeIcon()).isEqualTo("pi pi-wrench"); } + @Test void roleDefault() { assertThat(make("ACTIF", List.of("MEMBRE_ACTIF")).typeIcon()).isEqualTo("pi pi-user"); } + @Test void rolesVide() { assertThat(make("ACTIF", List.of()).typeIcon()).isEqualTo("pi pi-user"); } + @Test void rolesNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, false, null, null, null, null); + assertThat(r.typeIcon()).isEqualTo("pi pi-user"); + } + } + + // ── dateAdhesion ─────────────────────────────────────────────────────── + + @Nested + @DisplayName("dateAdhesion()") + class DateAdhesion { + + @Test + @DisplayName("dateAdhesion est accessible via le composant record") + void accesseur() { + LocalDate date = LocalDate.of(2024, 3, 15); + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, "Jean", "Dupont", null, + null, null, "ACTIF", null, null, true, List.of(), null, null, date); + assertThat(r.dateAdhesion()).isEqualTo(date); + } + + @Test + @DisplayName("dateAdhesion null par défaut") + void nullParDefaut() { + assertThat(make("ACTIF", List.of()).dateAdhesion()).isNull(); + } + } + + // ── Getters JavaBean ─────────────────────────────────────────────────── + + @Nested + @DisplayName("getters JavaBean (compatibilité JSF EL)") + class GettersJavaBean { + + @Test + @DisplayName("getId/getNumeroMembre/getPrenom/getNom/getEmail/getTelephone/getProfession") + void champsSimples() { + LocalDate date = LocalDate.of(2024, 1, 1); + UUID orgId = UUID.randomUUID(); + UUID id = UUID.randomUUID(); + MembreSummaryResponse r = new MembreSummaryResponse( + id, "UF-001", "Jean", "Dupont", "jean@test.com", + "0100000000", "Dev", "ACTIF", "Actif", "success", + true, List.of("MEMBRE_ACTIF"), orgId, "Org Test", date); + assertThat(r.getId()).isEqualTo(id); + assertThat(r.getNumeroMembre()).isEqualTo("UF-001"); + assertThat(r.getPrenom()).isEqualTo("Jean"); + assertThat(r.getNom()).isEqualTo("Dupont"); + assertThat(r.getEmail()).isEqualTo("jean@test.com"); + assertThat(r.getTelephone()).isEqualTo("0100000000"); + assertThat(r.getProfession()).isEqualTo("Dev"); + assertThat(r.getStatutCompte()).isEqualTo("ACTIF"); + assertThat(r.getStatutCompteLibelle()).isEqualTo("Actif"); + assertThat(r.getStatutCompteSeverity()).isEqualTo("success"); + assertThat(r.getActif()).isTrue(); + assertThat(r.isActif()).isTrue(); + assertThat(r.getRoles()).containsExactly("MEMBRE_ACTIF"); + assertThat(r.getOrganisationId()).isEqualTo(orgId); + assertThat(r.getOrganisationNom()).isEqualTo("Org Test"); + assertThat(r.getDateAdhesion()).isEqualTo(date); + } + + @Test + @DisplayName("isActif false quand actif null") + void isActifNull() { + MembreSummaryResponse r = new MembreSummaryResponse( + UUID.randomUUID(), null, null, null, null, + null, null, null, null, null, null, null, null, null, null); + assertThat(r.isActif()).isFalse(); + assertThat(r.getActif()).isNull(); + } + + @Test + @DisplayName("getters calculés délèguent aux méthodes calculées") + void gettersCalcules() { + MembreSummaryResponse r = make("ACTIF", List.of("PRESIDENT")); + assertThat(r.getNomComplet()).isEqualTo(r.nomComplet()); + assertThat(r.getStatut()).isEqualTo(r.statut()); + assertThat(r.getStatutLibelle()).isEqualTo(r.statutLibelle()); + assertThat(r.getStatutSeverity()).isEqualTo(r.statutSeverity()); + assertThat(r.getStatutIcon()).isEqualTo(r.statutIcon()); + assertThat(r.getTypeMembre()).isEqualTo(r.typeMembre()); + assertThat(r.getTypeSeverity()).isEqualTo(r.typeSeverity()); + assertThat(r.getTypeIcon()).isEqualTo(r.typeIcon()); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequestTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequestTest.java index 152bb3b..b63ecff 100644 --- a/src/test/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequestTest.java +++ b/src/test/java/dev/lions/unionflow/server/api/dto/membre/request/CreateMembreRequestTest.java @@ -158,7 +158,7 @@ class CreateMembreRequestTest { .nom("User") .email("test@example.com") .dateNaissance(LocalDate.of(1990, 1, 1)) - .telephoneWave("+22177123456789") + .telephoneWave("+221771234567890123456") // 22 chars > max 20 .build(); Set> violations = validator.validate(request); diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequestTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequestTest.java index 312e80a..89d4d4f 100644 --- a/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequestTest.java +++ b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/CreateOrganisationRequestTest.java @@ -52,6 +52,13 @@ class CreateOrganisationRequestTest { .notes("Organisation de référence dans le secteur") .latitude(new BigDecimal("14.6937")) .longitude(new BigDecimal("-17.4441")) + .adresse("123 Rue des Entreprises, Plateau") + .ville("Dakar") + .region("Dakar") + .pays("Sénégal") + .codePostal("BP 3456") + .organisationPublique(true) + .accepteNouveauxMembres(false) .build(); assertThat(request).isNotNull(); @@ -76,6 +83,13 @@ class CreateOrganisationRequestTest { assertThat(request.certifications()).isEqualTo("ISO 9001"); assertThat(request.latitude()).isEqualByComparingTo(new BigDecimal("14.6937")); assertThat(request.longitude()).isEqualByComparingTo(new BigDecimal("-17.4441")); + assertThat(request.adresse()).isEqualTo("123 Rue des Entreprises, Plateau"); + assertThat(request.ville()).isEqualTo("Dakar"); + assertThat(request.region()).isEqualTo("Dakar"); + assertThat(request.pays()).isEqualTo("Sénégal"); + assertThat(request.codePostal()).isEqualTo("BP 3456"); + assertThat(request.organisationPublique()).isTrue(); + assertThat(request.accepteNouveauxMembres()).isFalse(); } @Test @@ -191,6 +205,59 @@ class CreateOrganisationRequestTest { assertThat(violations).isEmpty(); } + @Test + void testBuilder_AdresseFields() { + CreateOrganisationRequest request = CreateOrganisationRequest.builder() + .nom("ONG Dakar Centre") + .email("contact@ongdc.sn") + .adresse("Rue 10, Médina") + .ville("Dakar") + .region("Dakar") + .pays("Sénégal") + .codePostal("BP 1234") + .organisationPublique(false) + .accepteNouveauxMembres(true) + .build(); + + assertThat(request.adresse()).isEqualTo("Rue 10, Médina"); + assertThat(request.ville()).isEqualTo("Dakar"); + assertThat(request.region()).isEqualTo("Dakar"); + assertThat(request.pays()).isEqualTo("Sénégal"); + assertThat(request.codePostal()).isEqualTo("BP 1234"); + assertThat(request.organisationPublique()).isFalse(); + assertThat(request.accepteNouveauxMembres()).isTrue(); + } + + @Test + void testBuilder_AdresseFieldsNull_WhenNotProvided() { + CreateOrganisationRequest request = CreateOrganisationRequest.builder() + .nom("Org Minimal") + .email("min@org.sn") + .build(); + + assertThat(request.adresse()).isNull(); + assertThat(request.ville()).isNull(); + assertThat(request.region()).isNull(); + assertThat(request.pays()).isNull(); + assertThat(request.codePostal()).isNull(); + assertThat(request.organisationPublique()).isNull(); + assertThat(request.accepteNouveauxMembres()).isNull(); + } + + @Test + void testValidation_CodePostalTooLong() { + CreateOrganisationRequest request = CreateOrganisationRequest.builder() + .nom("Organisation Test") + .email("test@org.sn") + .codePostal("A".repeat(21)) + .build(); + + Set> violations = validator.validate(request); + + assertThat(violations).isNotEmpty(); + assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("codePostal")); + } + @Test void testEquals() { CreateOrganisationRequest request1 = CreateOrganisationRequest.builder() diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequestTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequestTest.java index b66234c..ef64981 100644 --- a/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequestTest.java +++ b/src/test/java/dev/lions/unionflow/server/api/dto/organisation/request/UpdateOrganisationRequestTest.java @@ -52,6 +52,13 @@ class UpdateOrganisationRequestTest { .notes("Notes mises à jour") .latitude(new BigDecimal("12.5")) .longitude(new BigDecimal("-15.5")) + .adresse("Avenue Cheikh Anta Diop, Fann") + .ville("Dakar") + .region("Dakar") + .pays("Sénégal") + .codePostal("BP 5000") + .organisationPublique(false) + .accepteNouveauxMembres(true) .build(); assertThat(request).isNotNull(); @@ -69,6 +76,13 @@ class UpdateOrganisationRequestTest { assertThat(request.budgetAnnuel()).isEqualByComparingTo(new BigDecimal("75000000.00")); assertThat(request.cotisationObligatoire()).isFalse(); assertThat(request.certifications()).isEqualTo("ISO 14001"); + assertThat(request.adresse()).isEqualTo("Avenue Cheikh Anta Diop, Fann"); + assertThat(request.ville()).isEqualTo("Dakar"); + assertThat(request.region()).isEqualTo("Dakar"); + assertThat(request.pays()).isEqualTo("Sénégal"); + assertThat(request.codePostal()).isEqualTo("BP 5000"); + assertThat(request.organisationPublique()).isFalse(); + assertThat(request.accepteNouveauxMembres()).isTrue(); } @Test @@ -168,6 +182,43 @@ class UpdateOrganisationRequestTest { assertThat(violations).isEmpty(); } + @Test + void testBuilder_AdresseFields() { + UpdateOrganisationRequest request = UpdateOrganisationRequest.builder() + .nom("Association Abidjan Nord") + .email("contact@abn.ci") + .adresse("Quartier Deux Plateaux, Rue des Jardins") + .ville("Abidjan") + .region("Lagunes") + .pays("Côte d'Ivoire") + .codePostal("01 BP 1234") + .organisationPublique(true) + .accepteNouveauxMembres(true) + .build(); + + assertThat(request.adresse()).isEqualTo("Quartier Deux Plateaux, Rue des Jardins"); + assertThat(request.ville()).isEqualTo("Abidjan"); + assertThat(request.region()).isEqualTo("Lagunes"); + assertThat(request.pays()).isEqualTo("Côte d'Ivoire"); + assertThat(request.codePostal()).isEqualTo("01 BP 1234"); + assertThat(request.organisationPublique()).isTrue(); + assertThat(request.accepteNouveauxMembres()).isTrue(); + } + + @Test + void testValidation_VilleTooLong() { + UpdateOrganisationRequest request = UpdateOrganisationRequest.builder() + .nom("Test Org") + .email("test@org.ci") + .ville("A".repeat(101)) + .build(); + + Set> violations = validator.validate(request); + + assertThat(violations).isNotEmpty(); + assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("ville")); + } + @Test void testEquals() { UpdateOrganisationRequest request1 = UpdateOrganisationRequest.builder() diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequestTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequestTest.java index 86f941a..5654f1c 100644 --- a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequestTest.java +++ b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/request/InitierPaiementEnLigneRequestTest.java @@ -65,10 +65,12 @@ class InitierPaiementEnLigneRequestTest { Set> violations = validator.validate(request); + // cotisationId et methodePaiement sont obligatoires + // numeroTelephone est optionnel (web QR sans restriction de payeur) assertThat(violations).isNotEmpty(); assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("cotisationId")); assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("methodePaiement")); - assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("numeroTelephone")); + assertThat(violations).noneMatch(v -> v.getPropertyPath().toString().equals("numeroTelephone")); } @Test diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponseTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponseTest.java new file mode 100644 index 0000000..5b8f0b4 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/paiement/response/IntentionStatutResponseTest.java @@ -0,0 +1,112 @@ +package dev.lions.unionflow.server.api.dto.paiement.response; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class IntentionStatutResponseTest { + + @Test + void testBuilder_AllFields() { + UUID intentionId = UUID.randomUUID(); + + IntentionStatutResponse response = IntentionStatutResponse.builder() + .intentionId(intentionId) + .statut("COMPLETEE") + .waveLaunchUrl("https://pay.wave.com/c/cos-abc123") + .waveCheckoutSessionId("cos-abc123") + .waveTransactionId("TCN4Y4ZC3FM") + .montant(new BigDecimal("5000")) + .referenceCotisation("COT-2025-001") + .message("Paiement confirmé !") + .build(); + + assertThat(response).isNotNull(); + assertThat(response.intentionId()).isEqualTo(intentionId); + assertThat(response.statut()).isEqualTo("COMPLETEE"); + assertThat(response.waveLaunchUrl()).isEqualTo("https://pay.wave.com/c/cos-abc123"); + assertThat(response.waveCheckoutSessionId()).isEqualTo("cos-abc123"); + assertThat(response.waveTransactionId()).isEqualTo("TCN4Y4ZC3FM"); + assertThat(response.montant()).isEqualByComparingTo(new BigDecimal("5000")); + assertThat(response.referenceCotisation()).isEqualTo("COT-2025-001"); + assertThat(response.message()).isEqualTo("Paiement confirmé !"); + } + + @Test + void testBuilder_EnCours() { + UUID intentionId = UUID.randomUUID(); + + IntentionStatutResponse response = IntentionStatutResponse.builder() + .intentionId(intentionId) + .statut("EN_COURS") + .waveLaunchUrl("https://pay.wave.com/c/cos-xyz789") + .waveCheckoutSessionId("cos-xyz789") + .montant(new BigDecimal("10000")) + .message("En attente de confirmation Wave...") + .build(); + + assertThat(response.intentionId()).isEqualTo(intentionId); + assertThat(response.statut()).isEqualTo("EN_COURS"); + assertThat(response.waveLaunchUrl()).isEqualTo("https://pay.wave.com/c/cos-xyz789"); + assertThat(response.waveTransactionId()).isNull(); + assertThat(response.referenceCotisation()).isNull(); + } + + @Test + void testBuilder_Expiree() { + UUID intentionId = UUID.randomUUID(); + + IntentionStatutResponse response = IntentionStatutResponse.builder() + .intentionId(intentionId) + .statut("EXPIREE") + .montant(new BigDecimal("2500")) + .message("Session Wave expirée") + .build(); + + assertThat(response.statut()).isEqualTo("EXPIREE"); + assertThat(response.waveLaunchUrl()).isNull(); + assertThat(response.waveCheckoutSessionId()).isNull(); + assertThat(response.waveTransactionId()).isNull(); + } + + @Test + void testEquals_SameFields() { + UUID intentionId = UUID.randomUUID(); + + IntentionStatutResponse r1 = IntentionStatutResponse.builder() + .intentionId(intentionId) + .statut("EN_COURS") + .waveLaunchUrl("https://pay.wave.com/c/cos-abc") + .waveCheckoutSessionId("cos-abc") + .montant(new BigDecimal("5000")) + .build(); + + IntentionStatutResponse r2 = IntentionStatutResponse.builder() + .intentionId(intentionId) + .statut("EN_COURS") + .waveLaunchUrl("https://pay.wave.com/c/cos-abc") + .waveCheckoutSessionId("cos-abc") + .montant(new BigDecimal("5000")) + .build(); + + assertThat(r1).isEqualTo(r2); + assertThat(r1.hashCode()).isEqualTo(r2.hashCode()); + } + + @Test + void testToString() { + IntentionStatutResponse response = IntentionStatutResponse.builder() + .intentionId(UUID.randomUUID()) + .statut("COMPLETEE") + .waveTransactionId("TCN4Y4ZC3FM") + .build(); + + String str = response.toString(); + assertThat(str).isNotNull(); + assertThat(str).contains("IntentionStatutResponse"); + assertThat(str).contains("COMPLETEE"); + assertThat(str).contains("TCN4Y4ZC3FM"); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDtosTest.java b/src/test/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDtosTest.java new file mode 100644 index 0000000..fc61b3d --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/dto/souscription/SouscriptionDtosTest.java @@ -0,0 +1,172 @@ +package dev.lions.unionflow.server.api.dto.souscription; + +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; + +@DisplayName("Tests des DTOs souscription") +class SouscriptionDtosTest { + + @Nested + @DisplayName("SouscriptionDemandeRequest") + class SouscriptionDemandeRequestTest { + + @Test + @DisplayName("Getters/Setters fonctionnent correctement") + void testGettersSetters() { + SouscriptionDemandeRequest req = new SouscriptionDemandeRequest(); + req.setTypeFormule("BASIC"); + req.setPlageMembres("PETITE"); + req.setTypePeriode("MENSUEL"); + req.setTypeOrganisation("ASSOCIATION"); + req.setOrganisationId("org-uuid-123"); + + assertThat(req.getTypeFormule()).isEqualTo("BASIC"); + assertThat(req.getPlageMembres()).isEqualTo("PETITE"); + assertThat(req.getTypePeriode()).isEqualTo("MENSUEL"); + assertThat(req.getTypeOrganisation()).isEqualTo("ASSOCIATION"); + assertThat(req.getOrganisationId()).isEqualTo("org-uuid-123"); + } + } + + @Nested + @DisplayName("SouscriptionStatutResponse") + class SouscriptionStatutResponseTest { + + @Test + @DisplayName("Getters/Setters fonctionnent correctement") + void testGettersSetters() { + LocalDate today = LocalDate.now(); + SouscriptionStatutResponse resp = new SouscriptionStatutResponse(); + resp.setSouscriptionId("sous-uuid-456"); + resp.setStatutValidation("EN_ATTENTE_PAIEMENT"); + resp.setStatutLibelle("En attente de paiement"); + resp.setTypeFormule("BASIC"); + resp.setPlageMembres("PETITE"); + resp.setPlageLibelle("Petite structure (1–100 membres)"); + resp.setTypePeriode("MENSUEL"); + resp.setTypeOrganisation("ASSOCIATION"); + resp.setMontantTotal(new BigDecimal("3000")); + resp.setMontantMensuelBase(new BigDecimal("3000")); + resp.setCoefficientApplique(new BigDecimal("1.0")); + resp.setWaveSessionId("wave-session-789"); + resp.setWaveLaunchUrl("https://pay.wave.com/c/session-789"); + resp.setDateDebut(today); + resp.setDateFin(today.plusMonths(1)); + resp.setDateValidation(null); + resp.setCommentaireRejet(null); + resp.setOrganisationId("org-uuid-123"); + resp.setOrganisationNom("Association Test"); + resp.setStatut("ACTIVE"); + + assertThat(resp.getSouscriptionId()).isEqualTo("sous-uuid-456"); + assertThat(resp.getStatutValidation()).isEqualTo("EN_ATTENTE_PAIEMENT"); + assertThat(resp.getStatutLibelle()).isEqualTo("En attente de paiement"); + assertThat(resp.getTypeFormule()).isEqualTo("BASIC"); + assertThat(resp.getPlageMembres()).isEqualTo("PETITE"); + assertThat(resp.getPlageLibelle()).isEqualTo("Petite structure (1–100 membres)"); + assertThat(resp.getTypePeriode()).isEqualTo("MENSUEL"); + assertThat(resp.getTypeOrganisation()).isEqualTo("ASSOCIATION"); + assertThat(resp.getMontantTotal()).isEqualByComparingTo(new BigDecimal("3000")); + assertThat(resp.getMontantMensuelBase()).isEqualByComparingTo(new BigDecimal("3000")); + assertThat(resp.getCoefficientApplique()).isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(resp.getWaveSessionId()).isEqualTo("wave-session-789"); + assertThat(resp.getWaveLaunchUrl()).isEqualTo("https://pay.wave.com/c/session-789"); + assertThat(resp.getDateDebut()).isEqualTo(today); + assertThat(resp.getDateFin()).isEqualTo(today.plusMonths(1)); + assertThat(resp.getDateValidation()).isNull(); + assertThat(resp.getCommentaireRejet()).isNull(); + assertThat(resp.getOrganisationId()).isEqualTo("org-uuid-123"); + assertThat(resp.getOrganisationNom()).isEqualTo("Association Test"); + assertThat(resp.getStatut()).isEqualTo("ACTIVE"); + } + + @Test + @DisplayName("Champs Option C (quota + plan commercial) fonctionnent correctement") + void testOptionCFields() { + SouscriptionStatutResponse resp = new SouscriptionStatutResponse(); + resp.setQuotaMax(200); + resp.setQuotaUtilise(75); + resp.setQuotaRestant(125); + resp.setQuotaDepasse(false); + resp.setPlanCommercial("ENTERPRISE"); + resp.setApiAccess(true); + resp.setFederationAccess(true); + resp.setSupportPrioritaire(true); + resp.setSlaGaranti("99.9%"); + resp.setMaxAdmins(10); + resp.setNiveauReporting("AVANCE"); + resp.setJoursAvantExpiration(180L); + + assertThat(resp.getQuotaMax()).isEqualTo(200); + assertThat(resp.getQuotaUtilise()).isEqualTo(75); + assertThat(resp.getQuotaRestant()).isEqualTo(125); + assertThat(resp.isQuotaDepasse()).isFalse(); + assertThat(resp.getPlanCommercial()).isEqualTo("ENTERPRISE"); + assertThat(resp.isApiAccess()).isTrue(); + assertThat(resp.isFederationAccess()).isTrue(); + assertThat(resp.isSupportPrioritaire()).isTrue(); + assertThat(resp.getSlaGaranti()).isEqualTo("99.9%"); + assertThat(resp.getMaxAdmins()).isEqualTo(10); + assertThat(resp.getNiveauReporting()).isEqualTo("AVANCE"); + assertThat(resp.getJoursAvantExpiration()).isEqualTo(180L); + } + } + + @Nested + @DisplayName("FormuleAbonnementResponse") + class FormuleAbonnementResponseTest { + + @Test + @DisplayName("Getters/Setters fonctionnent correctement") + void testGettersSetters() { + FormuleAbonnementResponse resp = new FormuleAbonnementResponse(); + resp.setCode("BASIC"); + resp.setLibelle("Basic"); + resp.setDescription("Formule de base pour petites structures"); + resp.setPlage("PETITE"); + resp.setPlageLibelle("Petite structure (1–100 membres)"); + resp.setMinMembres(1); + resp.setMaxMembres(100); + resp.setPrixMensuel(new BigDecimal("3000")); + resp.setPrixAnnuel(new BigDecimal("28800")); + resp.setOrdreAffichage(1); + + assertThat(resp.getCode()).isEqualTo("BASIC"); + assertThat(resp.getLibelle()).isEqualTo("Basic"); + assertThat(resp.getDescription()).isEqualTo("Formule de base pour petites structures"); + assertThat(resp.getPlage()).isEqualTo("PETITE"); + assertThat(resp.getPlageLibelle()).isEqualTo("Petite structure (1–100 membres)"); + assertThat(resp.getMinMembres()).isEqualTo(1); + assertThat(resp.getMaxMembres()).isEqualTo(100); + assertThat(resp.getPrixMensuel()).isEqualByComparingTo(new BigDecimal("3000")); + assertThat(resp.getPrixAnnuel()).isEqualByComparingTo(new BigDecimal("28800")); + assertThat(resp.getOrdreAffichage()).isEqualTo(1); + } + + @Test + @DisplayName("Champs Option C fonctionnent correctement") + void testOptionCFields() { + FormuleAbonnementResponse resp = new FormuleAbonnementResponse(); + resp.setPlanCommercial("ENTERPRISE"); + resp.setNiveauReporting("AVANCE"); + resp.setApiAccess(true); + resp.setFederationAccess(true); + resp.setSupportPrioritaire(true); + resp.setSlaGaranti("99.9%"); + resp.setMaxAdmins(10); + + assertThat(resp.getPlanCommercial()).isEqualTo("ENTERPRISE"); + assertThat(resp.getNiveauReporting()).isEqualTo("AVANCE"); + assertThat(resp.isApiAccess()).isTrue(); + assertThat(resp.isFederationAccess()).isTrue(); + assertThat(resp.isSupportPrioritaire()).isTrue(); + assertThat(resp.getSlaGaranti()).isEqualTo("99.9%"); + assertThat(resp.getMaxAdmins()).isEqualTo(10); + } + } +} 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 index e7da2b3..4b9241d 100644 --- a/src/test/java/dev/lions/unionflow/server/api/enums/EnumsRefactoringTest.java +++ b/src/test/java/dev/lions/unionflow/server/api/enums/EnumsRefactoringTest.java @@ -139,10 +139,9 @@ class EnumsRefactoringTest { @DisplayName("TypeFormule - Tous les types disponibles") void testTypeFormuleTousLesTypes() { // Given & When & Then - assertThat(TypeFormule.STARTER.getLibelle()).contains("Starter"); + assertThat(TypeFormule.BASIC.getLibelle()).contains("Basic"); assertThat(TypeFormule.STANDARD.getLibelle()).contains("Standard"); assertThat(TypeFormule.PREMIUM.getLibelle()).contains("Premium"); - assertThat(TypeFormule.CRYSTAL.getLibelle()).contains("Crystal"); } @Test diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembresTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembresTest.java new file mode 100644 index 0000000..ef03726 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/PlageMembresTest.java @@ -0,0 +1,125 @@ +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 PlageMembres") +class PlageMembresTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(PlageMembres.PETITE).isNotNull(); + assertThat(PlageMembres.TRES_GRANDE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + PlageMembres[] values = PlageMembres.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + PlageMembres.PETITE, + PlageMembres.MOYENNE, + PlageMembres.GRANDE, + PlageMembres.TRES_GRANDE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(PlageMembres.valueOf("PETITE")).isEqualTo(PlageMembres.PETITE); + assertThat(PlageMembres.valueOf("MOYENNE")).isEqualTo(PlageMembres.MOYENNE); + assertThat(PlageMembres.valueOf("GRANDE")).isEqualTo(PlageMembres.GRANDE); + assertThat(PlageMembres.valueOf("TRES_GRANDE")).isEqualTo(PlageMembres.TRES_GRANDE); + + assertThatThrownBy(() -> PlageMembres.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(PlageMembres.class) + @DisplayName("Test getLibelle pour toutes les valeurs - non null et non vide") + void testGetLibelle(PlageMembres plage) { + assertThat(plage.getLibelle()).isNotNull().isNotEmpty(); + } + + @Test + @DisplayName("Test bornes min/max de chaque plage") + void testBornesMinMax() { + assertThat(PlageMembres.PETITE.getMin()).isEqualTo(1); + assertThat(PlageMembres.PETITE.getMax()).isEqualTo(100); + + assertThat(PlageMembres.MOYENNE.getMin()).isEqualTo(101); + assertThat(PlageMembres.MOYENNE.getMax()).isEqualTo(500); + + assertThat(PlageMembres.GRANDE.getMin()).isEqualTo(501); + assertThat(PlageMembres.GRANDE.getMax()).isEqualTo(2000); + + assertThat(PlageMembres.TRES_GRANDE.getMin()).isEqualTo(2001); + assertThat(PlageMembres.TRES_GRANDE.getMax()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + @DisplayName("Test getMaxAffichage - TRES_GRANDE retourne -1") + void testGetMaxAffichage() { + assertThat(PlageMembres.PETITE.getMaxAffichage()).isEqualTo(100); + assertThat(PlageMembres.MOYENNE.getMaxAffichage()).isEqualTo(500); + assertThat(PlageMembres.GRANDE.getMaxAffichage()).isEqualTo(2000); + assertThat(PlageMembres.TRES_GRANDE.getMaxAffichage()).isEqualTo(-1); + } + } + + @Nested + @DisplayName("Tests de fromNombreMembres") + class TestsFromNombreMembres { + + @Test + @DisplayName("Valeurs limites - PETITE (1-100)") + void testPetite() { + assertThat(PlageMembres.fromNombreMembres(1)).isEqualTo(PlageMembres.PETITE); + assertThat(PlageMembres.fromNombreMembres(50)).isEqualTo(PlageMembres.PETITE); + assertThat(PlageMembres.fromNombreMembres(100)).isEqualTo(PlageMembres.PETITE); + } + + @Test + @DisplayName("Valeurs limites - MOYENNE (101-500)") + void testMoyenne() { + assertThat(PlageMembres.fromNombreMembres(101)).isEqualTo(PlageMembres.MOYENNE); + assertThat(PlageMembres.fromNombreMembres(300)).isEqualTo(PlageMembres.MOYENNE); + assertThat(PlageMembres.fromNombreMembres(500)).isEqualTo(PlageMembres.MOYENNE); + } + + @Test + @DisplayName("Valeurs limites - GRANDE (501-2000)") + void testGrande() { + assertThat(PlageMembres.fromNombreMembres(501)).isEqualTo(PlageMembres.GRANDE); + assertThat(PlageMembres.fromNombreMembres(1000)).isEqualTo(PlageMembres.GRANDE); + assertThat(PlageMembres.fromNombreMembres(2000)).isEqualTo(PlageMembres.GRANDE); + } + + @Test + @DisplayName("Valeurs limites - TRES_GRANDE (2001+)") + void testTresGrande() { + assertThat(PlageMembres.fromNombreMembres(2001)).isEqualTo(PlageMembres.TRES_GRANDE); + assertThat(PlageMembres.fromNombreMembres(10000)).isEqualTo(PlageMembres.TRES_GRANDE); + assertThat(PlageMembres.fromNombreMembres(Integer.MAX_VALUE)).isEqualTo(PlageMembres.TRES_GRANDE); + } + + @Test + @DisplayName("Nombre négatif - retourne TRES_GRANDE par défaut") + void testNombreNegatif() { + assertThat(PlageMembres.fromNombreMembres(-1)).isEqualTo(PlageMembres.TRES_GRANDE); + } + } +} 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 index 8d114e0..40c8e57 100644 --- 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 @@ -27,8 +27,9 @@ class StatutSouscriptionTest { @DisplayName("Test toutes les valeurs enum") void testToutesValeurs() { StatutSouscription[] values = StatutSouscription.values(); - assertThat(values).hasSize(4); + assertThat(values).hasSize(5); assertThat(values).containsExactly( + StatutSouscription.EN_ATTENTE, StatutSouscription.ACTIVE, StatutSouscription.EXPIREE, StatutSouscription.SUSPENDUE, @@ -38,6 +39,7 @@ class StatutSouscriptionTest { @Test @DisplayName("Test valueOf - toutes les constantes") void testValueOf() { + assertThat(StatutSouscription.valueOf("EN_ATTENTE")).isEqualTo(StatutSouscription.EN_ATTENTE); assertThat(StatutSouscription.valueOf("ACTIVE")).isEqualTo(StatutSouscription.ACTIVE); assertThat(StatutSouscription.valueOf("EXPIREE")).isEqualTo(StatutSouscription.EXPIREE); assertThat(StatutSouscription.valueOf("SUSPENDUE")).isEqualTo(StatutSouscription.SUSPENDUE); diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscriptionTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscriptionTest.java new file mode 100644 index 0000000..33f78af --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/StatutValidationSouscriptionTest.java @@ -0,0 +1,89 @@ +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 StatutValidationSouscription") +class StatutValidationSouscriptionTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(StatutValidationSouscription.EN_ATTENTE_PAIEMENT).isNotNull(); + assertThat(StatutValidationSouscription.VALIDEE).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + StatutValidationSouscription[] values = StatutValidationSouscription.values(); + assertThat(values).hasSize(5); + assertThat(values).containsExactly( + StatutValidationSouscription.EN_ATTENTE_PAIEMENT, + StatutValidationSouscription.PAIEMENT_INITIE, + StatutValidationSouscription.PAIEMENT_CONFIRME, + StatutValidationSouscription.VALIDEE, + StatutValidationSouscription.REJETEE); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(StatutValidationSouscription.valueOf("EN_ATTENTE_PAIEMENT")) + .isEqualTo(StatutValidationSouscription.EN_ATTENTE_PAIEMENT); + assertThat(StatutValidationSouscription.valueOf("PAIEMENT_INITIE")) + .isEqualTo(StatutValidationSouscription.PAIEMENT_INITIE); + assertThat(StatutValidationSouscription.valueOf("PAIEMENT_CONFIRME")) + .isEqualTo(StatutValidationSouscription.PAIEMENT_CONFIRME); + assertThat(StatutValidationSouscription.valueOf("VALIDEE")) + .isEqualTo(StatutValidationSouscription.VALIDEE); + assertThat(StatutValidationSouscription.valueOf("REJETEE")) + .isEqualTo(StatutValidationSouscription.REJETEE); + + assertThatThrownBy(() -> StatutValidationSouscription.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(StatutValidationSouscription.class) + @DisplayName("Test getLibelle pour toutes les valeurs - non null et non vide") + void testGetLibelle(StatutValidationSouscription statut) { + assertThat(statut.getLibelle()).isNotNull().isNotEmpty(); + } + } + + @Nested + @DisplayName("Tests des méthodes métier") + class TestsMethodesMetier { + + @Test + @DisplayName("isTerminal - seuls VALIDEE et REJETEE sont terminaux") + void testIsTerminal() { + assertThat(StatutValidationSouscription.EN_ATTENTE_PAIEMENT.isTerminal()).isFalse(); + assertThat(StatutValidationSouscription.PAIEMENT_INITIE.isTerminal()).isFalse(); + assertThat(StatutValidationSouscription.PAIEMENT_CONFIRME.isTerminal()).isFalse(); + assertThat(StatutValidationSouscription.VALIDEE.isTerminal()).isTrue(); + assertThat(StatutValidationSouscription.REJETEE.isTerminal()).isTrue(); + } + + @Test + @DisplayName("peutInitierPaiement - seul EN_ATTENTE_PAIEMENT permet d'initier") + void testPeutInitierPaiement() { + assertThat(StatutValidationSouscription.EN_ATTENTE_PAIEMENT.peutInitierPaiement()).isTrue(); + assertThat(StatutValidationSouscription.PAIEMENT_INITIE.peutInitierPaiement()).isFalse(); + assertThat(StatutValidationSouscription.PAIEMENT_CONFIRME.peutInitierPaiement()).isFalse(); + assertThat(StatutValidationSouscription.VALIDEE.peutInitierPaiement()).isFalse(); + assertThat(StatutValidationSouscription.REJETEE.peutInitierPaiement()).isFalse(); + } + } +} 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 index b2e3c8c..026c95b 100644 --- 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 @@ -15,7 +15,7 @@ class TypeFormuleTest { @Test @DisplayName("Test de base - enum peut être utilisé") void testEnumUtilisable() { - assertThat(TypeFormule.STARTER).isNotNull(); + assertThat(TypeFormule.BASIC).isNotNull(); } @Nested @@ -26,21 +26,19 @@ class TypeFormuleTest { @DisplayName("Test toutes les valeurs enum") void testToutesValeurs() { TypeFormule[] values = TypeFormule.values(); - assertThat(values).hasSize(4); + assertThat(values).hasSize(3); assertThat(values).containsExactly( - TypeFormule.STARTER, + TypeFormule.BASIC, TypeFormule.STANDARD, - TypeFormule.PREMIUM, - TypeFormule.CRYSTAL); + TypeFormule.PREMIUM); } @Test @DisplayName("Test valueOf") void testValueOf() { - assertThat(TypeFormule.valueOf("STARTER")).isEqualTo(TypeFormule.STARTER); + assertThat(TypeFormule.valueOf("BASIC")).isEqualTo(TypeFormule.BASIC); 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); diff --git a/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturationTest.java b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturationTest.java new file mode 100644 index 0000000..8159c37 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/api/enums/abonnement/TypeOrganisationFacturationTest.java @@ -0,0 +1,124 @@ +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 java.math.BigDecimal; +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 TypeOrganisationFacturation") +class TypeOrganisationFacturationTest { + + @Test + @DisplayName("Test de base - enum peut être utilisé") + void testEnumUtilisable() { + assertThat(TypeOrganisationFacturation.ASSOCIATION).isNotNull(); + assertThat(TypeOrganisationFacturation.FEDERATION).isNotNull(); + } + + @Nested + @DisplayName("Tests des valeurs enum") + class TestsValeursEnum { + + @Test + @DisplayName("Test toutes les valeurs enum") + void testToutesValeurs() { + TypeOrganisationFacturation[] values = TypeOrganisationFacturation.values(); + assertThat(values).hasSize(4); + assertThat(values).containsExactly( + TypeOrganisationFacturation.ASSOCIATION, + TypeOrganisationFacturation.MUTUELLE, + TypeOrganisationFacturation.COOPERATIVE, + TypeOrganisationFacturation.FEDERATION); + } + + @Test + @DisplayName("Test valueOf - toutes les constantes") + void testValueOf() { + assertThat(TypeOrganisationFacturation.valueOf("ASSOCIATION")) + .isEqualTo(TypeOrganisationFacturation.ASSOCIATION); + assertThat(TypeOrganisationFacturation.valueOf("MUTUELLE")) + .isEqualTo(TypeOrganisationFacturation.MUTUELLE); + assertThat(TypeOrganisationFacturation.valueOf("COOPERATIVE")) + .isEqualTo(TypeOrganisationFacturation.COOPERATIVE); + assertThat(TypeOrganisationFacturation.valueOf("FEDERATION")) + .isEqualTo(TypeOrganisationFacturation.FEDERATION); + + assertThatThrownBy(() -> TypeOrganisationFacturation.valueOf("INEXISTANT")) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @EnumSource(TypeOrganisationFacturation.class) + @DisplayName("Test getLibelle pour toutes les valeurs - non null et non vide") + void testGetLibelle(TypeOrganisationFacturation type) { + assertThat(type.getLibelle()).isNotNull().isNotEmpty(); + } + + @ParameterizedTest + @EnumSource(TypeOrganisationFacturation.class) + @DisplayName("Test coefficients - toujours positifs") + void testCoefficientsPositifs(TypeOrganisationFacturation type) { + assertThat(type.getCoefficientBase()).isGreaterThan(BigDecimal.ZERO); + assertThat(type.getCoefficientPremium()).isGreaterThan(BigDecimal.ZERO); + } + } + + @Nested + @DisplayName("Tests de getCoefficient") + class TestsGetCoefficient { + + @Test + @DisplayName("ASSOCIATION - coefficient 1.0 pour toutes les formules") + void testAssociation() { + assertThat(TypeOrganisationFacturation.ASSOCIATION.getCoefficient("BASIC")) + .isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(TypeOrganisationFacturation.ASSOCIATION.getCoefficient("STANDARD")) + .isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(TypeOrganisationFacturation.ASSOCIATION.getCoefficient("PREMIUM")) + .isEqualByComparingTo(new BigDecimal("1.0")); + } + + @Test + @DisplayName("MUTUELLE - coefficient 1.2 pour toutes les formules") + void testMutuelle() { + assertThat(TypeOrganisationFacturation.MUTUELLE.getCoefficient("BASIC")) + .isEqualByComparingTo(new BigDecimal("1.2")); + assertThat(TypeOrganisationFacturation.MUTUELLE.getCoefficient("PREMIUM")) + .isEqualByComparingTo(new BigDecimal("1.2")); + } + + @Test + @DisplayName("COOPERATIVE - coefficient 1.3 pour toutes les formules") + void testCooperative() { + assertThat(TypeOrganisationFacturation.COOPERATIVE.getCoefficient("BASIC")) + .isEqualByComparingTo(new BigDecimal("1.3")); + assertThat(TypeOrganisationFacturation.COOPERATIVE.getCoefficient("PREMIUM")) + .isEqualByComparingTo(new BigDecimal("1.3")); + } + + @Test + @DisplayName("FEDERATION - coefficient 1.0 en BASIC/STANDARD, 1.5 en PREMIUM") + void testFederation() { + assertThat(TypeOrganisationFacturation.FEDERATION.getCoefficient("BASIC")) + .isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(TypeOrganisationFacturation.FEDERATION.getCoefficient("STANDARD")) + .isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(TypeOrganisationFacturation.FEDERATION.getCoefficient("PREMIUM")) + .isEqualByComparingTo(new BigDecimal("1.5")); + } + + @Test + @DisplayName("Formule inconnue - utilise coefficientBase") + void testFormuleInconnue() { + assertThat(TypeOrganisationFacturation.FEDERATION.getCoefficient("STARTER")) + .isEqualByComparingTo(new BigDecimal("1.0")); + assertThat(TypeOrganisationFacturation.MUTUELLE.getCoefficient("INCONNU")) + .isEqualByComparingTo(new BigDecimal("1.2")); + } + } +} 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 index 21a3de9..9fd22f0 100644 --- 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 @@ -27,9 +27,11 @@ class TypePeriodeAbonnementTest { @DisplayName("Test toutes les valeurs enum") void testToutesValeurs() { TypePeriodeAbonnement[] values = TypePeriodeAbonnement.values(); - assertThat(values).hasSize(2); + assertThat(values).hasSize(4); assertThat(values).containsExactly( TypePeriodeAbonnement.MENSUEL, + TypePeriodeAbonnement.TRIMESTRIEL, + TypePeriodeAbonnement.SEMESTRIEL, TypePeriodeAbonnement.ANNUEL); } @@ -37,6 +39,8 @@ class TypePeriodeAbonnementTest { @DisplayName("Test valueOf - toutes les constantes") void testValueOf() { assertThat(TypePeriodeAbonnement.valueOf("MENSUEL")).isEqualTo(TypePeriodeAbonnement.MENSUEL); + assertThat(TypePeriodeAbonnement.valueOf("TRIMESTRIEL")).isEqualTo(TypePeriodeAbonnement.TRIMESTRIEL); + assertThat(TypePeriodeAbonnement.valueOf("SEMESTRIEL")).isEqualTo(TypePeriodeAbonnement.SEMESTRIEL); assertThat(TypePeriodeAbonnement.valueOf("ANNUEL")).isEqualTo(TypePeriodeAbonnement.ANNUEL); assertThatThrownBy(() -> TypePeriodeAbonnement.valueOf("INEXISTANT")) @@ -54,14 +58,36 @@ class TypePeriodeAbonnementTest { @DisplayName("Test getLibelle valeurs exactes") void testGetLibelleValeursExactes() { assertThat(TypePeriodeAbonnement.MENSUEL.getLibelle()).isEqualTo("Mensuel"); - assertThat(TypePeriodeAbonnement.ANNUEL.getLibelle()).isEqualTo("Annuel — 2 mois offerts"); + assertThat(TypePeriodeAbonnement.TRIMESTRIEL.getLibelle()).contains("Trimestriel"); + assertThat(TypePeriodeAbonnement.SEMESTRIEL.getLibelle()).contains("Semestriel"); + assertThat(TypePeriodeAbonnement.ANNUEL.getLibelle()).contains("Annuel"); } @Test @DisplayName("Test name()") void testName() { assertThat(TypePeriodeAbonnement.MENSUEL.name()).isEqualTo("MENSUEL"); + assertThat(TypePeriodeAbonnement.TRIMESTRIEL.name()).isEqualTo("TRIMESTRIEL"); + assertThat(TypePeriodeAbonnement.SEMESTRIEL.name()).isEqualTo("SEMESTRIEL"); assertThat(TypePeriodeAbonnement.ANNUEL.name()).isEqualTo("ANNUEL"); } + + @Test + @DisplayName("Test getNombreMois - durée en mois de chaque période") + void testGetNombreMois() { + assertThat(TypePeriodeAbonnement.MENSUEL.getNombreMois()).isEqualTo(1); + assertThat(TypePeriodeAbonnement.TRIMESTRIEL.getNombreMois()).isEqualTo(3); + assertThat(TypePeriodeAbonnement.SEMESTRIEL.getNombreMois()).isEqualTo(6); + assertThat(TypePeriodeAbonnement.ANNUEL.getNombreMois()).isEqualTo(12); + } + + @Test + @DisplayName("Test getCoefficient - remise appliquée par période") + void testGetCoefficient() { + assertThat(TypePeriodeAbonnement.MENSUEL.getCoefficient()).isEqualByComparingTo("1.00"); + assertThat(TypePeriodeAbonnement.TRIMESTRIEL.getCoefficient()).isEqualByComparingTo("0.95"); + assertThat(TypePeriodeAbonnement.SEMESTRIEL.getCoefficient()).isEqualByComparingTo("0.90"); + assertThat(TypePeriodeAbonnement.ANNUEL.getCoefficient()).isEqualByComparingTo("0.80"); + } } } 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 index cf066e3..1ef77b0 100644 --- 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 @@ -27,8 +27,9 @@ class StatutMembreTest { @DisplayName("Test toutes les valeurs enum") void testToutesValeurs() { StatutMembre[] values = StatutMembre.values(); - assertThat(values).hasSize(8); + assertThat(values).hasSize(10); assertThat(values).contains( + StatutMembre.INVITE, StatutMembre.EN_ATTENTE_VALIDATION, StatutMembre.ACTIF, StatutMembre.INACTIF, @@ -36,7 +37,8 @@ class StatutMembreTest { StatutMembre.DEMISSIONNAIRE, StatutMembre.RADIE, StatutMembre.HONORAIRE, - StatutMembre.DECEDE); + StatutMembre.DECEDE, + StatutMembre.ARCHIVE); } @Test 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 index ab3c57c..d7f5df9 100644 --- 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 @@ -7,7 +7,6 @@ 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") @@ -75,20 +74,6 @@ class TypeOrganisationTest { 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() { @@ -101,4 +86,3 @@ class TypeOrganisationTest { } } } -