Refactoring - Version OK
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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("=====================================");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user