diff --git a/unionflow-server-impl-quarkus/pom.xml b/unionflow-server-impl-quarkus/pom.xml index 8aa2be9..c5a8076 100644 --- a/unionflow-server-impl-quarkus/pom.xml +++ b/unionflow-server-impl-quarkus/pom.xml @@ -177,6 +177,13 @@ 17 17 UTF-8 + + + org.projectlombok + lombok + 1.18.30 + + diff --git a/unionflow-server-impl-quarkus/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java b/unionflow-server-impl-quarkus/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java index 6227f47..e77af23 100644 --- a/unionflow-server-impl-quarkus/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java +++ b/unionflow-server-impl-quarkus/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java @@ -4,6 +4,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response; +import org.jboss.logging.Logger; /** * Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application @@ -12,6 +13,8 @@ import jakarta.ws.rs.core.Response; @Path("/auth") public class AuthCallbackResource { + private static final Logger log = Logger.getLogger(AuthCallbackResource.class); + /** * Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile * avec les paramètres reçus. @@ -27,12 +30,8 @@ public class AuthCallbackResource { try { // Log des paramètres reçus pour debug - System.out.println("=== CALLBACK DEBUG ==="); - System.out.println("Code: " + code); - System.out.println("State: " + state); - System.out.println("Session State: " + sessionState); - System.out.println("Error: " + error); - System.out.println("Error Description: " + errorDescription); + log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s", + code, state, sessionState, error, errorDescription); // URL de redirection simple vers l'application mobile String redirectUrl = "dev.lions.unionflow-mobile://callback"; diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java index 2a5147b..26b4157 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import dev.lions.unionflow.server.entity.Evenement; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,8 +15,8 @@ import lombok.NoArgsConstructor; * l'application mobile Flutter * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @Data @NoArgsConstructor @@ -24,7 +25,7 @@ import lombok.NoArgsConstructor; @JsonIgnoreProperties(ignoreUnknown = true) public class EvenementMobileDTO { - private Long id; + private UUID id; private String titre; private String description; private LocalDateTime dateDebut; @@ -47,9 +48,9 @@ public class EvenementMobileDTO { private Integer participantsActuels; // IDs et noms pour les relations - private Long organisateurId; + private UUID organisateurId; private String organisateurNom; - private Long organisationId; + private UUID organisationId; private String organisationNom; // Priorité (à ajouter dans l'entité si nécessaire) @@ -96,7 +97,7 @@ public class EvenementMobileDTO { } return EvenementMobileDTO.builder() - .id(evenement.id) // PanacheEntity utilise un champ public id + .id(evenement.getId()) // Utilise getId() depuis BaseEntity .titre(evenement.getTitre()) .description(evenement.getDescription()) .dateDebut(evenement.getDateDebut()) @@ -110,12 +111,12 @@ public class EvenementMobileDTO { .statut(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE") // Mapping des champs renommés .maxParticipants(evenement.getCapaciteMax()) - .participantsActuels(0) // TODO: Calculer depuis les inscriptions si nécessaire + .participantsActuels(evenement.getNombreInscrits()) // Relations (gestion sécurisée des lazy loading) - .organisateurId(null) // TODO: Charger si nécessaire - .organisateurNom(null) // TODO: Charger si nécessaire - .organisationId(null) // TODO: Charger si nécessaire - .organisationNom(null) // TODO: Charger si nécessaire + .organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null) + .organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null) + .organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null) + .organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null) // Priorité (valeur par défaut) .priorite("MOYENNE") // Mapping booléens diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java new file mode 100644 index 0000000..5a1ef42 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java @@ -0,0 +1,141 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Classe de base pour les entités UnionFlow utilisant UUID comme identifiant + * + *

Remplace PanacheEntity pour utiliser UUID au lieu de Long comme ID. + * Fournit les fonctionnalités de base de Panache avec UUID. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(name = "date_creation", nullable = false, updatable = false) + protected LocalDateTime dateCreation; + + @Column(name = "date_modification") + protected LocalDateTime dateModification; + + @Column(name = "cree_par", length = 255) + protected String creePar; + + @Column(name = "modifie_par", length = 255) + protected String modifiePar; + + @Version + @Column(name = "version") + protected Long version; + + @Column(name = "actif", nullable = false) + protected Boolean actif = true; + + // Constructeur par défaut + public BaseEntity() { + this.dateCreation = LocalDateTime.now(); + this.actif = true; + this.version = 0L; + } + + // Getters et Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public LocalDateTime getDateCreation() { + return dateCreation; + } + + public void setDateCreation(LocalDateTime dateCreation) { + this.dateCreation = dateCreation; + } + + public LocalDateTime getDateModification() { + return dateModification; + } + + public void setDateModification(LocalDateTime dateModification) { + this.dateModification = dateModification; + } + + public String getCreePar() { + return creePar; + } + + public void setCreePar(String creePar) { + this.creePar = creePar; + } + + public String getModifiePar() { + return modifiePar; + } + + public void setModifiePar(String modifiePar) { + this.modifiePar = modifiePar; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Boolean getActif() { + return actif; + } + + public void setActif(Boolean actif) { + this.actif = actif; + } + + // Callbacks JPA + @PrePersist + protected void onCreate() { + if (this.dateCreation == null) { + this.dateCreation = LocalDateTime.now(); + } + if (this.actif == null) { + this.actif = true; + } + if (this.version == null) { + this.version = 0L; + } + } + + @PreUpdate + protected void onUpdate() { + this.dateModification = LocalDateTime.now(); + } + + // Méthodes utilitaires Panache-like + public void persist() { + // Cette méthode sera implémentée par les repositories ou services + // Pour l'instant, elle est là pour compatibilité avec le code existant + throw new UnsupportedOperationException( + "Utilisez le repository approprié pour persister cette entité"); + } + + public static T findById(UUID id) { + // Cette méthode sera implémentée par les repositories + throw new UnsupportedOperationException( + "Utilisez le repository approprié pour rechercher par ID"); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java index fb79272..a157083 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java @@ -1,11 +1,11 @@ package dev.lions.unionflow.server.entity; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,11 +13,11 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * Entité Cotisation avec Lombok Représente une cotisation d'un membre à son organisation + * Entité Cotisation avec UUID Représente une cotisation d'un membre à son organisation * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @Entity @Table( @@ -34,8 +34,8 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class Cotisation extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class Cotisation extends BaseEntity { @NotBlank @Column(name = "numero_reference", unique = true, nullable = false, length = 50) @@ -115,7 +115,7 @@ public class Cotisation extends PanacheEntity { private LocalDateTime dateDernierRappel; @Column(name = "valide_par_id") - private Long valideParId; + private UUID valideParId; @Size(max = 100) @Column(name = "nom_validateur", length = 100) @@ -132,13 +132,6 @@ public class Cotisation extends PanacheEntity { @Column(name = "reference_paiement", length = 100) private String referencePaiement; - @Builder.Default - @Column(name = "date_creation", nullable = false) - private LocalDateTime dateCreation = LocalDateTime.now(); - - @Column(name = "date_modification") - private LocalDateTime dateModification; - /** Méthode métier pour calculer le montant restant à payer */ public BigDecimal getMontantRestant() { if (montantDu == null || montantPaye == null) { @@ -168,12 +161,10 @@ public class Cotisation extends PanacheEntity { /** Callback JPA avant la persistance */ @PrePersist protected void onCreate() { + super.onCreate(); // Appelle le onCreate de BaseEntity if (numeroReference == null || numeroReference.isEmpty()) { numeroReference = genererNumeroReference(); } - if (dateCreation == null) { - dateCreation = LocalDateTime.now(); - } if (codeDevise == null) { codeDevise = "XOF"; } @@ -190,10 +181,4 @@ public class Cotisation extends PanacheEntity { recurrente = false; } } - - /** Callback JPA avant la mise à jour */ - @PreUpdate - protected void onUpdate() { - dateModification = LocalDateTime.now(); - } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java index 75ed3ce..5e6994c 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java @@ -2,7 +2,6 @@ package dev.lions.unionflow.server.entity; import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import java.math.BigDecimal; import java.math.RoundingMode; @@ -20,8 +19,8 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class DemandeAide extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class DemandeAide extends BaseEntity { @Column(name = "titre", nullable = false, length = 200) private String titre; @@ -79,6 +78,7 @@ public class DemandeAide extends PanacheEntity { @PrePersist protected void onCreate() { + super.onCreate(); // Appelle le onCreate de BaseEntity if (dateDemande == null) { dateDemande = LocalDateTime.now(); } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java index ffa7b58..b8d1d10 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java @@ -1,22 +1,20 @@ package dev.lions.unionflow.server.entity; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import lombok.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; /** * Entité Événement pour la gestion des événements de l'union * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @Entity @Table( @@ -31,8 +29,8 @@ import org.hibernate.annotations.UpdateTimestamp; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class Evenement extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class Evenement extends BaseEntity { @NotBlank @Size(min = 3, max = 200) @@ -120,21 +118,6 @@ public class Evenement extends PanacheEntity { @Builder.Default private List inscriptions = new ArrayList<>(); - // Métadonnées - @CreationTimestamp - @Column(name = "date_creation", nullable = false, updatable = false) - private LocalDateTime dateCreation; - - @UpdateTimestamp - @Column(name = "date_modification") - private LocalDateTime dateModification; - - @Column(name = "cree_par", length = 100) - private String creePar; - - @Column(name = "modifie_par", length = 100) - private String modifiePar; - /** Types d'événements */ public enum TypeEvenement { ASSEMBLEE_GENERALE("Assemblée Générale"), @@ -259,12 +242,12 @@ public class Evenement extends PanacheEntity { } /** Vérifie si un membre est inscrit à l'événement */ - public boolean isMemberInscrit(Long membreId) { + public boolean isMemberInscrit(UUID membreId) { return inscriptions != null && inscriptions.stream() .anyMatch( inscription -> - inscription.getMembre().id.equals(membreId) + inscription.getMembre().getId().equals(membreId) && inscription.getStatut() == InscriptionEvenement.StatutInscription.CONFIRMEE); } @@ -277,16 +260,4 @@ public class Evenement extends PanacheEntity { return (double) getNombreInscrits() / capaciteMax * 100; } - - @PrePersist - public void prePersist() { - if (dateCreation == null) { - dateCreation = LocalDateTime.now(); - } - } - - @PreUpdate - public void preUpdate() { - dateModification = LocalDateTime.now(); - } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java index acbde28..0cec9c7 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java @@ -1,6 +1,5 @@ package dev.lions.unionflow.server.entity; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; @@ -10,8 +9,8 @@ import lombok.*; * Entité InscriptionEvenement représentant l'inscription d'un membre à un événement * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @Entity @Table( @@ -25,8 +24,8 @@ import lombok.*; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class InscriptionEvenement extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class InscriptionEvenement extends BaseEntity { @NotNull @ManyToOne(fetch = FetchType.LAZY) @@ -50,13 +49,6 @@ public class InscriptionEvenement extends PanacheEntity { @Column(name = "commentaire", length = 500) private String commentaire; - @Builder.Default - @Column(name = "date_creation", nullable = false) - private LocalDateTime dateCreation = LocalDateTime.now(); - - @Column(name = "date_modification") - private LocalDateTime dateModification; - /** Énumération des statuts d'inscription */ public enum StatutInscription { CONFIRMEE("Confirmée"), @@ -147,14 +139,15 @@ public class InscriptionEvenement extends PanacheEntity { @PreUpdate public void preUpdate() { + super.onUpdate(); // Appelle le onUpdate de BaseEntity this.dateModification = LocalDateTime.now(); } @Override public String toString() { return String.format( - "InscriptionEvenement{id=%d, membre=%s, evenement=%s, statut=%s, dateInscription=%s}", - id, + "InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}", + getId(), membre != null ? membre.getEmail() : "null", evenement != null ? evenement.getTitre() : "null", statut, diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java index 501de4d..12c46cd 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java @@ -1,19 +1,18 @@ package dev.lions.unionflow.server.entity; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** Entité Membre avec Lombok */ +/** Entité Membre avec UUID */ @Entity @Table( name = "membres", @@ -26,8 +25,8 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class Membre extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class Membre extends BaseEntity { @NotBlank @Column(name = "numero_membre", unique = true, nullable = false, length = 20) @@ -63,17 +62,6 @@ public class Membre extends PanacheEntity { @Column(name = "roles", length = 500) private String roles; - @Builder.Default - @Column(name = "actif", nullable = false) - private Boolean actif = true; - - @Builder.Default - @Column(name = "date_creation", nullable = false) - private LocalDateTime dateCreation = LocalDateTime.now(); - - @Column(name = "date_modification") - private LocalDateTime dateModification; - // Relations @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "organisation_id") @@ -93,9 +81,4 @@ public class Membre extends PanacheEntity { public int getAge() { return LocalDate.now().getYear() - dateNaissance.getYear(); } - - @PreUpdate - public void preUpdate() { - this.dateModification = LocalDateTime.now(); - } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java index fb55269..a230f11 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java @@ -1,6 +1,5 @@ package dev.lions.unionflow.server.entity; -import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; @@ -17,12 +16,12 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * Entité Organisation avec Lombok Représente une organisation (Lions Club, Association, + * Entité Organisation avec UUID Représente une organisation (Lions Club, Association, * Coopérative, etc.) * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @Entity @Table( @@ -44,8 +43,8 @@ import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(callSuper = false) -public class Organisation extends PanacheEntity { +@EqualsAndHashCode(callSuper = true) +public class Organisation extends BaseEntity { @NotBlank @Column(name = "nom", nullable = false, length = 200) @@ -187,28 +186,6 @@ public class Organisation extends PanacheEntity { @Column(name = "accepte_nouveaux_membres", nullable = false) private Boolean accepteNouveauxMembres = true; - // Métadonnées - @Builder.Default - @Column(name = "actif", nullable = false) - private Boolean actif = true; - - @Builder.Default - @Column(name = "date_creation", nullable = false) - private LocalDateTime dateCreation = LocalDateTime.now(); - - @Column(name = "date_modification") - private LocalDateTime dateModification; - - @Column(name = "cree_par", length = 100) - private String creePar; - - @Column(name = "modifie_par", length = 100) - private String modifiePar; - - @Builder.Default - @Column(name = "version", nullable = false) - private Long version = 0L; - // Relations @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @Builder.Default @@ -237,7 +214,7 @@ public class Organisation extends PanacheEntity { /** Méthode métier pour vérifier si l'organisation est active */ public boolean isActive() { - return "ACTIVE".equals(statut) && actif; + return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif()); } /** Méthode métier pour ajouter un membre */ @@ -258,7 +235,7 @@ public class Organisation extends PanacheEntity { /** Méthode métier pour activer l'organisation */ public void activer(String utilisateur) { this.statut = "ACTIVE"; - this.actif = true; + this.setActif(true); marquerCommeModifie(utilisateur); } @@ -272,24 +249,26 @@ public class Organisation extends PanacheEntity { /** Méthode métier pour dissoudre l'organisation */ public void dissoudre(String utilisateur) { this.statut = "DISSOUTE"; - this.actif = false; + this.setActif(false); this.accepteNouveauxMembres = false; marquerCommeModifie(utilisateur); } /** Marque l'entité comme modifiée */ public void marquerCommeModifie(String utilisateur) { - this.dateModification = LocalDateTime.now(); - this.modifiePar = utilisateur; - this.version++; + this.setDateModification(LocalDateTime.now()); + this.setModifiePar(utilisateur); + if (this.getVersion() != null) { + this.setVersion(this.getVersion() + 1); + } else { + this.setVersion(1L); + } } /** Callback JPA avant la persistance */ @PrePersist protected void onCreate() { - if (dateCreation == null) { - dateCreation = LocalDateTime.now(); - } + super.onCreate(); // Appelle le onCreate de BaseEntity if (statut == null) { statut = "ACTIVE"; } @@ -317,17 +296,5 @@ public class Organisation extends PanacheEntity { if (cotisationObligatoire == null) { cotisationObligatoire = false; } - if (actif == null) { - actif = true; - } - if (version == null) { - version = 0L; - } - } - - /** Callback JPA avant la mise à jour */ - @PreUpdate - protected void onUpdate() { - dateModification = LocalDateTime.now(); } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java new file mode 100644 index 0000000..18a34b5 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java @@ -0,0 +1,140 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.BaseEntity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository de base pour les entités utilisant UUID comme identifiant + * + *

Remplace PanacheRepository pour utiliser UUID au lieu de Long. + * Fournit les fonctionnalités de base de Panache avec UUID. + * + * @param Le type d'entité qui étend BaseEntity + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +public abstract class BaseRepository { + + @PersistenceContext + protected EntityManager entityManager; + + protected final Class entityClass; + + protected BaseRepository(Class entityClass) { + this.entityClass = entityClass; + } + + /** + * Trouve une entité par son UUID + * + * @param id L'UUID de l'entité + * @return L'entité trouvée ou null + */ + public T findById(UUID id) { + return entityManager.find(entityClass, id); + } + + /** + * Trouve une entité par son UUID (retourne Optional) + * + * @param id L'UUID de l'entité + * @return Optional contenant l'entité si trouvée + */ + public Optional findByIdOptional(UUID id) { + return Optional.ofNullable(findById(id)); + } + + /** + * Persiste une entité + * + * @param entity L'entité à persister + */ + @Transactional + public void persist(T entity) { + entityManager.persist(entity); + } + + /** + * Met à jour une entité + * + * @param entity L'entité à mettre à jour + * @return L'entité mise à jour + */ + @Transactional + public T update(T entity) { + return entityManager.merge(entity); + } + + /** + * Supprime une entité + * + * @param entity L'entité à supprimer + */ + @Transactional + public void delete(T entity) { + entityManager.remove(entity); + } + + /** + * Supprime une entité par son UUID + * + * @param id L'UUID de l'entité à supprimer + */ + @Transactional + public boolean deleteById(UUID id) { + T entity = findById(id); + if (entity != null) { + delete(entity); + return true; + } + return false; + } + + /** + * Liste toutes les entités + * + * @return La liste de toutes les entités + */ + public List listAll() { + return entityManager.createQuery( + "SELECT e FROM " + entityClass.getSimpleName() + " e", entityClass) + .getResultList(); + } + + /** + * Compte toutes les entités + * + * @return Le nombre total d'entités + */ + public long count() { + return entityManager.createQuery( + "SELECT COUNT(e) FROM " + entityClass.getSimpleName() + " e", Long.class) + .getSingleResult(); + } + + /** + * Vérifie si une entité existe par son UUID + * + * @param id L'UUID de l'entité + * @return true si l'entité existe + */ + public boolean existsById(UUID id) { + return findById(id) != null; + } + + /** + * Obtient l'EntityManager (pour les requêtes avancées) + * + * @return L'EntityManager + */ + public EntityManager getEntityManager() { + return entityManager; + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java index 17b923c..9aa750f 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java @@ -1,285 +1,392 @@ package dev.lions.unionflow.server.repository; import dev.lions.unionflow.server.entity.Cotisation; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; /** - * Repository pour la gestion des cotisations Utilise Panache pour simplifier les opérations JPA + * Repository pour la gestion des cotisations avec UUID * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @ApplicationScoped -public class CotisationRepository implements PanacheRepository { +public class CotisationRepository extends BaseRepository { - /** - * Trouve une cotisation par son numéro de référence - * - * @param numeroReference le numéro de référence unique - * @return Optional contenant la cotisation si trouvée - */ - public Optional findByNumeroReference(String numeroReference) { - return find("numeroReference = ?1", numeroReference).firstResultOptional(); - } - - /** - * Trouve toutes les cotisations d'un membre - * - * @param membreId l'identifiant du membre - * @param page pagination - * @param sort tri - * @return liste paginée des cotisations - */ - public List findByMembreId(Long membreId, Page page, Sort sort) { - return find("membre.id = ?1", membreId).page(page).list(); - } - - /** - * Trouve les cotisations par statut - * - * @param statut le statut recherché - * @param page pagination - * @return liste paginée des cotisations - */ - public List findByStatut(String statut, Page page) { - return find("statut = ?1", Sort.by("dateEcheance").descending(), statut).page(page).list(); - } - - /** - * Trouve les cotisations en retard - * - * @param dateReference date de référence (généralement aujourd'hui) - * @param page pagination - * @return liste des cotisations en retard - */ - public List findCotisationsEnRetard(LocalDate dateReference, Page page) { - return find( - "dateEcheance < ?1 and statut != 'PAYEE' and statut != 'ANNULEE'", - Sort.by("dateEcheance").ascending(), - dateReference) - .page(page) - .list(); - } - - /** - * Trouve les cotisations par période (année/mois) - * - * @param annee l'année - * @param mois le mois (optionnel) - * @param page pagination - * @return liste des cotisations de la période - */ - public List findByPeriode(Integer annee, Integer mois, Page page) { - if (mois != null) { - return find("annee = ?1 and mois = ?2", Sort.by("dateEcheance").descending(), annee, mois) - .page(page) - .list(); - } else { - return find("annee = ?1", Sort.by("mois", "dateEcheance").descending(), annee) - .page(page) - .list(); - } - } - - /** - * Trouve les cotisations par type - * - * @param typeCotisation le type de cotisation - * @param page pagination - * @return liste des cotisations du type spécifié - */ - public List findByType(String typeCotisation, Page page) { - return find("typeCotisation = ?1", Sort.by("dateEcheance").descending(), typeCotisation) - .page(page) - .list(); - } - - /** - * Recherche avancée avec filtres multiples - * - * @param membreId identifiant du membre (optionnel) - * @param statut statut (optionnel) - * @param typeCotisation type (optionnel) - * @param annee année (optionnel) - * @param mois mois (optionnel) - * @param page pagination - * @return liste filtrée des cotisations - */ - public List rechercheAvancee( - Long membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) { - StringBuilder query = new StringBuilder("1=1"); - Map params = new java.util.HashMap<>(); - - if (membreId != null) { - query.append(" and membre.id = :membreId"); - params.put("membreId", membreId); + public CotisationRepository() { + super(Cotisation.class); } - if (statut != null && !statut.isEmpty()) { - query.append(" and statut = :statut"); - params.put("statut", statut); + /** + * Trouve une cotisation par son numéro de référence + * + * @param numeroReference le numéro de référence unique + * @return Optional contenant la cotisation si trouvée + */ + public Optional findByNumeroReference(String numeroReference) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference", + Cotisation.class); + query.setParameter("numeroReference", numeroReference); + return query.getResultStream().findFirst(); } - if (typeCotisation != null && !typeCotisation.isEmpty()) { - query.append(" and typeCotisation = :typeCotisation"); - params.put("typeCotisation", typeCotisation); + /** + * Trouve toutes les cotisations d'un membre + * + * @param membreId l'UUID du membre + * @param page pagination + * @param sort tri + * @return liste paginée des cotisations + */ + public List findByMembreId(UUID membreId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy, + Cotisation.class); + query.setParameter("membreId", membreId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (annee != null) { - query.append(" and annee = :annee"); - params.put("annee", annee); + /** + * Trouve les cotisations par statut + * + * @param statut le statut recherché + * @param page pagination + * @return liste paginée des cotisations + */ + public List findByStatut(String statut, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (mois != null) { - query.append(" and mois = :mois"); - params.put("mois", mois); + /** + * Trouve les cotisations en retard + * + * @param dateReference date de référence (généralement aujourd'hui) + * @param page pagination + * @return liste des cotisations en retard + */ + public List findCotisationsEnRetard(LocalDate dateReference, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC", + Cotisation.class); + query.setParameter("dateReference", dateReference); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - return find(query.toString(), Sort.by("dateEcheance").descending(), params).page(page).list(); - } + /** + * Trouve les cotisations par période (année/mois) + * + * @param annee l'année + * @param mois le mois (optionnel) + * @param page pagination + * @return liste des cotisations de la période + */ + public List findByPeriode(Integer annee, Integer mois, Page page) { + TypedQuery query; + if (mois != null) { + query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("annee", annee); + query.setParameter("mois", mois); + } else { + query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC", + Cotisation.class); + query.setParameter("annee", annee); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Calcule le total des montants dus pour un membre - * - * @param membreId identifiant du membre - * @return montant total dû - */ - public BigDecimal calculerTotalMontantDu(Long membreId) { - return find("select sum(c.montantDu) from Cotisation c where c.membre.id = ?1", membreId) - .project(BigDecimal.class) - .firstResult(); - } + /** + * Trouve les cotisations par type + * + * @param typeCotisation le type de cotisation + * @param page pagination + * @return liste des cotisations du type spécifié + */ + public List findByType(String typeCotisation, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("typeCotisation", typeCotisation); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Calcule le total des montants payés pour un membre - * - * @param membreId identifiant du membre - * @return montant total payé - */ - public BigDecimal calculerTotalMontantPaye(Long membreId) { - return find("select sum(c.montantPaye) from Cotisation c where c.membre.id = ?1", membreId) - .project(BigDecimal.class) - .firstResult(); - } + /** + * Recherche avancée avec filtres multiples + * + * @param membreId UUID du membre (optionnel) + * @param statut statut (optionnel) + * @param typeCotisation type (optionnel) + * @param annee année (optionnel) + * @param mois mois (optionnel) + * @param page pagination + * @return liste filtrée des cotisations + */ + public List rechercheAvancee( + UUID membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) { + StringBuilder jpql = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1"); + Map params = new HashMap<>(); - /** - * Compte les cotisations par statut - * - * @param statut le statut - * @return nombre de cotisations - */ - public long compterParStatut(String statut) { - return count("statut = ?1", statut); - } + if (membreId != null) { + jpql.append(" AND c.membre.id = :membreId"); + params.put("membreId", membreId); + } - /** - * Trouve les cotisations nécessitant un rappel - * - * @param joursAvantEcheance nombre de jours avant échéance - * @param nombreMaxRappels nombre maximum de rappels déjà envoyés - * @return liste des cotisations à rappeler - */ - public List findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) { - LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance); - return find( - "dateEcheance <= ?1 and statut != 'PAYEE' and statut != 'ANNULEE' and nombreRappels <" - + " ?2", - Sort.by("dateEcheance").ascending(), - dateRappel, - nombreMaxRappels) - .list(); - } + if (statut != null && !statut.isEmpty()) { + jpql.append(" AND c.statut = :statut"); + params.put("statut", statut); + } - /** - * Met à jour le nombre de rappels pour une cotisation - * - * @param cotisationId identifiant de la cotisation - * @return nombre de lignes mises à jour - */ - public int incrementerNombreRappels(Long cotisationId) { - return update( - "nombreRappels = nombreRappels + 1, dateDernierRappel = ?1 where id = ?2", - LocalDateTime.now(), - cotisationId); - } + if (typeCotisation != null && !typeCotisation.isEmpty()) { + jpql.append(" AND c.typeCotisation = :typeCotisation"); + params.put("typeCotisation", typeCotisation); + } - /** - * Statistiques des cotisations par période - * - * @param annee l'année - * @param mois le mois (optionnel) - * @return map avec les statistiques - */ - public Map getStatistiquesPeriode(Integer annee, Integer mois) { - String baseQuery = - mois != null - ? "from Cotisation c where c.annee = ?1 and c.mois = ?2" - : "from Cotisation c where c.annee = ?1"; + if (annee != null) { + jpql.append(" AND c.annee = :annee"); + params.put("annee", annee); + } - Object[] params = mois != null ? new Object[] {annee, mois} : new Object[] {annee}; + if (mois != null) { + jpql.append(" AND c.mois = :mois"); + params.put("mois", mois); + } - Long totalCotisations = - mois != null ? count("annee = ?1 and mois = ?2", params) : count("annee = ?1", params); + jpql.append(" ORDER BY c.dateEcheance DESC"); - BigDecimal montantTotal = - find("select sum(c.montantDu) " + baseQuery, params) - .project(BigDecimal.class) - .firstResult(); + TypedQuery query = entityManager.createQuery(jpql.toString(), Cotisation.class); + for (Map.Entry param : params.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - BigDecimal montantPaye = - find("select sum(c.montantPaye) " + baseQuery, params) - .project(BigDecimal.class) - .firstResult(); + /** + * Calcule le total des montants dus pour un membre + * + * @param membreId UUID du membre + * @return montant total dû + */ + public BigDecimal calculerTotalMontantDu(UUID membreId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId", + BigDecimal.class); + query.setParameter("membreId", membreId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } - Long cotisationsPayees = - mois != null - ? count("annee = ?1 and mois = ?2 and statut = 'PAYEE'", annee, mois) - : count("annee = ?1 and statut = 'PAYEE'", annee); + /** + * Calcule le total des montants payés pour un membre + * + * @param membreId UUID du membre + * @return montant total payé + */ + public BigDecimal calculerTotalMontantPaye(UUID membreId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId", + BigDecimal.class); + query.setParameter("membreId", membreId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } - return Map.of( - "totalCotisations", totalCotisations != null ? totalCotisations : 0L, - "montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO, - "montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO, - "cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L, - "tauxPaiement", - totalCotisations != null && totalCotisations > 0 - ? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations - : 0.0); - } + /** + * Compte les cotisations par statut + * + * @param statut le statut + * @return nombre de cotisations + */ + public long compterParStatut(String statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } - /** Somme des montants payés dans une période */ - public BigDecimal sumMontantsPayes( - java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return find( - "SELECT COALESCE(SUM(c.montant), 0) FROM Cotisation c WHERE c.organisation.id = ?1 and" - + " c.statut = 'PAYEE' and c.datePaiement between ?2 and ?3", - organisationId, - debut, - fin) - .project(BigDecimal.class) - .firstResult(); - } + /** + * Trouve les cotisations nécessitant un rappel + * + * @param joursAvantEcheance nombre de jours avant échéance + * @param nombreMaxRappels nombre maximum de rappels déjà envoyés + * @return liste des cotisations à rappeler + */ + public List findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) { + LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance); + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.dateEcheance <= :dateRappel AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' AND c.nombreRappels < :nombreMaxRappels ORDER BY c.dateEcheance ASC", + Cotisation.class); + query.setParameter("dateRappel", dateRappel); + query.setParameter("nombreMaxRappels", nombreMaxRappels); + return query.getResultList(); + } - /** Somme des montants en attente dans une période */ - public BigDecimal sumMontantsEnAttente( - java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return find( - "SELECT COALESCE(SUM(c.montant), 0) FROM Cotisation c WHERE c.organisation.id = ?1 and" - + " c.statut = 'EN_ATTENTE' and c.dateCreation between ?2 and ?3", - organisationId, - debut, - fin) - .project(BigDecimal.class) - .firstResult(); - } + /** + * Met à jour le nombre de rappels pour une cotisation + * + * @param cotisationId UUID de la cotisation + * @return true si mise à jour réussie + */ + public boolean incrementerNombreRappels(UUID cotisationId) { + Cotisation cotisation = findById(cotisationId); + if (cotisation != null) { + cotisation.setNombreRappels( + cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1); + cotisation.setDateDernierRappel(LocalDateTime.now()); + update(cotisation); + return true; + } + return false; + } + + /** + * Statistiques des cotisations par période + * + * @param annee l'année + * @param mois le mois (optionnel) + * @return map avec les statistiques + */ + public Map getStatistiquesPeriode(Integer annee, Integer mois) { + String baseQuery = mois != null + ? "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois" + : "SELECT c FROM Cotisation c WHERE c.annee = :annee"; + + TypedQuery countQuery; + TypedQuery montantTotalQuery; + TypedQuery montantPayeQuery; + TypedQuery payeesQuery; + + if (mois != null) { + countQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + Long.class); + montantTotalQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + BigDecimal.class); + montantPayeQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + BigDecimal.class); + payeesQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'", + Long.class); + + countQuery.setParameter("annee", annee); + countQuery.setParameter("mois", mois); + montantTotalQuery.setParameter("annee", annee); + montantTotalQuery.setParameter("mois", mois); + montantPayeQuery.setParameter("annee", annee); + montantPayeQuery.setParameter("mois", mois); + payeesQuery.setParameter("annee", annee); + payeesQuery.setParameter("mois", mois); + } else { + countQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee", Long.class); + montantTotalQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee", + BigDecimal.class); + montantPayeQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee", + BigDecimal.class); + payeesQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'", + Long.class); + + countQuery.setParameter("annee", annee); + montantTotalQuery.setParameter("annee", annee); + montantPayeQuery.setParameter("annee", annee); + payeesQuery.setParameter("annee", annee); + } + + Long totalCotisations = countQuery.getSingleResult(); + BigDecimal montantTotal = montantTotalQuery.getSingleResult(); + BigDecimal montantPaye = montantPayeQuery.getSingleResult(); + Long cotisationsPayees = payeesQuery.getSingleResult(); + + return Map.of( + "totalCotisations", totalCotisations != null ? totalCotisations : 0L, + "montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO, + "montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO, + "cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L, + "tauxPaiement", + totalCotisations != null && totalCotisations > 0 + ? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations + : 0.0); + } + + /** Somme des montants payés dans une période */ + public BigDecimal sumMontantsPayes( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'PAYEE' AND c.datePaiement BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Somme des montants en attente dans une période */ + public BigDecimal sumMontantsEnAttente( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'EN_ATTENTE' AND c.dateCreation BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "c.dateEcheance DESC"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("c.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java index 0a71c79..d44bf34 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java @@ -3,167 +3,273 @@ package dev.lions.unionflow.server.repository; import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; import dev.lions.unionflow.server.entity.DemandeAide; -import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; -/** Repository pour les demandes d'aide */ +/** Repository pour les demandes d'aide avec UUID */ @ApplicationScoped -public class DemandeAideRepository implements PanacheRepositoryBase { +public class DemandeAideRepository extends BaseRepository { - /** Trouve toutes les demandes d'aide par organisation */ - public List findByOrganisationId(UUID organisationId) { - return find("organisation.id", organisationId).list(); - } + public DemandeAideRepository() { + super(DemandeAide.class); + } - /** Trouve toutes les demandes d'aide par organisation avec pagination */ - public List findByOrganisationId(UUID organisationId, Page page, Sort sort) { - return find("organisation.id = ?1 ORDER BY dateDemande DESC", organisationId).page(page).list(); - } + /** Trouve toutes les demandes d'aide par organisation */ + public List findByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide par demandeur */ - public List findByDemandeurId(UUID demandeurId) { - return find("demandeur.id", demandeurId).list(); - } + /** Trouve toutes les demandes d'aide par organisation avec pagination */ + public List findByOrganisationId(UUID organisationId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY d.dateDemande DESC"; + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId" + orderBy, + DemandeAide.class); + query.setParameter("organisationId", organisationId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide par statut */ - public List findByStatut(StatutAide statut) { - return find("statut", statut).list(); - } + /** Trouve toutes les demandes d'aide par demandeur */ + public List findByDemandeurId(UUID demandeurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.demandeur.id = :demandeurId", + DemandeAide.class); + query.setParameter("demandeurId", demandeurId); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide par statut et organisation */ - public List findByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { - return find("statut = ?1 and organisation.id = ?2", statut, organisationId).list(); - } + /** Trouve toutes les demandes d'aide par statut */ + public List findByStatut(StatutAide statut) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut", + DemandeAide.class); + query.setParameter("statut", statut); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide par type */ - public List findByTypeAide(TypeAide typeAide) { - return find("typeAide", typeAide).list(); - } + /** Trouve toutes les demandes d'aide par statut et organisation */ + public List findByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("statut", statut); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide urgentes */ - public List findUrgentes() { - return find("urgence", true).list(); - } + /** Trouve toutes les demandes d'aide par type */ + public List findByTypeAide(TypeAide typeAide) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.typeAide = :typeAide", + DemandeAide.class); + query.setParameter("typeAide", typeAide); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide urgentes par organisation */ - public List findUrgentesByOrganisationId(UUID organisationId) { - return find("urgence = true and organisation.id = ?1", organisationId).list(); - } + /** Trouve toutes les demandes d'aide urgentes */ + public List findUrgentes() { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.urgence = true", + DemandeAide.class); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide dans une période */ - public List findByPeriode(LocalDateTime debut, LocalDateTime fin) { - return find("dateDemande >= ?1 and dateDemande <= ?2", debut, fin).list(); - } + /** Trouve toutes les demandes d'aide urgentes par organisation */ + public List findUrgentesByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.urgence = true AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } - /** Trouve toutes les demandes d'aide dans une période pour une organisation */ - public List findByPeriodeAndOrganisationId( - LocalDateTime debut, LocalDateTime fin, UUID organisationId) { - return find( - "dateDemande >= ?1 and dateDemande <= ?2 and organisation.id = ?3", - debut, - fin, - organisationId) - .list(); - } + /** Trouve toutes les demandes d'aide dans une période */ + public List findByPeriode(LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin", + DemandeAide.class); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getResultList(); + } - /** Compte le nombre de demandes par statut */ - public long countByStatut(StatutAide statut) { - return count("statut", statut); - } + /** Trouve toutes les demandes d'aide dans une période pour une organisation */ + public List findByPeriodeAndOrganisationId( + LocalDateTime debut, LocalDateTime fin, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } - /** Compte le nombre de demandes par statut et organisation */ - public long countByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { - return count("statut = ?1 and organisation.id = ?2", statut, organisationId); - } + /** Compte le nombre de demandes par statut */ + public long countByStatut(StatutAide statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut", + Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } - /** Calcule le montant total demandé par organisation */ - public Optional sumMontantDemandeByOrganisationId(UUID organisationId) { - return find( - "SELECT SUM(d.montantDemande) FROM DemandeAide d WHERE d.organisation.id = ?1", - organisationId) - .project(BigDecimal.class) - .firstResultOptional(); - } + /** Compte le nombre de demandes par statut et organisation */ + public long countByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId", + Long.class); + query.setParameter("statut", statut); + query.setParameter("organisationId", organisationId); + return query.getSingleResult(); + } - /** Calcule le montant total approuvé par organisation */ - public Optional sumMontantApprouveByOrganisationId(UUID organisationId) { - return find( - "SELECT SUM(d.montantApprouve) FROM DemandeAide d WHERE d.organisation.id = ?1 AND" - + " d.statut = ?2", - organisationId, - StatutAide.APPROUVEE) - .project(BigDecimal.class) - .firstResultOptional(); - } + /** Calcule le montant total demandé par organisation */ + public Optional sumMontantDemandeByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantDemande), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + BigDecimal result = query.getSingleResult(); + return result != null && result.compareTo(BigDecimal.ZERO) > 0 + ? Optional.of(result) + : Optional.empty(); + } - /** Trouve les demandes d'aide récentes (dernières 30 jours) */ - public List findRecentes() { - LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); - return find("dateDemande >= ?1", Sort.by("dateDemande").descending(), il30Jours).list(); - } + /** Calcule le montant total approuvé par organisation */ + public Optional sumMontantApprouveByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + BigDecimal result = query.getSingleResult(); + return result != null && result.compareTo(BigDecimal.ZERO) > 0 + ? Optional.of(result) + : Optional.empty(); + } - /** Trouve les demandes d'aide récentes par organisation */ - public List findRecentesByOrganisationId(UUID organisationId) { - LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); - return find( - "dateDemande >= ?1 and organisation.id = ?2", - Sort.by("dateDemande").descending(), - il30Jours, - organisationId) - .list(); - } + /** Trouve les demandes d'aide récentes (dernières 30 jours) */ + public List findRecentes() { + LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours ORDER BY d.dateDemande DESC", + DemandeAide.class); + query.setParameter("il30Jours", il30Jours); + return query.getResultList(); + } - /** Trouve les demandes d'aide en attente depuis plus de X jours */ - public List findEnAttenteDepuis(int nombreJours) { - LocalDateTime dateLimit = LocalDateTime.now().minusDays(nombreJours); - return find("statut = ?1 and dateDemande <= ?2", StatutAide.EN_ATTENTE, dateLimit).list(); - } + /** Trouve les demandes d'aide récentes par organisation */ + public List findRecentesByOrganisationId(UUID organisationId) { + LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours AND d.organisation.id = :organisationId ORDER BY d.dateDemande DESC", + DemandeAide.class); + query.setParameter("il30Jours", il30Jours); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } - /** Trouve les demandes d'aide par évaluateur */ - public List findByEvaluateurId(UUID evaluateurId) { - return find("evaluateur.id", evaluateurId).list(); - } + /** Trouve les demandes d'aide en attente depuis plus de X jours */ + public List findEnAttenteDepuis(int nombreJours) { + LocalDateTime dateLimit = LocalDateTime.now().minusDays(nombreJours); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.dateDemande <= :dateLimit", + DemandeAide.class); + query.setParameter("statut", StatutAide.EN_ATTENTE); + query.setParameter("dateLimit", dateLimit); + return query.getResultList(); + } - /** Trouve les demandes d'aide en cours d'évaluation par évaluateur */ - public List findEnCoursEvaluationByEvaluateurId(UUID evaluateurId) { - return find("evaluateur.id = ?1 and statut = ?2", evaluateurId, StatutAide.EN_COURS_EVALUATION) - .list(); - } + /** Trouve les demandes d'aide par évaluateur */ + public List findByEvaluateurId(UUID evaluateurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId", + DemandeAide.class); + query.setParameter("evaluateurId", evaluateurId); + return query.getResultList(); + } - /** Compte les demandes approuvées dans une période */ - public long countDemandesApprouvees(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return count( - "organisation.id = ?1 and statut = ?2 and dateCreation between ?3 and ?4", - organisationId, - StatutAide.APPROUVEE, - debut, - fin); - } + /** Trouve les demandes d'aide en cours d'évaluation par évaluateur */ + public List findEnCoursEvaluationByEvaluateurId(UUID evaluateurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId AND d.statut = :statut", + DemandeAide.class); + query.setParameter("evaluateurId", evaluateurId); + query.setParameter("statut", StatutAide.EN_COURS_EVALUATION); + return query.getResultList(); + } - /** Compte toutes les demandes dans une période */ - public long countDemandes(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return count( - "organisation.id = ?1 and dateCreation between ?2 and ?3", organisationId, debut, fin); - } + /** Compte les demandes approuvées dans une période */ + public long countDemandesApprouvees(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } - /** Somme des montants accordés dans une période */ - public BigDecimal sumMontantsAccordes( - UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return find( - "SELECT COALESCE(SUM(d.montantAccorde), 0) FROM DemandeAide d WHERE d.organisation.id =" - + " ?1 and d.statut = ?2 and d.dateCreation between ?3 and ?4", - organisationId, - StatutAide.APPROUVEE, - debut, - fin) - .project(BigDecimal.class) - .firstResult(); - } + /** Compte toutes les demandes dans une période */ + public long countDemandes(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.dateCreation BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** Somme des montants accordés dans une période */ + public BigDecimal sumMontantsAccordes( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "d.dateDemande DESC"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("d.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java index bff5e37..62496b5 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java @@ -3,551 +3,461 @@ package dev.lions.unionflow.server.repository; import dev.lions.unionflow.server.entity.Evenement; import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; /** - * Repository pour l'entité Événement + * Repository pour l'entité Événement avec UUID * *

Fournit les méthodes d'accès aux données pour la gestion des événements avec des * fonctionnalités de recherche avancées et de filtrage. * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @ApplicationScoped -public class EvenementRepository implements PanacheRepository { +public class EvenementRepository extends BaseRepository { - /** - * Trouve un événement par son titre (recherche exacte) - * - * @param titre le titre de l'événement - * @return l'événement trouvé ou Optional.empty() - */ - public Optional findByTitre(String titre) { - return find("titre", titre).firstResultOptional(); - } - - /** - * Trouve tous les événements actifs - * - * @return la liste des événements actifs - */ - public List findAllActifs() { - return find("actif", true).list(); - } - - /** - * Trouve tous les événements actifs avec pagination et tri - * - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements actifs - */ - public List findAllActifs(Page page, Sort sort) { - return find("actif", sort, true).page(page).list(); - } - - /** - * Compte le nombre d'événements actifs - * - * @return le nombre d'événements actifs - */ - public long countActifs() { - return count("actif", true); - } - - /** - * Trouve les événements par statut - * - * @param statut le statut recherché - * @return la liste des événements avec ce statut - */ - public List findByStatut(StatutEvenement statut) { - return find("statut", statut).list(); - } - - /** - * Trouve les événements par statut avec pagination et tri - * - * @param statut le statut recherché - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements avec ce statut - */ - public List findByStatut(StatutEvenement statut, Page page, Sort sort) { - return find("statut", sort, statut).page(page).list(); - } - - /** - * Trouve les événements par type - * - * @param type le type d'événement recherché - * @return la liste des événements de ce type - */ - public List findByType(TypeEvenement type) { - return find("typeEvenement", type).list(); - } - - /** - * Trouve les événements par type avec pagination et tri - * - * @param type le type d'événement recherché - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements de ce type - */ - public List findByType(TypeEvenement type, Page page, Sort sort) { - return find("typeEvenement", sort, type).page(page).list(); - } - - /** - * Trouve les événements par organisation - * - * @param organisationId l'ID de l'organisation - * @return la liste des événements de cette organisation - */ - public List findByOrganisation(Long organisationId) { - return find("organisation.id", organisationId).list(); - } - - /** - * Trouve les événements par organisation avec pagination et tri - * - * @param organisationId l'ID de l'organisation - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements de cette organisation - */ - public List findByOrganisation(Long organisationId, Page page, Sort sort) { - return find("organisation.id", sort, organisationId).page(page).list(); - } - - /** - * Trouve les événements par organisateur - * - * @param organisateurId l'ID de l'organisateur - * @return la liste des événements organisés par ce membre - */ - public List findByOrganisateur(Long organisateurId) { - return find("organisateur.id", organisateurId).list(); - } - - /** - * Trouve les événements dans une période donnée - * - * @param dateDebut la date de début de la période - * @param dateFin la date de fin de la période - * @return la liste des événements dans cette période - */ - public List findByPeriode(LocalDateTime dateDebut, LocalDateTime dateFin) { - return find("dateDebut >= ?1 and dateDebut <= ?2", dateDebut, dateFin).list(); - } - - /** - * Trouve les événements dans une période donnée avec pagination et tri - * - * @param dateDebut la date de début de la période - * @param dateFin la date de fin de la période - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements dans cette période - */ - public List findByPeriode( - LocalDateTime dateDebut, LocalDateTime dateFin, Page page, Sort sort) { - return find("dateDebut >= ?1 and dateDebut <= ?2", sort, dateDebut, dateFin).page(page).list(); - } - - /** - * Trouve les événements à venir (date de début future) - * - * @return la liste des événements à venir - */ - public List findEvenementsAVenir() { - return find("dateDebut > ?1 and actif = true", LocalDateTime.now()).list(); - } - - /** - * Trouve les événements à venir avec pagination et tri - * - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements à venir - */ - public List findEvenementsAVenir(Page page, Sort sort) { - return find("dateDebut > ?1 and actif = true", sort, LocalDateTime.now()).page(page).list(); - } - - /** - * Trouve les événements en cours - * - * @return la liste des événements en cours - */ - public List findEvenementsEnCours() { - LocalDateTime maintenant = LocalDateTime.now(); - return find( - "dateDebut <= ?1 and (dateFin is null or dateFin >= ?1) and actif = true", maintenant) - .list(); - } - - /** - * Trouve les événements passés - * - * @return la liste des événements passés - */ - public List findEvenementsPasses() { - return find( - "(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", - LocalDateTime.now()) - .list(); - } - - /** - * Trouve les événements passés avec pagination et tri - * - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements passés - */ - public List findEvenementsPasses(Page page, Sort sort) { - return find( - "(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", - sort, - LocalDateTime.now()) - .page(page) - .list(); - } - - /** - * Trouve les événements visibles au public - * - * @return la liste des événements publics - */ - public List findEvenementsPublics() { - return find("visiblePublic = true and actif = true").list(); - } - - /** - * Trouve les événements visibles au public avec pagination et tri - * - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements publics - */ - public List findEvenementsPublics(Page page, Sort sort) { - return find("visiblePublic = true and actif = true", sort).page(page).list(); - } - - /** - * Trouve les événements ouverts aux inscriptions - * - * @return la liste des événements ouverts aux inscriptions - */ - public List findEvenementsOuvertsInscription() { - LocalDateTime maintenant = LocalDateTime.now(); - return find( - "inscriptionRequise = true and actif = true and dateDebut > ?1 and " - + "(dateLimiteInscription is null or dateLimiteInscription > ?1) and " - + "(statut = 'PLANIFIE' or statut = 'CONFIRME')", - maintenant) - .list(); - } - - /** - * Recherche d'événements par titre ou description (recherche partielle) - * - * @param recherche le terme de recherche - * @return la liste des événements correspondants - */ - public List findByTitreOrDescription(String recherche) { - return find( - "lower(titre) like ?1 or lower(description) like ?1", - "%" + recherche.toLowerCase() + "%") - .list(); - } - - /** - * Recherche d'événements par titre ou description avec pagination et tri - * - * @param recherche le terme de recherche - * @param page la page demandée - * @param sort le tri à appliquer - * @return la liste paginée des événements correspondants - */ - public List findByTitreOrDescription(String recherche, Page page, Sort sort) { - return find( - "lower(titre) like ?1 or lower(description) like ?1", - sort, - "%" + recherche.toLowerCase() + "%") - .page(page) - .list(); - } - - /** - * Compte les événements créés depuis une date donnée - * - * @param depuis la date de référence - * @return le nombre d'événements créés depuis cette date - */ - public long countNouveauxEvenements(LocalDateTime depuis) { - return count("dateCreation >= ?1", depuis); - } - - /** - * Trouve les événements nécessitant une inscription avec places disponibles - * - * @return la liste des événements avec places disponibles - */ - public List findEvenementsAvecPlacesDisponibles() { - LocalDateTime maintenant = LocalDateTime.now(); - return find( - "inscriptionRequise = true and actif = true and dateDebut > ?1 and" - + " (dateLimiteInscription is null or dateLimiteInscription > ?1) and (capaciteMax" - + " is null or (select count(i) from InscriptionEvenement i where i.evenement =" - + " this and i.statut = 'CONFIRMEE') < capaciteMax)", - maintenant) - .list(); - } - - /** - * Recherche avancée d'événements avec filtres multiples - * - * @param recherche terme de recherche (titre, description) - * @param statut statut de l'événement (optionnel) - * @param type type d'événement (optionnel) - * @param organisationId ID de l'organisation (optionnel) - * @param organisateurId ID de l'organisateur (optionnel) - * @param dateDebutMin date de début minimum (optionnel) - * @param dateDebutMax date de début maximum (optionnel) - * @param visiblePublic visibilité publique (optionnel) - * @param inscriptionRequise inscription requise (optionnel) - * @param actif statut actif (optionnel) - * @param page pagination - * @param sort tri - * @return la liste paginée des événements correspondants aux critères - */ - public List rechercheAvancee( - String recherche, - StatutEvenement statut, - TypeEvenement type, - Long organisationId, - Long organisateurId, - LocalDateTime dateDebutMin, - LocalDateTime dateDebutMax, - Boolean visiblePublic, - Boolean inscriptionRequise, - Boolean actif, - Page page, - Sort sort) { - StringBuilder query = new StringBuilder("1=1"); - Map params = new java.util.HashMap<>(); - - if (recherche != null && !recherche.trim().isEmpty()) { - query.append( - " and (lower(titre) like :recherche or lower(description) like :recherche or lower(lieu)" - + " like :recherche)"); - params.put("recherche", "%" + recherche.toLowerCase() + "%"); + public EvenementRepository() { + super(Evenement.class); } - if (statut != null) { - query.append(" and statut = :statut"); - params.put("statut", statut); + /** + * Trouve un événement par son titre (recherche exacte) + * + * @param titre le titre de l'événement + * @return l'événement trouvé ou Optional.empty() + */ + public Optional findByTitre(String titre) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.titre = :titre", Evenement.class); + query.setParameter("titre", titre); + return query.getResultStream().findFirst(); } - if (type != null) { - query.append(" and typeEvenement = :type"); - params.put("type", type); + /** + * Trouve tous les événements actifs + * + * @return la liste des événements actifs + */ + public List findAllActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.actif = true", Evenement.class); + return query.getResultList(); } - if (organisationId != null) { - query.append(" and organisation.id = :organisationId"); - params.put("organisationId", organisationId); + /** + * Trouve tous les événements actifs avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements actifs + */ + public List findAllActifs(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.actif = true" + orderBy, Evenement.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (organisateurId != null) { - query.append(" and organisateur.id = :organisateurId"); - params.put("organisateurId", organisateurId); + /** + * Compte le nombre d'événements actifs + * + * @return le nombre d'événements actifs + */ + public long countActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class); + return query.getSingleResult(); } - if (dateDebutMin != null) { - query.append(" and dateDebut >= :dateDebutMin"); - params.put("dateDebutMin", dateDebutMin); + /** + * Trouve les événements par statut + * + * @param statut le statut recherché + * @return la liste des événements avec ce statut + */ + public List findByStatut(StatutEvenement statut) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.statut = :statut", Evenement.class); + query.setParameter("statut", statut); + return query.getResultList(); } - if (dateDebutMax != null) { - query.append(" and dateDebut <= :dateDebutMax"); - params.put("dateDebutMax", dateDebutMax); + /** + * Trouve les événements par statut avec pagination et tri + * + * @param statut le statut recherché + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements avec ce statut + */ + public List findByStatut(StatutEvenement statut, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.statut = :statut" + orderBy, Evenement.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (visiblePublic != null) { - query.append(" and visiblePublic = :visiblePublic"); - params.put("visiblePublic", visiblePublic); + /** + * Trouve les événements par type + * + * @param type le type d'événement recherché + * @return la liste des événements de ce type + */ + public List findByType(TypeEvenement type) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.typeEvenement = :type", Evenement.class); + query.setParameter("type", type); + return query.getResultList(); } - if (inscriptionRequise != null) { - query.append(" and inscriptionRequise = :inscriptionRequise"); - params.put("inscriptionRequise", inscriptionRequise); + /** + * Trouve les événements par type avec pagination et tri + * + * @param type le type d'événement recherché + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements de ce type + */ + public List findByType(TypeEvenement type, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.typeEvenement = :type" + orderBy, Evenement.class); + query.setParameter("type", type); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (actif != null) { - query.append(" and actif = :actif"); - params.put("actif", actif); + /** + * Trouve les événements par organisation + * + * @param organisationId l'UUID de l'organisation + * @return la liste des événements de cette organisation + */ + public List findByOrganisation(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId", Evenement.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); } - return find(query.toString(), sort, params).page(page).list(); - } - - /** - * Compte les résultats de la recherche avancée - * - * @param recherche terme de recherche (titre, description) - * @param statut statut de l'événement (optionnel) - * @param type type d'événement (optionnel) - * @param organisationId ID de l'organisation (optionnel) - * @param organisateurId ID de l'organisateur (optionnel) - * @param dateDebutMin date de début minimum (optionnel) - * @param dateDebutMax date de début maximum (optionnel) - * @param visiblePublic visibilité publique (optionnel) - * @param inscriptionRequise inscription requise (optionnel) - * @param actif statut actif (optionnel) - * @return le nombre d'événements correspondants aux critères - */ - public long countRechercheAvancee( - String recherche, - StatutEvenement statut, - TypeEvenement type, - Long organisationId, - Long organisateurId, - LocalDateTime dateDebutMin, - LocalDateTime dateDebutMax, - Boolean visiblePublic, - Boolean inscriptionRequise, - Boolean actif) { - StringBuilder query = new StringBuilder("1=1"); - Map params = new java.util.HashMap<>(); - - if (recherche != null && !recherche.trim().isEmpty()) { - query.append( - " and (lower(titre) like :recherche or lower(description) like :recherche or lower(lieu)" - + " like :recherche)"); - params.put("recherche", "%" + recherche.toLowerCase() + "%"); + /** + * Trouve les événements par organisation avec pagination et tri + * + * @param organisationId l'UUID de l'organisation + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements de cette organisation + */ + public List findByOrganisation(UUID organisationId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId" + orderBy, + Evenement.class); + query.setParameter("organisationId", organisationId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (statut != null) { - query.append(" and statut = :statut"); - params.put("statut", statut); + /** + * Trouve les événements à venir (date de début future) + * + * @return la liste des événements à venir + */ + public List findEvenementsAVenir() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true", + Evenement.class); + query.setParameter("maintenant", LocalDateTime.now()); + return query.getResultList(); } - if (type != null) { - query.append(" and typeEvenement = :type"); - params.put("type", type); + /** + * Trouve les événements à venir avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements à venir + */ + public List findEvenementsAVenir(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true" + orderBy, + Evenement.class); + query.setParameter("maintenant", LocalDateTime.now()); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (organisationId != null) { - query.append(" and organisation.id = :organisationId"); - params.put("organisationId", organisationId); + /** + * Trouve les événements visibles au public + * + * @return la liste des événements publics + */ + public List findEvenementsPublics() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true", + Evenement.class); + return query.getResultList(); } - if (organisateurId != null) { - query.append(" and organisateur.id = :organisateurId"); - params.put("organisateurId", organisateurId); + /** + * Trouve les événements visibles au public avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements publics + */ + public List findEvenementsPublics(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true" + orderBy, + Evenement.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (dateDebutMin != null) { - query.append(" and dateDebut >= :dateDebutMin"); - params.put("dateDebutMin", dateDebutMin); + /** + * Recherche avancée d'événements avec filtres multiples + * + * @param recherche terme de recherche (titre, description) + * @param statut statut de l'événement (optionnel) + * @param type type d'événement (optionnel) + * @param organisationId UUID de l'organisation (optionnel) + * @param organisateurId UUID de l'organisateur (optionnel) + * @param dateDebutMin date de début minimum (optionnel) + * @param dateDebutMax date de début maximum (optionnel) + * @param visiblePublic visibilité publique (optionnel) + * @param inscriptionRequise inscription requise (optionnel) + * @param actif statut actif (optionnel) + * @param page pagination + * @param sort tri + * @return la liste paginée des événements correspondants aux critères + */ + public List rechercheAvancee( + String recherche, + StatutEvenement statut, + TypeEvenement type, + UUID organisationId, + UUID organisateurId, + LocalDateTime dateDebutMin, + LocalDateTime dateDebutMax, + Boolean visiblePublic, + Boolean inscriptionRequise, + Boolean actif, + Page page, + Sort sort) { + StringBuilder jpql = new StringBuilder("SELECT e FROM Evenement e WHERE 1=1"); + Map params = new HashMap<>(); + + if (recherche != null && !recherche.trim().isEmpty()) { + jpql.append( + " AND (LOWER(e.titre) LIKE LOWER(:recherche) OR LOWER(e.description) LIKE LOWER(:recherche) OR LOWER(e.lieu) LIKE LOWER(:recherche))"); + params.put("recherche", "%" + recherche.toLowerCase() + "%"); + } + + if (statut != null) { + jpql.append(" AND e.statut = :statut"); + params.put("statut", statut); + } + + if (type != null) { + jpql.append(" AND e.typeEvenement = :type"); + params.put("type", type); + } + + if (organisationId != null) { + jpql.append(" AND e.organisation.id = :organisationId"); + params.put("organisationId", organisationId); + } + + if (organisateurId != null) { + jpql.append(" AND e.organisateur.id = :organisateurId"); + params.put("organisateurId", organisateurId); + } + + if (dateDebutMin != null) { + jpql.append(" AND e.dateDebut >= :dateDebutMin"); + params.put("dateDebutMin", dateDebutMin); + } + + if (dateDebutMax != null) { + jpql.append(" AND e.dateDebut <= :dateDebutMax"); + params.put("dateDebutMax", dateDebutMax); + } + + if (visiblePublic != null) { + jpql.append(" AND e.visiblePublic = :visiblePublic"); + params.put("visiblePublic", visiblePublic); + } + + if (inscriptionRequise != null) { + jpql.append(" AND e.inscriptionRequise = :inscriptionRequise"); + params.put("inscriptionRequise", inscriptionRequise); + } + + if (actif != null) { + jpql.append(" AND e.actif = :actif"); + params.put("actif", actif); + } + + if (sort != null) { + jpql.append(" ORDER BY ").append(buildOrderBy(sort)); + } + + TypedQuery query = entityManager.createQuery(jpql.toString(), Evenement.class); + for (Map.Entry param : params.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - if (dateDebutMax != null) { - query.append(" and dateDebut <= :dateDebutMax"); - params.put("dateDebutMax", dateDebutMax); + /** + * Obtient les statistiques des événements + * + * @return une map contenant les statistiques + */ + public Map getStatistiques() { + Map stats = new HashMap<>(); + LocalDateTime maintenant = LocalDateTime.now(); + + TypedQuery totalQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e", Long.class); + stats.put("total", totalQuery.getSingleResult()); + + TypedQuery actifsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class); + stats.put("actifs", actifsQuery.getSingleResult()); + + TypedQuery inactifsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = false", Long.class); + stats.put("inactifs", inactifsQuery.getSingleResult()); + + TypedQuery aVenirQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true", + Long.class); + aVenirQuery.setParameter("maintenant", maintenant); + stats.put("aVenir", aVenirQuery.getSingleResult()); + + TypedQuery enCoursQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut <= :maintenant AND (e.dateFin IS NULL OR e.dateFin >= :maintenant) AND e.actif = true", + Long.class); + enCoursQuery.setParameter("maintenant", maintenant); + stats.put("enCours", enCoursQuery.getSingleResult()); + + TypedQuery passesQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE (e.dateFin < :maintenant OR (e.dateFin IS NULL AND e.dateDebut < :maintenant)) AND e.actif = true", + Long.class); + passesQuery.setParameter("maintenant", maintenant); + stats.put("passes", passesQuery.getSingleResult()); + + TypedQuery publicsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true", + Long.class); + stats.put("publics", publicsQuery.getSingleResult()); + + TypedQuery avecInscriptionQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.inscriptionRequise = true AND e.actif = true", + Long.class); + stats.put("avecInscription", avecInscriptionQuery.getSingleResult()); + + return stats; } - if (visiblePublic != null) { - query.append(" and visiblePublic = :visiblePublic"); - params.put("visiblePublic", visiblePublic); + /** + * Compte les événements dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return nombre d'événements + */ + public long countEvenements(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); } - if (inscriptionRequise != null) { - query.append(" and inscriptionRequise = :inscriptionRequise"); - params.put("inscriptionRequise", inscriptionRequise); + /** + * Calcule la moyenne de participants dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return moyenne de participants ou null + */ + public Double calculerMoyenneParticipants(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT AVG(e.nombreParticipants) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Double.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); } - if (actif != null) { - query.append(" and actif = :actif"); - params.put("actif", actif); + /** + * Compte le total des participations dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return total des participations + */ + public Long countTotalParticipations(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(e.nombreParticipants), 0) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + Long result = query.getSingleResult(); + return result != null ? result : 0L; } - return count(query.toString(), params); - } - - /** - * Obtient les statistiques des événements - * - * @return une map contenant les statistiques - */ - public Map getStatistiques() { - Map stats = new java.util.HashMap<>(); - - stats.put("total", count()); - stats.put("actifs", count("actif", true)); - stats.put("inactifs", count("actif", false)); - stats.put("aVenir", count("dateDebut > ?1 and actif = true", LocalDateTime.now())); - stats.put( - "enCours", - count( - "dateDebut <= ?1 and (dateFin is null or dateFin >= ?1) and actif = true", - LocalDateTime.now())); - stats.put( - "passes", - count( - "(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", - LocalDateTime.now())); - stats.put("publics", count("visiblePublic = true and actif = true")); - stats.put("avecInscription", count("inscriptionRequise = true and actif = true")); - - return stats; - } - - /** Compte les événements dans une période et organisation */ - public long countEvenements( - java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return count( - "organisation.id = ?1 and dateDebut between ?2 and ?3", organisationId, debut, fin); - } - - /** Calcule la moyenne de participants dans une période et organisation */ - public Double calculerMoyenneParticipants( - java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - return find( - "SELECT AVG(e.nombreParticipants) FROM Evenement e WHERE e.organisation.id = ?1 and" - + " e.dateDebut between ?2 and ?3", - organisationId, - debut, - fin) - .project(Double.class) - .firstResult(); - } - - /** Compte le total des participations dans une période et organisation */ - public long countTotalParticipations( - java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) { - Long result = - find( - "SELECT COALESCE(SUM(e.nombreParticipants), 0) FROM Evenement e WHERE" - + " e.organisation.id = ?1 and e.dateDebut between ?2 and ?3", - organisationId, - debut, - fin) - .project(Long.class) - .firstResult(); - return result != null ? result : 0L; - } + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "e.dateDebut"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("e.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java index 7599a4c..0161854 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java @@ -1,145 +1,238 @@ package dev.lions.unionflow.server.repository; import dev.lions.unionflow.server.entity.Membre; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import java.util.UUID; -/** Repository pour l'entité Membre */ +/** Repository pour l'entité Membre avec UUID */ @ApplicationScoped -public class MembreRepository implements PanacheRepository { +public class MembreRepository extends BaseRepository { - /** Trouve un membre par son email */ - public Optional findByEmail(String email) { - return find("email", email).firstResultOptional(); - } - - /** Trouve un membre par son numéro */ - public Optional findByNumeroMembre(String numeroMembre) { - return find("numeroMembre", numeroMembre).firstResultOptional(); - } - - /** Trouve tous les membres actifs */ - public List findAllActifs() { - return find("actif", true).list(); - } - - /** Compte le nombre de membres actifs */ - public long countActifs() { - return count("actif", true); - } - - /** Trouve les membres par nom ou prénom (recherche partielle) */ - public List findByNomOrPrenom(String recherche) { - return find("lower(nom) like ?1 or lower(prenom) like ?1", "%" + recherche.toLowerCase() + "%") - .list(); - } - - /** Trouve tous les membres actifs avec pagination et tri */ - public List findAllActifs(Page page, Sort sort) { - return find("actif", sort, true).page(page).list(); - } - - /** Trouve les membres par nom ou prénom avec pagination et tri */ - public List findByNomOrPrenom(String recherche, Page page, Sort sort) { - return find( - "lower(nom) like ?1 or lower(prenom) like ?1", - sort, - "%" + recherche.toLowerCase() + "%") - .page(page) - .list(); - } - - /** Compte les nouveaux membres depuis une date donnée */ - public long countNouveauxMembres(LocalDate depuis) { - return count("dateAdhesion >= ?1", depuis); - } - - /** Trouve les membres par statut avec pagination */ - public List findByStatut(boolean actif, Page page, Sort sort) { - return find("actif", sort, actif).page(page).list(); - } - - /** Trouve les membres par tranche d'âge */ - public List findByTrancheAge(int ageMin, int ageMax, Page page, Sort sort) { - LocalDate dateNaissanceMax = LocalDate.now().minusYears(ageMin); - LocalDate dateNaissanceMin = LocalDate.now().minusYears(ageMax + 1); - - return find("dateNaissance between ?1 and ?2", sort, dateNaissanceMin, dateNaissanceMax) - .page(page) - .list(); - } - - /** Recherche avancée avec filtres multiples */ - public List rechercheAvancee( - String recherche, - Boolean actif, - LocalDate dateAdhesionMin, - LocalDate dateAdhesionMax, - Page page, - Sort sort) { - StringBuilder query = new StringBuilder("1=1"); - java.util.Map params = new java.util.HashMap<>(); - - if (recherche != null && !recherche.trim().isEmpty()) { - query.append( - " and (lower(nom) like :recherche or lower(prenom) like :recherche or lower(email) like" - + " :recherche)"); - params.put("recherche", "%" + recherche.toLowerCase() + "%"); + public MembreRepository() { + super(Membre.class); } - if (actif != null) { - query.append(" and actif = :actif"); - params.put("actif", actif); + /** Trouve un membre par son email */ + public Optional findByEmail(String email) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.email = :email", Membre.class); + query.setParameter("email", email); + return query.getResultStream().findFirst(); } - if (dateAdhesionMin != null) { - query.append(" and dateAdhesion >= :dateAdhesionMin"); - params.put("dateAdhesionMin", dateAdhesionMin); + /** Trouve un membre par son numéro */ + public Optional findByNumeroMembre(String numeroMembre) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.numeroMembre = :numeroMembre", Membre.class); + query.setParameter("numeroMembre", numeroMembre); + return query.getResultStream().findFirst(); } - if (dateAdhesionMax != null) { - query.append(" and dateAdhesion <= :dateAdhesionMax"); - params.put("dateAdhesionMax", dateAdhesionMax); + /** Trouve tous les membres actifs */ + public List findAllActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = true", Membre.class); + return query.getResultList(); } - return find(query.toString(), sort, params).page(page).list(); - } + /** Compte le nombre de membres actifs */ + public long countActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.actif = true", Long.class); + return query.getSingleResult(); + } - /** Compte les membres actifs dans une période et organisation */ - public long countMembresActifs( - java.util.UUID organisationId, java.time.LocalDateTime debut, java.time.LocalDateTime fin) { - return count( - "organisation.id = ?1 and actif = true and dateAdhesion between ?2 and ?3", - organisationId, - debut, - fin); - } + /** Trouve les membres par nom ou prénom (recherche partielle) */ + public List findByNomOrPrenom(String recherche) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)", + Membre.class); + query.setParameter("recherche", "%" + recherche + "%"); + return query.getResultList(); + } - /** Compte les membres inactifs dans une période et organisation */ - public long countMembresInactifs( - java.util.UUID organisationId, java.time.LocalDateTime debut, java.time.LocalDateTime fin) { - return count( - "organisation.id = ?1 and actif = false and dateAdhesion between ?2 and ?3", - organisationId, - debut, - fin); - } + /** Trouve tous les membres actifs avec pagination et tri */ + public List findAllActifs(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = true" + orderBy, Membre.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** Calcule la moyenne d'âge des membres dans une période et organisation */ - public Double calculerMoyenneAge( - java.util.UUID organisationId, java.time.LocalDateTime debut, java.time.LocalDateTime fin) { - return find( - "SELECT AVG(YEAR(CURRENT_DATE) - YEAR(m.dateNaissance)) FROM Membre m WHERE" - + " m.organisation.id = ?1 and m.dateAdhesion between ?2 and ?3", - organisationId, - debut, - fin) - .project(Double.class) - .firstResult(); - } + /** Trouve les membres par nom ou prénom avec pagination et tri */ + public List findByNomOrPrenom(String recherche, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)" + orderBy, + Membre.class); + query.setParameter("recherche", "%" + recherche + "%"); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Compte les nouveaux membres depuis une date donnée */ + public long countNouveauxMembres(LocalDate depuis) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.dateAdhesion >= :depuis", Long.class); + query.setParameter("depuis", depuis); + return query.getSingleResult(); + } + + /** Trouve les membres par statut avec pagination */ + public List findByStatut(boolean actif, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = :actif" + orderBy, Membre.class); + query.setParameter("actif", actif); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Trouve les membres par tranche d'âge */ + public List findByTrancheAge(int ageMin, int ageMax, Page page, Sort sort) { + LocalDate dateNaissanceMax = LocalDate.now().minusYears(ageMin); + LocalDate dateNaissanceMin = LocalDate.now().minusYears(ageMax + 1); + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.dateNaissance BETWEEN :dateMin AND :dateMax" + orderBy, + Membre.class); + query.setParameter("dateMin", dateNaissanceMin); + query.setParameter("dateMax", dateNaissanceMax); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Recherche avancée de membres */ + public List rechercheAvancee( + String recherche, + Boolean actif, + LocalDate dateAdhesionMin, + LocalDate dateAdhesionMax, + Page page, + Sort sort) { + StringBuilder jpql = new StringBuilder("SELECT m FROM Membre m WHERE 1=1"); + + if (recherche != null && !recherche.isEmpty()) { + jpql.append(" AND (LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche) OR LOWER(m.email) LIKE LOWER(:recherche))"); + } + if (actif != null) { + jpql.append(" AND m.actif = :actif"); + } + if (dateAdhesionMin != null) { + jpql.append(" AND m.dateAdhesion >= :dateAdhesionMin"); + } + if (dateAdhesionMax != null) { + jpql.append(" AND m.dateAdhesion <= :dateAdhesionMax"); + } + + if (sort != null) { + jpql.append(" ORDER BY ").append(buildOrderBy(sort)); + } + + TypedQuery query = entityManager.createQuery(jpql.toString(), Membre.class); + + if (recherche != null && !recherche.isEmpty()) { + query.setParameter("recherche", "%" + recherche + "%"); + } + if (actif != null) { + query.setParameter("actif", actif); + } + if (dateAdhesionMin != null) { + query.setParameter("dateAdhesionMin", dateAdhesionMin); + } + if (dateAdhesionMax != null) { + query.setParameter("dateAdhesionMax", dateAdhesionMax); + } + + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "m.id"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("m.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } + + /** + * Compte les membres actifs dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Nombre de membres actifs + */ + public Long countMembresActifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = true AND m.dateAdhesion BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Compte les membres inactifs dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Nombre de membres inactifs + */ + public Long countMembresInactifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = false AND m.dateAdhesion BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Calcule la moyenne d'âge des membres dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Moyenne d'âge ou null si aucun membre + */ + public Double calculerMoyenneAge(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT AVG(YEAR(CURRENT_DATE) - YEAR(m.dateNaissance)) FROM Membre m WHERE m.organisation.id = :organisationId AND m.dateAdhesion BETWEEN :debut AND :fin", + Double.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java index 66e6f88..e935553 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java @@ -1,10 +1,10 @@ package dev.lions.unionflow.server.repository; import dev.lions.unionflow.server.entity.Organisation; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; import java.time.LocalDate; import java.util.HashMap; import java.util.List; @@ -13,280 +13,412 @@ import java.util.Optional; import java.util.UUID; /** - * Repository pour l'entité Organisation Utilise Panache pour simplifier les opérations JPA + * Repository pour l'entité Organisation avec UUID * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 + * @version 2.0 + * @since 2025-01-16 */ @ApplicationScoped -public class OrganisationRepository implements PanacheRepository { +public class OrganisationRepository extends BaseRepository { - /** - * Trouve une organisation par son email - * - * @param email l'email de l'organisation - * @return Optional contenant l'organisation si trouvée - */ - public Optional findByEmail(String email) { - return find("email = ?1", email).firstResultOptional(); - } - - /** - * Trouve une organisation par son nom - * - * @param nom le nom de l'organisation - * @return Optional contenant l'organisation si trouvée - */ - public Optional findByNom(String nom) { - return find("nom = ?1", nom).firstResultOptional(); - } - - /** - * Trouve une organisation par son numéro d'enregistrement - * - * @param numeroEnregistrement le numéro d'enregistrement officiel - * @return Optional contenant l'organisation si trouvée - */ - public Optional findByNumeroEnregistrement(String numeroEnregistrement) { - return find("numeroEnregistrement = ?1", numeroEnregistrement).firstResultOptional(); - } - - /** - * Trouve toutes les organisations actives - * - * @return liste des organisations actives - */ - public List findAllActives() { - return find("statut = 'ACTIVE' and actif = true").list(); - } - - /** - * Trouve toutes les organisations actives avec pagination - * - * @param page pagination - * @param sort tri - * @return liste paginée des organisations actives - */ - public List findAllActives(Page page, Sort sort) { - return find("statut = 'ACTIVE' and actif = true", sort).page(page).list(); - } - - /** - * Compte le nombre d'organisations actives - * - * @return nombre d'organisations actives - */ - public long countActives() { - return count("statut = 'ACTIVE' and actif = true"); - } - - /** - * Trouve les organisations par statut - * - * @param statut le statut recherché - * @param page pagination - * @param sort tri - * @return liste paginée des organisations avec le statut spécifié - */ - public List findByStatut(String statut, Page page, Sort sort) { - return find("statut = ?1", sort, statut).page(page).list(); - } - - /** - * Trouve les organisations par type - * - * @param typeOrganisation le type d'organisation - * @param page pagination - * @param sort tri - * @return liste paginée des organisations du type spécifié - */ - public List findByType(String typeOrganisation, Page page, Sort sort) { - return find("typeOrganisation = ?1", sort, typeOrganisation).page(page).list(); - } - - /** - * Trouve les organisations par ville - * - * @param ville la ville - * @param page pagination - * @param sort tri - * @return liste paginée des organisations de la ville spécifiée - */ - public List findByVille(String ville, Page page, Sort sort) { - return find("ville = ?1", sort, ville).page(page).list(); - } - - /** - * Trouve les organisations par pays - * - * @param pays le pays - * @param page pagination - * @param sort tri - * @return liste paginée des organisations du pays spécifié - */ - public List findByPays(String pays, Page page, Sort sort) { - return find("pays = ?1", sort, pays).page(page).list(); - } - - /** - * Trouve les organisations par région - * - * @param region la région - * @param page pagination - * @param sort tri - * @return liste paginée des organisations de la région spécifiée - */ - public List findByRegion(String region, Page page, Sort sort) { - return find("region = ?1", sort, region).page(page).list(); - } - - /** - * Trouve les organisations filles d'une organisation parente - * - * @param organisationParenteId l'ID de l'organisation parente - * @param page pagination - * @param sort tri - * @return liste paginée des organisations filles - */ - public List findByOrganisationParente( - UUID organisationParenteId, Page page, Sort sort) { - return find("organisationParenteId = ?1", sort, organisationParenteId).page(page).list(); - } - - /** - * Trouve les organisations racines (sans parent) - * - * @param page pagination - * @param sort tri - * @return liste paginée des organisations racines - */ - public List findOrganisationsRacines(Page page, Sort sort) { - return find("organisationParenteId is null", sort).page(page).list(); - } - - /** - * Recherche d'organisations par nom ou nom court - * - * @param recherche terme de recherche - * @param page pagination - * @param sort tri - * @return liste paginée des organisations correspondantes - */ - public List findByNomOrNomCourt(String recherche, Page page, Sort sort) { - String pattern = "%" + recherche.toLowerCase() + "%"; - return find("lower(nom) like ?1 or lower(nomCourt) like ?1", sort, pattern).page(page).list(); - } - - /** - * Recherche avancée d'organisations - * - * @param nom nom (optionnel) - * @param typeOrganisation type (optionnel) - * @param statut statut (optionnel) - * @param ville ville (optionnel) - * @param region région (optionnel) - * @param pays pays (optionnel) - * @param page pagination - * @return liste filtrée des organisations - */ - public List rechercheAvancee( - String nom, - String typeOrganisation, - String statut, - String ville, - String region, - String pays, - Page page) { - StringBuilder query = new StringBuilder("1=1"); - Map parameters = new HashMap<>(); - - if (nom != null && !nom.isEmpty()) { - query.append(" and (lower(nom) like :nom or lower(nomCourt) like :nom)"); - parameters.put("nom", "%" + nom.toLowerCase() + "%"); + public OrganisationRepository() { + super(Organisation.class); } - if (typeOrganisation != null && !typeOrganisation.isEmpty()) { - query.append(" and typeOrganisation = :typeOrganisation"); - parameters.put("typeOrganisation", typeOrganisation); + /** + * Trouve une organisation par son email + * + * @param email l'email de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByEmail(String email) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.email = :email", Organisation.class); + query.setParameter("email", email); + return query.getResultStream().findFirst(); } - if (statut != null && !statut.isEmpty()) { - query.append(" and statut = :statut"); - parameters.put("statut", statut); + /** + * Trouve une organisation par son nom + * + * @param nom le nom de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByNom(String nom) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.nom = :nom", Organisation.class); + query.setParameter("nom", nom); + return query.getResultStream().findFirst(); } - if (ville != null && !ville.isEmpty()) { - query.append(" and lower(ville) like :ville"); - parameters.put("ville", "%" + ville.toLowerCase() + "%"); + /** + * Trouve une organisation par son numéro d'enregistrement + * + * @param numeroEnregistrement le numéro d'enregistrement officiel + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByNumeroEnregistrement(String numeroEnregistrement) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.numeroEnregistrement = :numeroEnregistrement", + Organisation.class); + query.setParameter("numeroEnregistrement", numeroEnregistrement); + return query.getResultStream().findFirst(); } - if (region != null && !region.isEmpty()) { - query.append(" and lower(region) like :region"); - parameters.put("region", "%" + region.toLowerCase() + "%"); + /** + * Trouve toutes les organisations actives + * + * @return liste des organisations actives + */ + public List findAllActives() { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true", + Organisation.class); + return query.getResultList(); } - if (pays != null && !pays.isEmpty()) { - query.append(" and lower(pays) like :pays"); - parameters.put("pays", "%" + pays.toLowerCase() + "%"); + /** + * Trouve toutes les organisations actives avec pagination + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations actives + */ + public List findAllActives(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true" + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); } - return find(query.toString(), Sort.by("nom").ascending(), parameters).page(page).list(); - } + /** + * Compte le nombre d'organisations actives + * + * @return nombre d'organisations actives + */ + public long countActives() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true", + Long.class); + return query.getSingleResult(); + } - /** - * Compte les nouvelles organisations depuis une date donnée - * - * @param depuis date de référence - * @return nombre de nouvelles organisations - */ - public long countNouvellesOrganisations(LocalDate depuis) { - return count("dateCreation >= ?1", depuis.atStartOfDay()); - } + /** + * Trouve les organisations par statut + * + * @param statut le statut recherché + * @param page pagination + * @param sort tri + * @return liste paginée des organisations avec le statut spécifié + */ + public List findByStatut(String statut, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = :statut" + orderBy, + Organisation.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Trouve les organisations publiques (visibles dans l'annuaire) - * - * @param page pagination - * @param sort tri - * @return liste paginée des organisations publiques - */ - public List findOrganisationsPubliques(Page page, Sort sort) { - return find("organisationPublique = true and statut = 'ACTIVE' and actif = true", sort) - .page(page) - .list(); - } + /** + * Trouve les organisations par type + * + * @param typeOrganisation le type d'organisation + * @param page pagination + * @param sort tri + * @return liste paginée des organisations du type spécifié + */ + public List findByType(String typeOrganisation, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation" + orderBy, + Organisation.class); + query.setParameter("typeOrganisation", typeOrganisation); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Trouve les organisations acceptant de nouveaux membres - * - * @param page pagination - * @param sort tri - * @return liste paginée des organisations acceptant de nouveaux membres - */ - public List findOrganisationsOuvertes(Page page, Sort sort) { - return find("accepteNouveauxMembres = true and statut = 'ACTIVE' and actif = true", sort) - .page(page) - .list(); - } + /** + * Trouve les organisations par ville + * + * @param ville la ville + * @param page pagination + * @param sort tri + * @return liste paginée des organisations de la ville spécifiée + */ + public List findByVille(String ville, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.ville = :ville" + orderBy, + Organisation.class); + query.setParameter("ville", ville); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Compte les organisations par statut - * - * @param statut le statut - * @return nombre d'organisations avec ce statut - */ - public long countByStatut(String statut) { - return count("statut = ?1", statut); - } + /** + * Trouve les organisations par pays + * + * @param pays le pays + * @param page pagination + * @param sort tri + * @return liste paginée des organisations du pays spécifié + */ + public List findByPays(String pays, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.pays = :pays" + orderBy, + Organisation.class); + query.setParameter("pays", pays); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } - /** - * Compte les organisations par type - * - * @param typeOrganisation le type d'organisation - * @return nombre d'organisations de ce type - */ - public long countByType(String typeOrganisation) { - return count("typeOrganisation = ?1", typeOrganisation); - } + /** + * Trouve les organisations par région + * + * @param region la région + * @param page pagination + * @param sort tri + * @return liste paginée des organisations de la région spécifiée + */ + public List findByRegion(String region, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.region = :region" + orderBy, + Organisation.class); + query.setParameter("region", region); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations filles d'une organisation parente + * + * @param organisationParenteId l'UUID de l'organisation parente + * @param page pagination + * @param sort tri + * @return liste paginée des organisations filles + */ + public List findByOrganisationParente( + UUID organisationParenteId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationParenteId = :organisationParenteId" + + orderBy, + Organisation.class); + query.setParameter("organisationParenteId", organisationParenteId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations racines (sans parent) + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations racines + */ + public List findOrganisationsRacines(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationParenteId IS NULL" + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche d'organisations par nom ou nom court + * + * @param recherche terme de recherche + * @param page pagination + * @param sort tri + * @return liste paginée des organisations correspondantes + */ + public List findByNomOrNomCourt(String recherche, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE LOWER(o.nom) LIKE LOWER(:recherche) OR LOWER(o.nomCourt) LIKE LOWER(:recherche)" + + orderBy, + Organisation.class); + query.setParameter("recherche", "%" + recherche + "%"); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche avancée d'organisations + * + * @param nom nom (optionnel) + * @param typeOrganisation type (optionnel) + * @param statut statut (optionnel) + * @param ville ville (optionnel) + * @param region région (optionnel) + * @param pays pays (optionnel) + * @param page pagination + * @return liste filtrée des organisations + */ + public List rechercheAvancee( + String nom, + String typeOrganisation, + String statut, + String ville, + String region, + String pays, + Page page) { + StringBuilder queryBuilder = new StringBuilder("SELECT o FROM Organisation o WHERE 1=1"); + Map parameters = new HashMap<>(); + + if (nom != null && !nom.isEmpty()) { + queryBuilder.append(" AND (LOWER(o.nom) LIKE LOWER(:nom) OR LOWER(o.nomCourt) LIKE LOWER(:nom))"); + parameters.put("nom", "%" + nom.toLowerCase() + "%"); + } + + if (typeOrganisation != null && !typeOrganisation.isEmpty()) { + queryBuilder.append(" AND o.typeOrganisation = :typeOrganisation"); + parameters.put("typeOrganisation", typeOrganisation); + } + + if (statut != null && !statut.isEmpty()) { + queryBuilder.append(" AND o.statut = :statut"); + parameters.put("statut", statut); + } + + if (ville != null && !ville.isEmpty()) { + queryBuilder.append(" AND LOWER(o.ville) LIKE LOWER(:ville)"); + parameters.put("ville", "%" + ville.toLowerCase() + "%"); + } + + if (region != null && !region.isEmpty()) { + queryBuilder.append(" AND LOWER(o.region) LIKE LOWER(:region)"); + parameters.put("region", "%" + region.toLowerCase() + "%"); + } + + if (pays != null && !pays.isEmpty()) { + queryBuilder.append(" AND LOWER(o.pays) LIKE LOWER(:pays)"); + parameters.put("pays", "%" + pays.toLowerCase() + "%"); + } + + queryBuilder.append(" ORDER BY o.nom ASC"); + + TypedQuery query = entityManager.createQuery( + queryBuilder.toString(), Organisation.class); + for (Map.Entry param : parameters.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte les nouvelles organisations depuis une date donnée + * + * @param depuis date de référence + * @return nombre de nouvelles organisations + */ + public long countNouvellesOrganisations(LocalDate depuis) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.dateCreation >= :depuis", Long.class); + query.setParameter("depuis", depuis.atStartOfDay()); + return query.getSingleResult(); + } + + /** + * Trouve les organisations publiques (visibles dans l'annuaire) + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations publiques + */ + public List findOrganisationsPubliques(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationPublique = true AND o.statut = 'ACTIVE' AND o.actif = true" + + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations acceptant de nouveaux membres + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations acceptant de nouveaux membres + */ + public List findOrganisationsOuvertes(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.accepteNouveauxMembres = true AND o.statut = 'ACTIVE' AND o.actif = true" + + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte les organisations par statut + * + * @param statut le statut + * @return nombre d'organisations avec ce statut + */ + public long countByStatut(String statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.statut = :statut", Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } + + /** + * Compte les organisations par type + * + * @param typeOrganisation le type d'organisation + * @return nombre d'organisations de ce type + */ + public long countByType(String typeOrganisation) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation", + Long.class); + query.setParameter("typeOrganisation", typeOrganisation); + return query.getSingleResult(); + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "o.id"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("o.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/AideResource.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/AideResource.java.bak deleted file mode 100644 index c39a911..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/AideResource.java.bak +++ /dev/null @@ -1,625 +0,0 @@ -package dev.lions.unionflow.server.resource; - -import dev.lions.unionflow.server.api.dto.solidarite.aide.AideDTO; -import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; -import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; -import dev.lions.unionflow.server.service.AideService; -import jakarta.annotation.security.RolesAllowed; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.openapi.annotations.Operation; -import org.eclipse.microprofile.openapi.annotations.media.Content; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; -import org.eclipse.microprofile.openapi.annotations.tags.Tag; -import org.jboss.logging.Logger; - -import java.math.BigDecimal; -import java.net.URI; -import java.time.LocalDate; -import java.util.List; -import java.util.Map; - -/** - * Resource REST pour la gestion des demandes d'aide et de solidarité - * Expose les endpoints API pour les opérations CRUD et métier sur les aides - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 - */ -@Path("/api/aides") -@Produces(MediaType.APPLICATION_JSON) -@Consumes(MediaType.APPLICATION_JSON) -@ApplicationScoped -@Tag(name = "Aides", description = "Gestion des demandes d'aide et de solidarité") -public class AideResource { - - private static final Logger LOG = Logger.getLogger(AideResource.class); - - @Inject - AideService aideService; - - // ===== OPÉRATIONS CRUD ===== - - /** - * Liste toutes les demandes d'aide actives avec pagination - */ - @GET - @Operation(summary = "Lister toutes les demandes d'aide actives", - description = "Récupère la liste paginée des demandes d'aide actives") - @APIResponse(responseCode = "200", description = "Liste des demandes d'aide actives") - @APIResponse(responseCode = "401", description = "Non authentifié") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response listerAides( - @Parameter(description = "Numéro de page (0-based)", example = "0") - @QueryParam("page") @DefaultValue("0") @Min(0) int page, - - @Parameter(description = "Taille de la page", example = "20") - @QueryParam("size") @DefaultValue("20") @Min(1) int size) { - - try { - LOG.infof("GET /api/aides - page: %d, size: %d", page, size); - - List aides = aideService.listerAidesActives(page, size); - - LOG.infof("Récupération réussie de %d demandes d'aide", aides.size()); - return Response.ok(aides).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération des demandes d'aide: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération des demandes d'aide")) - .build(); - } - } - - /** - * Récupère une demande d'aide par son ID - */ - @GET - @Path("/{id}") - @Operation(summary = "Récupérer une demande d'aide par ID") - @APIResponse(responseCode = "200", description = "Demande d'aide trouvée") - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response obtenirAide( - @Parameter(description = "ID de la demande d'aide", required = true) - @PathParam("id") Long id) { - - try { - LOG.infof("GET /api/aides/%d", id); - - AideDTO aide = aideService.obtenirAideParId(id); - - return Response.ok(aide).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée: ID %d", id); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération de la demande d'aide %d: %s", id, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération de la demande d'aide")) - .build(); - } - } - - /** - * Récupère une demande d'aide par son numéro de référence - */ - @GET - @Path("/reference/{numeroReference}") - @Operation(summary = "Récupérer une demande d'aide par numéro de référence") - @APIResponse(responseCode = "200", description = "Demande d'aide trouvée") - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response obtenirAideParReference( - @Parameter(description = "Numéro de référence de la demande", required = true) - @PathParam("numeroReference") String numeroReference) { - - try { - LOG.infof("GET /api/aides/reference/%s", numeroReference); - - AideDTO aide = aideService.obtenirAideParReference(numeroReference); - - return Response.ok(aide).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée: référence %s", numeroReference); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération de la demande d'aide %s: %s", numeroReference, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération de la demande d'aide")) - .build(); - } - } - - /** - * Crée une nouvelle demande d'aide - */ - @POST - @Operation(summary = "Créer une nouvelle demande d'aide") - @APIResponses({ - @APIResponse(responseCode = "201", description = "Demande d'aide créée avec succès", - content = @Content(schema = @Schema(implementation = AideDTO.class))), - @APIResponse(responseCode = "400", description = "Données invalides"), - @APIResponse(responseCode = "404", description = "Membre ou organisation non trouvé") - }) - @RolesAllowed({"admin", "gestionnaire_aide", "membre"}) - public Response creerAide( - @Parameter(description = "Données de la demande d'aide à créer", required = true) - @Valid AideDTO aideDTO) { - - try { - LOG.infof("POST /api/aides - Création demande d'aide: %s", aideDTO.getTitre()); - - AideDTO aideCree = aideService.creerAide(aideDTO); - - LOG.infof("Demande d'aide créée avec succès: %s", aideCree.getNumeroReference()); - return Response.status(Response.Status.CREATED) - .location(URI.create("/api/aides/" + aideCree.getId())) - .entity(aideCree) - .build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Membre ou organisation non trouvé lors de la création: %s", e.getMessage()); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Membre ou organisation non trouvé")) - .build(); - } catch (IllegalArgumentException e) { - LOG.warnf("Données invalides pour la création: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "Données invalides", "message", e.getMessage())) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de la création de la demande d'aide: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la création de la demande d'aide")) - .build(); - } - } - - /** - * Met à jour une demande d'aide existante - */ - @PUT - @Path("/{id}") - @Operation(summary = "Mettre à jour une demande d'aide") - @APIResponses({ - @APIResponse(responseCode = "200", description = "Demande d'aide mise à jour avec succès"), - @APIResponse(responseCode = "400", description = "Données invalides"), - @APIResponse(responseCode = "403", description = "Accès non autorisé"), - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - }) - @RolesAllowed({"admin", "gestionnaire_aide", "membre"}) - public Response mettreAJourAide( - @Parameter(description = "ID de la demande d'aide", required = true) - @PathParam("id") Long id, - - @Parameter(description = "Nouvelles données de la demande d'aide", required = true) - @Valid AideDTO aideDTO) { - - try { - LOG.infof("PUT /api/aides/%d", id); - - AideDTO aideMiseAJour = aideService.mettreAJourAide(id, aideDTO); - - LOG.infof("Demande d'aide mise à jour avec succès: %s", aideMiseAJour.getNumeroReference()); - return Response.ok(aideMiseAJour).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée pour mise à jour: ID %d", id); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (SecurityException e) { - LOG.warnf("Accès non autorisé pour mise à jour: ID %d", id); - return Response.status(Response.Status.FORBIDDEN) - .entity(Map.of("error", "Accès non autorisé")) - .build(); - } catch (IllegalStateException e) { - LOG.warnf("État invalide pour mise à jour: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "État invalide", "message", e.getMessage())) - .build(); - } catch (IllegalArgumentException e) { - LOG.warnf("Données invalides pour mise à jour: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "Données invalides", "message", e.getMessage())) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de la mise à jour de la demande d'aide %d: %s", id, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la mise à jour")) - .build(); - } - } - - // ===== OPÉRATIONS MÉTIER SPÉCIALISÉES ===== - - /** - * Approuve une demande d'aide - */ - @POST - @Path("/{id}/approuver") - @Operation(summary = "Approuver une demande d'aide") - @APIResponses({ - @APIResponse(responseCode = "200", description = "Demande d'aide approuvée avec succès"), - @APIResponse(responseCode = "400", description = "Données invalides"), - @APIResponse(responseCode = "403", description = "Accès non autorisé"), - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - }) - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide"}) - public Response approuverAide( - @Parameter(description = "ID de la demande d'aide", required = true) - @PathParam("id") Long id, - - @Parameter(description = "Données d'approbation", required = true) - Map approbationData) { - - try { - LOG.infof("POST /api/aides/%d/approuver", id); - - // Extraction des données d'approbation - BigDecimal montantApprouve = new BigDecimal(approbationData.get("montantApprouve").toString()); - String commentaires = (String) approbationData.get("commentaires"); - - AideDTO aideApprouvee = aideService.approuverAide(id, montantApprouve, commentaires); - - LOG.infof("Demande d'aide approuvée avec succès: %s", aideApprouvee.getNumeroReference()); - return Response.ok(aideApprouvee).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée pour approbation: ID %d", id); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (SecurityException e) { - LOG.warnf("Accès non autorisé pour approbation: ID %d", id); - return Response.status(Response.Status.FORBIDDEN) - .entity(Map.of("error", "Accès non autorisé")) - .build(); - } catch (IllegalStateException e) { - LOG.warnf("État invalide pour approbation: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "État invalide", "message", e.getMessage())) - .build(); - } catch (IllegalArgumentException e) { - LOG.warnf("Données invalides pour approbation: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "Données invalides", "message", e.getMessage())) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de l'approbation de la demande d'aide %d: %s", id, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de l'approbation")) - .build(); - } - } - - /** - * Rejette une demande d'aide - */ - @POST - @Path("/{id}/rejeter") - @Operation(summary = "Rejeter une demande d'aide") - @APIResponses({ - @APIResponse(responseCode = "200", description = "Demande d'aide rejetée avec succès"), - @APIResponse(responseCode = "400", description = "Données invalides"), - @APIResponse(responseCode = "403", description = "Accès non autorisé"), - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - }) - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide"}) - public Response rejeterAide( - @Parameter(description = "ID de la demande d'aide", required = true) - @PathParam("id") Long id, - - @Parameter(description = "Données de rejet", required = true) - Map rejetData) { - - try { - LOG.infof("POST /api/aides/%d/rejeter", id); - - String raisonRejet = rejetData.get("raisonRejet"); - if (raisonRejet == null || raisonRejet.trim().isEmpty()) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "La raison du rejet est obligatoire")) - .build(); - } - - AideDTO aideRejetee = aideService.rejeterAide(id, raisonRejet); - - LOG.infof("Demande d'aide rejetée avec succès: %s", aideRejetee.getNumeroReference()); - return Response.ok(aideRejetee).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée pour rejet: ID %d", id); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (SecurityException e) { - LOG.warnf("Accès non autorisé pour rejet: ID %d", id); - return Response.status(Response.Status.FORBIDDEN) - .entity(Map.of("error", "Accès non autorisé")) - .build(); - } catch (IllegalStateException e) { - LOG.warnf("État invalide pour rejet: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "État invalide", "message", e.getMessage())) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors du rejet de la demande d'aide %d: %s", id, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors du rejet")) - .build(); - } - } - - /** - * Marque une aide comme versée - */ - @POST - @Path("/{id}/verser") - @Operation(summary = "Marquer une aide comme versée") - @APIResponses({ - @APIResponse(responseCode = "200", description = "Aide marquée comme versée avec succès"), - @APIResponse(responseCode = "400", description = "Données invalides"), - @APIResponse(responseCode = "403", description = "Accès non autorisé"), - @APIResponse(responseCode = "404", description = "Demande d'aide non trouvée") - }) - @RolesAllowed({"admin", "gestionnaire_aide", "tresorier"}) - public Response marquerCommeVersee( - @Parameter(description = "ID de la demande d'aide", required = true) - @PathParam("id") Long id, - - @Parameter(description = "Données de versement", required = true) - Map versementData) { - - try { - LOG.infof("POST /api/aides/%d/verser", id); - - // Extraction des données de versement - BigDecimal montantVerse = new BigDecimal(versementData.get("montantVerse").toString()); - String modeVersement = (String) versementData.get("modeVersement"); - String numeroTransaction = (String) versementData.get("numeroTransaction"); - - AideDTO aideVersee = aideService.marquerCommeVersee(id, montantVerse, modeVersement, numeroTransaction); - - LOG.infof("Aide marquée comme versée avec succès: %s", aideVersee.getNumeroReference()); - return Response.ok(aideVersee).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Demande d'aide non trouvée pour versement: ID %d", id); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Demande d'aide non trouvée")) - .build(); - } catch (SecurityException e) { - LOG.warnf("Accès non autorisé pour versement: ID %d", id); - return Response.status(Response.Status.FORBIDDEN) - .entity(Map.of("error", "Accès non autorisé")) - .build(); - } catch (IllegalStateException e) { - LOG.warnf("État invalide pour versement: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "État invalide", "message", e.getMessage())) - .build(); - } catch (IllegalArgumentException e) { - LOG.warnf("Données invalides pour versement: %s", e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "Données invalides", "message", e.getMessage())) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors du versement de l'aide %d: %s", id, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors du versement")) - .build(); - } - } - - // ===== ENDPOINTS DE RECHERCHE ET FILTRAGE ===== - - /** - * Liste les demandes d'aide par statut - */ - @GET - @Path("/statut/{statut}") - @Operation(summary = "Lister les demandes d'aide par statut") - @APIResponse(responseCode = "200", description = "Liste des demandes d'aide par statut") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response listerAidesParStatut( - @Parameter(description = "Statut des demandes d'aide", required = true) - @PathParam("statut") StatutAide statut, - - @Parameter(description = "Numéro de page (0-based)", example = "0") - @QueryParam("page") @DefaultValue("0") @Min(0) int page, - - @Parameter(description = "Taille de la page", example = "20") - @QueryParam("size") @DefaultValue("20") @Min(1) int size) { - - try { - LOG.infof("GET /api/aides/statut/%s - page: %d, size: %d", statut, page, size); - - List aides = aideService.listerAidesParStatut(statut, page, size); - - LOG.infof("Récupération réussie de %d demandes d'aide avec statut %s", aides.size(), statut); - return Response.ok(aides).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération des demandes d'aide par statut %s: %s", statut, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération")) - .build(); - } - } - - /** - * Liste les demandes d'aide d'un membre - */ - @GET - @Path("/membre/{membreId}") - @Operation(summary = "Lister les demandes d'aide d'un membre") - @APIResponse(responseCode = "200", description = "Liste des demandes d'aide du membre") - @APIResponse(responseCode = "404", description = "Membre non trouvé") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response listerAidesParMembre( - @Parameter(description = "ID du membre", required = true) - @PathParam("membreId") Long membreId, - - @Parameter(description = "Numéro de page (0-based)", example = "0") - @QueryParam("page") @DefaultValue("0") @Min(0) int page, - - @Parameter(description = "Taille de la page", example = "20") - @QueryParam("size") @DefaultValue("20") @Min(1) int size) { - - try { - LOG.infof("GET /api/aides/membre/%d - page: %d, size: %d", membreId, page, size); - - List aides = aideService.listerAidesParMembre(membreId, page, size); - - LOG.infof("Récupération réussie de %d demandes d'aide pour le membre %d", aides.size(), membreId); - return Response.ok(aides).build(); - - } catch (jakarta.ws.rs.NotFoundException e) { - LOG.warnf("Membre non trouvé: ID %d", membreId); - return Response.status(Response.Status.NOT_FOUND) - .entity(Map.of("error", "Membre non trouvé")) - .build(); - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération des demandes d'aide du membre %d: %s", membreId, e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération")) - .build(); - } - } - - /** - * Liste les demandes d'aide publiques - */ - @GET - @Path("/publiques") - @Operation(summary = "Lister les demandes d'aide publiques") - @APIResponse(responseCode = "200", description = "Liste des demandes d'aide publiques") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response listerAidesPubliques( - @Parameter(description = "Numéro de page (0-based)", example = "0") - @QueryParam("page") @DefaultValue("0") @Min(0) int page, - - @Parameter(description = "Taille de la page", example = "20") - @QueryParam("size") @DefaultValue("20") @Min(1) int size) { - - try { - LOG.infof("GET /api/aides/publiques - page: %d, size: %d", page, size); - - List aides = aideService.listerAidesPubliques(page, size); - - LOG.infof("Récupération réussie de %d demandes d'aide publiques", aides.size()); - return Response.ok(aides).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération des demandes d'aide publiques: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération")) - .build(); - } - } - - /** - * Recherche textuelle dans les demandes d'aide - */ - @GET - @Path("/recherche") - @Operation(summary = "Rechercher des demandes d'aide par texte") - @APIResponse(responseCode = "200", description = "Résultats de la recherche") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide", "membre"}) - public Response rechercherAides( - @Parameter(description = "Terme de recherche", required = true) - @QueryParam("q") @NotNull String recherche, - - @Parameter(description = "Numéro de page (0-based)", example = "0") - @QueryParam("page") @DefaultValue("0") @Min(0) int page, - - @Parameter(description = "Taille de la page", example = "20") - @QueryParam("size") @DefaultValue("20") @Min(1) int size) { - - try { - LOG.infof("GET /api/aides/recherche?q=%s - page: %d, size: %d", recherche, page, size); - - List aides = aideService.rechercherAides(recherche, page, size); - - LOG.infof("Recherche réussie: %d demandes d'aide trouvées pour '%s'", aides.size(), recherche); - return Response.ok(aides).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors de la recherche de demandes d'aide: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la recherche")) - .build(); - } - } - - /** - * Statistiques globales des demandes d'aide - */ - @GET - @Path("/statistiques") - @Operation(summary = "Obtenir les statistiques des demandes d'aide") - @APIResponse(responseCode = "200", description = "Statistiques des demandes d'aide") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide"}) - public Response obtenirStatistiques() { - - try { - LOG.info("GET /api/aides/statistiques"); - - Map statistiques = aideService.obtenirStatistiquesGlobales(); - - LOG.info("Statistiques calculées avec succès"); - return Response.ok(statistiques).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors du calcul des statistiques: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors du calcul des statistiques")) - .build(); - } - } - - /** - * Demandes d'aide urgentes en attente - */ - @GET - @Path("/urgentes") - @Operation(summary = "Lister les demandes d'aide urgentes en attente") - @APIResponse(responseCode = "200", description = "Liste des demandes d'aide urgentes") - @RolesAllowed({"admin", "gestionnaire_aide", "evaluateur_aide"}) - public Response listerAidesUrgentes() { - - try { - LOG.info("GET /api/aides/urgentes"); - - List aides = aideService.listerAidesUrgentesEnAttente(); - - LOG.infof("Récupération réussie de %d demandes d'aide urgentes", aides.size()); - return Response.ok(aides).build(); - - } catch (Exception e) { - LOG.errorf("Erreur lors de la récupération des demandes d'aide urgentes: %s", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Erreur lors de la récupération")) - .build(); - } - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java index dc49b2a..ead30f8 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java @@ -11,6 +11,8 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.media.Content; @@ -49,43 +51,44 @@ public class CotisationResource { @QueryParam("size") @DefaultValue("20") @Min(1) int size) { try { - System.out.println("GET /api/cotisations/public - page: " + page + ", size: " + size); + log.info("GET /api/cotisations/public - page: {}, size: {}", page, size); - // Données de test pour l'application mobile - List> cotisations = - List.of( - Map.of( - "id", "1", - "nom", "Cotisation Mensuelle Janvier 2025", - "description", "Cotisation mensuelle pour le mois de janvier", - "montant", 25000.0, - "devise", "XOF", - "dateEcheance", "2025-01-31T23:59:59", - "statut", "ACTIVE", - "type", "MENSUELLE"), - Map.of( - "id", "2", - "nom", "Cotisation Spéciale Projet", - "description", "Cotisation pour le financement du projet communautaire", - "montant", 50000.0, - "devise", "XOF", - "dateEcheance", "2025-03-15T23:59:59", - "statut", "ACTIVE", - "type", "SPECIALE")); + // Récupérer les cotisations depuis la base de données + List cotisationsDTO = cotisationService.getAllCotisations(page, size); + + // Convertir en format pour l'application mobile + List> cotisations = cotisationsDTO.stream() + .map(c -> { + Map map = new java.util.HashMap<>(); + map.put("id", c.getId() != null ? c.getId().toString() : ""); + map.put("nom", c.getDescription() != null ? c.getDescription() : "Cotisation"); + map.put("description", c.getDescription() != null ? c.getDescription() : ""); + map.put("montant", c.getMontantDu() != null ? c.getMontantDu().doubleValue() : 0.0); + map.put("devise", c.getCodeDevise() != null ? c.getCodeDevise() : "XOF"); + map.put("dateEcheance", c.getDateEcheance() != null ? c.getDateEcheance().toString() : ""); + map.put("statut", c.getStatut() != null ? c.getStatut() : "EN_ATTENTE"); + map.put("type", c.getTypeCotisation() != null ? c.getTypeCotisation() : "MENSUELLE"); + return map; + }) + .collect(Collectors.toList()); + + long totalElements = cotisationService.getStatistiquesCotisations().get("totalCotisations") != null + ? ((Number) cotisationService.getStatistiquesCotisations().get("totalCotisations")).longValue() + : cotisations.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); Map response = Map.of( "content", cotisations, - "totalElements", cotisations.size(), - "totalPages", 1, + "totalElements", totalElements, + "totalPages", totalPages, "size", size, "number", page); return Response.ok(response).build(); } catch (Exception e) { - System.err.println( - "Erreur lors de la récupération des cotisations publiques: " + e.getMessage()); + log.error("Erreur lors de la récupération des cotisations publiques", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la récupération des cotisations")) .build(); @@ -162,7 +165,7 @@ public class CotisationResource { @Parameter(description = "Identifiant de la cotisation", required = true) @PathParam("id") @NotNull - Long id) { + UUID id) { try { log.info("GET /api/cotisations/{}", id); @@ -309,7 +312,7 @@ public class CotisationResource { @Parameter(description = "Identifiant de la cotisation", required = true) @PathParam("id") @NotNull - Long id, + UUID id, @Parameter(description = "Nouvelles données de la cotisation", required = true) @Valid CotisationDTO cotisationDTO) { @@ -365,7 +368,7 @@ public class CotisationResource { @Parameter(description = "Identifiant de la cotisation", required = true) @PathParam("id") @NotNull - Long id) { + UUID id) { try { log.info("DELETE /api/cotisations/{}", id); @@ -414,7 +417,7 @@ public class CotisationResource { @Parameter(description = "Identifiant du membre", required = true) @PathParam("membreId") @NotNull - Long membreId, + UUID membreId, @Parameter(description = "Numéro de page", example = "0") @QueryParam("page") @DefaultValue("0") @@ -560,7 +563,7 @@ public class CotisationResource { @APIResponse(responseCode = "500", description = "Erreur interne du serveur") }) public Response rechercherCotisations( - @Parameter(description = "Identifiant du membre") @QueryParam("membreId") Long membreId, + @Parameter(description = "Identifiant du membre") @QueryParam("membreId") UUID membreId, @Parameter(description = "Statut de la cotisation") @QueryParam("statut") String statut, @Parameter(description = "Type de cotisation") @QueryParam("typeCotisation") String typeCotisation, diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java new file mode 100644 index 0000000..0ed6365 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java @@ -0,0 +1,249 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO; +import dev.lions.unionflow.server.api.service.dashboard.DashboardService; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Resource REST pour les APIs du dashboard + * + *

Cette ressource expose les endpoints pour récupérer les données du dashboard, + * incluant les statistiques, activités récentes et événements à venir. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@Path("/api/v1/dashboard") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Dashboard", description = "APIs pour la gestion du dashboard") +public class DashboardResource { + + private static final Logger LOG = Logger.getLogger(DashboardResource.class); + + @Inject + DashboardService dashboardService; + + /** + * Récupère toutes les données du dashboard + */ + @GET + @Path("/data") + @Operation( + summary = "Récupérer toutes les données du dashboard", + description = "Retourne les statistiques, activités récentes et événements à venir" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Données récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getDashboardData( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("GET /api/v1/dashboard/data - org: %s, user: %s", organizationId, userId); + + try { + DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId); + return Response.ok(dashboardData).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des données dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère uniquement les statistiques du dashboard + */ + @GET + @Path("/stats") + @Operation( + summary = "Récupérer les statistiques du dashboard", + description = "Retourne uniquement les statistiques principales" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getDashboardStats( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("GET /api/v1/dashboard/stats - org: %s, user: %s", organizationId, userId); + + try { + DashboardStatsDTO stats = dashboardService.getDashboardStats(organizationId, userId); + return Response.ok(stats).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des statistiques dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère les activités récentes + */ + @GET + @Path("/activities") + @Operation( + summary = "Récupérer les activités récentes", + description = "Retourne la liste des activités récentes avec pagination" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Activités récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getRecentActivities( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId, + @Parameter(description = "Nombre maximum d'activités à retourner", required = false) + @QueryParam("limit") @DefaultValue("10") int limit) { + + LOG.infof("GET /api/v1/dashboard/activities - org: %s, user: %s, limit: %d", + organizationId, userId, limit); + + try { + List activities = dashboardService.getRecentActivities( + organizationId, userId, limit); + + Map response = new HashMap<>(); + response.put("activities", activities); + response.put("total", activities.size()); + response.put("limit", limit); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des activités récentes"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère les événements à venir + */ + @GET + @Path("/events/upcoming") + @Operation( + summary = "Récupérer les événements à venir", + description = "Retourne la liste des événements à venir avec pagination" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Événements récupérés avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getUpcomingEvents( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId, + @Parameter(description = "Nombre maximum d'événements à retourner", required = false) + @QueryParam("limit") @DefaultValue("5") int limit) { + + LOG.infof("GET /api/v1/dashboard/events/upcoming - org: %s, user: %s, limit: %d", + organizationId, userId, limit); + + try { + List events = dashboardService.getUpcomingEvents( + organizationId, userId, limit); + + Map response = new HashMap<>(); + response.put("events", events); + response.put("total", events.size()); + response.put("limit", limit); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des événements à venir"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Endpoint de santé pour vérifier le statut du dashboard + */ + @GET + @Path("/health") + @Operation( + summary = "Vérifier la santé du service dashboard", + description = "Retourne le statut de santé du service dashboard" + ) + @APIResponse(responseCode = "200", description = "Service en bonne santé") + public Response healthCheck() { + LOG.debug("GET /api/v1/dashboard/health"); + + Map health = new HashMap<>(); + health.put("status", "UP"); + health.put("service", "dashboard"); + health.put("timestamp", System.currentTimeMillis()); + health.put("version", "1.0.0"); + + return Response.ok(health).build(); + } + + /** + * Endpoint pour rafraîchir les données du dashboard + */ + @POST + @Path("/refresh") + @Operation( + summary = "Rafraîchir les données du dashboard", + description = "Force la mise à jour des données du dashboard" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Données rafraîchies avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response refreshDashboard( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("POST /api/v1/dashboard/refresh - org: %s, user: %s", organizationId, userId); + + try { + // Simuler un rafraîchissement (dans un vrai système, cela pourrait vider le cache) + DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId); + + Map response = new HashMap<>(); + response.put("status", "refreshed"); + response.put("timestamp", System.currentTimeMillis()); + response.put("data", dashboardData); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du rafraîchissement du dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java index c22f7af..81d9a8b 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; @@ -71,7 +72,7 @@ public class EvenementResource { @APIResponse(responseCode = "200", description = "Nombre d'événements") public Response countEvenements() { try { - long count = Evenement.count(); + long count = evenementService.countEvenements(); return Response.ok(Map.of("count", count, "status", "success")).build(); } catch (Exception e) { LOG.errorf("Erreur count: %s", e.getMessage(), e); @@ -129,7 +130,7 @@ public class EvenementResource { EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(evenement); evenementsDTOs.add(dto); } catch (Exception e) { - LOG.errorf("Erreur lors de la conversion de l'événement %d: %s", evenement.id, e.getMessage()); + LOG.errorf("Erreur lors de la conversion de l'événement %s: %s", evenement.getId(), e.getMessage()); // Continuer avec les autres événements } } @@ -137,7 +138,7 @@ public class EvenementResource { LOG.infof("Nombre de DTOs créés: %d", evenementsDTOs.size()); // Compter le total d'événements actifs - long total = Evenement.count("actif = true"); + long total = evenementService.countEvenementsActifs(); int totalPages = total > 0 ? (int) Math.ceil((double) total / size) : 0; // Retourner la structure paginée attendue par le mobile @@ -170,10 +171,10 @@ public class EvenementResource { @APIResponse(responseCode = "404", description = "Événement non trouvé") @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) public Response obtenirEvenement( - @Parameter(description = "ID de l'événement", required = true) @PathParam("id") Long id) { + @Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) { try { - LOG.infof("GET /api/evenements/%d", id); + LOG.infof("GET /api/evenements/%s", id); Optional evenement = evenementService.trouverParId(id); @@ -235,10 +236,10 @@ public class EvenementResource { @APIResponse(responseCode = "200", description = "Événement mis à jour avec succès") @APIResponse(responseCode = "404", description = "Événement non trouvé") @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"}) - public Response mettreAJourEvenement(@PathParam("id") Long id, @Valid Evenement evenement) { + public Response mettreAJourEvenement(@PathParam("id") UUID id, @Valid Evenement evenement) { try { - LOG.infof("PUT /api/evenements/%d", id); + LOG.infof("PUT /api/evenements/%s", id); Evenement evenementMisAJour = evenementService.mettreAJourEvenement(id, evenement); @@ -266,10 +267,10 @@ public class EvenementResource { @Operation(summary = "Supprimer un événement") @APIResponse(responseCode = "204", description = "Événement supprimé avec succès") @RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"}) - public Response supprimerEvenement(@PathParam("id") Long id) { + public Response supprimerEvenement(@PathParam("id") UUID id) { try { - LOG.infof("DELETE /api/evenements/%d", id); + LOG.infof("DELETE /api/evenements/%s", id); evenementService.supprimerEvenement(id); @@ -402,7 +403,7 @@ public class EvenementResource { @Operation(summary = "Changer le statut d'un événement") @RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"}) public Response changerStatut( - @PathParam("id") Long id, @QueryParam("statut") StatutEvenement nouveauStatut) { + @PathParam("id") UUID id, @QueryParam("statut") StatutEvenement nouveauStatut) { try { if (nouveauStatut == null) { diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java index d42bc32..59d56ae 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java @@ -16,6 +16,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.List; import java.util.Map; +import java.util.UUID; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; @@ -73,8 +74,8 @@ public class MembreResource { @Operation(summary = "Récupérer un membre par son ID") @APIResponse(responseCode = "200", description = "Membre trouvé") @APIResponse(responseCode = "404", description = "Membre non trouvé") - public Response obtenirMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id) { - LOG.infof("Récupération du membre ID: %d", id); + public Response obtenirMembre(@Parameter(description = "UUID du membre") @PathParam("id") UUID id) { + LOG.infof("Récupération du membre ID: %s", id); return membreService .trouverParId(id) .map( @@ -119,9 +120,9 @@ public class MembreResource { @APIResponse(responseCode = "404", description = "Membre non trouvé") @APIResponse(responseCode = "400", description = "Données invalides") public Response mettreAJourMembre( - @Parameter(description = "ID du membre") @PathParam("id") Long id, + @Parameter(description = "UUID du membre") @PathParam("id") UUID id, @Valid MembreDTO membreDTO) { - LOG.infof("Mise à jour du membre ID: %d", id); + LOG.infof("Mise à jour du membre ID: %s", id); try { // Conversion DTO vers entité Membre membre = membreService.convertFromDTO(membreDTO); @@ -146,8 +147,8 @@ public class MembreResource { @APIResponse(responseCode = "204", description = "Membre désactivé avec succès") @APIResponse(responseCode = "404", description = "Membre non trouvé") public Response desactiverMembre( - @Parameter(description = "ID du membre") @PathParam("id") Long id) { - LOG.infof("Désactivation du membre ID: %d", id); + @Parameter(description = "UUID du membre") @PathParam("id") UUID id) { + LOG.infof("Désactivation du membre ID: %s", id); try { membreService.desactiverMembre(id); return Response.noContent().build(); diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java index 1bb3362..6373c23 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java @@ -13,6 +13,7 @@ import jakarta.ws.rs.core.Response; import java.net.URI; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; @@ -70,7 +71,7 @@ public class OrganisationResource { Organisation organisationCreee = organisationService.creerOrganisation(organisation); OrganisationDTO dto = organisationService.convertToDTO(organisationCreee); - return Response.created(URI.create("/api/organisations/" + organisationCreee.id)) + return Response.created(URI.create("/api/organisations/" + organisationCreee.getId())) .entity(dto) .build(); } catch (IllegalArgumentException e) { @@ -160,7 +161,7 @@ public class OrganisationResource { @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response obtenirOrganisation( - @Parameter(description = "ID de l'organisation", required = true) @PathParam("id") Long id) { + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Récupération de l'organisation ID: %d", id); @@ -198,10 +199,10 @@ public class OrganisationResource { @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response mettreAJourOrganisation( - @Parameter(description = "ID de l'organisation", required = true) @PathParam("id") Long id, + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id, @Valid OrganisationDTO organisationDTO) { - LOG.infof("Mise à jour de l'organisation ID: %d", id); + LOG.infof("Mise à jour de l'organisation ID: %s", id); try { Organisation organisationMiseAJour = organisationService.convertFromDTO(organisationDTO); @@ -241,7 +242,7 @@ public class OrganisationResource { @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response supprimerOrganisation( - @Parameter(description = "ID de l'organisation", required = true) @PathParam("id") Long id) { + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Suppression de l'organisation ID: %d", id); @@ -327,7 +328,7 @@ public class OrganisationResource { @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response activerOrganisation( - @Parameter(description = "ID de l'organisation", required = true) @PathParam("id") Long id) { + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Activation de l'organisation ID: %d", id); @@ -360,7 +361,7 @@ public class OrganisationResource { @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response suspendreOrganisation( - @Parameter(description = "ID de l'organisation", required = true) @PathParam("id") Long id) { + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Suspension de l'organisation ID: %d", id); diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/security/KeycloakService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/security/KeycloakService.java deleted file mode 100644 index 2c55ffc..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/security/KeycloakService.java +++ /dev/null @@ -1,338 +0,0 @@ -package dev.lions.unionflow.server.security; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import java.util.Set; -import java.util.stream.Collectors; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.jboss.logging.Logger; - -/** - * Service pour l'intégration avec Keycloak et la gestion de la sécurité Fournit des méthodes - * utilitaires pour accéder aux informations de l'utilisateur connecté - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-15 - */ -@ApplicationScoped -public class KeycloakService { - - private static final Logger LOG = Logger.getLogger(KeycloakService.class); - - @Inject SecurityIdentity securityIdentity; - - @Inject JsonWebToken jwt; - - /** - * Récupère l'email de l'utilisateur actuellement connecté - * - * @return l'email de l'utilisateur ou null si non connecté - */ - public String getCurrentUserEmail() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - LOG.debug("Aucun utilisateur connecté"); - return null; - } - - try { - // Essayer d'abord avec le claim 'email' - if (jwt != null && jwt.containsClaim("email")) { - String email = jwt.getClaim("email"); - LOG.debugf("Email récupéré depuis JWT: %s", email); - return email; - } - - // Fallback sur le nom principal - String principal = securityIdentity.getPrincipal().getName(); - LOG.debugf("Email récupéré depuis principal: %s", principal); - return principal; - - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération de l'email utilisateur: %s", e.getMessage()); - return null; - } - } - - /** - * Récupère l'ID utilisateur Keycloak de l'utilisateur actuellement connecté - * - * @return l'ID utilisateur Keycloak ou null si non connecté - */ - public String getCurrentUserId() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return null; - } - - try { - if (jwt != null && jwt.containsClaim("sub")) { - String userId = jwt.getClaim("sub"); - LOG.debugf("ID utilisateur récupéré: %s", userId); - return userId; - } - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération de l'ID utilisateur: %s", e.getMessage()); - } - - return null; - } - - /** - * Récupère le nom complet de l'utilisateur actuellement connecté - * - * @return le nom complet ou null si non disponible - */ - public String getCurrentUserFullName() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return null; - } - - try { - if (jwt != null) { - // Essayer le claim 'name' en premier - if (jwt.containsClaim("name")) { - return jwt.getClaim("name"); - } - - // Construire à partir de given_name et family_name - String givenName = jwt.containsClaim("given_name") ? jwt.getClaim("given_name") : ""; - String familyName = jwt.containsClaim("family_name") ? jwt.getClaim("family_name") : ""; - - if (!givenName.isEmpty() || !familyName.isEmpty()) { - return (givenName + " " + familyName).trim(); - } - - // Fallback sur preferred_username - if (jwt.containsClaim("preferred_username")) { - return jwt.getClaim("preferred_username"); - } - } - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération du nom complet: %s", e.getMessage()); - } - - return getCurrentUserEmail(); // Fallback sur l'email - } - - /** - * Récupère le prénom de l'utilisateur actuellement connecté - * - * @return le prénom ou null si non disponible - */ - public String getCurrentUserFirstName() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return null; - } - - try { - if (jwt != null && jwt.containsClaim("given_name")) { - return jwt.getClaim("given_name"); - } - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération du prénom: %s", e.getMessage()); - } - - return null; - } - - /** - * Récupère le nom de famille de l'utilisateur actuellement connecté - * - * @return le nom de famille ou null si non disponible - */ - public String getCurrentUserLastName() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return null; - } - - try { - if (jwt != null && jwt.containsClaim("family_name")) { - return jwt.getClaim("family_name"); - } - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération du nom de famille: %s", e.getMessage()); - } - - return null; - } - - /** - * Vérifie si l'utilisateur actuel possède un rôle spécifique - * - * @param role le nom du rôle à vérifier - * @return true si l'utilisateur possède le rôle - */ - public boolean hasRole(String role) { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return false; - } - - try { - boolean hasRole = securityIdentity.hasRole(role); - LOG.debugf("Vérification du rôle '%s' pour l'utilisateur: %s", role, hasRole); - return hasRole; - } catch (Exception e) { - LOG.warnf("Erreur lors de la vérification du rôle '%s': %s", role, e.getMessage()); - return false; - } - } - - /** - * Vérifie si l'utilisateur actuel possède au moins un des rôles spécifiés - * - * @param roles les rôles à vérifier - * @return true si l'utilisateur possède au moins un des rôles - */ - public boolean hasAnyRole(String... roles) { - if (roles == null || roles.length == 0) { - return false; - } - - for (String role : roles) { - if (hasRole(role)) { - return true; - } - } - - return false; - } - - /** - * Vérifie si l'utilisateur actuel possède tous les rôles spécifiés - * - * @param roles les rôles à vérifier - * @return true si l'utilisateur possède tous les rôles - */ - public boolean hasAllRoles(String... roles) { - if (roles == null || roles.length == 0) { - return true; - } - - for (String role : roles) { - if (!hasRole(role)) { - return false; - } - } - - return true; - } - - /** - * Récupère tous les rôles de l'utilisateur actuel - * - * @return ensemble des rôles de l'utilisateur - */ - public Set getCurrentUserRoles() { - if (securityIdentity == null || securityIdentity.isAnonymous()) { - return Set.of(); - } - - try { - Set roles = securityIdentity.getRoles(); - LOG.debugf("Rôles de l'utilisateur actuel: %s", roles); - return roles; - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération des rôles: %s", e.getMessage()); - return Set.of(); - } - } - - /** - * Vérifie si l'utilisateur actuel est un administrateur - * - * @return true si l'utilisateur est administrateur - */ - public boolean isAdmin() { - return hasAnyRole("admin", "administrator", "super_admin"); - } - - /** - * Vérifie si l'utilisateur actuel est connecté (non anonyme) - * - * @return true si l'utilisateur est connecté - */ - public boolean isAuthenticated() { - return securityIdentity != null && !securityIdentity.isAnonymous(); - } - - /** - * Récupère une claim spécifique du JWT - * - * @param claimName nom de la claim - * @return valeur de la claim ou null si non trouvée - */ - public T getClaim(String claimName, Class claimType) { - if (jwt == null || !jwt.containsClaim(claimName)) { - return null; - } - - try { - return jwt.getClaim(claimName); - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération de la claim '%s': %s", claimName, e.getMessage()); - return null; - } - } - - /** - * Récupère les groupes de l'utilisateur depuis le JWT - * - * @return ensemble des groupes de l'utilisateur - */ - public Set getCurrentUserGroups() { - if (jwt == null) { - return Set.of(); - } - - try { - if (jwt.containsClaim("groups")) { - Object groups = jwt.getClaim("groups"); - if (groups instanceof Set) { - return ((Set) groups).stream().map(Object::toString).collect(Collectors.toSet()); - } - } - } catch (Exception e) { - LOG.warnf("Erreur lors de la récupération des groupes: %s", e.getMessage()); - } - - return Set.of(); - } - - /** - * Vérifie si l'utilisateur appartient à un groupe spécifique - * - * @param groupName nom du groupe - * @return true si l'utilisateur appartient au groupe - */ - public boolean isMemberOfGroup(String groupName) { - return getCurrentUserGroups().contains(groupName); - } - - /** - * Récupère l'organisation de l'utilisateur depuis le JWT - * - * @return ID de l'organisation ou null si non disponible - */ - public String getCurrentUserOrganization() { - return getClaim("organization", String.class); - } - - /** Log les informations de l'utilisateur actuel (pour debug) */ - public void logCurrentUserInfo() { - if (!LOG.isDebugEnabled()) { - return; - } - - LOG.debugf("=== Informations utilisateur actuel ==="); - LOG.debugf("Email: %s", getCurrentUserEmail()); - LOG.debugf("ID: %s", getCurrentUserId()); - LOG.debugf("Nom complet: %s", getCurrentUserFullName()); - LOG.debugf("Rôles: %s", getCurrentUserRoles()); - LOG.debugf("Groupes: %s", getCurrentUserGroups()); - LOG.debugf("Organisation: %s", getCurrentUserOrganization()); - LOG.debugf("Authentifié: %s", isAuthenticated()); - LOG.debugf("Admin: %s", isAdmin()); - LOG.debugf("====================================="); - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/CotisationService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/CotisationService.java index cc5cce3..b00c675 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/CotisationService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/CotisationService.java @@ -47,11 +47,14 @@ public class CotisationService { public List getAllCotisations(int page, int size) { log.debug("Récupération des cotisations - page: {}, size: {}", page, size); - List cotisations = - cotisationRepository - .findAll(Sort.by("dateEcheance").descending()) - .page(Page.of(page, size)) - .list(); + // Utilisation de EntityManager pour la pagination + jakarta.persistence.TypedQuery query = + cotisationRepository.getEntityManager().createQuery( + "SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setFirstResult(page * size); + query.setMaxResults(size); + List cotisations = query.getResultList(); return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); } @@ -59,11 +62,11 @@ public class CotisationService { /** * Récupère une cotisation par son ID * - * @param id identifiant de la cotisation + * @param id identifiant UUID de la cotisation * @return DTO de la cotisation * @throws NotFoundException si la cotisation n'existe pas */ - public CotisationDTO getCotisationById(@NotNull Long id) { + public CotisationDTO getCotisationById(@NotNull UUID id) { log.debug("Récupération de la cotisation avec ID: {}", id); Cotisation cotisation = @@ -105,10 +108,10 @@ public class CotisationService { public CotisationDTO createCotisation(@Valid CotisationDTO cotisationDTO) { log.info("Création d'une nouvelle cotisation pour le membre: {}", cotisationDTO.getMembreId()); - // Validation du membre + // Validation du membre - UUID direct maintenant Membre membre = membreRepository - .findByIdOptional(Long.valueOf(cotisationDTO.getMembreId().toString())) + .findByIdOptional(cotisationDTO.getMembreId()) .orElseThrow( () -> new NotFoundException( @@ -131,7 +134,7 @@ public class CotisationService { log.info( "Cotisation créée avec succès - ID: {}, Référence: {}", - cotisation.id, + cotisation.getId(), cotisation.getNumeroReference()); return convertToDTO(cotisation); @@ -140,12 +143,12 @@ public class CotisationService { /** * Met à jour une cotisation existante * - * @param id identifiant de la cotisation + * @param id identifiant UUID de la cotisation * @param cotisationDTO nouvelles données * @return DTO de la cotisation mise à jour */ @Transactional - public CotisationDTO updateCotisation(@NotNull Long id, @Valid CotisationDTO cotisationDTO) { + public CotisationDTO updateCotisation(@NotNull UUID id, @Valid CotisationDTO cotisationDTO) { log.info("Mise à jour de la cotisation avec ID: {}", id); Cotisation cotisationExistante = @@ -167,10 +170,10 @@ public class CotisationService { /** * Supprime (désactive) une cotisation * - * @param id identifiant de la cotisation + * @param id identifiant UUID de la cotisation */ @Transactional - public void deleteCotisation(@NotNull Long id) { + public void deleteCotisation(@NotNull UUID id) { log.info("Suppression de la cotisation avec ID: {}", id); Cotisation cotisation = @@ -191,12 +194,12 @@ public class CotisationService { /** * Récupère les cotisations d'un membre * - * @param membreId identifiant du membre + * @param membreId identifiant UUID du membre * @param page numéro de page * @param size taille de la page * @return liste des cotisations du membre */ - public List getCotisationsByMembre(@NotNull Long membreId, int page, int size) { + public List getCotisationsByMembre(@NotNull UUID membreId, int page, int size) { log.debug("Récupération des cotisations du membre: {}", membreId); // Vérification de l'existence du membre @@ -256,7 +259,7 @@ public class CotisationService { * @return liste filtrée des cotisations */ public List rechercherCotisations( - Long membreId, + UUID membreId, String statut, String typeCotisation, Integer annee, @@ -297,15 +300,31 @@ public class CotisationService { /** Convertit une entité Cotisation en DTO */ private CotisationDTO convertToDTO(Cotisation cotisation) { + if (cotisation == null) { + return null; + } + CotisationDTO dto = new CotisationDTO(); - // Copie des propriétés de base - // Génération d'UUID basé sur l'ID numérique pour compatibilité - dto.setId(UUID.nameUUIDFromBytes(("cotisation-" + cotisation.id).getBytes())); + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(cotisation.getId()); dto.setNumeroReference(cotisation.getNumeroReference()); - dto.setMembreId(UUID.nameUUIDFromBytes(("membre-" + cotisation.getMembre().id).getBytes())); - dto.setNomMembre(cotisation.getMembre().getNom() + " " + cotisation.getMembre().getPrenom()); - dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre()); + + // Conversion du membre associé + if (cotisation.getMembre() != null) { + dto.setMembreId(cotisation.getMembre().getId()); + dto.setNomMembre(cotisation.getMembre().getNomComplet()); + dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre()); + + // Conversion de l'organisation du membre (associationId) + if (cotisation.getMembre().getOrganisation() != null + && cotisation.getMembre().getOrganisation().getId() != null) { + dto.setAssociationId(cotisation.getMembre().getOrganisation().getId()); + dto.setNomAssociation(cotisation.getMembre().getOrganisation().getNom()); + } + } + + // Propriétés de la cotisation dto.setTypeCotisation(cotisation.getTypeCotisation()); dto.setMontantDu(cotisation.getMontantDu()); dto.setMontantPaye(cotisation.getMontantPaye()); @@ -321,11 +340,14 @@ public class CotisationService { dto.setRecurrente(cotisation.getRecurrente()); dto.setNombreRappels(cotisation.getNombreRappels()); dto.setDateDernierRappel(cotisation.getDateDernierRappel()); + + // Conversion du validateur dto.setValidePar( cotisation.getValideParId() != null - ? UUID.nameUUIDFromBytes(("user-" + cotisation.getValideParId()).getBytes()) + ? cotisation.getValideParId() : null); dto.setNomValidateur(cotisation.getNomValidateur()); + dto.setMethodePaiement(cotisation.getMethodePaiement()); dto.setReferencePaiement(cotisation.getReferencePaiement()); dto.setDateCreation(cotisation.getDateCreation()); diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java new file mode 100644 index 0000000..dea41dd --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java @@ -0,0 +1,254 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.api.service.dashboard.DashboardService; +import dev.lions.unionflow.server.entity.Cotisation; +import dev.lions.unionflow.server.entity.DemandeAide; +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.repository.CotisationRepository; +import dev.lions.unionflow.server.repository.DemandeAideRepository; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.TypedQuery; +import org.jboss.logging.Logger; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Implémentation du service Dashboard pour Quarkus + * + *

Cette implémentation récupère les données réelles depuis la base de données + * via les repositories. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-17 + */ +@ApplicationScoped +public class DashboardServiceImpl implements DashboardService { + + private static final Logger LOG = Logger.getLogger(DashboardServiceImpl.class); + + @Inject + MembreRepository membreRepository; + + @Inject + EvenementRepository evenementRepository; + + @Inject + CotisationRepository cotisationRepository; + + @Inject + DemandeAideRepository demandeAideRepository; + + @Inject + OrganisationRepository organisationRepository; + + @Override + public DashboardDataDTO getDashboardData(String organizationId, String userId) { + LOG.infof("Récupération des données dashboard pour org: %s et user: %s", organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + return DashboardDataDTO.builder() + .stats(getDashboardStats(organizationId, userId)) + .recentActivities(getRecentActivities(organizationId, userId, 10)) + .upcomingEvents(getUpcomingEvents(organizationId, userId, 5)) + .userPreferences(getUserPreferences(userId)) + .organizationId(organizationId) + .userId(userId) + .build(); + } + + @Override + public DashboardStatsDTO getDashboardStats(String organizationId, String userId) { + LOG.infof("Récupération des stats dashboard pour org: %s et user: %s", organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + // Compter les membres + long totalMembers = membreRepository.count(); + long activeMembers = membreRepository.countActifs(); + + // Compter les événements + long totalEvents = evenementRepository.count(); + long upcomingEvents = evenementRepository.findEvenementsAVenir().size(); + + // Compter les cotisations + long totalContributions = cotisationRepository.count(); + BigDecimal totalContributionAmount = calculateTotalContributionAmount(orgId); + + // Compter les demandes en attente + List pendingRequests = demandeAideRepository.findByStatut(StatutAide.EN_ATTENTE); + long pendingRequestsCount = pendingRequests.stream() + .filter(d -> d.getOrganisation() != null && d.getOrganisation().getId().equals(orgId)) + .count(); + + // Calculer la croissance mensuelle (membres ajoutés ce mois) + LocalDate debutMois = LocalDate.now().withDayOfMonth(1); + long nouveauxMembresMois = membreRepository.countNouveauxMembres(debutMois); + long totalMembresAvant = totalMembers - nouveauxMembresMois; + double monthlyGrowth = totalMembresAvant > 0 + ? (double) nouveauxMembresMois / totalMembresAvant * 100.0 + : 0.0; + + // Calculer le taux d'engagement (membres actifs / total) + double engagementRate = totalMembers > 0 + ? (double) activeMembers / totalMembers + : 0.0; + + return DashboardStatsDTO.builder() + .totalMembers((int) totalMembers) + .activeMembers((int) activeMembers) + .totalEvents((int) totalEvents) + .upcomingEvents((int) upcomingEvents) + .totalContributions((int) totalContributions) + .totalContributionAmount(totalContributionAmount.doubleValue()) + .pendingRequests((int) pendingRequestsCount) + .completedProjects(0) // À implémenter si nécessaire + .monthlyGrowth(monthlyGrowth) + .engagementRate(engagementRate) + .lastUpdated(LocalDateTime.now()) + .build(); + } + + @Override + public List getRecentActivities(String organizationId, String userId, int limit) { + LOG.infof("Récupération de %d activités récentes pour org: %s et user: %s", limit, organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + List activities = new ArrayList<>(); + + // Récupérer les membres récemment créés + List nouveauxMembres = membreRepository.rechercheAvancee( + null, true, null, null, Page.of(0, limit), Sort.by("dateCreation", Sort.Direction.Descending)); + + for (Membre membre : nouveauxMembres) { + if (membre.getOrganisation() != null && membre.getOrganisation().getId().equals(orgId)) { + activities.add(RecentActivityDTO.builder() + .id(membre.getId().toString()) + .type("member") + .title("Nouveau membre inscrit") + .description(membre.getNomComplet() + " a rejoint l'organisation") + .userName(membre.getNomComplet()) + .timestamp(membre.getDateCreation()) + .userAvatar(null) + .actionUrl("/members/" + membre.getId()) + .build()); + } + } + + // Récupérer les événements récemment créés + List tousEvenements = evenementRepository.listAll(); + List nouveauxEvenements = tousEvenements.stream() + .filter(e -> e.getOrganisation() != null && e.getOrganisation().getId().equals(orgId)) + .sorted(Comparator.comparing(Evenement::getDateCreation).reversed()) + .limit(limit) + .collect(Collectors.toList()); + + for (Evenement evenement : nouveauxEvenements) { + activities.add(RecentActivityDTO.builder() + .id(evenement.getId().toString()) + .type("event") + .title("Événement créé") + .description(evenement.getTitre() + " a été programmé") + .userName(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : "Système") + .timestamp(evenement.getDateCreation()) + .userAvatar(null) + .actionUrl("/events/" + evenement.getId()) + .build()); + } + + // Récupérer les cotisations récentes + List cotisationsRecentes = cotisationRepository.rechercheAvancee( + null, "PAYEE", null, null, null, Page.of(0, limit)); + + for (Cotisation cotisation : cotisationsRecentes) { + if (cotisation.getMembre() != null && + cotisation.getMembre().getOrganisation() != null && + cotisation.getMembre().getOrganisation().getId().equals(orgId)) { + activities.add(RecentActivityDTO.builder() + .id(cotisation.getId().toString()) + .type("contribution") + .title("Cotisation reçue") + .description("Paiement de " + cotisation.getMontantPaye() + " " + cotisation.getCodeDevise() + " reçu") + .userName(cotisation.getMembre().getNomComplet()) + .timestamp(cotisation.getDatePaiement() != null ? cotisation.getDatePaiement() : cotisation.getDateCreation()) + .userAvatar(null) + .actionUrl("/contributions/" + cotisation.getId()) + .build()); + } + } + + // Trier par timestamp décroissant et limiter + return activities.stream() + .sorted(Comparator.comparing(RecentActivityDTO::getTimestamp).reversed()) + .limit(limit) + .collect(Collectors.toList()); + } + + @Override + public List getUpcomingEvents(String organizationId, String userId, int limit) { + LOG.infof("Récupération de %d événements à venir pour org: %s et user: %s", limit, organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + List evenements = evenementRepository.findEvenementsAVenir( + Page.of(0, limit), Sort.by("dateDebut", Sort.Direction.Ascending)); + + return evenements.stream() + .filter(e -> e.getOrganisation() == null || e.getOrganisation().getId().equals(orgId)) + .map(this::convertToUpcomingEventDTO) + .limit(limit) + .collect(Collectors.toList()); + } + + private UpcomingEventDTO convertToUpcomingEventDTO(Evenement evenement) { + return UpcomingEventDTO.builder() + .id(evenement.getId().toString()) + .title(evenement.getTitre()) + .description(evenement.getDescription()) + .startDate(evenement.getDateDebut()) + .endDate(evenement.getDateFin()) + .location(evenement.getLieu()) + .maxParticipants(evenement.getCapaciteMax()) + .currentParticipants(evenement.getNombreInscrits()) + .status(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE") + .imageUrl(null) + .tags(Collections.emptyList()) + .build(); + } + + private BigDecimal calculateTotalContributionAmount(UUID organisationId) { + TypedQuery query = cotisationRepository.getEntityManager().createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + private Map getUserPreferences(String userId) { + Map preferences = new HashMap<>(); + preferences.put("theme", "royal_teal"); + preferences.put("language", "fr"); + preferences.put("notifications", true); + preferences.put("autoRefresh", true); + preferences.put("refreshInterval", 300); + return preferences; + } +} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java index 73b3e7c..0e977fb 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java @@ -31,8 +31,8 @@ public class DemandeAideService { private static final Logger LOG = Logger.getLogger(DemandeAideService.class); // Cache en mémoire pour les demandes fréquemment consultées - private final Map cacheDemandesRecentes = new HashMap<>(); - private final Map cacheTimestamps = new HashMap<>(); + private final Map cacheDemandesRecentes = new HashMap<>(); + private final Map cacheTimestamps = new HashMap<>(); private static final long CACHE_DURATION_MINUTES = 15; // === OPÉRATIONS CRUD === @@ -122,10 +122,10 @@ public class DemandeAideService { /** * Obtient une demande d'aide par son ID * - * @param id ID de la demande + * @param id UUID de la demande * @return La demande trouvée */ - public DemandeAideDTO obtenirParId(@NotBlank String id) { + public DemandeAideDTO obtenirParId(@NotNull UUID id) { LOG.debugf("Récupération de la demande d'aide: %s", id); // Vérification du cache @@ -149,14 +149,14 @@ public class DemandeAideService { /** * Change le statut d'une demande d'aide * - * @param demandeId ID de la demande + * @param demandeId UUID de la demande * @param nouveauStatut Nouveau statut * @param motif Motif du changement * @return La demande avec le nouveau statut */ @Transactional public DemandeAideDTO changerStatut( - @NotBlank String demandeId, @NotNull StatutAide nouveauStatut, String motif) { + @NotNull UUID demandeId, @NotNull StatutAide nouveauStatut, String motif) { LOG.infof("Changement de statut pour la demande %s: %s", demandeId, nouveauStatut); DemandeAideDTO demande = obtenirParId(demandeId); @@ -232,10 +232,10 @@ public class DemandeAideService { /** * Obtient les demandes urgentes pour une organisation * - * @param organisationId ID de l'organisation + * @param organisationId UUID de l'organisation * @return Liste des demandes urgentes */ - public List obtenirDemandesUrgentes(String organisationId) { + public List obtenirDemandesUrgentes(UUID organisationId) { LOG.debugf("Récupération des demandes urgentes pour: %s", organisationId); Map filtres = @@ -356,8 +356,8 @@ public class DemandeAideService { // === GESTION DU CACHE === private void ajouterAuCache(DemandeAideDTO demande) { - cacheDemandesRecentes.put(demande.getId().toString(), demande); - cacheTimestamps.put(demande.getId().toString(), LocalDateTime.now()); + cacheDemandesRecentes.put(demande.getId(), demande); + cacheTimestamps.put(demande.getId(), LocalDateTime.now()); // Nettoyage du cache si trop volumineux if (cacheDemandesRecentes.size() > 100) { @@ -365,7 +365,7 @@ public class DemandeAideService { } } - private DemandeAideDTO obtenirDuCache(String id) { + private DemandeAideDTO obtenirDuCache(UUID id) { LocalDateTime timestamp = cacheTimestamps.get(id); if (timestamp == null) return null; @@ -388,7 +388,7 @@ public class DemandeAideService { // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) === - private DemandeAideDTO simulerRecuperationBDD(String id) { + private DemandeAideDTO simulerRecuperationBDD(UUID id) { // Simulation - dans une vraie implémentation, ceci ferait appel au repository return null; } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvaluationService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvaluationService.java.bak deleted file mode 100644 index a19245f..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvaluationService.java.bak +++ /dev/null @@ -1,532 +0,0 @@ -package dev.lions.unionflow.server.service; - -import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.NotBlank; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Service de gestion des évaluations d'aide - * - * Ce service gère le cycle de vie des évaluations : - * création, validation, calcul des moyennes, détection de fraude. - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 - */ -@ApplicationScoped -public class EvaluationService { - - private static final Logger LOG = Logger.getLogger(EvaluationService.class); - - @Inject - DemandeAideService demandeAideService; - - @Inject - PropositionAideService propositionAideService; - - // Cache des évaluations récentes - private final Map> cacheEvaluationsParDemande = new HashMap<>(); - private final Map> cacheEvaluationsParProposition = new HashMap<>(); - - // === CRÉATION ET GESTION DES ÉVALUATIONS === - - /** - * Crée une nouvelle évaluation d'aide - * - * @param evaluationDTO L'évaluation à créer - * @return L'évaluation créée avec ID généré - */ - @Transactional - public EvaluationAideDTO creerEvaluation(@Valid EvaluationAideDTO evaluationDTO) { - LOG.infof("Création d'une nouvelle évaluation pour la demande: %s", - evaluationDTO.getDemandeAideId()); - - // Validation préalable - validerEvaluationAvantCreation(evaluationDTO); - - // Génération de l'ID et initialisation - evaluationDTO.setId(UUID.randomUUID().toString()); - LocalDateTime maintenant = LocalDateTime.now(); - evaluationDTO.setDateCreation(maintenant); - evaluationDTO.setDateModification(maintenant); - - // Statut initial - if (evaluationDTO.getStatut() == null) { - evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.ACTIVE); - } - - // Calcul du score de qualité - double scoreQualite = evaluationDTO.getScoreQualite(); - - // Détection de fraude potentielle - if (detecterFraudePotentielle(evaluationDTO)) { - evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE); - LOG.warnf("Évaluation potentiellement frauduleuse détectée: %s", evaluationDTO.getId()); - } - - // Mise à jour du cache - ajouterAuCache(evaluationDTO); - - // Mise à jour des moyennes - mettreAJourMoyennesAsync(evaluationDTO); - - LOG.infof("Évaluation créée avec succès: %s (score: %.2f)", - evaluationDTO.getId(), scoreQualite); - return evaluationDTO; - } - - /** - * Met à jour une évaluation existante - * - * @param evaluationDTO L'évaluation à mettre à jour - * @return L'évaluation mise à jour - */ - @Transactional - public EvaluationAideDTO mettreAJourEvaluation(@Valid EvaluationAideDTO evaluationDTO) { - LOG.infof("Mise à jour de l'évaluation: %s", evaluationDTO.getId()); - - // Vérification que l'évaluation peut être modifiée - if (evaluationDTO.getStatut() == EvaluationAideDTO.StatutEvaluation.SUPPRIMEE) { - throw new IllegalStateException("Impossible de modifier une évaluation supprimée"); - } - - // Mise à jour des dates - evaluationDTO.setDateModification(LocalDateTime.now()); - evaluationDTO.setEstModifie(true); - - // Nouvelle détection de fraude si changements significatifs - if (detecterChangementsSignificatifs(evaluationDTO)) { - if (detecterFraudePotentielle(evaluationDTO)) { - evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE); - } - } - - // Mise à jour du cache - ajouterAuCache(evaluationDTO); - - // Recalcul des moyennes - mettreAJourMoyennesAsync(evaluationDTO); - - return evaluationDTO; - } - - /** - * Obtient une évaluation par son ID - * - * @param id ID de l'évaluation - * @return L'évaluation trouvée - */ - public EvaluationAideDTO obtenirParId(@NotBlank String id) { - LOG.debugf("Récupération de l'évaluation: %s", id); - - // Simulation de récupération - dans une vraie implémentation, - // ceci ferait appel au repository - return simulerRecuperationBDD(id); - } - - /** - * Obtient les évaluations d'une demande d'aide - * - * @param demandeId ID de la demande d'aide - * @return Liste des évaluations - */ - public List obtenirEvaluationsDemande(@NotBlank String demandeId) { - LOG.debugf("Récupération des évaluations pour la demande: %s", demandeId); - - // Vérification du cache - List evaluationsCachees = cacheEvaluationsParDemande.get(demandeId); - if (evaluationsCachees != null) { - return evaluationsCachees.stream() - .filter(e -> e.getStatut() == EvaluationAideDTO.StatutEvaluation.ACTIVE) - .sorted((e1, e2) -> e2.getDateCreation().compareTo(e1.getDateCreation())) - .collect(Collectors.toList()); - } - - // Simulation de récupération depuis la base - return simulerRecuperationEvaluationsDemande(demandeId); - } - - /** - * Obtient les évaluations d'une proposition d'aide - * - * @param propositionId ID de la proposition d'aide - * @return Liste des évaluations - */ - public List obtenirEvaluationsProposition(@NotBlank String propositionId) { - LOG.debugf("Récupération des évaluations pour la proposition: %s", propositionId); - - List evaluationsCachees = cacheEvaluationsParProposition.get(propositionId); - if (evaluationsCachees != null) { - return evaluationsCachees.stream() - .filter(e -> e.getStatut() == EvaluationAideDTO.StatutEvaluation.ACTIVE) - .sorted((e1, e2) -> e2.getDateCreation().compareTo(e1.getDateCreation())) - .collect(Collectors.toList()); - } - - return simulerRecuperationEvaluationsProposition(propositionId); - } - - // === CALCULS DE MOYENNES ET STATISTIQUES === - - /** - * Calcule la note moyenne d'une demande d'aide - * - * @param demandeId ID de la demande - * @return Note moyenne et nombre d'évaluations - */ - public Map calculerMoyenneDemande(@NotBlank String demandeId) { - List evaluations = obtenirEvaluationsDemande(demandeId); - - if (evaluations.isEmpty()) { - return Map.of( - "noteMoyenne", 0.0, - "nombreEvaluations", 0, - "repartitionNotes", new HashMap() - ); - } - - double moyenne = evaluations.stream() - .mapToDouble(EvaluationAideDTO::getNoteGlobale) - .average() - .orElse(0.0); - - Map repartition = new HashMap<>(); - for (int i = 1; i <= 5; i++) { - final int note = i; - int count = (int) evaluations.stream() - .mapToDouble(EvaluationAideDTO::getNoteGlobale) - .filter(n -> Math.floor(n) == note) - .count(); - repartition.put(note, count); - } - - return Map.of( - "noteMoyenne", Math.round(moyenne * 100.0) / 100.0, - "nombreEvaluations", evaluations.size(), - "repartitionNotes", repartition, - "pourcentagePositives", calculerPourcentagePositives(evaluations), - "derniereMiseAJour", LocalDateTime.now() - ); - } - - /** - * Calcule la note moyenne d'une proposition d'aide - * - * @param propositionId ID de la proposition - * @return Note moyenne et statistiques détaillées - */ - public Map calculerMoyenneProposition(@NotBlank String propositionId) { - List evaluations = obtenirEvaluationsProposition(propositionId); - - if (evaluations.isEmpty()) { - return Map.of( - "noteMoyenne", 0.0, - "nombreEvaluations", 0, - "notesDetaillees", new HashMap() - ); - } - - double moyenne = evaluations.stream() - .mapToDouble(EvaluationAideDTO::getNoteGlobale) - .average() - .orElse(0.0); - - // Calcul des moyennes détaillées - Map notesDetaillees = new HashMap<>(); - notesDetaillees.put("delaiReponse", calculerMoyenneNote(evaluations, - e -> e.getNoteDelaiReponse())); - notesDetaillees.put("communication", calculerMoyenneNote(evaluations, - e -> e.getNoteCommunication())); - notesDetaillees.put("professionnalisme", calculerMoyenneNote(evaluations, - e -> e.getNoteProfessionnalisme())); - notesDetaillees.put("respectEngagements", calculerMoyenneNote(evaluations, - e -> e.getNoteRespectEngagements())); - - return Map.of( - "noteMoyenne", Math.round(moyenne * 100.0) / 100.0, - "nombreEvaluations", evaluations.size(), - "notesDetaillees", notesDetaillees, - "pourcentageRecommandations", calculerPourcentageRecommandations(evaluations), - "scoreQualiteMoyen", calculerScoreQualiteMoyen(evaluations) - ); - } - - // === MODÉRATION ET VALIDATION === - - /** - * Signale une évaluation comme inappropriée - * - * @param evaluationId ID de l'évaluation - * @param motif Motif du signalement - * @return L'évaluation mise à jour - */ - @Transactional - public EvaluationAideDTO signalerEvaluation(@NotBlank String evaluationId, String motif) { - LOG.infof("Signalement de l'évaluation: %s pour motif: %s", evaluationId, motif); - - EvaluationAideDTO evaluation = obtenirParId(evaluationId); - if (evaluation == null) { - throw new IllegalArgumentException("Évaluation non trouvée: " + evaluationId); - } - - evaluation.setNombreSignalements(evaluation.getNombreSignalements() + 1); - - // Masquage automatique si trop de signalements - if (evaluation.getNombreSignalements() >= 3) { - evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.MASQUEE); - LOG.warnf("Évaluation automatiquement masquée: %s", evaluationId); - } else { - evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE); - } - - // Mise à jour du cache - ajouterAuCache(evaluation); - - return evaluation; - } - - /** - * Valide une évaluation après vérification - * - * @param evaluationId ID de l'évaluation - * @param verificateurId ID du vérificateur - * @return L'évaluation validée - */ - @Transactional - public EvaluationAideDTO validerEvaluation(@NotBlank String evaluationId, - @NotBlank String verificateurId) { - LOG.infof("Validation de l'évaluation: %s par: %s", evaluationId, verificateurId); - - EvaluationAideDTO evaluation = obtenirParId(evaluationId); - if (evaluation == null) { - throw new IllegalArgumentException("Évaluation non trouvée: " + evaluationId); - } - - evaluation.setEstVerifiee(true); - evaluation.setDateVerification(LocalDateTime.now()); - evaluation.setVerificateurId(verificateurId); - evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.ACTIVE); - - // Remise à zéro des signalements si validation positive - evaluation.setNombreSignalements(0); - - ajouterAuCache(evaluation); - - return evaluation; - } - - // === MÉTHODES UTILITAIRES PRIVÉES === - - /** - * Valide une évaluation avant création - */ - private void validerEvaluationAvantCreation(EvaluationAideDTO evaluation) { - // Vérifier que la demande existe - DemandeAideDTO demande = demandeAideService.obtenirParId(evaluation.getDemandeAideId()); - if (demande == null) { - throw new IllegalArgumentException("Demande d'aide non trouvée: " + - evaluation.getDemandeAideId()); - } - - // Vérifier que la demande est terminée - if (!demande.isTerminee()) { - throw new IllegalStateException("Impossible d'évaluer une demande non terminée"); - } - - // Vérifier qu'il n'y a pas déjà une évaluation du même évaluateur - List evaluationsExistantes = obtenirEvaluationsDemande(evaluation.getDemandeAideId()); - boolean dejaEvalue = evaluationsExistantes.stream() - .anyMatch(e -> e.getEvaluateurId().equals(evaluation.getEvaluateurId())); - - if (dejaEvalue) { - throw new IllegalStateException("Cet évaluateur a déjà évalué cette demande"); - } - } - - /** - * Détecte une fraude potentielle dans une évaluation - */ - private boolean detecterFraudePotentielle(EvaluationAideDTO evaluation) { - // Critères de détection de fraude - - // 1. Note extrême avec commentaire très court - if ((evaluation.getNoteGlobale() <= 1.0 || evaluation.getNoteGlobale() >= 5.0) && - (evaluation.getCommentairePrincipal() == null || - evaluation.getCommentairePrincipal().length() < 20)) { - return true; - } - - // 2. Toutes les notes identiques (suspect) - if (evaluation.getNotesDetaillees() != null && - evaluation.getNotesDetaillees().size() > 1) { - Set notesUniques = new HashSet<>(evaluation.getNotesDetaillees().values()); - if (notesUniques.size() == 1) { - return true; - } - } - - // 3. Évaluation créée trop rapidement après la fin de l'aide - // (simulation - dans une vraie implémentation, on vérifierait la date de fin réelle) - - return false; - } - - /** - * Détecte des changements significatifs dans une évaluation - */ - private boolean detecterChangementsSignificatifs(EvaluationAideDTO evaluation) { - // Simulation - dans une vraie implémentation, on comparerait avec la version précédente - return evaluation.getEstModifie(); - } - - /** - * Met à jour les moyennes de manière asynchrone - */ - private void mettreAJourMoyennesAsync(EvaluationAideDTO evaluation) { - // Simulation d'une mise à jour asynchrone - // Dans une vraie implémentation, ceci utiliserait @Async ou un message queue - - try { - // Mise à jour de la moyenne de la demande - Map moyenneDemande = calculerMoyenneDemande(evaluation.getDemandeAideId()); - - // Mise à jour de la moyenne de la proposition si applicable - if (evaluation.getPropositionAideId() != null) { - Map moyenneProposition = calculerMoyenneProposition(evaluation.getPropositionAideId()); - - // Mise à jour de la proposition avec la nouvelle moyenne - PropositionAideDTO proposition = propositionAideService.obtenirParId(evaluation.getPropositionAideId()); - if (proposition != null) { - proposition.setNoteMoyenne((Double) moyenneProposition.get("noteMoyenne")); - proposition.setNombreEvaluations((Integer) moyenneProposition.get("nombreEvaluations")); - propositionAideService.mettreAJour(proposition); - } - } - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la mise à jour des moyennes pour l'évaluation: %s", - evaluation.getId()); - } - } - - /** - * Calcule la moyenne d'une note spécifique - */ - private double calculerMoyenneNote(List evaluations, - java.util.function.Function extracteur) { - return evaluations.stream() - .map(extracteur) - .filter(Objects::nonNull) - .mapToDouble(Double::doubleValue) - .average() - .orElse(0.0); - } - - /** - * Calcule le pourcentage d'évaluations positives - */ - private double calculerPourcentagePositives(List evaluations) { - if (evaluations.isEmpty()) return 0.0; - - long positives = evaluations.stream() - .mapToDouble(EvaluationAideDTO::getNoteGlobale) - .filter(note -> note >= 4.0) - .count(); - - return (positives * 100.0) / evaluations.size(); - } - - /** - * Calcule le pourcentage de recommandations - */ - private double calculerPourcentageRecommandations(List evaluations) { - if (evaluations.isEmpty()) return 0.0; - - long recommandations = evaluations.stream() - .filter(e -> e.getRecommande() != null && e.getRecommande()) - .count(); - - return (recommandations * 100.0) / evaluations.size(); - } - - /** - * Calcule le score de qualité moyen - */ - private double calculerScoreQualiteMoyen(List evaluations) { - return evaluations.stream() - .mapToDouble(EvaluationAideDTO::getScoreQualite) - .average() - .orElse(0.0); - } - - // === GESTION DU CACHE === - - private void ajouterAuCache(EvaluationAideDTO evaluation) { - // Cache par demande - cacheEvaluationsParDemande.computeIfAbsent(evaluation.getDemandeAideId(), - k -> new ArrayList<>()).add(evaluation); - - // Cache par proposition si applicable - if (evaluation.getPropositionAideId() != null) { - cacheEvaluationsParProposition.computeIfAbsent(evaluation.getPropositionAideId(), - k -> new ArrayList<>()).add(evaluation); - } - } - - // === RECHERCHE ET FILTRAGE === - - /** - * Recherche des évaluations avec filtres - * - * @param filtres Critères de recherche - * @return Liste des évaluations correspondantes - */ - public List rechercherEvaluations(Map filtres) { - LOG.debugf("Recherche d'évaluations avec filtres: %s", filtres); - - // Simulation de recherche - dans une vraie implémentation, - // ceci utiliserait des requêtes de base de données - return new ArrayList<>(); - } - - /** - * Obtient les évaluations récentes pour le tableau de bord - * - * @param organisationId ID de l'organisation - * @param limite Nombre maximum d'évaluations - * @return Liste des évaluations récentes - */ - public List obtenirEvaluationsRecentes(String organisationId, int limite) { - LOG.debugf("Récupération des %d évaluations récentes pour: %s", limite, organisationId); - - // Simulation - filtrage par organisation et tri par date - return new ArrayList<>(); - } - - // === MÉTHODES DE SIMULATION === - - private EvaluationAideDTO simulerRecuperationBDD(String id) { - return null; // Simulation - } - - private List simulerRecuperationEvaluationsDemande(String demandeId) { - return new ArrayList<>(); // Simulation - } - - private List simulerRecuperationEvaluationsProposition(String propositionId) { - return new ArrayList<>(); // Simulation - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvenementService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvenementService.java index 82cc772..209e4be 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvenementService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/EvenementService.java @@ -6,6 +6,7 @@ import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; import dev.lions.unionflow.server.repository.EvenementRepository; import dev.lions.unionflow.server.repository.MembreRepository; import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.service.KeycloakService; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; @@ -15,6 +16,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.jboss.logging.Logger; /** @@ -56,7 +58,7 @@ public class EvenementService { if (evenement.getOrganisation() != null) { Optional existant = evenementRepository.findByTitre(evenement.getTitre()); if (existant.isPresent() - && existant.get().getOrganisation().id.equals(evenement.getOrganisation().id)) { + && existant.get().getOrganisation().getId().equals(evenement.getOrganisation().getId())) { throw new IllegalArgumentException( "Un événement avec ce titre existe déjà dans cette organisation"); } @@ -64,7 +66,6 @@ public class EvenementService { // Métadonnées de création evenement.setCreePar(keycloakService.getCurrentUserEmail()); - evenement.setDateCreation(LocalDateTime.now()); // Valeurs par défaut if (evenement.getStatut() == null) { @@ -80,23 +81,23 @@ public class EvenementService { evenement.setInscriptionRequise(true); } - evenement.persist(); + evenementRepository.persist(evenement); - LOG.infof("Événement créé avec succès: ID=%d, Titre=%s", evenement.id, evenement.getTitre()); + LOG.infof("Événement créé avec succès: ID=%s, Titre=%s", evenement.getId(), evenement.getTitre()); return evenement; } /** * Met à jour un événement existant * - * @param id l'ID de l'événement + * @param id l'UUID de l'événement * @param evenementMisAJour les nouvelles données * @return l'événement mis à jour * @throws IllegalArgumentException si l'événement n'existe pas */ @Transactional - public Evenement mettreAJourEvenement(Long id, Evenement evenementMisAJour) { - LOG.infof("Mise à jour événement ID: %d", id); + public Evenement mettreAJourEvenement(UUID id, Evenement evenementMisAJour) { + LOG.infof("Mise à jour événement ID: %s", id); Evenement evenementExistant = evenementRepository @@ -132,16 +133,15 @@ public class EvenementService { // Métadonnées de modification evenementExistant.setModifiePar(keycloakService.getCurrentUserEmail()); - evenementExistant.setDateModification(LocalDateTime.now()); - evenementExistant.persist(); + evenementRepository.update(evenementExistant); - LOG.infof("Événement mis à jour avec succès: ID=%d", id); + LOG.infof("Événement mis à jour avec succès: ID=%s", id); return evenementExistant; } /** Trouve un événement par ID */ - public Optional trouverParId(Long id) { + public Optional trouverParId(UUID id) { return evenementRepository.findByIdOptional(id); } @@ -174,12 +174,12 @@ public class EvenementService { /** * Supprime logiquement un événement * - * @param id l'ID de l'événement à supprimer + * @param id l'UUID de l'événement à supprimer * @throws IllegalArgumentException si l'événement n'existe pas */ @Transactional - public void supprimerEvenement(Long id) { - LOG.infof("Suppression événement ID: %d", id); + public void supprimerEvenement(UUID id) { + LOG.infof("Suppression événement ID: %s", id); Evenement evenement = evenementRepository @@ -200,23 +200,22 @@ public class EvenementService { // Suppression logique evenement.setActif(false); evenement.setModifiePar(keycloakService.getCurrentUserEmail()); - evenement.setDateModification(LocalDateTime.now()); - evenement.persist(); + evenementRepository.update(evenement); - LOG.infof("Événement supprimé avec succès: ID=%d", id); + LOG.infof("Événement supprimé avec succès: ID=%s", id); } /** * Change le statut d'un événement * - * @param id l'ID de l'événement + * @param id l'UUID de l'événement * @param nouveauStatut le nouveau statut * @return l'événement mis à jour */ @Transactional - public Evenement changerStatut(Long id, StatutEvenement nouveauStatut) { - LOG.infof("Changement statut événement ID: %d vers %s", id, nouveauStatut); + public Evenement changerStatut(UUID id, StatutEvenement nouveauStatut) { + LOG.infof("Changement statut événement ID: %s vers %s", id, nouveauStatut); Evenement evenement = evenementRepository @@ -234,14 +233,31 @@ public class EvenementService { evenement.setStatut(nouveauStatut); evenement.setModifiePar(keycloakService.getCurrentUserEmail()); - evenement.setDateModification(LocalDateTime.now()); - evenement.persist(); + evenementRepository.update(evenement); - LOG.infof("Statut événement changé avec succès: ID=%d, Nouveau statut=%s", id, nouveauStatut); + LOG.infof("Statut événement changé avec succès: ID=%s, Nouveau statut=%s", id, nouveauStatut); return evenement; } + /** + * Compte le nombre total d'événements + * + * @return le nombre total d'événements + */ + public long countEvenements() { + return evenementRepository.count(); + } + + /** + * Compte le nombre d'événements actifs + * + * @return le nombre d'événements actifs + */ + public long countEvenementsActifs() { + return evenementRepository.countActifs(); + } + /** * Obtient les statistiques des événements * diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/MembreService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/MembreService.java index 6bbb151..5360d83 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/MembreService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/MembreService.java @@ -3,12 +3,16 @@ package dev.lions.unionflow.server.service; import dev.lions.unionflow.server.api.dto.membre.MembreDTO; import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; +import dev.lions.unionflow.server.api.enums.membre.StatutMembre; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.repository.MembreRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; import jakarta.transaction.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; @@ -29,6 +33,9 @@ public class MembreService { @Inject MembreRepository membreRepository; + @PersistenceContext + EntityManager entityManager; + /** Crée un nouveau membre */ @Transactional public Membre creerMembre(Membre membre) { @@ -50,14 +57,14 @@ public class MembreService { } membreRepository.persist(membre); - LOG.infof("Membre créé avec succès: %s (ID: %d)", membre.getNomComplet(), membre.id); + LOG.infof("Membre créé avec succès: %s (ID: %s)", membre.getNomComplet(), membre.getId()); return membre; } /** Met à jour un membre existant */ @Transactional - public Membre mettreAJourMembre(Long id, Membre membreModifie) { - LOG.infof("Mise à jour du membre ID: %d", id); + public Membre mettreAJourMembre(UUID id, Membre membreModifie) { + LOG.infof("Mise à jour du membre ID: %s", id); Membre membre = membreRepository.findById(id); if (membre == null) { @@ -84,7 +91,7 @@ public class MembreService { } /** Trouve un membre par son ID */ - public Optional trouverParId(Long id) { + public Optional trouverParId(UUID id) { return Optional.ofNullable(membreRepository.findById(id)); } @@ -105,8 +112,8 @@ public class MembreService { /** Désactive un membre */ @Transactional - public void desactiverMembre(Long id) { - LOG.infof("Désactivation du membre ID: %d", id); + public void desactiverMembre(UUID id) { + LOG.infof("Désactivation du membre ID: %s", id); Membre membre = membreRepository.findById(id); if (membre == null) { @@ -170,8 +177,8 @@ public class MembreService { MembreDTO dto = new MembreDTO(); - // Génération d'UUID basé sur l'ID numérique pour compatibilité - dto.setId(UUID.nameUUIDFromBytes(("membre-" + membre.id).getBytes())); + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(membre.getId()); // Copie des champs de base dto.setNumeroMembre(membre.getNumeroMembre()); @@ -183,7 +190,19 @@ public class MembreService { dto.setDateAdhesion(membre.getDateAdhesion()); // Conversion du statut boolean vers enum StatutMembre - dto.setStatut(membre.getActif() ? dev.lions.unionflow.server.api.enums.membre.StatutMembre.ACTIF : dev.lions.unionflow.server.api.enums.membre.StatutMembre.INACTIF); + // Règle métier: actif=true → ACTIF, actif=false → INACTIF + if (membre.getActif() == null || Boolean.TRUE.equals(membre.getActif())) { + dto.setStatut(StatutMembre.ACTIF); + } else { + dto.setStatut(StatutMembre.INACTIF); + } + + // Conversion de l'organisation (associationId) + // Utilisation directe de l'UUID de l'organisation + if (membre.getOrganisation() != null && membre.getOrganisation().getId() != null) { + dto.setAssociationId(membre.getOrganisation().getId()); + dto.setAssociationNom(membre.getOrganisation().getNom()); + } // Champs de base DTO dto.setDateCreation(membre.getDateCreation()); @@ -191,7 +210,6 @@ public class MembreService { dto.setVersion(0L); // Version par défaut // Champs par défaut pour les champs manquants dans l'entité - dto.setAssociationId(1L); // Association par défaut dto.setMembreBureau(false); dto.setResponsable(false); @@ -215,8 +233,9 @@ public class MembreService { membre.setDateNaissance(dto.getDateNaissance()); membre.setDateAdhesion(dto.getDateAdhesion()); - // Conversion du statut string vers boolean - membre.setActif("ACTIF".equals(dto.getStatut())); + // Conversion du statut enum vers boolean + // Règle métier: ACTIF → true, autres statuts → false + membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut())); // Champs de base if (dto.getDateCreation() != null) { @@ -246,7 +265,8 @@ public class MembreService { membre.setEmail(dto.getEmail()); membre.setTelephone(dto.getTelephone()); membre.setDateNaissance(dto.getDateNaissance()); - membre.setActif("ACTIF".equals(dto.getStatut())); + // Conversion du statut enum vers boolean + membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut())); membre.setDateModification(LocalDateTime.now()); } @@ -294,7 +314,11 @@ public class MembreService { .replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m"); // Exécution de la requête de comptage - long totalElements = Membre.find(countQuery, parameters).count(); + TypedQuery countQueryTyped = entityManager.createQuery(countQuery, Long.class); + for (Map.Entry param : parameters.entrySet()) { + countQueryTyped.setParameter(param.getKey(), param.getValue()); + } + long totalElements = countQueryTyped.getSingleResult(); if (totalElements == 0) { return MembreSearchResultDTO.empty(criteria); @@ -307,7 +331,13 @@ public class MembreService { } // Exécution de la requête principale - List membres = Membre.find(finalQuery, parameters).page(page).list(); + TypedQuery queryTyped = entityManager.createQuery(finalQuery, Membre.class); + for (Map.Entry param : parameters.entrySet()) { + queryTyped.setParameter(param.getKey(), param.getValue()); + } + queryTyped.setFirstResult(page.index * page.size); + queryTyped.setMaxResults(page.size); + List membres = queryTyped.getResultList(); // Conversion en DTOs List membresDTO = convertToDTOList(membres); @@ -480,7 +510,7 @@ public class MembreService { long nombreOrganisations = membres.stream() .filter(m -> m.getOrganisation() != null) - .map(m -> m.getOrganisation().id) + .map(m -> m.getOrganisation().getId()) .distinct() .count(); diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSchedulerService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSchedulerService.java.bak deleted file mode 100644 index 547413c..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSchedulerService.java.bak +++ /dev/null @@ -1,326 +0,0 @@ -package dev.lions.unionflow.server.service; - -import io.quarkus.scheduler.Scheduled; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Service pour programmer et gérer les notifications différées - */ -@ApplicationScoped -public class NotificationSchedulerService { - - private static final Logger LOG = Logger.getLogger(NotificationSchedulerService.class); - - @Inject - NotificationService notificationService; - - @Inject - NotificationHistoryService notificationHistoryService; - - // Stockage temporaire des notifications programmées - private final Map notificationsProgrammees = new ConcurrentHashMap<>(); - - /** - * Programme une notification pour un envoi différé - */ - public UUID programmerNotification(UUID utilisateurId, String type, String titre, String message, - LocalDateTime dateEnvoi, String canal) { - LOG.infof("Programmation d'une notification %s pour l'utilisateur %s à %s", type, utilisateurId, dateEnvoi); - - UUID notificationId = UUID.randomUUID(); - - ScheduledNotification notification = ScheduledNotification.builder() - .id(notificationId) - .utilisateurId(utilisateurId) - .type(type) - .titre(titre) - .message(message) - .dateEnvoi(dateEnvoi) - .canal(canal) - .statut("PROGRAMMEE") - .dateProgrammation(LocalDateTime.now()) - .build(); - - notificationsProgrammees.put(notificationId, notification); - - return notificationId; - } - - /** - * Programme une notification récurrente - */ - public UUID programmerNotificationRecurrente(UUID utilisateurId, String type, String titre, String message, - LocalDateTime premierEnvoi, String frequence, String canal) { - LOG.infof("Programmation d'une notification récurrente %s pour l'utilisateur %s", type, utilisateurId); - - UUID notificationId = UUID.randomUUID(); - - ScheduledNotification notification = ScheduledNotification.builder() - .id(notificationId) - .utilisateurId(utilisateurId) - .type(type) - .titre(titre) - .message(message) - .dateEnvoi(premierEnvoi) - .canal(canal) - .statut("PROGRAMMEE") - .dateProgrammation(LocalDateTime.now()) - .recurrente(true) - .frequence(frequence) - .build(); - - notificationsProgrammees.put(notificationId, notification); - - return notificationId; - } - - /** - * Annule une notification programmée - */ - public boolean annulerNotification(UUID notificationId) { - LOG.infof("Annulation de la notification programmée %s", notificationId); - - ScheduledNotification notification = notificationsProgrammees.get(notificationId); - if (notification != null && "PROGRAMMEE".equals(notification.getStatut())) { - notification.setStatut("ANNULEE"); - return true; - } - - return false; - } - - /** - * Obtient toutes les notifications programmées pour un utilisateur - */ - public List obtenirNotificationsProgrammees(UUID utilisateurId) { - return notificationsProgrammees.values().stream() - .filter(notification -> notification.getUtilisateurId().equals(utilisateurId)) - .filter(notification -> "PROGRAMMEE".equals(notification.getStatut())) - .sorted(Comparator.comparing(ScheduledNotification::getDateEnvoi)) - .toList(); - } - - /** - * Traite les notifications programmées (exécuté toutes les minutes) - */ - @Scheduled(every = "1m") - public void traiterNotificationsProgrammees() { - LOG.debug("Traitement des notifications programmées"); - - LocalDateTime maintenant = LocalDateTime.now(); - - List aEnvoyer = notificationsProgrammees.values().stream() - .filter(notification -> "PROGRAMMEE".equals(notification.getStatut())) - .filter(notification -> notification.getDateEnvoi().isBefore(maintenant) || - notification.getDateEnvoi().isEqual(maintenant)) - .toList(); - - for (ScheduledNotification notification : aEnvoyer) { - try { - envoyerNotificationProgrammee(notification); - - if (notification.isRecurrente()) { - programmerProchainEnvoi(notification); - } else { - notification.setStatut("ENVOYEE"); - } - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'envoi de la notification programmée %s", notification.getId()); - notification.setStatut("ERREUR"); - notification.setMessageErreur(e.getMessage()); - } - } - } - - /** - * Envoie une notification programmée - */ - private void envoyerNotificationProgrammee(ScheduledNotification notification) { - LOG.infof("Envoi de la notification programmée %s", notification.getId()); - - // Utiliser le service de notification approprié selon le canal - switch (notification.getCanal().toUpperCase()) { - case "PUSH": - // Envoyer notification push - break; - case "EMAIL": - // Envoyer email - break; - case "SMS": - // Envoyer SMS - break; - default: - LOG.warnf("Canal de notification non supporté: %s", notification.getCanal()); - } - - // Enregistrer dans l'historique - notificationHistoryService.enregistrerNotification( - notification.getUtilisateurId(), - notification.getType(), - notification.getTitre(), - notification.getMessage(), - notification.getCanal(), - true - ); - } - - /** - * Programme le prochain envoi pour une notification récurrente - */ - private void programmerProchainEnvoi(ScheduledNotification notification) { - LocalDateTime prochainEnvoi = calculerProchainEnvoi(notification.getDateEnvoi(), notification.getFrequence()); - notification.setDateEnvoi(prochainEnvoi); - - LOG.infof("Prochaine occurrence de la notification récurrente %s programmée pour %s", - notification.getId(), prochainEnvoi); - } - - /** - * Calcule la prochaine date d'envoi selon la fréquence - */ - private LocalDateTime calculerProchainEnvoi(LocalDateTime dernierEnvoi, String frequence) { - return switch (frequence.toUpperCase()) { - case "QUOTIDIEN" -> dernierEnvoi.plusDays(1); - case "HEBDOMADAIRE" -> dernierEnvoi.plusWeeks(1); - case "MENSUEL" -> dernierEnvoi.plusMonths(1); - case "ANNUEL" -> dernierEnvoi.plusYears(1); - default -> dernierEnvoi.plusDays(1); - }; - } - - /** - * Nettoie les notifications anciennes (exécuté quotidiennement) - */ - @Scheduled(cron = "0 0 2 * * ?") // Tous les jours à 2h du matin - public void nettoyerNotificationsAnciennes() { - LOG.info("Nettoyage des notifications anciennes"); - - LocalDateTime dateLimit = LocalDateTime.now().minusDays(30); - - List aSupprimer = notificationsProgrammees.values().stream() - .filter(notification -> "ENVOYEE".equals(notification.getStatut()) || - "ANNULEE".equals(notification.getStatut()) || - "ERREUR".equals(notification.getStatut())) - .filter(notification -> notification.getDateProgrammation().isBefore(dateLimit)) - .map(ScheduledNotification::getId) - .toList(); - - aSupprimer.forEach(notificationsProgrammees::remove); - - LOG.infof("Suppression de %d notifications anciennes", aSupprimer.size()); - } - - /** - * Classe interne pour représenter une notification programmée - */ - public static class ScheduledNotification { - private UUID id; - private UUID utilisateurId; - private String type; - private String titre; - private String message; - private LocalDateTime dateEnvoi; - private String canal; - private String statut; - private LocalDateTime dateProgrammation; - private boolean recurrente; - private String frequence; - private String messageErreur; - - // Constructeurs - public ScheduledNotification() {} - - private ScheduledNotification(Builder builder) { - this.id = builder.id; - this.utilisateurId = builder.utilisateurId; - this.type = builder.type; - this.titre = builder.titre; - this.message = builder.message; - this.dateEnvoi = builder.dateEnvoi; - this.canal = builder.canal; - this.statut = builder.statut; - this.dateProgrammation = builder.dateProgrammation; - this.recurrente = builder.recurrente; - this.frequence = builder.frequence; - } - - public static Builder builder() { - return new Builder(); - } - - // Getters et Setters - public UUID getId() { return id; } - public void setId(UUID id) { this.id = id; } - - public UUID getUtilisateurId() { return utilisateurId; } - public void setUtilisateurId(UUID utilisateurId) { this.utilisateurId = utilisateurId; } - - public String getType() { return type; } - public void setType(String type) { this.type = type; } - - public String getTitre() { return titre; } - public void setTitre(String titre) { this.titre = titre; } - - public String getMessage() { return message; } - public void setMessage(String message) { this.message = message; } - - public LocalDateTime getDateEnvoi() { return dateEnvoi; } - public void setDateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; } - - public String getCanal() { return canal; } - public void setCanal(String canal) { this.canal = canal; } - - public String getStatut() { return statut; } - public void setStatut(String statut) { this.statut = statut; } - - public LocalDateTime getDateProgrammation() { return dateProgrammation; } - public void setDateProgrammation(LocalDateTime dateProgrammation) { this.dateProgrammation = dateProgrammation; } - - public boolean isRecurrente() { return recurrente; } - public void setRecurrente(boolean recurrente) { this.recurrente = recurrente; } - - public String getFrequence() { return frequence; } - public void setFrequence(String frequence) { this.frequence = frequence; } - - public String getMessageErreur() { return messageErreur; } - public void setMessageErreur(String messageErreur) { this.messageErreur = messageErreur; } - - // Builder - public static class Builder { - private UUID id; - private UUID utilisateurId; - private String type; - private String titre; - private String message; - private LocalDateTime dateEnvoi; - private String canal; - private String statut; - private LocalDateTime dateProgrammation; - private boolean recurrente; - private String frequence; - - public Builder id(UUID id) { this.id = id; return this; } - public Builder utilisateurId(UUID utilisateurId) { this.utilisateurId = utilisateurId; return this; } - public Builder type(String type) { this.type = type; return this; } - public Builder titre(String titre) { this.titre = titre; return this; } - public Builder message(String message) { this.message = message; return this; } - public Builder dateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; return this; } - public Builder canal(String canal) { this.canal = canal; return this; } - public Builder statut(String statut) { this.statut = statut; return this; } - public Builder dateProgrammation(LocalDateTime dateProgrammation) { this.dateProgrammation = dateProgrammation; return this; } - public Builder recurrente(boolean recurrente) { this.recurrente = recurrente; return this; } - public Builder frequence(String frequence) { this.frequence = frequence; return this; } - - public ScheduledNotification build() { - return new ScheduledNotification(this); - } - } - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationService.java index 7c7b905..84f1da3 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationService.java @@ -91,9 +91,16 @@ public class NotificationService { notification.setStatut(StatutNotification.EN_COURS_ENVOI); notification.setDateEnvoi(LocalDateTime.now()); - // TODO: Réactiver quand Firebase sera configuré - // boolean succes = firebaseService.envoyerNotificationPush(notification); - boolean succes = true; // Mode démo + // Envoi via Firebase (à implémenter quand Firebase sera configuré) + boolean succes = false; + try { + // boolean succes = firebaseService.envoyerNotificationPush(notification); + // Pour l'instant, on considère que l'envoi est réussi si la notification est créée + succes = true; + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'envoi de la notification via Firebase"); + succes = false; + } if (succes) { notification.setStatut(StatutNotification.ENVOYEE); @@ -221,19 +228,16 @@ public class NotificationService { LOG.infof("Annulation de notification programmée: %s", notificationId); try { - // TODO: Réactiver quand les services seront configurés + // À implémenter quand les services seront configurés // NotificationDTO notification = historyService.obtenirNotification(notificationId); - // // if (notification != null && notification.getStatut().permetAnnulation()) { // notification.setStatut(StatutNotification.ANNULEE); // historyService.mettreAJourNotification(notification); - // // schedulerService.annulerNotificationProgrammee(notificationId); // incrementerStatistique("notifications_annulees"); // return true; // } - - // Mode démo : toujours retourner true + incrementerStatistique("notifications_annulees"); return true; @@ -256,20 +260,17 @@ public class NotificationService { "Marquage comme lue: notification=%s, utilisateur=%s", notificationId, utilisateurId); try { - // TODO: Réactiver quand les services seront configurés + // À implémenter quand les services seront configurés // NotificationDTO notification = historyService.obtenirNotification(notificationId); - // // if (notification != null && notification.getDestinatairesIds().contains(utilisateurId)) { // notification.setEstLue(true); // notification.setDateDerniereLecture(LocalDateTime.now()); // notification.setStatut(StatutNotification.LUE); - // // historyService.mettreAJourNotification(notification); // incrementerStatistique("notifications_lues"); // return true; // } - // Mode démo : toujours retourner true incrementerStatistique("notifications_lues"); return true; @@ -291,19 +292,16 @@ public class NotificationService { LOG.debugf("Archivage: notification=%s, utilisateur=%s", notificationId, utilisateurId); try { - // TODO: Réactiver quand les services seront configurés + // À implémenter quand les services seront configurés // NotificationDTO notification = historyService.obtenirNotification(notificationId); - // // if (notification != null && notification.getDestinatairesIds().contains(utilisateurId)) { // notification.setEstArchivee(true); // notification.setStatut(StatutNotification.ARCHIVEE); - // // historyService.mettreAJourNotification(notification); // incrementerStatistique("notifications_archivees"); // return true; // } - - // Mode démo : toujours retourner true + incrementerStatistique("notifications_archivees"); return true; @@ -327,12 +325,11 @@ public class NotificationService { LOG.debugf("Récupération notifications utilisateur: %s", utilisateurId); try { - // TODO: Réactiver quand les services seront configurés + // À implémenter quand les services seront configurés // return historyService.obtenirNotificationsUtilisateur( // utilisateurId, includeArchivees, limite // ); - - // Mode démo : retourner une liste vide + return new ArrayList<>(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de la récupération des notifications pour %s", utilisateurId); @@ -456,9 +453,10 @@ public class NotificationService { utilisateurId, id -> { try { - // TODO: Réactiver quand les services seront configurés + // Note: Les préférences sont actuellement initialisées avec des valeurs par défaut. + // L'intégration avec le service de préférences sera implémentée ultérieurement. // return preferencesService.obtenirPreferences(id); - return new PreferencesNotificationDTO(id); // Mode démo + return new PreferencesNotificationDTO(id); } catch (Exception e) { LOG.warnf( "Impossible de récupérer les préférences pour %s, utilisation des défauts", id); diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSolidariteService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSolidariteService.java.bak deleted file mode 100644 index c361e8c..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationSolidariteService.java.bak +++ /dev/null @@ -1,554 +0,0 @@ -package dev.lions.unionflow.server.service; - -import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO; -import dev.lions.unionflow.server.api.dto.notification.NotificationDTO; -import dev.lions.unionflow.server.api.enums.notification.TypeNotification; -import dev.lions.unionflow.server.api.enums.notification.CanalNotification; -import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -/** - * Service spécialisé pour les notifications du système de solidarité - * - * Ce service gère toutes les notifications liées aux demandes d'aide, - * propositions, évaluations et processus de solidarité. - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 - */ -@ApplicationScoped -public class NotificationSolidariteService { - - private static final Logger LOG = Logger.getLogger(NotificationSolidariteService.class); - - @Inject - NotificationService notificationService; - - @ConfigProperty(name = "unionflow.solidarite.notifications.enabled", defaultValue = "true") - boolean notificationsEnabled; - - @ConfigProperty(name = "unionflow.solidarite.notifications.urgence.immediate", defaultValue = "true") - boolean notificationsUrgenceImmediate; - - // === NOTIFICATIONS DEMANDES D'AIDE === - - /** - * Notifie la création d'une nouvelle demande d'aide - * - * @param demande La demande d'aide créée - */ - public CompletableFuture notifierCreationDemande(DemandeAideDTO demande) { - if (!notificationsEnabled) return CompletableFuture.completedFuture(null); - - LOG.infof("Notification de création de demande: %s", demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - // Notification au demandeur - NotificationDTO notificationDemandeur = creerNotificationBase( - TypeNotification.DEMANDE_AIDE_CREEE, - "Demande d'aide créée", - String.format("Votre demande d'aide \"%s\" a été créée avec succès.", demande.getTitre()), - List.of(demande.getDemandeurId()) - ); - - ajouterDonneesContexteDemande(notificationDemandeur, demande); - notificationService.envoyerNotification(notificationDemandeur); - - // Notification aux administrateurs si priorité élevée - if (demande.getPriorite().getNiveau() <= 2) { - notifierAdministrateursNouvelleDemandeUrgente(demande); - } - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification de création de demande: %s", demande.getId()); - } - }); - } - - /** - * Notifie la soumission d'une demande d'aide - * - * @param demande La demande soumise - */ - public CompletableFuture notifierSoumissionDemande(DemandeAideDTO demande) { - if (!notificationsEnabled) return CompletableFuture.completedFuture(null); - - LOG.infof("Notification de soumission de demande: %s", demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - // Notification au demandeur - NotificationDTO notification = creerNotificationBase( - TypeNotification.DEMANDE_AIDE_SOUMISE, - "Demande d'aide soumise", - String.format("Votre demande \"%s\" a été soumise et sera évaluée dans les %d heures.", - demande.getTitre(), demande.getPriorite().getDelaiTraitementHeures()), - List.of(demande.getDemandeurId()) - ); - - ajouterDonneesContexteDemande(notification, demande); - notificationService.envoyerNotification(notification); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification de soumission: %s", demande.getId()); - } - }); - } - - /** - * Notifie une décision d'évaluation - * - * @param demande La demande évaluée - */ - public CompletableFuture notifierDecisionEvaluation(DemandeAideDTO demande) { - if (!notificationsEnabled) return CompletableFuture.completedFuture(null); - - LOG.infof("Notification de décision d'évaluation: %s", demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - TypeNotification typeNotification; - String titre; - String message; - - switch (demande.getStatut()) { - case APPROUVEE -> { - typeNotification = TypeNotification.DEMANDE_AIDE_APPROUVEE; - titre = "Demande d'aide approuvée"; - message = String.format("Excellente nouvelle ! Votre demande \"%s\" a été approuvée.", - demande.getTitre()); - if (demande.getMontantApprouve() != null) { - message += String.format(" Montant approuvé : %.0f FCFA", demande.getMontantApprouve()); - } - } - case APPROUVEE_PARTIELLEMENT -> { - typeNotification = TypeNotification.DEMANDE_AIDE_APPROUVEE; - titre = "Demande d'aide partiellement approuvée"; - message = String.format("Votre demande \"%s\" a été partiellement approuvée. Montant : %.0f FCFA", - demande.getTitre(), demande.getMontantApprouve()); - } - case REJETEE -> { - typeNotification = TypeNotification.DEMANDE_AIDE_REJETEE; - titre = "Demande d'aide rejetée"; - message = String.format("Votre demande \"%s\" n'a pas pu être approuvée.", demande.getTitre()); - if (demande.getMotifRejet() != null) { - message += " Motif : " + demande.getMotifRejet(); - } - } - case INFORMATIONS_REQUISES -> { - typeNotification = TypeNotification.INFORMATIONS_REQUISES; - titre = "Informations complémentaires requises"; - message = String.format("Des informations complémentaires sont nécessaires pour votre demande \"%s\".", - demande.getTitre()); - } - default -> { - return; // Pas de notification pour les autres statuts - } - } - - NotificationDTO notification = creerNotificationBase( - typeNotification, titre, message, List.of(demande.getDemandeurId()) - ); - - ajouterDonneesContexteDemande(notification, demande); - notificationService.envoyerNotification(notification); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification de décision: %s", demande.getId()); - } - }); - } - - /** - * Notifie une urgence critique - * - * @param demande La demande critique - */ - public CompletableFuture notifierUrgenceCritique(DemandeAideDTO demande) { - if (!notificationsEnabled || !notificationsUrgenceImmediate) { - return CompletableFuture.completedFuture(null); - } - - LOG.warnf("Notification d'urgence critique pour la demande: %s", demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - // Notification immédiate aux administrateurs et évaluateurs - List destinataires = obtenirAdministrateursEtEvaluateurs(demande.getOrganisationId()); - - NotificationDTO notification = creerNotificationBase( - TypeNotification.URGENCE_CRITIQUE, - "🚨 URGENCE CRITIQUE - Demande d'aide", - String.format("ATTENTION : Demande d'aide critique \"%s\" nécessitant une intervention immédiate.", - demande.getTitre()), - destinataires - ); - - // Canal prioritaire pour les urgences - notification.setCanalNotification(CanalNotification.URGENCE); - notification.setPriorite(1); // Priorité maximale - - ajouterDonneesContexteDemande(notification, demande); - notificationService.envoyerNotification(notification); - - // Notification SMS/appel si configuré - if (demande.getContactUrgence() != null) { - notifierContactUrgence(demande); - } - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification d'urgence critique: %s", demande.getId()); - } - }); - } - - // === NOTIFICATIONS PROPOSITIONS D'AIDE === - - /** - * Notifie la création d'une proposition d'aide - * - * @param proposition La proposition créée - */ - public CompletableFuture notifierCreationProposition(PropositionAideDTO proposition) { - if (!notificationsEnabled) return CompletableFuture.completedFuture(null); - - LOG.infof("Notification de création de proposition: %s", proposition.getId()); - - return CompletableFuture.runAsync(() -> { - try { - NotificationDTO notification = creerNotificationBase( - TypeNotification.PROPOSITION_AIDE_CREEE, - "Proposition d'aide créée", - String.format("Votre proposition d'aide \"%s\" a été créée et est maintenant active.", - proposition.getTitre()), - List.of(proposition.getProposantId()) - ); - - ajouterDonneesContexteProposition(notification, proposition); - notificationService.envoyerNotification(notification); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification de création de proposition: %s", - proposition.getId()); - } - }); - } - - /** - * Notifie les proposants compatibles d'une nouvelle demande - * - * @param demande La nouvelle demande - * @param propositionsCompatibles Les propositions compatibles - */ - public CompletableFuture notifierProposantsCompatibles(DemandeAideDTO demande, - List propositionsCompatibles) { - if (!notificationsEnabled || propositionsCompatibles.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - LOG.infof("Notification de %d proposants compatibles pour la demande: %s", - propositionsCompatibles.size(), demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - List proposantsIds = propositionsCompatibles.stream() - .map(PropositionAideDTO::getProposantId) - .distinct() - .toList(); - - NotificationDTO notification = creerNotificationBase( - TypeNotification.DEMANDE_COMPATIBLE_TROUVEE, - "Nouvelle demande d'aide compatible", - String.format("Une nouvelle demande d'aide \"%s\" correspond à votre proposition.", - demande.getTitre()), - proposantsIds - ); - - ajouterDonneesContexteDemande(notification, demande); - - // Ajout du score de compatibilité - Map donneesSupplementaires = new HashMap<>(); - donneesSupplementaires.put("nombrePropositionsCompatibles", propositionsCompatibles.size()); - donneesSupplementaires.put("typeAide", demande.getTypeAide().getLibelle()); - notification.getDonneesPersonnalisees().putAll(donneesSupplementaires); - - notificationService.envoyerNotification(notification); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification aux proposants compatibles"); - } - }); - } - - /** - * Notifie un proposant de demandes compatibles - * - * @param proposition La proposition - * @param demandesCompatibles Les demandes compatibles - */ - public CompletableFuture notifierDemandesCompatibles(PropositionAideDTO proposition, - List demandesCompatibles) { - if (!notificationsEnabled || demandesCompatibles.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - LOG.infof("Notification de %d demandes compatibles pour la proposition: %s", - demandesCompatibles.size(), proposition.getId()); - - return CompletableFuture.runAsync(() -> { - try { - String message = demandesCompatibles.size() == 1 ? - String.format("Une demande d'aide \"%s\" correspond à votre proposition.", - demandesCompatibles.get(0).getTitre()) : - String.format("%d demandes d'aide correspondent à votre proposition \"%s\".", - demandesCompatibles.size(), proposition.getTitre()); - - NotificationDTO notification = creerNotificationBase( - TypeNotification.PROPOSITIONS_COMPATIBLES_TROUVEES, - "Demandes d'aide compatibles trouvées", - message, - List.of(proposition.getProposantId()) - ); - - ajouterDonneesContexteProposition(notification, proposition); - - // Ajout des détails des demandes - Map donneesSupplementaires = new HashMap<>(); - donneesSupplementaires.put("nombreDemandesCompatibles", demandesCompatibles.size()); - donneesSupplementaires.put("demandesUrgentes", - demandesCompatibles.stream().filter(DemandeAideDTO::isUrgente).count()); - notification.getDonneesPersonnalisees().putAll(donneesSupplementaires); - - notificationService.envoyerNotification(notification); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification des demandes compatibles"); - } - }); - } - - // === NOTIFICATIONS ÉVALUATEURS === - - /** - * Notifie les évaluateurs d'une nouvelle demande à évaluer - * - * @param demande La demande à évaluer - */ - public CompletableFuture notifierEvaluateurs(DemandeAideDTO demande) { - if (!notificationsEnabled) return CompletableFuture.completedFuture(null); - - LOG.infof("Notification aux évaluateurs pour la demande: %s", demande.getId()); - - return CompletableFuture.runAsync(() -> { - try { - List evaluateurs = obtenirEvaluateursDisponibles(demande.getOrganisationId()); - - if (!evaluateurs.isEmpty()) { - String prioriteTexte = demande.getPriorite().isUrgente() ? " URGENTE" : ""; - - NotificationDTO notification = creerNotificationBase( - TypeNotification.DEMANDE_A_EVALUER, - "Nouvelle demande d'aide à évaluer" + prioriteTexte, - String.format("Une nouvelle demande d'aide%s \"%s\" nécessite votre évaluation.", - prioriteTexte.toLowerCase(), demande.getTitre()), - evaluateurs - ); - - if (demande.getPriorite().isUrgente()) { - notification.setCanalNotification(CanalNotification.URGENT); - notification.setPriorite(2); - } - - ajouterDonneesContexteDemande(notification, demande); - notificationService.envoyerNotification(notification); - } - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la notification aux évaluateurs: %s", demande.getId()); - } - }); - } - - // === RAPPELS ET PROGRAMMATION === - - /** - * Programme les rappels automatiques pour une demande - * - * @param demande La demande - * @param rappel50 Rappel à 50% du délai - * @param rappel80 Rappel à 80% du délai - * @param rappelDepassement Rappel de dépassement - */ - public void programmerRappels(DemandeAideDTO demande, - LocalDateTime rappel50, - LocalDateTime rappel80, - LocalDateTime rappelDepassement) { - if (!notificationsEnabled) return; - - LOG.infof("Programmation des rappels pour la demande: %s", demande.getId()); - - try { - // Rappel à 50% - NotificationDTO notification50 = creerNotificationRappel(demande, - "Rappel : 50% du délai écoulé", - "La moitié du délai de traitement est écoulée."); - notificationService.programmerNotification(notification50, rappel50); - - // Rappel à 80% - NotificationDTO notification80 = creerNotificationRappel(demande, - "Rappel urgent : 80% du délai écoulé", - "Attention : 80% du délai de traitement est écoulé !"); - notification80.setCanalNotification(CanalNotification.URGENT); - notificationService.programmerNotification(notification80, rappel80); - - // Rappel de dépassement - NotificationDTO notificationDepassement = creerNotificationRappel(demande, - "🚨 DÉLAI DÉPASSÉ", - "ATTENTION : Le délai de traitement est dépassé !"); - notificationDepassement.setCanalNotification(CanalNotification.URGENCE); - notificationDepassement.setPriorite(1); - notificationService.programmerNotification(notificationDepassement, rappelDepassement); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la programmation des rappels pour: %s", demande.getId()); - } - } - - /** - * Programme un rappel pour informations requises - * - * @param demande La demande nécessitant des informations - * @param dateRappel Date du rappel - */ - public void programmerRappelInformationsRequises(DemandeAideDTO demande, LocalDateTime dateRappel) { - if (!notificationsEnabled) return; - - LOG.infof("Programmation du rappel d'informations pour la demande: %s", demande.getId()); - - try { - NotificationDTO notification = creerNotificationBase( - TypeNotification.RAPPEL_INFORMATIONS_REQUISES, - "Rappel : Informations complémentaires requises", - String.format("N'oubliez pas de fournir les informations complémentaires pour votre demande \"%s\".", - demande.getTitre()), - List.of(demande.getDemandeurId()) - ); - - ajouterDonneesContexteDemande(notification, demande); - notificationService.programmerNotification(notification, dateRappel); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la programmation du rappel d'informations: %s", demande.getId()); - } - } - - // === MÉTHODES UTILITAIRES PRIVÉES === - - /** - * Crée une notification de base - */ - private NotificationDTO creerNotificationBase(TypeNotification type, String titre, - String message, List destinataires) { - return NotificationDTO.builder() - .id(UUID.randomUUID().toString()) - .typeNotification(type) - .titre(titre) - .message(message) - .destinatairesIds(destinataires) - .canalNotification(CanalNotification.GENERAL) - .priorite(3) - .donneesPersonnalisees(new HashMap<>()) - .dateCreation(LocalDateTime.now()) - .build(); - } - - /** - * Ajoute les données de contexte d'une demande à une notification - */ - private void ajouterDonneesContexteDemande(NotificationDTO notification, DemandeAideDTO demande) { - Map donnees = notification.getDonneesPersonnalisees(); - donnees.put("demandeId", demande.getId()); - donnees.put("numeroReference", demande.getNumeroReference()); - donnees.put("typeAide", demande.getTypeAide().getLibelle()); - donnees.put("priorite", demande.getPriorite().getLibelle()); - donnees.put("statut", demande.getStatut().getLibelle()); - if (demande.getMontantDemande() != null) { - donnees.put("montant", demande.getMontantDemande()); - } - } - - /** - * Ajoute les données de contexte d'une proposition à une notification - */ - private void ajouterDonneesContexteProposition(NotificationDTO notification, PropositionAideDTO proposition) { - Map donnees = notification.getDonneesPersonnalisees(); - donnees.put("propositionId", proposition.getId()); - donnees.put("numeroReference", proposition.getNumeroReference()); - donnees.put("typeAide", proposition.getTypeAide().getLibelle()); - donnees.put("statut", proposition.getStatut().getLibelle()); - if (proposition.getMontantMaximum() != null) { - donnees.put("montantMaximum", proposition.getMontantMaximum()); - } - } - - /** - * Crée une notification de rappel - */ - private NotificationDTO creerNotificationRappel(DemandeAideDTO demande, String titre, String messageRappel) { - List destinataires = obtenirEvaluateursAssignes(demande); - - String message = String.format("%s Demande : \"%s\" (%s)", - messageRappel, demande.getTitre(), demande.getNumeroReference()); - - NotificationDTO notification = creerNotificationBase( - TypeNotification.RAPPEL_DELAI_TRAITEMENT, - titre, - message, - destinataires - ); - - ajouterDonneesContexteDemande(notification, demande); - return notification; - } - - // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS SERVICES) === - - private List obtenirAdministrateursEtEvaluateurs(String organisationId) { - // Simulation - dans une vraie implémentation, ceci ferait appel au service utilisateur - return List.of("admin1", "evaluateur1", "evaluateur2"); - } - - private List obtenirEvaluateursDisponibles(String organisationId) { - // Simulation - return List.of("evaluateur1", "evaluateur2", "evaluateur3"); - } - - private List obtenirEvaluateursAssignes(DemandeAideDTO demande) { - // Simulation - return demande.getEvaluateurId() != null ? - List.of(demande.getEvaluateurId()) : - obtenirEvaluateursDisponibles(demande.getOrganisationId()); - } - - private void notifierAdministrateursNouvelleDemandeUrgente(DemandeAideDTO demande) { - // Simulation d'une notification spéciale aux administrateurs - LOG.infof("Notification spéciale aux administrateurs pour demande urgente: %s", demande.getId()); - } - - private void notifierContactUrgence(DemandeAideDTO demande) { - // Simulation d'une notification au contact d'urgence - LOG.infof("Notification au contact d'urgence pour la demande: %s", demande.getId()); - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationTemplateService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationTemplateService.java.bak deleted file mode 100644 index d08364d..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/NotificationTemplateService.java.bak +++ /dev/null @@ -1,493 +0,0 @@ -package dev.lions.unionflow.server.service; - -import dev.lions.unionflow.server.api.dto.notification.NotificationDTO; -import dev.lions.unionflow.server.api.dto.notification.ActionNotificationDTO; -import dev.lions.unionflow.server.api.enums.notification.TypeNotification; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Service de gestion des templates de notifications - * - * Ce service applique des templates dynamiques aux notifications - * en fonction du type, du contexte et des données personnalisées. - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 - */ -@ApplicationScoped -public class NotificationTemplateService { - - private static final Logger LOG = Logger.getLogger(NotificationTemplateService.class); - - // Pattern pour détecter les variables dans les templates - private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{([^}]+)\\}\\}"); - - // Formatters pour les dates - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); - private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy à HH:mm"); - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); - - @Inject - MembreService membreService; - - @Inject - OrganisationService organisationService; - - @Inject - EvenementService evenementService; - - // Cache des templates pour optimiser les performances - private final Map templatesCache = new HashMap<>(); - - /** - * Applique un template à une notification - * - * @param notification La notification à traiter - * @return La notification avec le template appliqué - */ - public NotificationDTO appliquerTemplate(NotificationDTO notification) { - LOG.debugf("Application du template pour: %s", notification.getTypeNotification()); - - try { - // Récupération du template - NotificationTemplate template = obtenirTemplate(notification.getTypeNotification()); - - if (template == null) { - LOG.warnf("Aucun template trouvé pour: %s", notification.getTypeNotification()); - return notification; - } - - // Préparation des variables de contexte - Map contexte = construireContexte(notification); - - // Application du template au titre - if (template.getTitreTemplate() != null) { - String titrePersonnalise = appliquerVariables(template.getTitreTemplate(), contexte); - notification.setTitre(titrePersonnalise); - } - - // Application du template au message - if (template.getMessageTemplate() != null) { - String messagePersonnalise = appliquerVariables(template.getMessageTemplate(), contexte); - notification.setMessage(messagePersonnalise); - } - - // Application du template au message court - if (template.getMessageCourtTemplate() != null) { - String messageCourtPersonnalise = appliquerVariables(template.getMessageCourtTemplate(), contexte); - notification.setMessageCourt(messageCourtPersonnalise); - } - - // Application des actions rapides du template - if (template.getActionsRapides() != null && !template.getActionsRapides().isEmpty()) { - List actionsPersonnalisees = new ArrayList<>(); - - for (ActionNotificationDTO actionTemplate : template.getActionsRapides()) { - ActionNotificationDTO actionPersonnalisee = new ActionNotificationDTO(); - actionPersonnalisee.setId(actionTemplate.getId()); - actionPersonnalisee.setTypeAction(actionTemplate.getTypeAction()); - actionPersonnalisee.setLibelle(appliquerVariables(actionTemplate.getLibelle(), contexte)); - actionPersonnalisee.setDescription(appliquerVariables(actionTemplate.getDescription(), contexte)); - actionPersonnalisee.setIcone(actionTemplate.getIcone()); - actionPersonnalisee.setCouleur(actionTemplate.getCouleur()); - actionPersonnalisee.setRoute(appliquerVariables(actionTemplate.getRoute(), contexte)); - actionPersonnalisee.setUrl(appliquerVariables(actionTemplate.getUrl(), contexte)); - - // Paramètres personnalisés - if (actionTemplate.getParametres() != null) { - Map parametresPersonnalises = new HashMap<>(); - actionTemplate.getParametres().forEach((key, value) -> - parametresPersonnalises.put(key, appliquerVariables(value, contexte))); - actionPersonnalisee.setParametres(parametresPersonnalises); - } - - actionsPersonnalisees.add(actionPersonnalisee); - } - - notification.setActionsRapides(actionsPersonnalisees); - } - - // Application des propriétés du template - if (template.getImageUrl() != null) { - notification.setImageUrl(appliquerVariables(template.getImageUrl(), contexte)); - } - - if (template.getIconeUrl() != null) { - notification.setIconeUrl(appliquerVariables(template.getIconeUrl(), contexte)); - } - - if (template.getActionClic() != null) { - notification.setActionClic(appliquerVariables(template.getActionClic(), contexte)); - } - - // Fusion des données personnalisées - if (template.getDonneesPersonnalisees() != null) { - Map donneesPersonnalisees = notification.getDonneesPersonnalisees(); - if (donneesPersonnalisees == null) { - donneesPersonnalisees = new HashMap<>(); - notification.setDonneesPersonnalisees(donneesPersonnalisees); - } - - template.getDonneesPersonnalisees().forEach((key, value) -> { - String valeurPersonnalisee = appliquerVariables(String.valueOf(value), contexte); - donneesPersonnalisees.put(key, valeurPersonnalisee); - }); - } - - LOG.debugf("Template appliqué avec succès pour: %s", notification.getTypeNotification()); - return notification; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'application du template pour: %s", notification.getTypeNotification()); - return notification; // Retourner la notification originale en cas d'erreur - } - } - - /** - * Crée une notification à partir d'un template - * - * @param typeNotification Type de notification - * @param destinatairesIds Liste des destinataires - * @param donneesContexte Données de contexte pour le template - * @return La notification créée - */ - public NotificationDTO creerDepuisTemplate( - TypeNotification typeNotification, - List destinatairesIds, - Map donneesContexte) { - - LOG.debugf("Création de notification depuis template: %s", typeNotification); - - // Création de la notification de base - NotificationDTO notification = new NotificationDTO(); - notification.setId(UUID.randomUUID().toString()); - notification.setTypeNotification(typeNotification); - notification.setDestinatairesIds(destinatairesIds); - notification.setDonneesPersonnalisees(donneesContexte); - - // Application du template - return appliquerTemplate(notification); - } - - // === MÉTHODES PRIVÉES === - - /** - * Obtient le template pour un type de notification - */ - private NotificationTemplate obtenirTemplate(TypeNotification type) { - return templatesCache.computeIfAbsent(type, this::chargerTemplate); - } - - /** - * Charge un template depuis la configuration - */ - private NotificationTemplate chargerTemplate(TypeNotification type) { - // Dans un vrai projet, les templates seraient stockés en base de données - // ou dans des fichiers de configuration. Ici, nous les définissons en dur. - - return switch (type) { - case NOUVEL_EVENEMENT -> creerTemplateNouvelEvenement(); - case RAPPEL_EVENEMENT -> creerTemplateRappelEvenement(); - case COTISATION_DUE -> creerTemplateCotisationDue(); - case COTISATION_PAYEE -> creerTemplateCotisationPayee(); - case NOUVELLE_DEMANDE_AIDE -> creerTemplateNouvelleDemandeAide(); - case NOUVEAU_MEMBRE -> creerTemplateNouveauMembre(); - case ANNIVERSAIRE_MEMBRE -> creerTemplateAnniversaireMembre(); - case ANNONCE_GENERALE -> creerTemplateAnnonceGenerale(); - case MESSAGE_PRIVE -> creerTemplateMessagePrive(); - default -> creerTemplateDefaut(type); - }; - } - - /** - * Construit le contexte de variables pour le template - */ - private Map construireContexte(NotificationDTO notification) { - Map contexte = new HashMap<>(); - - // Variables de base - contexte.put("notification_id", notification.getId()); - contexte.put("type", notification.getTypeNotification().getLibelle()); - contexte.put("date_creation", DATE_FORMATTER.format(notification.getDateCreation())); - contexte.put("datetime_creation", DATETIME_FORMATTER.format(notification.getDateCreation())); - contexte.put("heure_creation", TIME_FORMATTER.format(notification.getDateCreation())); - - // Variables de l'expéditeur - if (notification.getExpediteurId() != null) { - try { - // Récupération des informations de l'expéditeur - var expediteur = membreService.obtenirMembre(notification.getExpediteurId()); - if (expediteur != null) { - contexte.put("expediteur_nom", expediteur.getNom()); - contexte.put("expediteur_prenom", expediteur.getPrenom()); - contexte.put("expediteur_nom_complet", expediteur.getNom() + " " + expediteur.getPrenom()); - } - } catch (Exception e) { - LOG.warnf("Impossible de récupérer les infos de l'expéditeur: %s", notification.getExpediteurId()); - } - } - - // Variables de l'organisation - if (notification.getOrganisationId() != null) { - try { - var organisation = organisationService.obtenirOrganisation(notification.getOrganisationId()); - if (organisation != null) { - contexte.put("organisation_nom", organisation.getNom()); - contexte.put("organisation_ville", organisation.getVille()); - } - } catch (Exception e) { - LOG.warnf("Impossible de récupérer les infos de l'organisation: %s", notification.getOrganisationId()); - } - } - - // Variables des données personnalisées - if (notification.getDonneesPersonnalisees() != null) { - notification.getDonneesPersonnalisees().forEach((key, value) -> { - contexte.put(key, value); - - // Formatage spécial pour les dates - if (value instanceof LocalDateTime) { - LocalDateTime dateTime = (LocalDateTime) value; - contexte.put(key + "_date", DATE_FORMATTER.format(dateTime)); - contexte.put(key + "_datetime", DATETIME_FORMATTER.format(dateTime)); - contexte.put(key + "_heure", TIME_FORMATTER.format(dateTime)); - } - }); - } - - // Variables calculées - contexte.put("nombre_destinataires", notification.getDestinatairesIds().size()); - contexte.put("est_groupe", notification.getDestinatairesIds().size() > 1); - - return contexte; - } - - /** - * Applique les variables à un template de texte - */ - private String appliquerVariables(String template, Map contexte) { - if (template == null) return null; - - Matcher matcher = VARIABLE_PATTERN.matcher(template); - StringBuffer result = new StringBuffer(); - - while (matcher.find()) { - String variableName = matcher.group(1).trim(); - Object value = contexte.get(variableName); - - String replacement; - if (value != null) { - replacement = String.valueOf(value); - } else { - // Variable non trouvée, on garde la variable originale - replacement = "{{" + variableName + "}}"; - LOG.warnf("Variable non trouvée dans le contexte: %s", variableName); - } - - matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); - } - matcher.appendTail(result); - - return result.toString(); - } - - // === TEMPLATES PRÉDÉFINIS === - - private NotificationTemplate creerTemplateNouvelEvenement() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Nouvel événement : {{evenement_titre}}"); - template.setMessageTemplate("Un nouvel événement \"{{evenement_titre}}\" a été créé pour le {{evenement_date}}. Inscrivez-vous dès maintenant !"); - template.setMessageCourtTemplate("Nouvel événement le {{evenement_date}}"); - template.setImageUrl("{{evenement_image_url}}"); - template.setActionClic("/evenements/{{evenement_id}}"); - - // Actions rapides - List actions = Arrays.asList( - new ActionNotificationDTO("voir", "Voir", "/evenements/{{evenement_id}}", "visibility"), - new ActionNotificationDTO("inscrire", "S'inscrire", "/evenements/{{evenement_id}}/inscription", "event_available") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateRappelEvenement() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Rappel : {{evenement_titre}}"); - template.setMessageTemplate("N'oubliez pas l'événement \"{{evenement_titre}}\" qui aura lieu {{evenement_date}} à {{evenement_heure}}."); - template.setMessageCourtTemplate("Événement dans {{temps_restant}}"); - template.setActionClic("/evenements/{{evenement_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("voir", "Voir", "/evenements/{{evenement_id}}", "visibility"), - new ActionNotificationDTO("itineraire", "Itinéraire", "geo:{{evenement_latitude}},{{evenement_longitude}}", "directions") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateCotisationDue() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Cotisation due"); - template.setMessageTemplate("Votre cotisation de {{montant}} FCFA est due. Échéance : {{date_echeance}}"); - template.setMessageCourtTemplate("Cotisation {{montant}} FCFA due"); - template.setActionClic("/cotisations/payer/{{cotisation_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("payer", "Payer maintenant", "/cotisations/payer/{{cotisation_id}}", "payment"), - new ActionNotificationDTO("reporter", "Reporter", "/cotisations/reporter/{{cotisation_id}}", "schedule") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateCotisationPayee() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Cotisation payée ✓"); - template.setMessageTemplate("Votre cotisation de {{montant}} FCFA a été payée avec succès. Merci !"); - template.setMessageCourtTemplate("Paiement {{montant}} FCFA confirmé"); - template.setActionClic("/cotisations/recu/{{cotisation_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("recu", "Voir le reçu", "/cotisations/recu/{{cotisation_id}}", "receipt") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateNouvelleDemandeAide() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Nouvelle demande d'aide"); - template.setMessageTemplate("{{demandeur_nom}} a fait une demande d'aide : {{demande_titre}}"); - template.setMessageCourtTemplate("Demande d'aide de {{demandeur_nom}}"); - template.setActionClic("/solidarite/demandes/{{demande_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("voir", "Voir", "/solidarite/demandes/{{demande_id}}", "visibility"), - new ActionNotificationDTO("aider", "Proposer aide", "/solidarite/demandes/{{demande_id}}/aider", "volunteer_activism") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateNouveauMembre() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Nouveau membre"); - template.setMessageTemplate("{{membre_nom}} {{membre_prenom}} a rejoint notre organisation. Souhaitons-lui la bienvenue !"); - template.setMessageCourtTemplate("{{membre_nom}} a rejoint l'organisation"); - template.setActionClic("/membres/{{membre_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("voir", "Voir profil", "/membres/{{membre_id}}", "person"), - new ActionNotificationDTO("message", "Envoyer message", "/messages/nouveau/{{membre_id}}", "message") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateAnniversaireMembre() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Joyeux anniversaire ! 🎉"); - template.setMessageTemplate("C'est l'anniversaire de {{membre_nom}} {{membre_prenom}} aujourd'hui ! Souhaitons-lui un joyeux anniversaire."); - template.setMessageCourtTemplate("Anniversaire de {{membre_nom}}"); - template.setActionClic("/membres/{{membre_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("feliciter", "Féliciter", "/membres/{{membre_id}}/feliciter", "cake"), - new ActionNotificationDTO("appeler", "Appeler", "tel:{{membre_telephone}}", "phone") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateAnnonceGenerale() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("{{annonce_titre}}"); - template.setMessageTemplate("{{annonce_contenu}}"); - template.setMessageCourtTemplate("Nouvelle annonce"); - template.setActionClic("/annonces/{{annonce_id}}"); - - return template; - } - - private NotificationTemplate creerTemplateMessagePrive() { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate("Message de {{expediteur_nom}}"); - template.setMessageTemplate("{{message_contenu}}"); - template.setMessageCourtTemplate("Nouveau message privé"); - template.setActionClic("/messages/{{message_id}}"); - - List actions = Arrays.asList( - new ActionNotificationDTO("repondre", "Répondre", "/messages/repondre/{{message_id}}", "reply"), - new ActionNotificationDTO("voir", "Voir", "/messages/{{message_id}}", "visibility") - ); - template.setActionsRapides(actions); - - return template; - } - - private NotificationTemplate creerTemplateDefaut(TypeNotification type) { - NotificationTemplate template = new NotificationTemplate(); - template.setTitreTemplate(type.getLibelle()); - template.setMessageTemplate("{{message}}"); - template.setMessageCourtTemplate("{{message_court}}"); - - return template; - } - - // === CLASSE INTERNE POUR LES TEMPLATES === - - private static class NotificationTemplate { - private String titreTemplate; - private String messageTemplate; - private String messageCourtTemplate; - private String imageUrl; - private String iconeUrl; - private String actionClic; - private List actionsRapides; - private Map donneesPersonnalisees; - - // Getters et setters - public String getTitreTemplate() { return titreTemplate; } - public void setTitreTemplate(String titreTemplate) { this.titreTemplate = titreTemplate; } - - public String getMessageTemplate() { return messageTemplate; } - public void setMessageTemplate(String messageTemplate) { this.messageTemplate = messageTemplate; } - - public String getMessageCourtTemplate() { return messageCourtTemplate; } - public void setMessageCourtTemplate(String messageCourtTemplate) { this.messageCourtTemplate = messageCourtTemplate; } - - public String getImageUrl() { return imageUrl; } - public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - - public String getIconeUrl() { return iconeUrl; } - public void setIconeUrl(String iconeUrl) { this.iconeUrl = iconeUrl; } - - public String getActionClic() { return actionClic; } - public void setActionClic(String actionClic) { this.actionClic = actionClic; } - - public List getActionsRapides() { return actionsRapides; } - public void setActionsRapides(List actionsRapides) { this.actionsRapides = actionsRapides; } - - public Map getDonneesPersonnalisees() { return donneesPersonnalisees; } - public void setDonneesPersonnalisees(Map donneesPersonnalisees) { - this.donneesPersonnalisees = donneesPersonnalisees; - } - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java index 00dcdb6..26f7dad 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java @@ -1,6 +1,8 @@ package dev.lions.unionflow.server.service; import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO; +import dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation; +import dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.repository.OrganisationRepository; import io.quarkus.panache.common.Page; @@ -70,9 +72,9 @@ public class OrganisationService { organisation.setTypeOrganisation("ASSOCIATION"); } - organisation.persist(); + organisationRepository.persist(organisation); LOG.infof( - "Organisation créée avec succès: ID=%d, Nom=%s", organisation.id, organisation.getNom()); + "Organisation créée avec succès: ID=%s, Nom=%s", organisation.getId(), organisation.getNom()); return organisation; } @@ -87,8 +89,8 @@ public class OrganisationService { */ @Transactional public Organisation mettreAJourOrganisation( - Long id, Organisation organisationMiseAJour, String utilisateur) { - LOG.infof("Mise à jour de l'organisation ID: %d", id); + UUID id, Organisation organisationMiseAJour, String utilisateur) { + LOG.infof("Mise à jour de l'organisation ID: %s", id); Organisation organisation = organisationRepository @@ -126,19 +128,19 @@ public class OrganisationService { organisation.marquerCommeModifie(utilisateur); - LOG.infof("Organisation mise à jour avec succès: ID=%d", id); + LOG.infof("Organisation mise à jour avec succès: ID=%s", id); return organisation; } /** * Supprime une organisation * - * @param id l'ID de l'organisation + * @param id l'UUID de l'organisation * @param utilisateur l'utilisateur effectuant la suppression */ @Transactional - public void supprimerOrganisation(Long id, String utilisateur) { - LOG.infof("Suppression de l'organisation ID: %d", id); + public void supprimerOrganisation(UUID id, String utilisateur) { + LOG.infof("Suppression de l'organisation ID: %s", id); Organisation organisation = organisationRepository @@ -156,16 +158,16 @@ public class OrganisationService { organisation.setStatut("DISSOUTE"); organisation.marquerCommeModifie(utilisateur); - LOG.infof("Organisation supprimée (soft delete) avec succès: ID=%d", id); + LOG.infof("Organisation supprimée (soft delete) avec succès: ID=%s", id); } /** * Trouve une organisation par son ID * - * @param id l'ID de l'organisation + * @param id l'UUID de l'organisation * @return Optional contenant l'organisation si trouvée */ - public Optional trouverParId(Long id) { + public Optional trouverParId(UUID id) { return organisationRepository.findByIdOptional(id); } @@ -246,8 +248,8 @@ public class OrganisationService { * @return l'organisation activée */ @Transactional - public Organisation activerOrganisation(Long id, String utilisateur) { - LOG.infof("Activation de l'organisation ID: %d", id); + public Organisation activerOrganisation(UUID id, String utilisateur) { + LOG.infof("Activation de l'organisation ID: %s", id); Organisation organisation = organisationRepository @@ -256,20 +258,20 @@ public class OrganisationService { organisation.activer(utilisateur); - LOG.infof("Organisation activée avec succès: ID=%d", id); + LOG.infof("Organisation activée avec succès: ID=%s", id); return organisation; } /** * Suspend une organisation * - * @param id l'ID de l'organisation + * @param id l'UUID de l'organisation * @param utilisateur l'utilisateur effectuant la suspension * @return l'organisation suspendue */ @Transactional - public Organisation suspendreOrganisation(Long id, String utilisateur) { - LOG.infof("Suspension de l'organisation ID: %d", id); + public Organisation suspendreOrganisation(UUID id, String utilisateur) { + LOG.infof("Suspension de l'organisation ID: %s", id); Organisation organisation = organisationRepository @@ -278,7 +280,7 @@ public class OrganisationService { organisation.suspendre(utilisateur); - LOG.infof("Organisation suspendue avec succès: ID=%d", id); + LOG.infof("Organisation suspendue avec succès: ID=%s", id); return organisation; } @@ -318,25 +320,95 @@ public class OrganisationService { } OrganisationDTO dto = new OrganisationDTO(); - dto.setId(UUID.randomUUID()); // Temporaire - à adapter selon votre logique d'ID + + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(organisation.getId()); + + // Informations de base dto.setNom(organisation.getNom()); dto.setNomCourt(organisation.getNomCourt()); dto.setDescription(organisation.getDescription()); dto.setEmail(organisation.getEmail()); dto.setTelephone(organisation.getTelephone()); + dto.setTelephoneSecondaire(organisation.getTelephoneSecondaire()); + dto.setEmailSecondaire(organisation.getEmailSecondaire()); dto.setAdresse(organisation.getAdresse()); dto.setVille(organisation.getVille()); dto.setCodePostal(organisation.getCodePostal()); dto.setRegion(organisation.getRegion()); dto.setPays(organisation.getPays()); + dto.setLatitude(organisation.getLatitude()); + dto.setLongitude(organisation.getLongitude()); dto.setSiteWeb(organisation.getSiteWeb()); + dto.setLogo(organisation.getLogo()); + dto.setReseauxSociaux(organisation.getReseauxSociaux()); dto.setObjectifs(organisation.getObjectifs()); dto.setActivitesPrincipales(organisation.getActivitesPrincipales()); dto.setNombreMembres(organisation.getNombreMembres()); + dto.setNombreAdministrateurs(organisation.getNombreAdministrateurs()); + dto.setBudgetAnnuel(organisation.getBudgetAnnuel()); + dto.setDevise(organisation.getDevise()); + dto.setDateFondation(organisation.getDateFondation()); + dto.setNumeroEnregistrement(organisation.getNumeroEnregistrement()); + dto.setNiveauHierarchique(organisation.getNiveauHierarchique()); + + // Conversion de l'organisation parente (UUID → UUID, pas de conversion nécessaire) + if (organisation.getOrganisationParenteId() != null) { + dto.setOrganisationParenteId(organisation.getOrganisationParenteId()); + } + + // Conversion du type d'organisation (String → Enum) + if (organisation.getTypeOrganisation() != null) { + try { + dto.setTypeOrganisation( + TypeOrganisation.valueOf(organisation.getTypeOrganisation().toUpperCase())); + } catch (IllegalArgumentException e) { + // Valeur par défaut si la conversion échoue + LOG.warnf( + "Type d'organisation inconnu: %s, utilisation de ASSOCIATION par défaut", + organisation.getTypeOrganisation()); + dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION); + } + } else { + dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION); + } + + // Conversion du statut (String → Enum) + if (organisation.getStatut() != null) { + try { + dto.setStatut( + StatutOrganisation.valueOf(organisation.getStatut().toUpperCase())); + } catch (IllegalArgumentException e) { + // Valeur par défaut si la conversion échoue + LOG.warnf( + "Statut d'organisation inconnu: %s, utilisation de ACTIVE par défaut", + organisation.getStatut()); + dto.setStatut(StatutOrganisation.ACTIVE); + } + } else { + dto.setStatut(StatutOrganisation.ACTIVE); + } + + // Champs de base DTO dto.setDateCreation(organisation.getDateCreation()); dto.setDateModification(organisation.getDateModification()); dto.setActif(organisation.getActif()); - dto.setVersion(organisation.getVersion()); + dto.setVersion(organisation.getVersion() != null ? organisation.getVersion() : 0L); + + // Champs par défaut + dto.setOrganisationPublique( + organisation.getOrganisationPublique() != null + ? organisation.getOrganisationPublique() + : true); + dto.setAccepteNouveauxMembres( + organisation.getAccepteNouveauxMembres() != null + ? organisation.getAccepteNouveauxMembres() + : true); + dto.setCotisationObligatoire( + organisation.getCotisationObligatoire() != null + ? organisation.getCotisationObligatoire() + : false); + dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle()); return dto; } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteAnalyticsService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteAnalyticsService.java.bak deleted file mode 100644 index 532a80c..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteAnalyticsService.java.bak +++ /dev/null @@ -1,440 +0,0 @@ -package dev.lions.unionflow.server.service; - -import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO; -import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; -import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; -import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Service d'analytics spécialisé pour le système de solidarité - * - * Ce service calcule les métriques, statistiques et indicateurs - * de performance du système de solidarité. - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 - */ -@ApplicationScoped -public class SolidariteAnalyticsService { - - private static final Logger LOG = Logger.getLogger(SolidariteAnalyticsService.class); - - @Inject - DemandeAideService demandeAideService; - - @Inject - PropositionAideService propositionAideService; - - @Inject - EvaluationService evaluationService; - - // Cache des statistiques calculées - private final Map> cacheStatistiques = new HashMap<>(); - private final Map cacheTimestamps = new HashMap<>(); - private static final long CACHE_DURATION_MINUTES = 30; - - // === STATISTIQUES GÉNÉRALES === - - /** - * Calcule les statistiques générales de solidarité pour une organisation - * - * @param organisationId ID de l'organisation - * @return Map des statistiques - */ - public Map calculerStatistiquesSolidarite(String organisationId) { - LOG.infof("Calcul des statistiques de solidarité pour: %s", organisationId); - - // Vérification du cache - String cacheKey = "stats_" + organisationId; - Map statsCache = obtenirDuCache(cacheKey); - if (statsCache != null) { - return statsCache; - } - - try { - Map statistiques = new HashMap<>(); - - // 1. Statistiques des demandes - Map statsDemandes = calculerStatistiquesDemandes(organisationId); - statistiques.put("demandes", statsDemandes); - - // 2. Statistiques des propositions - Map statsPropositions = calculerStatistiquesPropositions(organisationId); - statistiques.put("propositions", statsPropositions); - - // 3. Statistiques financières - Map statsFinancieres = calculerStatistiquesFinancieres(organisationId); - statistiques.put("financier", statsFinancieres); - - // 4. Indicateurs de performance - Map kpis = calculerKPIsSolidarite(organisationId); - statistiques.put("kpis", kpis); - - // 5. Tendances - Map tendances = calculerTendances(organisationId); - statistiques.put("tendances", tendances); - - // 6. Métadonnées - statistiques.put("dateCalcul", LocalDateTime.now()); - statistiques.put("organisationId", organisationId); - - // Mise en cache - ajouterAuCache(cacheKey, statistiques); - - return statistiques; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors du calcul des statistiques pour: %s", organisationId); - return new HashMap<>(); - } - } - - /** - * Calcule les statistiques des demandes d'aide - */ - private Map calculerStatistiquesDemandes(String organisationId) { - Map filtres = Map.of("organisationId", organisationId); - List demandes = demandeAideService.rechercherAvecFiltres(filtres); - - Map stats = new HashMap<>(); - - // Nombre total de demandes - stats.put("total", demandes.size()); - - // Répartition par statut - Map repartitionStatut = demandes.stream() - .collect(Collectors.groupingBy(DemandeAideDTO::getStatut, Collectors.counting())); - stats.put("parStatut", repartitionStatut); - - // Répartition par type d'aide - Map repartitionType = demandes.stream() - .collect(Collectors.groupingBy(DemandeAideDTO::getTypeAide, Collectors.counting())); - stats.put("parType", repartitionType); - - // Répartition par priorité - Map repartitionPriorite = demandes.stream() - .collect(Collectors.groupingBy(DemandeAideDTO::getPriorite, Collectors.counting())); - stats.put("parPriorite", repartitionPriorite); - - // Demandes urgentes - long demandesUrgentes = demandes.stream() - .filter(DemandeAideDTO::isUrgente) - .count(); - stats.put("urgentes", demandesUrgentes); - - // Demandes en retard - long demandesEnRetard = demandes.stream() - .filter(DemandeAideDTO::isDelaiDepasse) - .filter(d -> !d.isTerminee()) - .count(); - stats.put("enRetard", demandesEnRetard); - - // Taux d'approbation - long demandesEvaluees = demandes.stream() - .filter(d -> d.getStatut().isEstFinal()) - .count(); - long demandesApprouvees = demandes.stream() - .filter(d -> d.getStatut() == StatutAide.APPROUVEE || - d.getStatut() == StatutAide.APPROUVEE_PARTIELLEMENT) - .count(); - - double tauxApprobation = demandesEvaluees > 0 ? - (demandesApprouvees * 100.0) / demandesEvaluees : 0.0; - stats.put("tauxApprobation", Math.round(tauxApprobation * 100.0) / 100.0); - - // Délai moyen de traitement - double delaiMoyenHeures = demandes.stream() - .filter(d -> d.isTerminee()) - .mapToLong(DemandeAideDTO::getDureeTraitementJours) - .average() - .orElse(0.0) * 24; // Conversion en heures - stats.put("delaiMoyenTraitementHeures", Math.round(delaiMoyenHeures * 100.0) / 100.0); - - return stats; - } - - /** - * Calcule les statistiques des propositions d'aide - */ - private Map calculerStatistiquesPropositions(String organisationId) { - Map filtres = Map.of("organisationId", organisationId); - List propositions = propositionAideService.rechercherAvecFiltres(filtres); - - Map stats = new HashMap<>(); - - // Nombre total de propositions - stats.put("total", propositions.size()); - - // Propositions actives - long propositionsActives = propositions.stream() - .filter(PropositionAideDTO::isActiveEtDisponible) - .count(); - stats.put("actives", propositionsActives); - - // Répartition par type d'aide - Map repartitionType = propositions.stream() - .collect(Collectors.groupingBy(PropositionAideDTO::getTypeAide, Collectors.counting())); - stats.put("parType", repartitionType); - - // Capacité totale disponible - int capaciteTotale = propositions.stream() - .filter(PropositionAideDTO::isActiveEtDisponible) - .mapToInt(PropositionAideDTO::getPlacesRestantes) - .sum(); - stats.put("capaciteDisponible", capaciteTotale); - - // Taux d'utilisation moyen - double tauxUtilisationMoyen = propositions.stream() - .filter(p -> p.getNombreMaxBeneficiaires() > 0) - .mapToDouble(PropositionAideDTO::getPourcentageCapaciteUtilisee) - .average() - .orElse(0.0); - stats.put("tauxUtilisationMoyen", Math.round(tauxUtilisationMoyen * 100.0) / 100.0); - - // Note moyenne des propositions - double noteMoyenne = propositions.stream() - .filter(p -> p.getNoteMoyenne() != null) - .mapToDouble(PropositionAideDTO::getNoteMoyenne) - .average() - .orElse(0.0); - stats.put("noteMoyenne", Math.round(noteMoyenne * 100.0) / 100.0); - - return stats; - } - - /** - * Calcule les statistiques financières - */ - private Map calculerStatistiquesFinancieres(String organisationId) { - Map filtres = Map.of("organisationId", organisationId); - List demandes = demandeAideService.rechercherAvecFiltres(filtres); - List propositions = propositionAideService.rechercherAvecFiltres(filtres); - - Map stats = new HashMap<>(); - - // Montant total demandé - double montantTotalDemande = demandes.stream() - .filter(d -> d.getMontantDemande() != null) - .mapToDouble(DemandeAideDTO::getMontantDemande) - .sum(); - stats.put("montantTotalDemande", montantTotalDemande); - - // Montant total approuvé - double montantTotalApprouve = demandes.stream() - .filter(d -> d.getMontantApprouve() != null) - .mapToDouble(DemandeAideDTO::getMontantApprouve) - .sum(); - stats.put("montantTotalApprouve", montantTotalApprouve); - - // Montant total versé - double montantTotalVerse = demandes.stream() - .filter(d -> d.getMontantVerse() != null) - .mapToDouble(DemandeAideDTO::getMontantVerse) - .sum(); - stats.put("montantTotalVerse", montantTotalVerse); - - // Capacité financière disponible (propositions) - double capaciteFinanciere = propositions.stream() - .filter(p -> p.getMontantMaximum() != null) - .filter(PropositionAideDTO::isActiveEtDisponible) - .mapToDouble(PropositionAideDTO::getMontantMaximum) - .sum(); - stats.put("capaciteFinanciereDisponible", capaciteFinanciere); - - // Montant moyen par demande - double montantMoyenDemande = demandes.stream() - .filter(d -> d.getMontantDemande() != null) - .mapToDouble(DemandeAideDTO::getMontantDemande) - .average() - .orElse(0.0); - stats.put("montantMoyenDemande", Math.round(montantMoyenDemande * 100.0) / 100.0); - - // Taux de versement - double tauxVersement = montantTotalApprouve > 0 ? - (montantTotalVerse * 100.0) / montantTotalApprouve : 0.0; - stats.put("tauxVersement", Math.round(tauxVersement * 100.0) / 100.0); - - return stats; - } - - /** - * Calcule les KPIs de solidarité - */ - private Map calculerKPIsSolidarite(String organisationId) { - Map kpis = new HashMap<>(); - - // Simulation de calculs KPI - dans une vraie implémentation, - // ces calculs seraient plus complexes et basés sur des données historiques - - // Efficacité du matching - kpis.put("efficaciteMatching", 78.5); // Pourcentage de demandes matchées avec succès - - // Temps de réponse moyen - kpis.put("tempsReponseMoyenHeures", 24.3); - - // Satisfaction globale - kpis.put("satisfactionGlobale", 4.2); // Sur 5 - - // Taux de résolution - kpis.put("tauxResolution", 85.7); // Pourcentage de demandes résolues - - // Impact social (nombre de personnes aidées) - kpis.put("personnesAidees", 156); - - // Engagement communautaire - kpis.put("engagementCommunautaire", 67.8); // Pourcentage de membres actifs - - return kpis; - } - - /** - * Calcule les tendances sur les 30 derniers jours - */ - private Map calculerTendances(String organisationId) { - Map tendances = new HashMap<>(); - - // Simulation de calculs de tendances - // Dans une vraie implémentation, on comparerait avec la période précédente - - tendances.put("evolutionDemandes", "+12.5%"); // Évolution du nombre de demandes - tendances.put("evolutionPropositions", "+8.3%"); // Évolution du nombre de propositions - tendances.put("evolutionMontants", "+15.7%"); // Évolution des montants - tendances.put("evolutionSatisfaction", "+2.1%"); // Évolution de la satisfaction - - // Prédictions pour le mois prochain - Map predictions = new HashMap<>(); - predictions.put("demandesPrevues", 45); - predictions.put("montantPrevu", 125000.0); - predictions.put("capaciteRequise", 38); - - tendances.put("predictions", predictions); - - return tendances; - } - - // === ENREGISTREMENT D'ÉVÉNEMENTS === - - /** - * Enregistre une nouvelle demande pour les analytics - * - * @param demande La nouvelle demande - */ - public void enregistrerNouvelledemande(DemandeAideDTO demande) { - LOG.debugf("Enregistrement d'une nouvelle demande pour analytics: %s", demande.getId()); - - try { - // Invalidation du cache pour forcer le recalcul - invaliderCache(demande.getOrganisationId()); - - // Dans une vraie implémentation, on enregistrerait l'événement - // dans une base de données d'événements ou un système de métriques - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'enregistrement de la nouvelle demande: %s", demande.getId()); - } - } - - /** - * Enregistre une nouvelle proposition pour les analytics - * - * @param proposition La nouvelle proposition - */ - public void enregistrerNouvelleProposition(PropositionAideDTO proposition) { - LOG.debugf("Enregistrement d'une nouvelle proposition pour analytics: %s", proposition.getId()); - - try { - invaliderCache(proposition.getOrganisationId()); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'enregistrement de la nouvelle proposition: %s", - proposition.getId()); - } - } - - /** - * Enregistre l'évaluation d'une demande - * - * @param demande La demande évaluée - */ - public void enregistrerEvaluationDemande(DemandeAideDTO demande) { - LOG.debugf("Enregistrement de l'évaluation pour analytics: %s", demande.getId()); - - try { - invaliderCache(demande.getOrganisationId()); - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'enregistrement de l'évaluation: %s", demande.getId()); - } - } - - /** - * Enregistre le rejet d'une demande avec motif - * - * @param demande La demande rejetée - * @param motif Le motif de rejet - */ - public void enregistrerRejetDemande(DemandeAideDTO demande, String motif) { - LOG.debugf("Enregistrement du rejet pour analytics: %s - motif: %s", demande.getId(), motif); - - try { - invaliderCache(demande.getOrganisationId()); - - // Dans une vraie implémentation, on analyserait les motifs de rejet - // pour identifier les problèmes récurrents - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'enregistrement du rejet: %s", demande.getId()); - } - } - - // === GESTION DU CACHE === - - private Map obtenirDuCache(String cacheKey) { - LocalDateTime timestamp = cacheTimestamps.get(cacheKey); - if (timestamp == null) return null; - - // Vérification de l'expiration - if (LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES).isAfter(timestamp)) { - cacheStatistiques.remove(cacheKey); - cacheTimestamps.remove(cacheKey); - return null; - } - - return cacheStatistiques.get(cacheKey); - } - - private void ajouterAuCache(String cacheKey, Map statistiques) { - cacheStatistiques.put(cacheKey, statistiques); - cacheTimestamps.put(cacheKey, LocalDateTime.now()); - - // Nettoyage du cache si trop volumineux - if (cacheStatistiques.size() > 50) { - nettoyerCache(); - } - } - - private void invaliderCache(String organisationId) { - String cacheKey = "stats_" + organisationId; - cacheStatistiques.remove(cacheKey); - cacheTimestamps.remove(cacheKey); - } - - private void nettoyerCache() { - LocalDateTime limite = LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES); - - cacheTimestamps.entrySet().removeIf(entry -> entry.getValue().isBefore(limite)); - cacheStatistiques.keySet().retainAll(cacheTimestamps.keySet()); - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteService.java.bak b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteService.java.bak deleted file mode 100644 index 3630b94..0000000 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/SolidariteService.java.bak +++ /dev/null @@ -1,610 +0,0 @@ -package dev.lions.unionflow.server.service; - -import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; -import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO; -import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; -import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; -import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.NotBlank; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.logging.Logger; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -/** - * Service principal de gestion de la solidarité UnionFlow - * - * Ce service orchestre toutes les opérations liées au système de solidarité : - * demandes d'aide, propositions d'aide, matching, évaluations et suivi. - * - * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 - */ -@ApplicationScoped -public class SolidariteService { - - private static final Logger LOG = Logger.getLogger(SolidariteService.class); - - @Inject - DemandeAideService demandeAideService; - - @Inject - PropositionAideService propositionAideService; - - @Inject - MatchingService matchingService; - - // @Inject - // EvaluationService evaluationService; - - // @Inject - // NotificationSolidariteService notificationService; - - @Inject - SolidariteAnalyticsService analyticsService; - - @ConfigProperty(name = "unionflow.solidarite.auto-matching.enabled", defaultValue = "true") - boolean autoMatchingEnabled; - - @ConfigProperty(name = "unionflow.solidarite.notification.enabled", defaultValue = "true") - boolean notificationEnabled; - - @ConfigProperty(name = "unionflow.solidarite.evaluation.obligatoire", defaultValue = "false") - boolean evaluationObligatoire; - - // === GESTION DES DEMANDES D'AIDE === - - /** - * Crée une nouvelle demande d'aide - * - * @param demandeDTO La demande d'aide à créer - * @return La demande d'aide créée - */ - @Transactional - public CompletableFuture creerDemandeAide(@Valid DemandeAideDTO demandeDTO) { - LOG.infof("Création d'une nouvelle demande d'aide: %s", demandeDTO.getTitre()); - - return CompletableFuture.supplyAsync(() -> { - try { - // 1. Créer la demande - DemandeAideDTO demandeCree = demandeAideService.creerDemande(demandeDTO); - - // 2. Calcul automatique de la priorité si non définie - // if (demandeCree.getPriorite() == null) { - // PrioriteAide prioriteCalculee = PrioriteAide.determinerPriorite(demandeCree.getTypeAide()); - // demandeCree.setPriorite(prioriteCalculee); - // demandeCree = demandeAideService.mettreAJour(demandeCree); - // } - - // 3. Matching automatique si activé - if (autoMatchingEnabled) { - CompletableFuture.runAsync(() -> { - try { - List propositionsCompatibles = - matchingService.trouverPropositionsCompatibles(demandeCree); - - if (!propositionsCompatibles.isEmpty()) { - LOG.infof("Trouvé %d propositions compatibles pour la demande %s", - propositionsCompatibles.size(), demandeCree.getId()); - - // Notification aux proposants - if (notificationEnabled) { - notificationService.notifierProposantsCompatibles( - demandeCree, propositionsCompatibles); - } - } - } catch (Exception e) { - LOG.errorf(e, "Erreur lors du matching automatique pour la demande %s", - demandeCree.getId()); - } - }); - } - - // 4. Notifications - if (notificationEnabled) { - notificationService.notifierCreationDemande(demandeCree); - - // Notification d'urgence si priorité critique - if (demandeCree.getPriorite() == PrioriteAide.CRITIQUE) { - notificationService.notifierUrgenceCritique(demandeCree); - } - } - - // 5. Mise à jour des analytics - analyticsService.enregistrerNouvelledemande(demandeCree); - - LOG.infof("Demande d'aide créée avec succès: %s", demandeCree.getId()); - return demandeCree; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la création de la demande d'aide"); - throw new RuntimeException("Erreur lors de la création de la demande d'aide", e); - } - }); - } - - /** - * Soumet une demande d'aide de manière asynchrone (passage de brouillon à soumise) - * - * @param demandeId ID de la demande - * @return La demande soumise - */ - @Transactional - public CompletableFuture soumettreDemandeeAsync(@NotBlank String demandeId) { - LOG.infof("Soumission de la demande d'aide: %s", demandeId); - - return CompletableFuture.supplyAsync(() -> { - try { - // 1. Récupérer et valider la demande - DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId); - - if (demande.getStatut() != StatutAide.BROUILLON) { - throw new IllegalStateException("Seules les demandes en brouillon peuvent être soumises"); - } - - // 2. Validation complète de la demande - validerDemandeAvantSoumission(demande); - - // 3. Changement de statut - demande = demandeAideService.changerStatut(demandeId, StatutAide.SOUMISE, - "Demande soumise par le demandeur"); - - // 4. Calcul de la date limite de traitement - LocalDateTime dateLimite = demande.getPriorite().getDateLimiteTraitement(); - demande.setDateLimiteTraitement(dateLimite); - demande = demandeAideService.mettreAJour(demande); - - // 5. Notifications - if (notificationEnabled) { - notificationService.notifierSoumissionDemande(demande); - notificationService.notifierEvaluateurs(demande); - } - - // 6. Programmation des rappels automatiques - programmerRappelsAutomatiques(demande); - - LOG.infof("Demande soumise avec succès: %s", demandeId); - return demande; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la soumission de la demande: %s", demandeId); - throw new RuntimeException("Erreur lors de la soumission de la demande", e); - } - }); - } - - /** - * Évalue une demande d'aide - * - * @param demandeId ID de la demande - * @param decision Décision d'évaluation (APPROUVEE, REJETEE, etc.) - * @param commentaires Commentaires de l'évaluateur - * @param montantApprouve Montant approuvé (si différent du montant demandé) - * @return La demande évaluée - */ - @Transactional - public CompletableFuture evaluerDemande( - @NotBlank String demandeId, - @NotNull StatutAide decision, - String commentaires, - Double montantApprouve) { - - LOG.infof("Évaluation de la demande: %s avec décision: %s", demandeId, decision); - - return CompletableFuture.supplyAsync(() -> { - try { - // 1. Récupérer la demande - DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId); - - // 2. Valider que la demande peut être évaluée - if (!demande.getStatut().peutTransitionnerVers(decision)) { - throw new IllegalStateException( - String.format("Transition invalide de %s vers %s", - demande.getStatut(), decision)); - } - - // 3. Mise à jour de la demande - demande.setCommentairesEvaluateur(commentaires); - demande.setDateEvaluation(LocalDateTime.now()); - - if (montantApprouve != null) { - demande.setMontantApprouve(montantApprouve); - } - - // 4. Changement de statut - demande = demandeAideService.changerStatut(demandeId, decision, commentaires); - - // 5. Actions spécifiques selon la décision - switch (decision) { - case APPROUVEE, APPROUVEE_PARTIELLEMENT -> { - demande.setDateApprobation(LocalDateTime.now()); - - // Recherche automatique de proposants si pas de montant spécifique - if (demande.getTypeAide().isFinancier() && autoMatchingEnabled) { - CompletableFuture.runAsync(() -> - matchingService.rechercherProposantsFinanciers(demande)); - } - } - case REJETEE -> { - // Enregistrement des raisons de rejet pour analytics - analyticsService.enregistrerRejetDemande(demande, commentaires); - } - case INFORMATIONS_REQUISES -> { - // Programmation d'un rappel - programmerRappelInformationsRequises(demande); - } - } - - // 6. Notifications - if (notificationEnabled) { - notificationService.notifierDecisionEvaluation(demande); - } - - // 7. Mise à jour des analytics - analyticsService.enregistrerEvaluationDemande(demande); - - LOG.infof("Demande évaluée avec succès: %s", demandeId); - return demande; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'évaluation de la demande: %s", demandeId); - throw new RuntimeException("Erreur lors de l'évaluation de la demande", e); - } - }); - } - - // === GESTION DES PROPOSITIONS D'AIDE === - - /** - * Crée une nouvelle proposition d'aide - * - * @param propositionDTO La proposition d'aide à créer - * @return La proposition d'aide créée - */ - @Transactional - public CompletableFuture creerPropositionAide(@Valid PropositionAideDTO propositionDTO) { - LOG.infof("Création d'une nouvelle proposition d'aide: %s", propositionDTO.getTitre()); - - return CompletableFuture.supplyAsync(() -> { - try { - // 1. Créer la proposition - PropositionAideDTO propositionCreee = propositionAideService.creerProposition(propositionDTO); - - // 2. Matching automatique avec les demandes existantes - if (autoMatchingEnabled) { - CompletableFuture.runAsync(() -> { - try { - List demandesCompatibles = - matchingService.trouverDemandesCompatibles(propositionCreee); - - if (!demandesCompatibles.isEmpty()) { - LOG.infof("Trouvé %d demandes compatibles pour la proposition %s", - demandesCompatibles.size(), propositionCreee.getId()); - - // Notification au proposant - if (notificationEnabled) { - notificationService.notifierDemandesCompatibles( - propositionCreee, demandesCompatibles); - } - } - } catch (Exception e) { - LOG.errorf(e, "Erreur lors du matching automatique pour la proposition %s", - propositionCreee.getId()); - } - }); - } - - // 3. Notifications - if (notificationEnabled) { - notificationService.notifierCreationProposition(propositionCreee); - } - - // 4. Mise à jour des analytics - analyticsService.enregistrerNouvelleProposition(propositionCreee); - - LOG.infof("Proposition d'aide créée avec succès: %s", propositionCreee.getId()); - return propositionCreee; - - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la création de la proposition d'aide"); - throw new RuntimeException("Erreur lors de la création de la proposition d'aide", e); - } - }); - } - - /** - * Obtient une proposition d'aide par son ID - * - * @param propositionId ID de la proposition - * @return La proposition trouvée - */ - public PropositionAideDTO obtenirPropositionAide(@NotBlank String propositionId) { - LOG.debugf("Récupération de la proposition d'aide: %s", propositionId); - - try { - return propositionAideService.obtenirParId(propositionId); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la récupération de la proposition: %s", propositionId); - return null; - } - } - - /** - * Recherche des propositions d'aide avec filtres - * - * @param filtres Critères de recherche - * @return Liste des propositions correspondantes - */ - public List rechercherPropositions(Map filtres) { - LOG.debugf("Recherche de propositions avec filtres: %s", filtres); - - try { - return propositionAideService.rechercherAvecFiltres(filtres); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la recherche de propositions"); - return new ArrayList<>(); - } - } - - /** - * Trouve les propositions compatibles avec une demande - * - * @param demandeId ID de la demande - * @return Liste des propositions compatibles - */ - public List trouverPropositionsCompatibles(@NotBlank String demandeId) { - LOG.infof("Recherche de propositions compatibles pour la demande: %s", demandeId); - - try { - DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId); - if (demande == null) { - throw new IllegalArgumentException("Demande non trouvée: " + demandeId); - } - - return matchingService.trouverPropositionsCompatibles(demande); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la recherche de propositions compatibles"); - return new ArrayList<>(); - } - } - - /** - * Trouve les demandes compatibles avec une proposition - * - * @param propositionId ID de la proposition - * @return Liste des demandes compatibles - */ - public List trouverDemandesCompatibles(@NotBlank String propositionId) { - LOG.infof("Recherche de demandes compatibles pour la proposition: %s", propositionId); - - try { - PropositionAideDTO proposition = propositionAideService.obtenirParId(propositionId); - if (proposition == null) { - throw new IllegalArgumentException("Proposition non trouvée: " + propositionId); - } - - return matchingService.trouverDemandesCompatibles(proposition); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la recherche de demandes compatibles"); - return new ArrayList<>(); - } - } - - // === RECHERCHE ET FILTRAGE === - - /** - * Obtient une demande d'aide par son ID - * - * @param demandeId ID de la demande - * @return La demande trouvée - */ - public DemandeAideDTO obtenirDemandeAide(@NotBlank String demandeId) { - LOG.debugf("Récupération de la demande d'aide: %s", demandeId); - - try { - return demandeAideService.obtenirParId(demandeId); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la récupération de la demande: %s", demandeId); - return null; - } - } - - /** - * Met à jour une demande d'aide - * - * @param demandeDTO La demande à mettre à jour - * @return La demande mise à jour - */ - @Transactional - public DemandeAideDTO mettreAJourDemandeAide(@Valid DemandeAideDTO demandeDTO) { - LOG.infof("Mise à jour de la demande d'aide: %s", demandeDTO.getId()); - - try { - return demandeAideService.mettreAJour(demandeDTO); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la mise à jour de la demande: %s", demandeDTO.getId()); - throw new RuntimeException("Erreur lors de la mise à jour de la demande", e); - } - } - - /** - * Évalue une demande d'aide (version synchrone pour l'API REST) - * - * @param demandeId ID de la demande - * @param evaluateurId ID de l'évaluateur - * @param decision Décision d'évaluation - * @param commentaire Commentaire de l'évaluateur - * @param montantApprouve Montant approuvé - * @return La demande évaluée - */ - @Transactional - public DemandeAideDTO evaluerDemande(@NotBlank String demandeId, - @NotBlank String evaluateurId, - @NotNull StatutAide decision, - String commentaire, - Double montantApprouve) { - LOG.infof("Évaluation synchrone de la demande: %s par: %s", demandeId, evaluateurId); - - try { - // Utilisation de la version asynchrone et attente du résultat - return evaluerDemande(demandeId, decision, commentaire, montantApprouve).get(); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de l'évaluation synchrone de la demande: %s", demandeId); - throw new RuntimeException("Erreur lors de l'évaluation de la demande", e); - } - } - - /** - * Soumet une demande d'aide (version synchrone pour l'API REST) - * - * @param demandeId ID de la demande - * @return La demande soumise - */ - @Transactional - public DemandeAideDTO soumettreDemande(@NotBlank String demandeId) { - LOG.infof("Soumission synchrone de la demande: %s", demandeId); - - try { - // Utilisation de la version asynchrone et attente du résultat - return soumettreDemandeeAsync(demandeId).get(); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la soumission synchrone de la demande: %s", demandeId); - throw new RuntimeException("Erreur lors de la soumission de la demande", e); - } - } - - /** - * Recherche des demandes d'aide avec filtres - * - * @param filtres Critères de recherche - * @return Liste des demandes correspondantes - */ - public List rechercherDemandes(Map filtres) { - LOG.debugf("Recherche de demandes avec filtres: %s", filtres); - - try { - return demandeAideService.rechercherAvecFiltres(filtres); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la recherche de demandes"); - return new ArrayList<>(); - } - } - - /** - * Obtient les demandes urgentes nécessitant une attention immédiate - * - * @param organisationId ID de l'organisation - * @return Liste des demandes urgentes - */ - public List obtenirDemandesUrgentes(String organisationId) { - LOG.debugf("Récupération des demandes urgentes pour l'organisation: %s", organisationId); - - try { - return demandeAideService.obtenirDemandesUrgentes(organisationId); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la récupération des demandes urgentes"); - return new ArrayList<>(); - } - } - - /** - * Obtient les statistiques de solidarité - * - * @param organisationId ID de l'organisation - * @return Map des statistiques - */ - public Map obtenirStatistiquesSolidarite(String organisationId) { - LOG.debugf("Calcul des statistiques de solidarité pour: %s", organisationId); - - try { - return analyticsService.calculerStatistiquesSolidarite(organisationId); - } catch (Exception e) { - LOG.errorf(e, "Erreur lors du calcul des statistiques"); - return new HashMap<>(); - } - } - - // === MÉTHODES UTILITAIRES PRIVÉES === - - /** - * Valide une demande avant soumission - */ - private void validerDemandeAvantSoumission(DemandeAideDTO demande) { - List erreurs = new ArrayList<>(); - - if (demande.getTitre() == null || demande.getTitre().trim().isEmpty()) { - erreurs.add("Le titre est obligatoire"); - } - - if (demande.getDescription() == null || demande.getDescription().trim().isEmpty()) { - erreurs.add("La description est obligatoire"); - } - - if (demande.getTypeAide().isNecessiteMontant() && demande.getMontantDemande() == null) { - erreurs.add("Le montant est obligatoire pour ce type d'aide"); - } - - if (demande.getMontantDemande() != null && - !demande.getTypeAide().isMontantValide(demande.getMontantDemande())) { - erreurs.add("Le montant demandé n'est pas dans la fourchette autorisée"); - } - - if (!erreurs.isEmpty()) { - throw new IllegalArgumentException("Erreurs de validation: " + String.join(", ", erreurs)); - } - } - - /** - * Programme les rappels automatiques pour une demande - */ - private void programmerRappelsAutomatiques(DemandeAideDTO demande) { - if (!notificationEnabled) return; - - try { - // Rappel à 50% du délai - LocalDateTime rappel50 = demande.getDateCreation() - .plusHours(demande.getPriorite().getDelaiTraitementHeures() / 2); - - // Rappel à 80% du délai - LocalDateTime rappel80 = demande.getDateCreation() - .plusHours((long) (demande.getPriorite().getDelaiTraitementHeures() * 0.8)); - - // Rappel de dépassement - LocalDateTime rappelDepassement = demande.getDateLimiteTraitement().plusHours(1); - - notificationService.programmerRappels(demande, rappel50, rappel80, rappelDepassement); - - } catch (Exception e) { - LOG.warnf(e, "Erreur lors de la programmation des rappels pour la demande %s", - demande.getId()); - } - } - - /** - * Programme un rappel pour les informations requises - */ - private void programmerRappelInformationsRequises(DemandeAideDTO demande) { - if (!notificationEnabled) return; - - try { - // Rappel dans 48h si pas de réponse - LocalDateTime rappel = LocalDateTime.now().plusHours(48); - notificationService.programmerRappelInformationsRequises(demande, rappel); - - } catch (Exception e) { - LOG.warnf(e, "Erreur lors de la programmation du rappel d'informations pour la demande %s", - demande.getId()); - } - } -} diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/util/IdConverter.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/util/IdConverter.java new file mode 100644 index 0000000..bb2b78b --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/util/IdConverter.java @@ -0,0 +1,150 @@ +package dev.lions.unionflow.server.util; + +import java.util.UUID; + +/** + * Utilitaire pour la conversion entre IDs Long (entités Panache) et UUID (DTOs) + * + *

DÉPRÉCIÉ: Cette classe est maintenant obsolète car toutes les entités + * utilisent désormais UUID directement. Elle est conservée uniquement pour compatibilité + * avec d'éventuels anciens scripts de migration de données. + * + *

Cette classe fournit des méthodes pour convertir de manière cohérente + * entre les identifiants Long utilisés par PanacheEntity et les UUID utilisés + * par les DTOs de l'API. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + * @deprecated Depuis la migration UUID complète (2025-01-16). Utilisez directement UUID dans toutes les entités. + */ +@Deprecated(since = "2025-01-16", forRemoval = true) +public final class IdConverter { + + private IdConverter() { + // Classe utilitaire - constructeur privé + } + + /** + * Convertit un ID Long en UUID de manière déterministe + * + *

DÉPRÉCIÉ: Utilisez directement UUID dans vos entités. + * + *

Utilise un namespace UUID fixe pour garantir la cohérence et éviter les collisions. + * Le même Long produira toujours le même UUID. + * + * @param entityType Le type d'entité (ex: "membre", "organisation", "cotisation") + * @param id L'ID Long de l'entité + * @return L'UUID correspondant, ou null si id est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID longToUUID(String entityType, Long id) { + if (id == null) { + return null; + } + + // Utilisation d'un namespace UUID fixe par type d'entité pour garantir la cohérence + UUID namespace = getNamespaceForEntityType(entityType); + String name = entityType + "-" + id; + return UUID.nameUUIDFromBytes((namespace.toString() + name).getBytes()); + } + + /** + * Convertit un UUID en ID Long approximatif + * + *

DÉPRÉCIÉ: Utilisez directement UUID dans vos entités. + * + *

ATTENTION: Cette conversion n'est pas parfaitement réversible car UUID → Long + * perd de l'information. Cette méthode est principalement utilisée pour la recherche + * approximative. Pour une conversion réversible, il faudrait stocker le mapping dans la DB. + * + * @param uuid L'UUID à convertir + * @return Une approximation de l'ID Long, ou null si uuid est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static Long uuidToLong(UUID uuid) { + if (uuid == null) { + return null; + } + + // Extraction d'une approximation de Long depuis les bits de l'UUID + // Cette méthode n'est pas parfaitement réversible + long mostSignificantBits = uuid.getMostSignificantBits(); + long leastSignificantBits = uuid.getLeastSignificantBits(); + + // Combinaison des bits pour obtenir un Long + // Utilisation de XOR pour mélanger les bits + long combined = mostSignificantBits ^ leastSignificantBits; + + // Conversion en valeur positive + return Math.abs(combined); + } + + /** + * Obtient le namespace UUID pour un type d'entité donné + * + * @param entityType Le type d'entité + * @return Le namespace UUID correspondant + */ + private static UUID getNamespaceForEntityType(String entityType) { + return switch (entityType.toLowerCase()) { + case "membre" -> UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + case "organisation" -> UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + case "cotisation" -> UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); + case "evenement" -> UUID.fromString("6ba7b813-9dad-11d1-80b4-00c04fd430c8"); + case "demandeaide" -> UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"); + case "inscriptionevenement" -> UUID.fromString("6ba7b815-9dad-11d1-80b4-00c04fd430c8"); + default -> UUID.fromString("6ba7b816-9dad-11d1-80b4-00c04fd430c8"); // Namespace par défaut + }; + } + + /** + * Convertit un ID Long d'organisation en UUID pour le DTO + * + * @param organisationId L'ID Long de l'organisation + * @return L'UUID correspondant, ou null si organisationId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID organisationIdToUUID(Long organisationId) { + return longToUUID("organisation", organisationId); + } + + /** + * Convertit un ID Long de membre en UUID pour le DTO + * + * @param membreId L'ID Long du membre + * @return L'UUID correspondant, ou null si membreId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID membreIdToUUID(Long membreId) { + return longToUUID("membre", membreId); + } + + /** + * Convertit un ID Long de cotisation en UUID pour le DTO + * + * @param cotisationId L'ID Long de la cotisation + * @return L'UUID correspondant, ou null si cotisationId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID cotisationIdToUUID(Long cotisationId) { + return longToUUID("cotisation", cotisationId); + } + + /** + * Convertit un ID Long d'événement en UUID pour le DTO + * + * @param evenementId L'ID Long de l'événement + * @return L'UUID correspondant, ou null si evenementId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID evenementIdToUUID(Long evenementId) { + return longToUUID("evenement", evenementId); + } +} diff --git a/unionflow-server-impl-quarkus/src/main/resources/application-prod.properties b/unionflow-server-impl-quarkus/src/main/resources/application-prod.properties new file mode 100644 index 0000000..17b76bc --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/resources/application-prod.properties @@ -0,0 +1,19 @@ +# Configuration UnionFlow Server - Profil Production +# Ce fichier est chargé automatiquement quand le profil 'prod' est actif + +# Configuration Hibernate pour production +quarkus.hibernate-orm.database.generation=validate +quarkus.hibernate-orm.log.sql=false + +# Configuration logging pour production +quarkus.log.console.level=WARN +quarkus.log.category."dev.lions.unionflow".level=INFO +quarkus.log.category.root.level=WARN + +# Configuration Keycloak pour production +quarkus.oidc.auth-server-url=${KEYCLOAK_SERVER_URL:http://localhost:8180/realms/unionflow} +quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:unionflow-server} +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc.tls.verification=required + + diff --git a/unionflow-server-impl-quarkus/src/main/resources/application-test.properties b/unionflow-server-impl-quarkus/src/main/resources/application-test.properties new file mode 100644 index 0000000..dec9daa --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/resources/application-test.properties @@ -0,0 +1,20 @@ +# Configuration UnionFlow Server - Profil Test +# Ce fichier est chargé automatiquement quand le profil 'test' est actif + +# Configuration Base de données H2 pour tests +quarkus.datasource.db-kind=h2 +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + +# Configuration Hibernate pour tests +quarkus.hibernate-orm.database.generation=drop-and-create + +# Configuration Flyway pour tests (désactivé) +quarkus.flyway.migrate-at-start=false + +# Configuration Keycloak pour tests (désactivé) +quarkus.oidc.tenant-enabled=false +quarkus.keycloak.policy-enforcer.enable=false + + diff --git a/unionflow-server-impl-quarkus/src/main/resources/application.properties b/unionflow-server-impl-quarkus/src/main/resources/application.properties index c0f8291..f1bd171 100644 --- a/unionflow-server-impl-quarkus/src/main/resources/application.properties +++ b/unionflow-server-impl-quarkus/src/main/resources/application.properties @@ -3,42 +3,58 @@ quarkus.application.name=unionflow-server quarkus.application.version=1.0.0 # Configuration HTTP -quarkus.http.port=8080 +quarkus.http.port=8085 quarkus.http.host=0.0.0.0 # Configuration CORS quarkus.http.cors=true -quarkus.http.cors.origins=* +quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:8086,https://unionflow.lions.dev,https://security.lions.dev} quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS quarkus.http.cors.headers=Content-Type,Authorization -# Configuration Base de données PostgreSQL +# Configuration Base de données PostgreSQL (par défaut) quarkus.datasource.db-kind=postgresql quarkus.datasource.username=${DB_USERNAME:unionflow} -quarkus.datasource.password=${DB_PASSWORD:unionflow123} +quarkus.datasource.password=${DB_PASSWORD} quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow} quarkus.datasource.jdbc.min-size=2 quarkus.datasource.jdbc.max-size=10 +# Configuration Base de données PostgreSQL pour développement +%dev.quarkus.datasource.username=skyfile +%dev.quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile} +%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow + # Configuration Hibernate -# Mode: update (production) | drop-and-create (développement avec import.sql) quarkus.hibernate-orm.database.generation=update quarkus.hibernate-orm.log.sql=false quarkus.hibernate-orm.jdbc.timezone=UTC quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity +# Configuration Hibernate pour développement +%dev.quarkus.hibernate-orm.database.generation=drop-and-create +%dev.quarkus.hibernate-orm.sql-load-script=import.sql +%dev.quarkus.hibernate-orm.log.sql=true + # Configuration Flyway pour migrations quarkus.flyway.migrate-at-start=true quarkus.flyway.baseline-on-migrate=true quarkus.flyway.baseline-version=1.0.0 -# Configuration Keycloak OIDC -quarkus.oidc.auth-server-url=http://192.168.1.11:8180/realms/unionflow +# Configuration Flyway pour développement (désactivé) +%dev.quarkus.flyway.migrate-at-start=false + +# Configuration Keycloak OIDC (par défaut) +quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow quarkus.oidc.client-id=unionflow-server -quarkus.oidc.credentials.secret=unionflow-secret-2025 +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} quarkus.oidc.tls.verification=none quarkus.oidc.application-type=service +# Configuration Keycloak pour développement +%dev.quarkus.oidc.tenant-enabled=false +%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow + # Configuration Keycloak Policy Enforcer (temporairement désactivé) quarkus.keycloak.policy-enforcer.enable=false quarkus.keycloak.policy-enforcer.lazy-load-paths=true @@ -52,7 +68,7 @@ quarkus.http.auth.permission.public.policy=permit quarkus.smallrye-openapi.info-title=UnionFlow Server API quarkus.smallrye-openapi.info-version=1.0.0 quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak -quarkus.smallrye-openapi.servers=http://localhost:8080 +quarkus.smallrye-openapi.servers=http://localhost:8085 # Configuration Swagger UI quarkus.swagger-ui.always-include=true @@ -69,58 +85,10 @@ quarkus.log.category."dev.lions.unionflow".level=INFO quarkus.log.category."org.hibernate".level=WARN quarkus.log.category."io.quarkus".level=INFO -# ======================================== -# PROFILS DE CONFIGURATION -# ======================================== - -# Profil de développement -%dev.quarkus.datasource.db-kind=postgresql -%dev.quarkus.datasource.username=skyfile -%dev.quarkus.datasource.password=skyfile -%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow -%dev.quarkus.hibernate-orm.database.generation=drop-and-create -%dev.quarkus.hibernate-orm.sql-load-script=import.sql -%dev.quarkus.hibernate-orm.log.sql=true -%dev.quarkus.flyway.migrate-at-start=false +# Configuration logging pour développement %dev.quarkus.log.category."dev.lions.unionflow".level=DEBUG %dev.quarkus.log.category."org.hibernate.SQL".level=DEBUG -# Configuration Keycloak pour développement (temporairement désactivé) -%dev.quarkus.oidc.tenant-enabled=false -%dev.quarkus.oidc.auth-server-url=http://192.168.1.11:8180/realms/unionflow -%dev.quarkus.oidc.client-id=unionflow-server -%dev.quarkus.oidc.credentials.secret=unionflow-secret-2025 -%dev.quarkus.oidc.tls.verification=none -%dev.quarkus.oidc.application-type=service -%dev.quarkus.keycloak.policy-enforcer.enable=false -%dev.quarkus.keycloak.policy-enforcer.lazy-load-paths=true -%dev.quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE - -# Profil de test -%test.quarkus.datasource.db-kind=h2 -%test.quarkus.datasource.username=sa -%test.quarkus.datasource.password= -%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 -%test.quarkus.hibernate-orm.database.generation=drop-and-create -%test.quarkus.flyway.migrate-at-start=false - -# Configuration Keycloak pour tests (désactivé) -%test.quarkus.oidc.tenant-enabled=false -%test.quarkus.keycloak.policy-enforcer.enable=false - -# Profil de production -%prod.quarkus.hibernate-orm.database.generation=validate -%prod.quarkus.hibernate-orm.log.sql=false -%prod.quarkus.log.console.level=WARN -%prod.quarkus.log.category."dev.lions.unionflow".level=INFO -%prod.quarkus.log.category.root.level=WARN - -# Configuration Keycloak pour production -%prod.quarkus.oidc.auth-server-url=${KEYCLOAK_SERVER_URL:http://192.168.1.11:8180/realms/unionflow} -%prod.quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:unionflow-server} -%prod.quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} -%prod.quarkus.oidc.tls.verification=required - # Configuration Jandex pour résoudre les warnings de réflexion quarkus.index-dependency.unionflow-server-api.group-id=dev.lions.unionflow quarkus.index-dependency.unionflow-server-api.artifact-id=unionflow-server-api diff --git a/unionflow-server-impl-quarkus/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql b/unionflow-server-impl-quarkus/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql new file mode 100644 index 0000000..c921d22 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql @@ -0,0 +1,419 @@ +-- Migration V1.3: Conversion des colonnes ID de BIGINT vers UUID +-- Auteur: UnionFlow Team +-- Date: 2025-01-16 +-- Description: Convertit toutes les colonnes ID et clés étrangères de BIGINT vers UUID +-- ATTENTION: Cette migration supprime toutes les données existantes pour simplifier la conversion +-- Pour une migration avec préservation des données, voir V1.3.1__Convert_Ids_To_UUID_With_Data.sql + +-- ============================================ +-- ÉTAPE 1: Suppression des contraintes de clés étrangères +-- ============================================ + +-- Supprimer les contraintes de clés étrangères existantes +DO $$ +BEGIN + -- Supprimer FK membres -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_membre_organisation' + AND table_name = 'membres' + ) THEN + ALTER TABLE membres DROP CONSTRAINT fk_membre_organisation; + END IF; + + -- Supprimer FK cotisations -> membres + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_cotisation%' + AND table_name = 'cotisations' + ) THEN + ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS fk_cotisation_membre CASCADE; + END IF; + + -- Supprimer FK evenements -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_evenement%' + AND table_name = 'evenements' + ) THEN + ALTER TABLE evenements DROP CONSTRAINT IF EXISTS fk_evenement_organisation CASCADE; + END IF; + + -- Supprimer FK inscriptions_evenement -> membres et evenements + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_inscription%' + AND table_name = 'inscriptions_evenement' + ) THEN + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_membre CASCADE; + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_evenement CASCADE; + END IF; + + -- Supprimer FK demandes_aide -> membres et organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_demande%' + AND table_name = 'demandes_aide' + ) THEN + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_demandeur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_evaluateur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_organisation CASCADE; + END IF; +END $$; + +-- ============================================ +-- ÉTAPE 2: Supprimer les séquences (BIGSERIAL) +-- ============================================ + +DROP SEQUENCE IF EXISTS membres_SEQ CASCADE; +DROP SEQUENCE IF EXISTS cotisations_SEQ CASCADE; +DROP SEQUENCE IF EXISTS evenements_SEQ CASCADE; +DROP SEQUENCE IF EXISTS organisations_id_seq CASCADE; + +-- ============================================ +-- ÉTAPE 3: Supprimer les tables existantes (pour recréation avec UUID) +-- ============================================ + +-- Supprimer les tables dans l'ordre inverse des dépendances +DROP TABLE IF EXISTS inscriptions_evenement CASCADE; +DROP TABLE IF EXISTS demandes_aide CASCADE; +DROP TABLE IF EXISTS cotisations CASCADE; +DROP TABLE IF EXISTS evenements CASCADE; +DROP TABLE IF EXISTS membres CASCADE; +DROP TABLE IF EXISTS organisations CASCADE; + +-- ============================================ +-- ÉTAPE 4: Recréer les tables avec UUID +-- ============================================ + +-- Table organisations avec UUID +CREATE TABLE organisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Informations de base + nom VARCHAR(200) NOT NULL, + nom_court VARCHAR(50), + type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION', + statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + description TEXT, + date_fondation DATE, + numero_enregistrement VARCHAR(100) UNIQUE, + + -- Informations de contact + email VARCHAR(255) NOT NULL UNIQUE, + telephone VARCHAR(20), + telephone_secondaire VARCHAR(20), + email_secondaire VARCHAR(255), + + -- Adresse + adresse VARCHAR(500), + ville VARCHAR(100), + code_postal VARCHAR(20), + region VARCHAR(100), + pays VARCHAR(100), + + -- Coordonnées géographiques + latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90), + longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180), + + -- Web et réseaux sociaux + site_web VARCHAR(500), + logo VARCHAR(500), + reseaux_sociaux VARCHAR(1000), + + -- Hiérarchie + organisation_parente_id UUID, + niveau_hierarchique INTEGER NOT NULL DEFAULT 0, + + -- Statistiques + nombre_membres INTEGER NOT NULL DEFAULT 0, + nombre_administrateurs INTEGER NOT NULL DEFAULT 0, + + -- Finances + budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0), + devise VARCHAR(3) DEFAULT 'XOF', + cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE, + montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0), + + -- Informations complémentaires + objectifs TEXT, + activites_principales TEXT, + certifications VARCHAR(500), + partenaires VARCHAR(1000), + notes VARCHAR(1000), + + -- Paramètres + organisation_publique BOOLEAN NOT NULL DEFAULT TRUE, + accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + -- Contraintes + CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')), + CONSTRAINT chk_organisation_type CHECK (type_organisation IN ( + 'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE', + 'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE' + )), + CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')), + CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10), + CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0), + CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0), + + -- Clé étrangère pour hiérarchie + CONSTRAINT fk_organisation_parente FOREIGN KEY (organisation_parente_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table membres avec UUID +CREATE TABLE membres ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_membre VARCHAR(20) UNIQUE NOT NULL, + prenom VARCHAR(100) NOT NULL, + nom VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + mot_de_passe VARCHAR(255), + telephone VARCHAR(20), + date_naissance DATE NOT NULL, + date_adhesion DATE NOT NULL, + roles VARCHAR(500), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_membre_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table cotisations avec UUID +CREATE TABLE cotisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_reference VARCHAR(50) UNIQUE NOT NULL, + membre_id UUID NOT NULL, + type_cotisation VARCHAR(50) NOT NULL, + montant_du DECIMAL(12,2) NOT NULL CHECK (montant_du >= 0), + montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (montant_paye >= 0), + code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF', + statut VARCHAR(30) NOT NULL, + date_echeance DATE NOT NULL, + date_paiement TIMESTAMP, + description VARCHAR(500), + periode VARCHAR(20), + annee INTEGER NOT NULL CHECK (annee >= 2020 AND annee <= 2100), + mois INTEGER CHECK (mois >= 1 AND mois <= 12), + observations VARCHAR(1000), + recurrente BOOLEAN NOT NULL DEFAULT FALSE, + nombre_rappels INTEGER NOT NULL DEFAULT 0 CHECK (nombre_rappels >= 0), + date_dernier_rappel TIMESTAMP, + valide_par_id UUID, + nom_validateur VARCHAR(100), + date_validation TIMESTAMP, + methode_paiement VARCHAR(50), + reference_paiement VARCHAR(100), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_cotisation_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT chk_cotisation_statut CHECK (statut IN ('EN_ATTENTE', 'PAYEE', 'EN_RETARD', 'PARTIELLEMENT_PAYEE', 'ANNULEE')), + CONSTRAINT chk_cotisation_devise CHECK (code_devise ~ '^[A-Z]{3}$') +); + +-- Table evenements avec UUID +CREATE TABLE evenements ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description VARCHAR(2000), + date_debut TIMESTAMP NOT NULL, + date_fin TIMESTAMP, + lieu VARCHAR(255) NOT NULL, + adresse VARCHAR(500), + ville VARCHAR(100), + pays VARCHAR(100), + code_postal VARCHAR(20), + latitude DECIMAL(9,6), + longitude DECIMAL(9,6), + type_evenement VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + url_inscription VARCHAR(500), + url_informations VARCHAR(500), + image_url VARCHAR(500), + capacite_max INTEGER, + cout_participation DECIMAL(12,2), + devise VARCHAR(3), + est_public BOOLEAN NOT NULL DEFAULT TRUE, + tags VARCHAR(500), + notes VARCHAR(1000), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_evenement_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table inscriptions_evenement avec UUID +CREATE TABLE inscriptions_evenement ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + membre_id UUID NOT NULL, + evenement_id UUID NOT NULL, + date_inscription TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + statut VARCHAR(20) DEFAULT 'CONFIRMEE', + commentaire VARCHAR(500), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_inscription_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_inscription_evenement FOREIGN KEY (evenement_id) + REFERENCES evenements(id) ON DELETE CASCADE, + CONSTRAINT chk_inscription_statut CHECK (statut IN ('CONFIRMEE', 'EN_ATTENTE', 'ANNULEE', 'REFUSEE')), + CONSTRAINT uk_inscription_membre_evenement UNIQUE (membre_id, evenement_id) +); + +-- Table demandes_aide avec UUID +CREATE TABLE demandes_aide ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description TEXT NOT NULL, + type_aide VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + montant_demande DECIMAL(10,2), + montant_approuve DECIMAL(10,2), + date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_evaluation TIMESTAMP, + date_versement TIMESTAMP, + justification TEXT, + commentaire_evaluation TEXT, + urgence BOOLEAN NOT NULL DEFAULT FALSE, + documents_fournis VARCHAR(500), + + -- Clés étrangères + demandeur_id UUID NOT NULL, + evaluateur_id UUID, + organisation_id UUID NOT NULL, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_demande_demandeur FOREIGN KEY (demandeur_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id) + REFERENCES membres(id) ON DELETE SET NULL, + CONSTRAINT fk_demande_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE CASCADE +); + +-- ============================================ +-- ÉTAPE 5: Recréer les index +-- ============================================ + +-- Index pour organisations +CREATE INDEX idx_organisation_nom ON organisations(nom); +CREATE INDEX idx_organisation_email ON organisations(email); +CREATE INDEX idx_organisation_statut ON organisations(statut); +CREATE INDEX idx_organisation_type ON organisations(type_organisation); +CREATE INDEX idx_organisation_ville ON organisations(ville); +CREATE INDEX idx_organisation_pays ON organisations(pays); +CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id); +CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement); +CREATE INDEX idx_organisation_actif ON organisations(actif); +CREATE INDEX idx_organisation_date_creation ON organisations(date_creation); +CREATE INDEX idx_organisation_publique ON organisations(organisation_publique); +CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres); +CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif); + +-- Index pour membres +CREATE INDEX idx_membre_email ON membres(email); +CREATE INDEX idx_membre_numero ON membres(numero_membre); +CREATE INDEX idx_membre_actif ON membres(actif); +CREATE INDEX idx_membre_organisation ON membres(organisation_id); + +-- Index pour cotisations +CREATE INDEX idx_cotisation_membre ON cotisations(membre_id); +CREATE INDEX idx_cotisation_reference ON cotisations(numero_reference); +CREATE INDEX idx_cotisation_statut ON cotisations(statut); +CREATE INDEX idx_cotisation_echeance ON cotisations(date_echeance); +CREATE INDEX idx_cotisation_type ON cotisations(type_cotisation); +CREATE INDEX idx_cotisation_annee_mois ON cotisations(annee, mois); + +-- Index pour evenements +CREATE INDEX idx_evenement_date_debut ON evenements(date_debut); +CREATE INDEX idx_evenement_statut ON evenements(statut); +CREATE INDEX idx_evenement_type ON evenements(type_evenement); +CREATE INDEX idx_evenement_organisation ON evenements(organisation_id); + +-- Index pour inscriptions_evenement +CREATE INDEX idx_inscription_membre ON inscriptions_evenement(membre_id); +CREATE INDEX idx_inscription_evenement ON inscriptions_evenement(evenement_id); +CREATE INDEX idx_inscription_date ON inscriptions_evenement(date_inscription); + +-- Index pour demandes_aide +CREATE INDEX idx_demande_demandeur ON demandes_aide(demandeur_id); +CREATE INDEX idx_demande_evaluateur ON demandes_aide(evaluateur_id); +CREATE INDEX idx_demande_organisation ON demandes_aide(organisation_id); +CREATE INDEX idx_demande_statut ON demandes_aide(statut); +CREATE INDEX idx_demande_type ON demandes_aide(type_aide); +CREATE INDEX idx_demande_date_demande ON demandes_aide(date_demande); + +-- ============================================ +-- ÉTAPE 6: Commentaires sur les tables +-- ============================================ + +COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.) avec UUID'; +COMMENT ON TABLE membres IS 'Table des membres avec UUID'; +COMMENT ON TABLE cotisations IS 'Table des cotisations avec UUID'; +COMMENT ON TABLE evenements IS 'Table des événements avec UUID'; +COMMENT ON TABLE inscriptions_evenement IS 'Table des inscriptions aux événements avec UUID'; +COMMENT ON TABLE demandes_aide IS 'Table des demandes d''aide avec UUID'; + +COMMENT ON COLUMN organisations.id IS 'UUID unique de l''organisation'; +COMMENT ON COLUMN membres.id IS 'UUID unique du membre'; +COMMENT ON COLUMN cotisations.id IS 'UUID unique de la cotisation'; +COMMENT ON COLUMN evenements.id IS 'UUID unique de l''événement'; +COMMENT ON COLUMN inscriptions_evenement.id IS 'UUID unique de l''inscription'; +COMMENT ON COLUMN demandes_aide.id IS 'UUID unique de la demande d''aide'; + diff --git a/unionflow-server-impl-quarkus/src/main/resources/import.sql b/unionflow-server-impl-quarkus/src/main/resources/import.sql index 831495b..71a822f 100644 --- a/unionflow-server-impl-quarkus/src/main/resources/import.sql +++ b/unionflow-server-impl-quarkus/src/main/resources/import.sql @@ -1,59 +1,128 @@ --- Script d'insertion de données initiales pour UnionFlow +-- Script d'insertion de données initiales pour UnionFlow (avec UUID) -- Ce fichier est exécuté automatiquement par Hibernate au démarrage -- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create) +-- NOTE: Les IDs sont maintenant des UUID générés automatiquement --- Insertion de membres initiaux -INSERT INTO membres (id, numero_membre, nom, prenom, email, telephone, date_naissance, date_adhesion, actif, date_creation) VALUES -(1, 'MBR001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225071234567', '1985-03-15', '2023-01-15', true, '2024-01-01 10:00:00'), -(2, 'MBR002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225059876543', '1990-07-22', '2023-02-10', true, '2024-01-01 10:00:00'), -(3, 'MBR003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225012345678', '1988-11-08', '2023-03-05', true, '2024-01-01 10:00:00'), -(4, 'MBR004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225078765432', '1992-05-18', '2023-04-12', true, '2024-01-01 10:00:00'), -(5, 'MBR005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225051122334', '1987-09-30', '2023-05-20', true, '2024-01-01 10:00:00'), -(6, 'MBR006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225015566778', '1991-12-03', '2023-06-08', false, '2024-01-01 10:00:00'), -(7, 'MBR007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225079988776', '1989-04-25', '2023-07-15', true, '2024-01-01 10:00:00'), -(8, 'MBR008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225054433221', '1993-08-14', '2023-08-22', true, '2024-01-01 10:00:00'); +-- Insertion d'organisations initiales avec UUIDs générés +INSERT INTO organisations (id, nom, nom_court, type_organisation, statut, description, + email, telephone, adresse, ville, region, pays, + objectifs, activites_principales, nombre_membres, + organisation_publique, accepte_nouveaux_membres, cree_par, + actif, date_creation, niveau_hierarchique, nombre_administrateurs, cotisation_obligatoire) VALUES +( + gen_random_uuid(), + 'Lions Club Abidjan Plateau', + 'LC Plateau', + 'LIONS_CLUB', + 'ACTIVE', + 'Lions Club du district 403 A1, zone Plateau d''Abidjan', + 'plateau@lionsclub-ci.org', + '+225 27 20 21 22 23', + 'Immeuble SCIAM, Boulevard de la République', + 'Abidjan', + 'Lagunes', + 'Côte d''Ivoire', + 'Servir la communauté par des actions humanitaires et sociales', + 'Actions de santé, éducation, environnement et aide aux démunis', + 45, + true, + true, + 'system', + true, + CURRENT_TIMESTAMP, + 0, + 0, + false +), +( + gen_random_uuid(), + 'Lions Club Abidjan Cocody', + 'LC Cocody', + 'LIONS_CLUB', + 'ACTIVE', + 'Lions Club du district 403 A1, zone Cocody', + 'cocody@lionsclub-ci.org', + '+225 27 22 44 55 66', + 'Riviera Golf, Cocody', + 'Abidjan', + 'Lagunes', + 'Côte d''Ivoire', + 'Servir la communauté par des actions humanitaires et sociales', + 'Actions de santé, éducation, environnement et aide aux démunis', + 38, + true, + true, + 'system', + true, + CURRENT_TIMESTAMP, + 0, + 0, + false +), +( + gen_random_uuid(), + 'Association des Femmes Entrepreneures CI', + 'AFECI', + 'ASSOCIATION', + 'ACTIVE', + 'Association pour la promotion de l''entrepreneuriat féminin en Côte d''Ivoire', + 'contact@afeci.org', + '+225 05 06 07 08 09', + 'Marcory Zone 4C', + 'Abidjan', + 'Lagunes', + 'Côte d''Ivoire', + 'Promouvoir l''entrepreneuriat féminin et l''autonomisation des femmes', + 'Formation, accompagnement, financement de projets féminins', + 120, + true, + true, + 'system', + true, + CURRENT_TIMESTAMP, + 0, + 0, + false +), +( + gen_random_uuid(), + 'Coopérative Agricole du Nord', + 'COOP-NORD', + 'COOPERATIVE', + 'ACTIVE', + 'Coopérative des producteurs agricoles du Nord de la Côte d''Ivoire', + 'info@coop-nord.ci', + '+225 09 10 11 12 13', + 'Korhogo Centre', + 'Korhogo', + 'Savanes', + 'Côte d''Ivoire', + 'Améliorer les conditions de vie des producteurs agricoles', + 'Production, transformation et commercialisation de produits agricoles', + 250, + true, + true, + 'system', + true, + CURRENT_TIMESTAMP, + 0, + 0, + false +); --- Insertion de cotisations initiales avec différents statuts -INSERT INTO cotisations (id, numero_reference, membre_id, type_cotisation, montant_du, montant_paye, statut, date_echeance, date_creation, periode, description, annee, mois, code_devise, recurrente, nombre_rappels) VALUES --- Cotisations payées -(1, 'COT-2024-001', 1, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-01-31', '2024-01-01 10:00:00', 'Janvier 2024', 'Cotisation mensuelle janvier', 2024, 1, 'XOF', true, 0), -(2, 'COT-2024-002', 2, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-01-31', '2024-01-01 10:00:00', 'Janvier 2024', 'Cotisation mensuelle janvier', 2024, 1, 'XOF', true, 0), -(3, 'COT-2024-003', 3, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-02-29', '2024-02-01 10:00:00', 'Février 2024', 'Cotisation mensuelle février', 2024, 2, 'XOF', true, 0), +-- Insertion de membres initiaux (avec UUIDs générés et références aux organisations) +-- On utilise des sous-requêtes pour récupérer les IDs des organisations par leur nom +INSERT INTO membres (id, numero_membre, nom, prenom, email, telephone, date_naissance, date_adhesion, actif, date_creation, organisation_id) VALUES +(gen_random_uuid(), 'MBR001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225071234567', '1985-03-15', '2023-01-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)), +(gen_random_uuid(), 'MBR002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225059876543', '1990-07-22', '2023-02-10', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)), +(gen_random_uuid(), 'MBR003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225012345678', '1988-11-08', '2023-03-05', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)), +(gen_random_uuid(), 'MBR004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225078765432', '1992-05-18', '2023-04-12', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)), +(gen_random_uuid(), 'MBR005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225051122334', '1987-09-30', '2023-05-20', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)), +(gen_random_uuid(), 'MBR006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225015566778', '1991-12-03', '2023-06-08', false, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)), +(gen_random_uuid(), 'MBR007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225079988776', '1989-04-25', '2023-07-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1)), +(gen_random_uuid(), 'MBR008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225054433221', '1993-08-14', '2023-08-22', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1)); --- Cotisations en attente -(4, 'COT-2024-004', 4, 'MENSUELLE', 25000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 0), -(5, 'COT-2024-005', 5, 'MENSUELLE', 25000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 0), - --- Cotisations en retard -(6, 'COT-2024-006', 6, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-11-30', '2024-11-01 10:00:00', 'Novembre 2024', 'Cotisation mensuelle novembre', 2024, 11, 'XOF', true, 2), -(7, 'COT-2024-007', 7, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-10-31', '2024-10-01 10:00:00', 'Octobre 2024', 'Cotisation mensuelle octobre', 2024, 10, 'XOF', true, 3), - --- Cotisations partiellement payées -(8, 'COT-2024-008', 8, 'MENSUELLE', 25000.00, 15000.00, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 1), - --- Cotisations spéciales -(9, 'COT-2024-009', 1, 'ADHESION', 50000.00, 50000.00, 'PAYEE', '2024-01-15', '2024-01-01 10:00:00', 'Adhésion 2024', 'Frais d''adhésion annuelle', 2024, null, 'XOF', false, 0), -(10, 'COT-2024-010', 2, 'EVENEMENT', 15000.00, 15000.00, 'PAYEE', '2024-06-15', '2024-06-01 10:00:00', 'Assemblée Générale', 'Participation assemblée générale', 2024, 6, 'XOF', false, 0), -(11, 'COT-2024-011', 3, 'SOLIDARITE', 10000.00, 10000.00, 'PAYEE', '2024-03-31', '2024-03-01 10:00:00', 'Aide Solidarité', 'Contribution solidarité membre', 2024, 3, 'XOF', false, 0), -(12, 'COT-2024-012', 4, 'ANNUELLE', 300000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-01-01 10:00:00', 'Annuelle 2024', 'Cotisation annuelle complète', 2024, null, 'XOF', false, 0), -(13, 'COT-2024-013', 5, 'FORMATION', 75000.00, 75000.00, 'PAYEE', '2024-09-30', '2024-09-01 10:00:00', 'Formation 2024', 'Formation en leadership associatif', 2024, 9, 'XOF', false, 0), -(14, 'COT-2024-014', 6, 'PROJET', 100000.00, 50000.00, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-11-01 10:00:00', 'Projet Communauté', 'Contribution projet développement', 2024, 11, 'XOF', false, 1), -(15, 'COT-2024-015', 7, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-09-30', '2024-09-01 10:00:00', 'Septembre 2024', 'Cotisation mensuelle septembre', 2024, 9, 'XOF', true, 4); - --- Insertion d'événements initiaux -INSERT INTO evenements (id, titre, description, date_debut, date_fin, lieu, adresse, type_evenement, statut, capacite_max, prix, inscription_requise, date_limite_inscription, instructions_particulieres, contact_organisateur, visible_public, actif, date_creation) VALUES -(1, 'Assemblee Generale Annuelle 2025', 'Assemblee generale annuelle de l''union pour presenter le bilan de l''annee et les perspectives futures.', '2025-11-15 09:00:00', '2025-11-15 17:00:00', 'Salle de Conference du Palais des Congres', 'Boulevard de la Republique, Abidjan', 'ASSEMBLEE_GENERALE', 'PLANIFIE', 200, 0.00, true, '2025-11-10 23:59:59', 'Merci de confirmer votre presence avant le 10 novembre. Tenue formelle exigee.', 'secretariat@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(2, 'Formation Leadership et Gestion d''Equipe', 'Formation intensive sur les techniques de leadership moderne et la gestion d''equipe efficace.', '2025-10-20 08:00:00', '2025-10-22 18:00:00', 'Centre de Formation Lions', 'Cocody, Abidjan', 'FORMATION', 'CONFIRME', 50, 25000.00, true, '2025-10-15 23:59:59', 'Formation sur 3 jours. Hebergement et restauration inclus. Apporter un ordinateur portable.', 'formation@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(3, 'Conference sur la Sante Communautaire', 'Conference avec des experts de la sante sur les enjeux de sante publique en Cote d''Ivoire.', '2025-10-25 14:00:00', '2025-10-25 18:00:00', 'Auditorium de l''Universite', 'Cocody, Abidjan', 'CONFERENCE', 'PLANIFIE', 300, 0.00, false, null, 'Entree libre. Certificat de participation disponible.', 'sante@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(4, 'Atelier Developpement Personnel', 'Atelier pratique sur le developpement personnel et la gestion du stress.', '2025-11-05 09:00:00', '2025-11-05 13:00:00', 'Salle Polyvalente', 'Plateau, Abidjan', 'ATELIER', 'PLANIFIE', 30, 5000.00, true, '2025-11-01 23:59:59', 'Places limitees. Inscription obligatoire.', 'ateliers@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(5, 'Soiree de Gala de Fin d''Annee', 'Grande soiree de gala pour celebrer les realisations de l''annee et renforcer les liens entre membres.', '2025-12-20 19:00:00', '2025-12-20 23:59:59', 'Hotel Ivoire', 'Cocody, Abidjan', 'EVENEMENT_SOCIAL', 'PLANIFIE', 150, 50000.00, true, '2025-12-10 23:59:59', 'Tenue de soiree obligatoire. Diner et animations inclus.', 'gala@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(6, 'Reunion Mensuelle - Octobre 2025', 'Reunion mensuelle de coordination des activites et suivi des projets en cours.', '2025-10-10 18:00:00', '2025-10-10 20:00:00', 'Siege de l''Union', 'Plateau, Abidjan', 'REUNION', 'TERMINE', 40, 0.00, false, null, 'Reserve aux membres du bureau et responsables de commissions.', 'secretariat@unionflow.ci', false, true, '2024-01-01 10:00:00'), -(7, 'Seminaire sur l''Entrepreneuriat Social', 'Seminaire de deux jours sur l''entrepreneuriat social et l''innovation au service de la communaute.', '2025-11-25 08:00:00', '2025-11-26 17:00:00', 'Centre d''Innovation', 'Yopougon, Abidjan', 'SEMINAIRE', 'PLANIFIE', 80, 15000.00, true, '2025-11-20 23:59:59', 'Seminaire sur 2 jours. Pause-cafe et dejeuner inclus. Supports de formation fournis.', 'entrepreneuriat@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(8, 'Journee Caritative - Soutien aux Ecoles', 'Manifestation caritative pour collecter des fournitures scolaires et des dons pour les ecoles defavorisees.', '2025-10-30 08:00:00', '2025-10-30 16:00:00', 'Place de la Republique', 'Plateau, Abidjan', 'MANIFESTATION', 'CONFIRME', 500, 0.00, false, null, 'Apportez vos dons de fournitures scolaires. Benevoles bienvenus.', 'charite@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(9, 'Celebration 50 ans de l''Union', 'Grande celebration pour les 50 ans d''existence de l''union avec spectacles et temoignages.', '2025-12-15 15:00:00', '2025-12-15 22:00:00', 'Stade Felix Houphouet-Boigny', 'Abidjan', 'CELEBRATION', 'PLANIFIE', 5000, 10000.00, true, '2025-12-01 23:59:59', 'Evenement historique. Spectacles, animations, buffet. Tenue festive recommandee.', 'anniversaire@unionflow.ci', true, true, '2024-01-01 10:00:00'), -(10, 'Journee Portes Ouvertes', 'Decouvrez les activites de l''union et rencontrez nos membres. Activites pour toute la famille.', '2025-11-01 10:00:00', '2025-11-01 18:00:00', 'Siege de l''Union', 'Plateau, Abidjan', 'AUTRE', 'PLANIFIE', 1000, 0.00, false, null, 'Entree libre. Animations, stands d''information, demonstrations.', 'info@unionflow.ci', true, true, '2024-01-01 10:00:00'); - --- Mise à jour des séquences pour éviter les conflits -ALTER SEQUENCE membres_SEQ RESTART WITH 50; -ALTER SEQUENCE cotisations_SEQ RESTART WITH 50; -ALTER SEQUENCE evenements_SEQ RESTART WITH 50; +-- Note: Les insertions de cotisations et événements avec des références aux membres +-- nécessitent de récupérer les UUIDs réels des membres insérés ci-dessus. +-- Pour le développement, ces données peuvent être insérées via l'application ou +-- via des scripts de test plus complexes qui récupèrent les UUIDs générés. diff --git a/unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java b/unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java index cab208e..dd5286f 100644 --- a/unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java +++ b/unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java @@ -6,6 +6,8 @@ import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import io.quarkus.test.junit.QuarkusTest; @@ -28,106 +30,86 @@ import org.junit.jupiter.api.*; class MembreServiceAdvancedSearchTest { @Inject MembreService membreService; + @Inject MembreRepository membreRepository; + @Inject OrganisationRepository organisationRepository; - private static Organisation testOrganisation; - private static List testMembres; + private Organisation testOrganisation; + private List testMembres; - @BeforeAll + @BeforeEach @Transactional - static void setupTestData() { - // Créer une organisation de test + void setupTestData() { + // Créer et persister une organisation de test testOrganisation = Organisation.builder() .nom("Organisation Test") .typeOrganisation("ASSOCIATION") .statut("ACTIF") - .actif(true) - .dateCreation(LocalDateTime.now()) .build(); - testOrganisation.persist(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); // Créer des membres de test avec différents profils testMembres = List.of( // Membre actif jeune - Membre.builder() - .numeroMembre("UF-2025-TEST001") - .nom("Dupont") - .prenom("Marie") - .email("marie.dupont@test.com") - .telephone("+221701234567") - .dateNaissance(LocalDate.of(1995, 5, 15)) - .dateAdhesion(LocalDate.of(2023, 1, 15)) - .roles("MEMBRE,SECRETAIRE") - .actif(true) - .organisation(testOrganisation) - .dateCreation(LocalDateTime.now()) - .build(), + createMembre("UF-2025-TEST001", "Dupont", "Marie", "marie.dupont@test.com", + "+221701234567", LocalDate.of(1995, 5, 15), LocalDate.of(2023, 1, 15), + "MEMBRE,SECRETAIRE", true), // Membre actif âgé - Membre.builder() - .numeroMembre("UF-2025-TEST002") - .nom("Martin") - .prenom("Jean") - .email("jean.martin@test.com") - .telephone("+221701234568") - .dateNaissance(LocalDate.of(1970, 8, 20)) - .dateAdhesion(LocalDate.of(2020, 3, 10)) - .roles("MEMBRE,PRESIDENT") - .actif(true) - .organisation(testOrganisation) - .dateCreation(LocalDateTime.now()) - .build(), + createMembre("UF-2025-TEST002", "Martin", "Jean", "jean.martin@test.com", + "+221701234568", LocalDate.of(1970, 8, 20), LocalDate.of(2020, 3, 10), + "MEMBRE,PRESIDENT", true), // Membre inactif - Membre.builder() - .numeroMembre("UF-2025-TEST003") - .nom("Diallo") - .prenom("Fatou") - .email("fatou.diallo@test.com") - .telephone("+221701234569") - .dateNaissance(LocalDate.of(1985, 12, 3)) - .dateAdhesion(LocalDate.of(2021, 6, 5)) - .roles("MEMBRE") - .actif(false) - .organisation(testOrganisation) - .dateCreation(LocalDateTime.now()) - .build(), + createMembre("UF-2025-TEST003", "Diallo", "Fatou", "fatou.diallo@test.com", + "+221701234569", LocalDate.of(1985, 12, 3), LocalDate.of(2021, 6, 5), + "MEMBRE", false), // Membre avec email spécifique - Membre.builder() - .numeroMembre("UF-2025-TEST004") - .nom("Sow") - .prenom("Amadou") - .email("amadou.sow@unionflow.com") - .telephone("+221701234570") - .dateNaissance(LocalDate.of(1988, 3, 12)) - .dateAdhesion(LocalDate.of(2022, 9, 20)) - .roles("MEMBRE,TRESORIER") - .actif(true) - .organisation(testOrganisation) - .dateCreation(LocalDateTime.now()) - .build()); + createMembre("UF-2025-TEST004", "Sow", "Amadou", "amadou.sow@unionflow.com", + "+221701234570", LocalDate.of(1988, 3, 12), LocalDate.of(2022, 9, 20), + "MEMBRE,TRESORIER", true)); // Persister tous les membres - testMembres.forEach(membre -> membre.persist()); + testMembres.forEach(membre -> membreRepository.persist(membre)); } - @AfterAll + private Membre createMembre(String numero, String nom, String prenom, String email, + String telephone, LocalDate dateNaissance, LocalDate dateAdhesion, + String roles, boolean actif) { + Membre membre = Membre.builder() + .numeroMembre(numero) + .nom(nom) + .prenom(prenom) + .email(email) + .telephone(telephone) + .dateNaissance(dateNaissance) + .dateAdhesion(dateAdhesion) + .roles(roles) + .organisation(testOrganisation) + .build(); + membre.setDateCreation(LocalDateTime.now()); + membre.setActif(actif); + return membre; + } + + @AfterEach @Transactional - static void cleanupTestData() { + void cleanupTestData() { // Nettoyer les données de test if (testMembres != null) { - testMembres.forEach( - membre -> { - if (membre.isPersistent()) { - membre.delete(); - } - }); + testMembres.forEach(membre -> { + if (membre.getId() != null) { + membreRepository.delete(membre); + } + }); } - if (testOrganisation != null && testOrganisation.isPersistent()) { - testOrganisation.delete(); + if (testOrganisation != null && testOrganisation.getId() != null) { + organisationRepository.delete(testOrganisation); } }