Refactoring - Version OK

This commit is contained in:
dahoud
2025-11-17 15:28:34 +00:00
parent edaa5b17ea
commit 40fdbedcad
43 changed files with 3564 additions and 5673 deletions

View File

@@ -177,6 +177,13 @@
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

View File

@@ -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";

View File

@@ -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

View File

@@ -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
*
* <p>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 extends BaseEntity> 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");
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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<InscriptionEvenement> 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();
}
}

View File

@@ -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,

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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
*
* <p>Remplace PanacheRepository pour utiliser UUID au lieu de Long.
* Fournit les fonctionnalités de base de Panache avec UUID.
*
* @param <T> Le type d'entité qui étend BaseEntity
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
public abstract class BaseRepository<T extends BaseEntity> {
@PersistenceContext
protected EntityManager entityManager;
protected final Class<T> entityClass;
protected BaseRepository(Class<T> 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<T> 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<T> 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;
}
}

View File

@@ -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<Cotisation> {
public class CotisationRepository extends BaseRepository<Cotisation> {
/**
* 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<Cotisation> 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<Cotisation> 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<Cotisation> 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<Cotisation> 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<Cotisation> 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<Cotisation> 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<Cotisation> rechercheAvancee(
Long membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) {
StringBuilder query = new StringBuilder("1=1");
Map<String, Object> 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<Cotisation> findByNumeroReference(String numeroReference) {
TypedQuery<Cotisation> 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<Cotisation> findByMembreId(UUID membreId, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Cotisation> 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<Cotisation> findByStatut(String statut, Page page) {
TypedQuery<Cotisation> 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<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
TypedQuery<Cotisation> 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<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
TypedQuery<Cotisation> 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<Cotisation> findByType(String typeCotisation, Page page) {
TypedQuery<Cotisation> 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<Cotisation> 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<String, Object> 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<Cotisation> 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<String, Object> 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<Cotisation> query = entityManager.createQuery(jpql.toString(), Cotisation.class);
for (Map.Entry<String, Object> 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<BigDecimal> 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<BigDecimal> 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<Long> 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<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
TypedQuery<Cotisation> 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<String, Object> 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<Long> countQuery;
TypedQuery<BigDecimal> montantTotalQuery;
TypedQuery<BigDecimal> montantPayeQuery;
TypedQuery<Long> 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<BigDecimal> 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<BigDecimal> 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();
}
}

View File

@@ -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<DemandeAide, UUID> {
public class DemandeAideRepository extends BaseRepository<DemandeAide> {
/** Trouve toutes les demandes d'aide par organisation */
public List<DemandeAide> 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<DemandeAide> 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<DemandeAide> findByOrganisationId(UUID organisationId) {
TypedQuery<DemandeAide> 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<DemandeAide> findByDemandeurId(UUID demandeurId) {
return find("demandeur.id", demandeurId).list();
}
/** Trouve toutes les demandes d'aide par organisation avec pagination */
public List<DemandeAide> findByOrganisationId(UUID organisationId, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY d.dateDemande DESC";
TypedQuery<DemandeAide> 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<DemandeAide> findByStatut(StatutAide statut) {
return find("statut", statut).list();
}
/** Trouve toutes les demandes d'aide par demandeur */
public List<DemandeAide> findByDemandeurId(UUID demandeurId) {
TypedQuery<DemandeAide> 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<DemandeAide> 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<DemandeAide> findByStatut(StatutAide statut) {
TypedQuery<DemandeAide> 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<DemandeAide> findByTypeAide(TypeAide typeAide) {
return find("typeAide", typeAide).list();
}
/** Trouve toutes les demandes d'aide par statut et organisation */
public List<DemandeAide> findByStatutAndOrganisationId(StatutAide statut, UUID organisationId) {
TypedQuery<DemandeAide> 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<DemandeAide> findUrgentes() {
return find("urgence", true).list();
}
/** Trouve toutes les demandes d'aide par type */
public List<DemandeAide> findByTypeAide(TypeAide typeAide) {
TypedQuery<DemandeAide> 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<DemandeAide> findUrgentesByOrganisationId(UUID organisationId) {
return find("urgence = true and organisation.id = ?1", organisationId).list();
}
/** Trouve toutes les demandes d'aide urgentes */
public List<DemandeAide> findUrgentes() {
TypedQuery<DemandeAide> 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<DemandeAide> 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<DemandeAide> findUrgentesByOrganisationId(UUID organisationId) {
TypedQuery<DemandeAide> 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<DemandeAide> 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<DemandeAide> findByPeriode(LocalDateTime debut, LocalDateTime fin) {
TypedQuery<DemandeAide> 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<DemandeAide> findByPeriodeAndOrganisationId(
LocalDateTime debut, LocalDateTime fin, UUID organisationId) {
TypedQuery<DemandeAide> 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<Long> 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<BigDecimal> 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<Long> 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<BigDecimal> 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<BigDecimal> sumMontantDemandeByOrganisationId(UUID organisationId) {
TypedQuery<BigDecimal> 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<DemandeAide> 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<BigDecimal> sumMontantApprouveByOrganisationId(UUID organisationId) {
TypedQuery<BigDecimal> 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<DemandeAide> 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<DemandeAide> findRecentes() {
LocalDateTime il30Jours = LocalDateTime.now().minusDays(30);
TypedQuery<DemandeAide> 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<DemandeAide> 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<DemandeAide> findRecentesByOrganisationId(UUID organisationId) {
LocalDateTime il30Jours = LocalDateTime.now().minusDays(30);
TypedQuery<DemandeAide> 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<DemandeAide> findByEvaluateurId(UUID evaluateurId) {
return find("evaluateur.id", evaluateurId).list();
}
/** Trouve les demandes d'aide en attente depuis plus de X jours */
public List<DemandeAide> findEnAttenteDepuis(int nombreJours) {
LocalDateTime dateLimit = LocalDateTime.now().minusDays(nombreJours);
TypedQuery<DemandeAide> 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<DemandeAide> 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<DemandeAide> findByEvaluateurId(UUID evaluateurId) {
TypedQuery<DemandeAide> 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<DemandeAide> findEnCoursEvaluationByEvaluateurId(UUID evaluateurId) {
TypedQuery<DemandeAide> 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<Long> 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<Long> 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<BigDecimal> 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();
}
}

View File

@@ -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
*
* <p>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<Evenement> {
public class EvenementRepository extends BaseRepository<Evenement> {
/**
* 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<Evenement> findByTitre(String titre) {
return find("titre", titre).firstResultOptional();
}
/**
* Trouve tous les événements actifs
*
* @return la liste des événements actifs
*/
public List<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<Evenement> 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<String, Object> 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<Evenement> findByTitre(String titre) {
TypedQuery<Evenement> 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<Evenement> findAllActifs() {
TypedQuery<Evenement> 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<Evenement> findAllActifs(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Long> 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<Evenement> findByStatut(StatutEvenement statut) {
TypedQuery<Evenement> 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<Evenement> findByStatut(StatutEvenement statut, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Evenement> findByType(TypeEvenement type) {
TypedQuery<Evenement> 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<Evenement> findByType(TypeEvenement type, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Evenement> findByOrganisation(UUID organisationId) {
TypedQuery<Evenement> 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<String, Object> 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<Evenement> findByOrganisation(UUID organisationId, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Evenement> findEvenementsAVenir() {
TypedQuery<Evenement> 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<Evenement> findEvenementsAVenir(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Evenement> findEvenementsPublics() {
TypedQuery<Evenement> 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<Evenement> findEvenementsPublics(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Evenement> 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<Evenement> 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<String, Object> 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<Evenement> query = entityManager.createQuery(jpql.toString(), Evenement.class);
for (Map.Entry<String, Object> 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<String, Long> getStatistiques() {
Map<String, Long> stats = new HashMap<>();
LocalDateTime maintenant = LocalDateTime.now();
TypedQuery<Long> totalQuery = entityManager.createQuery(
"SELECT COUNT(e) FROM Evenement e", Long.class);
stats.put("total", totalQuery.getSingleResult());
TypedQuery<Long> actifsQuery = entityManager.createQuery(
"SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class);
stats.put("actifs", actifsQuery.getSingleResult());
TypedQuery<Long> inactifsQuery = entityManager.createQuery(
"SELECT COUNT(e) FROM Evenement e WHERE e.actif = false", Long.class);
stats.put("inactifs", inactifsQuery.getSingleResult());
TypedQuery<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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<Double> 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<Long> 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<String, Long> getStatistiques() {
Map<String, Long> 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();
}
}

View File

@@ -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<Membre> {
public class MembreRepository extends BaseRepository<Membre> {
/** Trouve un membre par son email */
public Optional<Membre> findByEmail(String email) {
return find("email", email).firstResultOptional();
}
/** Trouve un membre par son numéro */
public Optional<Membre> findByNumeroMembre(String numeroMembre) {
return find("numeroMembre", numeroMembre).firstResultOptional();
}
/** Trouve tous les membres actifs */
public List<Membre> 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<Membre> 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<Membre> 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<Membre> 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<Membre> findByStatut(boolean actif, Page page, Sort sort) {
return find("actif", sort, actif).page(page).list();
}
/** Trouve les membres par tranche d'âge */
public List<Membre> 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<Membre> rechercheAvancee(
String recherche,
Boolean actif,
LocalDate dateAdhesionMin,
LocalDate dateAdhesionMax,
Page page,
Sort sort) {
StringBuilder query = new StringBuilder("1=1");
java.util.Map<String, Object> 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<Membre> findByEmail(String email) {
TypedQuery<Membre> 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<Membre> findByNumeroMembre(String numeroMembre) {
TypedQuery<Membre> 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<Membre> findAllActifs() {
TypedQuery<Membre> 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<Long> 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<Membre> findByNomOrPrenom(String recherche) {
TypedQuery<Membre> 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<Membre> findAllActifs(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Membre> 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<Membre> findByNomOrPrenom(String recherche, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Membre> 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<Long> 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<Membre> findByStatut(boolean actif, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Membre> 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<Membre> 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<Membre> 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<Membre> 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<Membre> 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<Long> 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<Long> 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<Double> 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();
}
}

View File

@@ -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<Organisation> {
public class OrganisationRepository extends BaseRepository<Organisation> {
/**
* Trouve une organisation par son email
*
* @param email l'email de l'organisation
* @return Optional contenant l'organisation si trouvée
*/
public Optional<Organisation> 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<Organisation> 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<Organisation> findByNumeroEnregistrement(String numeroEnregistrement) {
return find("numeroEnregistrement = ?1", numeroEnregistrement).firstResultOptional();
}
/**
* Trouve toutes les organisations actives
*
* @return liste des organisations actives
*/
public List<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> 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<Organisation> rechercheAvancee(
String nom,
String typeOrganisation,
String statut,
String ville,
String region,
String pays,
Page page) {
StringBuilder query = new StringBuilder("1=1");
Map<String, Object> 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<Organisation> findByEmail(String email) {
TypedQuery<Organisation> 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<Organisation> findByNom(String nom) {
TypedQuery<Organisation> 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<Organisation> findByNumeroEnregistrement(String numeroEnregistrement) {
TypedQuery<Organisation> 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<Organisation> findAllActives() {
TypedQuery<Organisation> 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<Organisation> findAllActives(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Long> 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<Organisation> findByStatut(String statut, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> 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<Organisation> findByType(String typeOrganisation, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> 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<Organisation> findByVille(String ville, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findByPays(String pays, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findByRegion(String region, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findByOrganisationParente(
UUID organisationParenteId, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findOrganisationsRacines(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findByNomOrNomCourt(String recherche, Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> 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<String, Object> 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<Organisation> query = entityManager.createQuery(
queryBuilder.toString(), Organisation.class);
for (Map.Entry<String, Object> 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<Long> 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<Organisation> findOrganisationsPubliques(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Organisation> findOrganisationsOuvertes(Page page, Sort sort) {
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
TypedQuery<Organisation> 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<Long> 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<Long> 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();
}
}

View File

@@ -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<AideDTO> 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<String, Object> 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<String, String> 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<String, Object> 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<AideDTO> 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<AideDTO> 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<AideDTO> 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<AideDTO> 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<String, Object> 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<AideDTO> 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();
}
}
}

View File

@@ -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<Map<String, Object>> 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<CotisationDTO> cotisationsDTO = cotisationService.getAllCotisations(page, size);
// Convertir en format pour l'application mobile
List<Map<String, Object>> cotisations = cotisationsDTO.stream()
.map(c -> {
Map<String, Object> 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<String, Object> 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,

View File

@@ -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
*
* <p>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<RecentActivityDTO> activities = dashboardService.getRecentActivities(
organizationId, userId, limit);
Map<String, Object> 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<UpcomingEventDTO> events = dashboardService.getUpcomingEvents(
organizationId, userId, limit);
Map<String, Object> 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<String, Object> 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<String, Object> 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();
}
}
}

View File

@@ -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> 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) {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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<String> getCurrentUserRoles() {
if (securityIdentity == null || securityIdentity.isAnonymous()) {
return Set.of();
}
try {
Set<String> 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> T getClaim(String claimName, Class<T> 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<String> 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("=====================================");
}
}

View File

@@ -47,11 +47,14 @@ public class CotisationService {
public List<CotisationDTO> getAllCotisations(int page, int size) {
log.debug("Récupération des cotisations - page: {}, size: {}", page, size);
List<Cotisation> cotisations =
cotisationRepository
.findAll(Sort.by("dateEcheance").descending())
.page(Page.of(page, size))
.list();
// Utilisation de EntityManager pour la pagination
jakarta.persistence.TypedQuery<Cotisation> query =
cotisationRepository.getEntityManager().createQuery(
"SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC",
Cotisation.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
List<Cotisation> 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<CotisationDTO> getCotisationsByMembre(@NotNull Long membreId, int page, int size) {
public List<CotisationDTO> 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<CotisationDTO> 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());

View File

@@ -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
*
* <p>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<DemandeAide> 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<RecentActivityDTO> 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<RecentActivityDTO> activities = new ArrayList<>();
// Récupérer les membres récemment créés
List<Membre> 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<Evenement> tousEvenements = evenementRepository.listAll();
List<Evenement> 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<Cotisation> 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<UpcomingEventDTO> 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<Evenement> 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<BigDecimal> 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<String, Object> getUserPreferences(String userId) {
Map<String, Object> 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;
}
}

View File

@@ -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<String, DemandeAideDTO> cacheDemandesRecentes = new HashMap<>();
private final Map<String, LocalDateTime> cacheTimestamps = new HashMap<>();
private final Map<UUID, DemandeAideDTO> cacheDemandesRecentes = new HashMap<>();
private final Map<UUID, LocalDateTime> 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<DemandeAideDTO> obtenirDemandesUrgentes(String organisationId) {
public List<DemandeAideDTO> obtenirDemandesUrgentes(UUID organisationId) {
LOG.debugf("Récupération des demandes urgentes pour: %s", organisationId);
Map<String, Object> 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;
}

View File

@@ -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<String, List<EvaluationAideDTO>> cacheEvaluationsParDemande = new HashMap<>();
private final Map<String, List<EvaluationAideDTO>> 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<EvaluationAideDTO> obtenirEvaluationsDemande(@NotBlank String demandeId) {
LOG.debugf("Récupération des évaluations pour la demande: %s", demandeId);
// Vérification du cache
List<EvaluationAideDTO> 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<EvaluationAideDTO> obtenirEvaluationsProposition(@NotBlank String propositionId) {
LOG.debugf("Récupération des évaluations pour la proposition: %s", propositionId);
List<EvaluationAideDTO> 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<String, Object> calculerMoyenneDemande(@NotBlank String demandeId) {
List<EvaluationAideDTO> evaluations = obtenirEvaluationsDemande(demandeId);
if (evaluations.isEmpty()) {
return Map.of(
"noteMoyenne", 0.0,
"nombreEvaluations", 0,
"repartitionNotes", new HashMap<Integer, Integer>()
);
}
double moyenne = evaluations.stream()
.mapToDouble(EvaluationAideDTO::getNoteGlobale)
.average()
.orElse(0.0);
Map<Integer, Integer> 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<String, Object> calculerMoyenneProposition(@NotBlank String propositionId) {
List<EvaluationAideDTO> evaluations = obtenirEvaluationsProposition(propositionId);
if (evaluations.isEmpty()) {
return Map.of(
"noteMoyenne", 0.0,
"nombreEvaluations", 0,
"notesDetaillees", new HashMap<String, Double>()
);
}
double moyenne = evaluations.stream()
.mapToDouble(EvaluationAideDTO::getNoteGlobale)
.average()
.orElse(0.0);
// Calcul des moyennes détaillées
Map<String, Double> 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<EvaluationAideDTO> 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<Double> 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<String, Object> moyenneDemande = calculerMoyenneDemande(evaluation.getDemandeAideId());
// Mise à jour de la moyenne de la proposition si applicable
if (evaluation.getPropositionAideId() != null) {
Map<String, Object> 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<EvaluationAideDTO> evaluations,
java.util.function.Function<EvaluationAideDTO, Double> 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<EvaluationAideDTO> 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<EvaluationAideDTO> 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<EvaluationAideDTO> 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<EvaluationAideDTO> rechercherEvaluations(Map<String, Object> 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<EvaluationAideDTO> 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<EvaluationAideDTO> simulerRecuperationEvaluationsDemande(String demandeId) {
return new ArrayList<>(); // Simulation
}
private List<EvaluationAideDTO> simulerRecuperationEvaluationsProposition(String propositionId) {
return new ArrayList<>(); // Simulation
}
}

View File

@@ -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<Evenement> 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<Evenement> trouverParId(Long id) {
public Optional<Evenement> 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
*

View File

@@ -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<Membre> trouverParId(Long id) {
public Optional<Membre> 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<Long> countQueryTyped = entityManager.createQuery(countQuery, Long.class);
for (Map.Entry<String, Object> 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<Membre> membres = Membre.find(finalQuery, parameters).page(page).list();
TypedQuery<Membre> queryTyped = entityManager.createQuery(finalQuery, Membre.class);
for (Map.Entry<String, Object> param : parameters.entrySet()) {
queryTyped.setParameter(param.getKey(), param.getValue());
}
queryTyped.setFirstResult(page.index * page.size);
queryTyped.setMaxResults(page.size);
List<Membre> membres = queryTyped.getResultList();
// Conversion en DTOs
List<MembreDTO> 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();

View File

@@ -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<UUID, ScheduledNotification> 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<ScheduledNotification> 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<ScheduledNotification> 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<UUID> 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);
}
}
}
}

View File

@@ -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);

View File

@@ -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<Void> 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<Void> 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<Void> 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<Void> 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<String> 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<Void> 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<Void> notifierProposantsCompatibles(DemandeAideDTO demande,
List<PropositionAideDTO> 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<String> 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<String, Object> 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<Void> notifierDemandesCompatibles(PropositionAideDTO proposition,
List<DemandeAideDTO> 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<String, Object> 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<Void> 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<String> 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<String> 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<String, Object> 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<String, Object> 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<String> 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<String> obtenirAdministrateursEtEvaluateurs(String organisationId) {
// Simulation - dans une vraie implémentation, ceci ferait appel au service utilisateur
return List.of("admin1", "evaluateur1", "evaluateur2");
}
private List<String> obtenirEvaluateursDisponibles(String organisationId) {
// Simulation
return List.of("evaluateur1", "evaluateur2", "evaluateur3");
}
private List<String> 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());
}
}

View File

@@ -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<TypeNotification, NotificationTemplate> 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<String, Object> 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<ActionNotificationDTO> 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<String, String> 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<String, Object> 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<String> destinatairesIds,
Map<String, Object> 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<String, Object> construireContexte(NotificationDTO notification) {
Map<String, Object> 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<String, Object> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> 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<ActionNotificationDTO> actionsRapides;
private Map<String, Object> 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<ActionNotificationDTO> getActionsRapides() { return actionsRapides; }
public void setActionsRapides(List<ActionNotificationDTO> actionsRapides) { this.actionsRapides = actionsRapides; }
public Map<String, Object> getDonneesPersonnalisees() { return donneesPersonnalisees; }
public void setDonneesPersonnalisees(Map<String, Object> donneesPersonnalisees) {
this.donneesPersonnalisees = donneesPersonnalisees;
}
}
}

View File

@@ -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<Organisation> trouverParId(Long id) {
public Optional<Organisation> 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;
}

View File

@@ -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<String, Map<String, Object>> cacheStatistiques = new HashMap<>();
private final Map<String, LocalDateTime> 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<String, Object> calculerStatistiquesSolidarite(String organisationId) {
LOG.infof("Calcul des statistiques de solidarité pour: %s", organisationId);
// Vérification du cache
String cacheKey = "stats_" + organisationId;
Map<String, Object> statsCache = obtenirDuCache(cacheKey);
if (statsCache != null) {
return statsCache;
}
try {
Map<String, Object> statistiques = new HashMap<>();
// 1. Statistiques des demandes
Map<String, Object> statsDemandes = calculerStatistiquesDemandes(organisationId);
statistiques.put("demandes", statsDemandes);
// 2. Statistiques des propositions
Map<String, Object> statsPropositions = calculerStatistiquesPropositions(organisationId);
statistiques.put("propositions", statsPropositions);
// 3. Statistiques financières
Map<String, Object> statsFinancieres = calculerStatistiquesFinancieres(organisationId);
statistiques.put("financier", statsFinancieres);
// 4. Indicateurs de performance
Map<String, Object> kpis = calculerKPIsSolidarite(organisationId);
statistiques.put("kpis", kpis);
// 5. Tendances
Map<String, Object> 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<String, Object> calculerStatistiquesDemandes(String organisationId) {
Map<String, Object> filtres = Map.of("organisationId", organisationId);
List<DemandeAideDTO> demandes = demandeAideService.rechercherAvecFiltres(filtres);
Map<String, Object> stats = new HashMap<>();
// Nombre total de demandes
stats.put("total", demandes.size());
// Répartition par statut
Map<StatutAide, Long> repartitionStatut = demandes.stream()
.collect(Collectors.groupingBy(DemandeAideDTO::getStatut, Collectors.counting()));
stats.put("parStatut", repartitionStatut);
// Répartition par type d'aide
Map<TypeAide, Long> repartitionType = demandes.stream()
.collect(Collectors.groupingBy(DemandeAideDTO::getTypeAide, Collectors.counting()));
stats.put("parType", repartitionType);
// Répartition par priorité
Map<PrioriteAide, Long> 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<String, Object> calculerStatistiquesPropositions(String organisationId) {
Map<String, Object> filtres = Map.of("organisationId", organisationId);
List<PropositionAideDTO> propositions = propositionAideService.rechercherAvecFiltres(filtres);
Map<String, Object> 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<TypeAide, Long> 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<String, Object> calculerStatistiquesFinancieres(String organisationId) {
Map<String, Object> filtres = Map.of("organisationId", organisationId);
List<DemandeAideDTO> demandes = demandeAideService.rechercherAvecFiltres(filtres);
List<PropositionAideDTO> propositions = propositionAideService.rechercherAvecFiltres(filtres);
Map<String, Object> 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<String, Object> calculerKPIsSolidarite(String organisationId) {
Map<String, Object> 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<String, Object> calculerTendances(String organisationId) {
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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());
}
}

View File

@@ -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<DemandeAideDTO> 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<PropositionAideDTO> 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<DemandeAideDTO> 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<DemandeAideDTO> 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<PropositionAideDTO> 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<DemandeAideDTO> 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<PropositionAideDTO> rechercherPropositions(Map<String, Object> 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<PropositionAideDTO> 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<DemandeAideDTO> 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<DemandeAideDTO> rechercherDemandes(Map<String, Object> 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<DemandeAideDTO> 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<String, Object> 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<String> 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());
}
}
}

View File

@@ -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)
*
* <p><strong>DÉPRÉCIÉ:</strong> 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.
*
* <p>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
*
* <p><strong>DÉPRÉCIÉ:</strong> Utilisez directement UUID dans vos entités.
*
* <p>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
*
* <p><strong>DÉPRÉCIÉ:</strong> Utilisez directement UUID dans vos entités.
*
* <p>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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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';

View File

@@ -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.

View File

@@ -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<Membre> testMembres;
private Organisation testOrganisation;
private List<Membre> 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);
}
}