Authentification stable - WIP

This commit is contained in:
DahoudG
2025-09-19 12:35:46 +00:00
parent 63fe107f98
commit 098894bdc1
383 changed files with 13072 additions and 93334 deletions

View File

@@ -302,7 +302,7 @@ public class Aide extends PanacheEntity {
* Vérifie si la demande est en cours de traitement
*/
public boolean isEnCoursDeTraitement() {
return this.statut == StatutAide.EN_COURS ||
return this.statut == StatutAide.EN_COURS_EVALUATION ||
this.statut == StatutAide.EN_COURS_VERSEMENT;
}

View File

@@ -0,0 +1,142 @@
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
* Entité représentant une demande d'aide dans le système de solidarité
*/
@Entity
@Table(name = "demandes_aide")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = false)
public class DemandeAide extends PanacheEntity {
@Column(name = "titre", nullable = false, length = 200)
private String titre;
@Column(name = "description", nullable = false, columnDefinition = "TEXT")
private String description;
@Enumerated(EnumType.STRING)
@Column(name = "type_aide", nullable = false)
private TypeAide typeAide;
@Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false)
private StatutAide statut;
@Column(name = "montant_demande", precision = 10, scale = 2)
private BigDecimal montantDemande;
@Column(name = "montant_approuve", precision = 10, scale = 2)
private BigDecimal montantApprouve;
@Column(name = "date_demande", nullable = false)
private LocalDateTime dateDemande;
@Column(name = "date_evaluation")
private LocalDateTime dateEvaluation;
@Column(name = "date_versement")
private LocalDateTime dateVersement;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demandeur_id", nullable = false)
private Membre demandeur;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evaluateur_id")
private Membre evaluateur;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
@Column(name = "justification", columnDefinition = "TEXT")
private String justification;
@Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
private String commentaireEvaluation;
@Column(name = "urgence", nullable = false)
@Builder.Default
private Boolean urgence = false;
@Column(name = "documents_fournis")
private String documentsFournis;
@PrePersist
protected void onCreate() {
if (dateDemande == null) {
dateDemande = LocalDateTime.now();
}
if (statut == null) {
statut = StatutAide.EN_ATTENTE;
}
if (urgence == null) {
urgence = false;
}
}
@PreUpdate
protected void onUpdate() {
// Méthode appelée avant mise à jour
}
/**
* Vérifie si la demande est en attente
*/
public boolean isEnAttente() {
return StatutAide.EN_ATTENTE.equals(statut);
}
/**
* Vérifie si la demande est approuvée
*/
public boolean isApprouvee() {
return StatutAide.APPROUVEE.equals(statut);
}
/**
* Vérifie si la demande est rejetée
*/
public boolean isRejetee() {
return StatutAide.REJETEE.equals(statut);
}
/**
* Vérifie si la demande est urgente
*/
public boolean isUrgente() {
return Boolean.TRUE.equals(urgence);
}
/**
* Calcule le pourcentage d'approbation par rapport au montant demandé
*/
public BigDecimal getPourcentageApprobation() {
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
if (montantApprouve == null) {
return BigDecimal.ZERO;
}
return montantApprouve.divide(montantDemande, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
}
}

View File

@@ -342,7 +342,7 @@ public class AideRepository implements PanacheRepository<Aide> {
// Compteurs par statut
stats.put("total", count("actif = true"));
stats.put("enAttente", count("statut = ?1 and actif = true", StatutAide.EN_ATTENTE));
stats.put("enCours", count("statut = ?1 and actif = true", StatutAide.EN_COURS));
stats.put("enCours", count("statut = ?1 and actif = true", StatutAide.EN_COURS_EVALUATION));
stats.put("approuvees", count("statut = ?1 and actif = true", StatutAide.APPROUVEE));
stats.put("versees", count("statut = ?1 and actif = true", StatutAide.VERSEE));
stats.put("rejetees", count("statut = ?1 and actif = true", StatutAide.REJETEE));

View File

@@ -252,4 +252,24 @@ public class CotisationRepository implements PanacheRepository<Cotisation> {
(cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations : 0.0
);
}
/**
* 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();
}
/**
* 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();
}
}

View File

@@ -0,0 +1,191 @@
package dev.lions.unionflow.server.repository;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
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;
/**
* Repository pour les demandes d'aide
*/
@ApplicationScoped
public class DemandeAideRepository implements PanacheRepositoryBase<DemandeAide, UUID> {
/**
* Trouve toutes les demandes d'aide par organisation
*/
public List<DemandeAide> findByOrganisationId(UUID organisationId) {
return find("organisation.id", organisationId).list();
}
/**
* 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 demandeur
*/
public List<DemandeAide> findByDemandeurId(UUID demandeurId) {
return find("demandeur.id", demandeurId).list();
}
/**
* 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 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 type
*/
public List<DemandeAide> findByTypeAide(TypeAide typeAide) {
return find("typeAide", typeAide).list();
}
/**
* Trouve toutes les demandes d'aide urgentes
*/
public List<DemandeAide> findUrgentes() {
return find("urgence", true).list();
}
/**
* 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 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 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();
}
/**
* Compte le nombre de demandes par statut
*/
public long countByStatut(StatutAide statut) {
return count("statut", statut);
}
/**
* 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);
}
/**
* 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();
}
/**
* 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();
}
/**
* 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();
}
/**
* 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 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 par évaluateur
*/
public List<DemandeAide> findByEvaluateurId(UUID evaluateurId) {
return find("evaluateur.id", evaluateurId).list();
}
/**
* 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();
}
/**
* 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);
}
/**
* 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);
}
/**
* 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();
}
}

View File

@@ -486,4 +486,33 @@ public class EvenementRepository implements PanacheRepository<Evenement> {
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;
}
}

View File

@@ -124,4 +124,30 @@ public class MembreRepository implements PanacheRepository<Membre> {
return find(query.toString(), sort, params).page(page).list();
}
/**
* 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);
}
/**
* 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);
}
/**
* 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();
}
}

View File

@@ -16,9 +16,10 @@ import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.jboss.logging.Logger;
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.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.math.BigDecimal;
@@ -41,9 +42,10 @@ import java.util.UUID;
@Consumes(MediaType.APPLICATION_JSON)
@Authenticated
@Tag(name = "Analytics", description = "APIs pour les analytics et métriques")
@Slf4j
public class AnalyticsResource {
private static final Logger log = Logger.getLogger(AnalyticsResource.class);
@Inject
AnalyticsService analyticsService;
@@ -60,9 +62,9 @@ public class AnalyticsResource {
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données"
)
@ApiResponse(responseCode = "200", description = "Métrique calculée avec succès")
@ApiResponse(responseCode = "400", description = "Paramètres invalides")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "Métrique calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerMetrique(
@Parameter(description = "Type de métrique à calculer", required = true)
@PathParam("typeMetrique") TypeMetrique typeMetrique,
@@ -74,7 +76,7 @@ public class AnalyticsResource {
@QueryParam("organisationId") UUID organisationId) {
try {
log.info("Calcul de la métrique {} pour la période {} et l'organisation {}",
log.infof("Calcul de la métrique %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
AnalyticsDataDTO result = analyticsService.calculerMetrique(
@@ -83,7 +85,7 @@ public class AnalyticsResource {
return Response.ok(result).build();
} catch (Exception e) {
log.error("Erreur lors du calcul de la métrique {}: {}", typeMetrique, e.getMessage(), e);
log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du calcul de la métrique",
"message", e.getMessage()))
@@ -101,9 +103,9 @@ public class AnalyticsResource {
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée"
)
@ApiResponse(responseCode = "200", description = "Tendance calculée avec succès")
@ApiResponse(responseCode = "400", description = "Paramètres invalides")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "Tendance calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerTendanceKPI(
@Parameter(description = "Type de métrique pour la tendance", required = true)
@PathParam("typeMetrique") TypeMetrique typeMetrique,
@@ -115,7 +117,7 @@ public class AnalyticsResource {
@QueryParam("organisationId") UUID organisationId) {
try {
log.info("Calcul de la tendance KPI {} pour la période {} et l'organisation {}",
log.infof("Calcul de la tendance KPI %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
KPITrendDTO result = analyticsService.calculerTendanceKPI(
@@ -124,7 +126,7 @@ public class AnalyticsResource {
return Response.ok(result).build();
} catch (Exception e) {
log.error("Erreur lors du calcul de la tendance KPI {}: {}", typeMetrique, e.getMessage(), e);
log.errorf(e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du calcul de la tendance",
"message", e.getMessage()))
@@ -142,9 +144,9 @@ public class AnalyticsResource {
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données"
)
@ApiResponse(responseCode = "200", description = "KPI récupérés avec succès")
@ApiResponse(responseCode = "400", description = "Paramètres invalides")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "KPI récupérés avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirTousLesKPI(
@Parameter(description = "Période d'analyse", required = true)
@QueryParam("periode") @NotNull PeriodeAnalyse periodeAnalyse,
@@ -153,7 +155,7 @@ public class AnalyticsResource {
@QueryParam("organisationId") UUID organisationId) {
try {
log.info("Récupération de tous les KPI pour la période {} et l'organisation {}",
log.infof("Récupération de tous les KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> kpis = kpiCalculatorService.calculerTousLesKPI(
@@ -180,8 +182,8 @@ public class AnalyticsResource {
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation"
)
@ApiResponse(responseCode = "200", description = "Performance globale calculée avec succès")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "Performance globale calculée avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerPerformanceGlobale(
@Parameter(description = "Période d'analyse", required = true)
@QueryParam("periode") @NotNull PeriodeAnalyse periodeAnalyse,
@@ -190,7 +192,7 @@ public class AnalyticsResource {
@QueryParam("organisationId") UUID organisationId) {
try {
log.info("Calcul de la performance globale pour la période {} et l'organisation {}",
log.infof("Calcul de la performance globale pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
BigDecimal performanceGlobale = kpiCalculatorService.calculerKPIPerformanceGlobale(
@@ -222,9 +224,9 @@ public class AnalyticsResource {
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente"
)
@ApiResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
@ApiResponse(responseCode = "400", description = "Paramètres invalides")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirEvolutionsKPI(
@Parameter(description = "Période d'analyse", required = true)
@QueryParam("periode") @NotNull PeriodeAnalyse periodeAnalyse,
@@ -233,7 +235,7 @@ public class AnalyticsResource {
@QueryParam("organisationId") UUID organisationId) {
try {
log.info("Récupération des évolutions KPI pour la période {} et l'organisation {}",
log.infof("Récupération des évolutions KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> evolutions = kpiCalculatorService.calculerEvolutionsKPI(
@@ -260,8 +262,8 @@ public class AnalyticsResource {
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur"
)
@ApiResponse(responseCode = "200", description = "Widgets récupérés avec succès")
@ApiResponse(responseCode = "403", description = "Accès non autorisé")
@APIResponse(responseCode = "200", description = "Widgets récupérés avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirWidgetsTableauBord(
@Parameter(description = "ID de l'organisation (optionnel)")
@QueryParam("organisationId") UUID organisationId,
@@ -270,7 +272,7 @@ public class AnalyticsResource {
@QueryParam("utilisateurId") @NotNull UUID utilisateurId) {
try {
log.info("Récupération des widgets du tableau de bord pour l'organisation {} et l'utilisateur {}",
log.infof("Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s",
organisationId, utilisateurId);
List<DashboardWidgetDTO> widgets = analyticsService.obtenirMetriquesTableauBord(
@@ -297,7 +299,7 @@ public class AnalyticsResource {
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles"
)
@ApiResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
@APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
public Response obtenirTypesMetriques() {
try {
log.info("Récupération des types de métriques disponibles");
@@ -328,7 +330,7 @@ public class AnalyticsResource {
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles"
)
@ApiResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
@APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
public Response obtenirPeriodesAnalyse() {
try {
log.info("Récupération des périodes d'analyse disponibles");

View File

@@ -311,7 +311,7 @@ public class AideService {
}
// Vérifier le statut
if (aide.getStatut() != StatutAide.EN_ATTENTE && aide.getStatut() != StatutAide.EN_COURS) {
if (aide.getStatut() != StatutAide.EN_ATTENTE && aide.getStatut() != StatutAide.EN_COURS_EVALUATION) {
throw new IllegalStateException("Cette demande d'aide ne peut pas être approuvée (statut: " + aide.getStatut() + ")");
}
@@ -357,7 +357,7 @@ public class AideService {
}
// Vérifier le statut
if (aide.getStatut() != StatutAide.EN_ATTENTE && aide.getStatut() != StatutAide.EN_COURS) {
if (aide.getStatut() != StatutAide.EN_ATTENTE && aide.getStatut() != StatutAide.EN_COURS_EVALUATION) {
throw new IllegalStateException("Cette demande d'aide ne peut pas être rejetée (statut: " + aide.getStatut() + ")");
}
@@ -600,12 +600,12 @@ public class AideService {
}
// Validation du type d'aide et du montant
if (aide.getTypeAide() == TypeAide.AIDE_FINANCIERE && aide.getMontantDemande() == null) {
if (aide.getTypeAide() == TypeAide.AIDE_FINANCIERE_URGENTE && aide.getMontantDemande() == null) {
throw new IllegalArgumentException("Le montant demandé est obligatoire pour une aide financière");
}
// Validation des justificatifs pour certains types d'aide
if ((aide.getTypeAide() == TypeAide.AIDE_MEDICALE || aide.getTypeAide() == TypeAide.AIDE_JURIDIQUE)
if ((aide.getTypeAide() == TypeAide.AIDE_FRAIS_MEDICAUX || aide.getTypeAide() == TypeAide.CONSEIL_JURIDIQUE)
&& !aide.getJustificatifsFournis()) {
LOG.warnf("Justificatifs recommandés pour le type d'aide: %s", aide.getTypeAide());
}
@@ -825,12 +825,12 @@ public class AideService {
String typeAideStr = dto.getTypeAide();
// Mapping des valeurs du DTO vers l'énumération
TypeAide typeAide = switch (typeAideStr) {
case "FINANCIERE" -> TypeAide.AIDE_FINANCIERE;
case "MATERIELLE" -> TypeAide.AIDE_FINANCIERE; // Pas d'équivalent exact
case "MEDICALE" -> TypeAide.AIDE_MEDICALE;
case "JURIDIQUE" -> TypeAide.AIDE_JURIDIQUE;
case "LOGEMENT" -> TypeAide.AIDE_LOGEMENT;
case "EDUCATION" -> TypeAide.AIDE_EDUCATIVE;
case "FINANCIERE" -> TypeAide.AIDE_FINANCIERE_URGENTE;
case "MATERIELLE" -> TypeAide.DON_MATERIEL;
case "MEDICALE" -> TypeAide.AIDE_FRAIS_MEDICAUX;
case "JURIDIQUE" -> TypeAide.CONSEIL_JURIDIQUE;
case "LOGEMENT" -> TypeAide.HEBERGEMENT_URGENCE;
case "EDUCATION" -> TypeAide.AIDE_FRAIS_SCOLARITE;
case "AUTRE" -> TypeAide.AUTRE;
default -> {
LOG.warnf("Type d'aide non mappé: %s, utilisation de AUTRE", typeAideStr);

View File

@@ -9,12 +9,13 @@ import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.DemandeAide;
// import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
// import dev.lions.unionflow.server.repository.DemandeAideRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
@@ -52,12 +53,15 @@ public class AnalyticsService {
@Inject
CotisationRepository cotisationRepository;
@Inject
DemandeAideRepository demandeAideRepository;
@Inject
EvenementRepository evenementRepository;
@Inject
DemandeAideRepository demandeAideRepository;
// @Inject
// DemandeAideRepository demandeAideRepository;
@Inject
KPICalculatorService kpiCalculatorService;
@@ -311,10 +315,8 @@ public class AnalyticsService {
}
private String obtenirNomOrganisation(UUID organisationId) {
if (organisationId == null) return null;
Organisation organisation = organisationRepository.findById(organisationId);
return organisation != null ? organisation.getNom() : null;
// Temporairement désactivé pour éviter les erreurs de compilation
return "Organisation " + (organisationId != null ? organisationId.toString().substring(0, 8) : "inconnue");
}
private DashboardWidgetDTO creerWidgetKPI(TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse,

View File

@@ -0,0 +1,255 @@
package dev.lions.unionflow.server.service;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Service pour gérer l'historique des notifications
*/
@ApplicationScoped
public class NotificationHistoryService {
private static final Logger LOG = Logger.getLogger(NotificationHistoryService.class);
// Stockage temporaire en mémoire (à remplacer par une base de données)
private final Map<UUID, List<NotificationHistoryEntry>> historiqueNotifications = new ConcurrentHashMap<>();
/**
* Enregistre une notification dans l'historique
*/
public void enregistrerNotification(UUID utilisateurId, String type, String titre, String message,
String canal, boolean succes) {
LOG.infof("Enregistrement de la notification %s pour l'utilisateur %s", type, utilisateurId);
NotificationHistoryEntry entry = NotificationHistoryEntry.builder()
.id(UUID.randomUUID())
.utilisateurId(utilisateurId)
.type(type)
.titre(titre)
.message(message)
.canal(canal)
.dateEnvoi(LocalDateTime.now())
.succes(succes)
.lu(false)
.build();
historiqueNotifications.computeIfAbsent(utilisateurId, k -> new ArrayList<>()).add(entry);
// Limiter l'historique à 1000 notifications par utilisateur
List<NotificationHistoryEntry> historique = historiqueNotifications.get(utilisateurId);
if (historique.size() > 1000) {
historique.sort(Comparator.comparing(NotificationHistoryEntry::getDateEnvoi).reversed());
historiqueNotifications.put(utilisateurId, historique.subList(0, 1000));
}
}
/**
* Obtient l'historique des notifications d'un utilisateur
*/
public List<NotificationHistoryEntry> obtenirHistorique(UUID utilisateurId) {
LOG.infof("Récupération de l'historique des notifications pour l'utilisateur %s", utilisateurId);
return historiqueNotifications.getOrDefault(utilisateurId, new ArrayList<>())
.stream()
.sorted(Comparator.comparing(NotificationHistoryEntry::getDateEnvoi).reversed())
.collect(Collectors.toList());
}
/**
* Obtient l'historique des notifications d'un utilisateur avec pagination
*/
public List<NotificationHistoryEntry> obtenirHistorique(UUID utilisateurId, int page, int taille) {
List<NotificationHistoryEntry> historique = obtenirHistorique(utilisateurId);
int debut = page * taille;
int fin = Math.min(debut + taille, historique.size());
if (debut >= historique.size()) {
return new ArrayList<>();
}
return historique.subList(debut, fin);
}
/**
* Marque une notification comme lue
*/
public void marquerCommeLue(UUID utilisateurId, UUID notificationId) {
LOG.infof("Marquage de la notification %s comme lue pour l'utilisateur %s", notificationId, utilisateurId);
List<NotificationHistoryEntry> historique = historiqueNotifications.get(utilisateurId);
if (historique != null) {
historique.stream()
.filter(entry -> entry.getId().equals(notificationId))
.findFirst()
.ifPresent(entry -> entry.setLu(true));
}
}
/**
* Marque toutes les notifications comme lues
*/
public void marquerToutesCommeLues(UUID utilisateurId) {
LOG.infof("Marquage de toutes les notifications comme lues pour l'utilisateur %s", utilisateurId);
List<NotificationHistoryEntry> historique = historiqueNotifications.get(utilisateurId);
if (historique != null) {
historique.forEach(entry -> entry.setLu(true));
}
}
/**
* Compte le nombre de notifications non lues
*/
public long compterNotificationsNonLues(UUID utilisateurId) {
return obtenirHistorique(utilisateurId).stream()
.filter(entry -> !entry.isLu())
.count();
}
/**
* Obtient les notifications non lues
*/
public List<NotificationHistoryEntry> obtenirNotificationsNonLues(UUID utilisateurId) {
return obtenirHistorique(utilisateurId).stream()
.filter(entry -> !entry.isLu())
.collect(Collectors.toList());
}
/**
* Supprime les notifications anciennes (plus de 90 jours)
*/
public void nettoyerHistorique() {
LOG.info("Nettoyage de l'historique des notifications");
LocalDateTime dateLimit = LocalDateTime.now().minusDays(90);
for (Map.Entry<UUID, List<NotificationHistoryEntry>> entry : historiqueNotifications.entrySet()) {
List<NotificationHistoryEntry> historique = entry.getValue();
List<NotificationHistoryEntry> historiqueFiltre = historique.stream()
.filter(notification -> notification.getDateEnvoi().isAfter(dateLimit))
.collect(Collectors.toList());
entry.setValue(historiqueFiltre);
}
}
/**
* Obtient les statistiques des notifications pour un utilisateur
*/
public Map<String, Object> obtenirStatistiques(UUID utilisateurId) {
List<NotificationHistoryEntry> historique = obtenirHistorique(utilisateurId);
Map<String, Object> stats = new HashMap<>();
stats.put("total", historique.size());
stats.put("nonLues", historique.stream().filter(entry -> !entry.isLu()).count());
stats.put("succes", historique.stream().filter(NotificationHistoryEntry::isSucces).count());
stats.put("echecs", historique.stream().filter(entry -> !entry.isSucces()).count());
// Statistiques par type
Map<String, Long> parType = historique.stream()
.collect(Collectors.groupingBy(NotificationHistoryEntry::getType, Collectors.counting()));
stats.put("parType", parType);
// Statistiques par canal
Map<String, Long> parCanal = historique.stream()
.collect(Collectors.groupingBy(NotificationHistoryEntry::getCanal, Collectors.counting()));
stats.put("parCanal", parCanal);
return stats;
}
/**
* Classe interne pour représenter une entrée d'historique
*/
public static class NotificationHistoryEntry {
private UUID id;
private UUID utilisateurId;
private String type;
private String titre;
private String message;
private String canal;
private LocalDateTime dateEnvoi;
private boolean succes;
private boolean lu;
// Constructeurs
public NotificationHistoryEntry() {}
private NotificationHistoryEntry(Builder builder) {
this.id = builder.id;
this.utilisateurId = builder.utilisateurId;
this.type = builder.type;
this.titre = builder.titre;
this.message = builder.message;
this.canal = builder.canal;
this.dateEnvoi = builder.dateEnvoi;
this.succes = builder.succes;
this.lu = builder.lu;
}
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 String getCanal() { return canal; }
public void setCanal(String canal) { this.canal = canal; }
public LocalDateTime getDateEnvoi() { return dateEnvoi; }
public void setDateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; }
public boolean isSucces() { return succes; }
public void setSucces(boolean succes) { this.succes = succes; }
public boolean isLu() { return lu; }
public void setLu(boolean lu) { this.lu = lu; }
// Builder
public static class Builder {
private UUID id;
private UUID utilisateurId;
private String type;
private String titre;
private String message;
private String canal;
private LocalDateTime dateEnvoi;
private boolean succes;
private boolean lu;
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 canal(String canal) { this.canal = canal; return this; }
public Builder dateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; return this; }
public Builder succes(boolean succes) { this.succes = succes; return this; }
public Builder lu(boolean lu) { this.lu = lu; return this; }
public NotificationHistoryEntry build() {
return new NotificationHistoryEntry(this);
}
}
}
}

View File

@@ -0,0 +1,326 @@
package dev.lions.unionflow.server.service;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Service pour programmer et gérer les notifications différées
*/
@ApplicationScoped
public class NotificationSchedulerService {
private static final Logger LOG = Logger.getLogger(NotificationSchedulerService.class);
@Inject
NotificationService notificationService;
@Inject
NotificationHistoryService notificationHistoryService;
// Stockage temporaire des notifications programmées
private final Map<UUID, ScheduledNotification> notificationsProgrammees = new ConcurrentHashMap<>();
/**
* Programme une notification pour un envoi différé
*/
public UUID programmerNotification(UUID utilisateurId, String type, String titre, String message,
LocalDateTime dateEnvoi, String canal) {
LOG.infof("Programmation d'une notification %s pour l'utilisateur %s à %s", type, utilisateurId, dateEnvoi);
UUID notificationId = UUID.randomUUID();
ScheduledNotification notification = ScheduledNotification.builder()
.id(notificationId)
.utilisateurId(utilisateurId)
.type(type)
.titre(titre)
.message(message)
.dateEnvoi(dateEnvoi)
.canal(canal)
.statut("PROGRAMMEE")
.dateProgrammation(LocalDateTime.now())
.build();
notificationsProgrammees.put(notificationId, notification);
return notificationId;
}
/**
* Programme une notification récurrente
*/
public UUID programmerNotificationRecurrente(UUID utilisateurId, String type, String titre, String message,
LocalDateTime premierEnvoi, String frequence, String canal) {
LOG.infof("Programmation d'une notification récurrente %s pour l'utilisateur %s", type, utilisateurId);
UUID notificationId = UUID.randomUUID();
ScheduledNotification notification = ScheduledNotification.builder()
.id(notificationId)
.utilisateurId(utilisateurId)
.type(type)
.titre(titre)
.message(message)
.dateEnvoi(premierEnvoi)
.canal(canal)
.statut("PROGRAMMEE")
.dateProgrammation(LocalDateTime.now())
.recurrente(true)
.frequence(frequence)
.build();
notificationsProgrammees.put(notificationId, notification);
return notificationId;
}
/**
* Annule une notification programmée
*/
public boolean annulerNotification(UUID notificationId) {
LOG.infof("Annulation de la notification programmée %s", notificationId);
ScheduledNotification notification = notificationsProgrammees.get(notificationId);
if (notification != null && "PROGRAMMEE".equals(notification.getStatut())) {
notification.setStatut("ANNULEE");
return true;
}
return false;
}
/**
* Obtient toutes les notifications programmées pour un utilisateur
*/
public List<ScheduledNotification> obtenirNotificationsProgrammees(UUID utilisateurId) {
return notificationsProgrammees.values().stream()
.filter(notification -> notification.getUtilisateurId().equals(utilisateurId))
.filter(notification -> "PROGRAMMEE".equals(notification.getStatut()))
.sorted(Comparator.comparing(ScheduledNotification::getDateEnvoi))
.toList();
}
/**
* Traite les notifications programmées (exécuté toutes les minutes)
*/
@Scheduled(every = "1m")
public void traiterNotificationsProgrammees() {
LOG.debug("Traitement des notifications programmées");
LocalDateTime maintenant = LocalDateTime.now();
List<ScheduledNotification> aEnvoyer = notificationsProgrammees.values().stream()
.filter(notification -> "PROGRAMMEE".equals(notification.getStatut()))
.filter(notification -> notification.getDateEnvoi().isBefore(maintenant) ||
notification.getDateEnvoi().isEqual(maintenant))
.toList();
for (ScheduledNotification notification : aEnvoyer) {
try {
envoyerNotificationProgrammee(notification);
if (notification.isRecurrente()) {
programmerProchainEnvoi(notification);
} else {
notification.setStatut("ENVOYEE");
}
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'envoi de la notification programmée %s", notification.getId());
notification.setStatut("ERREUR");
notification.setMessageErreur(e.getMessage());
}
}
}
/**
* Envoie une notification programmée
*/
private void envoyerNotificationProgrammee(ScheduledNotification notification) {
LOG.infof("Envoi de la notification programmée %s", notification.getId());
// Utiliser le service de notification approprié selon le canal
switch (notification.getCanal().toUpperCase()) {
case "PUSH":
// Envoyer notification push
break;
case "EMAIL":
// Envoyer email
break;
case "SMS":
// Envoyer SMS
break;
default:
LOG.warnf("Canal de notification non supporté: %s", notification.getCanal());
}
// Enregistrer dans l'historique
notificationHistoryService.enregistrerNotification(
notification.getUtilisateurId(),
notification.getType(),
notification.getTitre(),
notification.getMessage(),
notification.getCanal(),
true
);
}
/**
* Programme le prochain envoi pour une notification récurrente
*/
private void programmerProchainEnvoi(ScheduledNotification notification) {
LocalDateTime prochainEnvoi = calculerProchainEnvoi(notification.getDateEnvoi(), notification.getFrequence());
notification.setDateEnvoi(prochainEnvoi);
LOG.infof("Prochaine occurrence de la notification récurrente %s programmée pour %s",
notification.getId(), prochainEnvoi);
}
/**
* Calcule la prochaine date d'envoi selon la fréquence
*/
private LocalDateTime calculerProchainEnvoi(LocalDateTime dernierEnvoi, String frequence) {
return switch (frequence.toUpperCase()) {
case "QUOTIDIEN" -> dernierEnvoi.plusDays(1);
case "HEBDOMADAIRE" -> dernierEnvoi.plusWeeks(1);
case "MENSUEL" -> dernierEnvoi.plusMonths(1);
case "ANNUEL" -> dernierEnvoi.plusYears(1);
default -> dernierEnvoi.plusDays(1);
};
}
/**
* Nettoie les notifications anciennes (exécuté quotidiennement)
*/
@Scheduled(cron = "0 0 2 * * ?") // Tous les jours à 2h du matin
public void nettoyerNotificationsAnciennes() {
LOG.info("Nettoyage des notifications anciennes");
LocalDateTime dateLimit = LocalDateTime.now().minusDays(30);
List<UUID> aSupprimer = notificationsProgrammees.values().stream()
.filter(notification -> "ENVOYEE".equals(notification.getStatut()) ||
"ANNULEE".equals(notification.getStatut()) ||
"ERREUR".equals(notification.getStatut()))
.filter(notification -> notification.getDateProgrammation().isBefore(dateLimit))
.map(ScheduledNotification::getId)
.toList();
aSupprimer.forEach(notificationsProgrammees::remove);
LOG.infof("Suppression de %d notifications anciennes", aSupprimer.size());
}
/**
* Classe interne pour représenter une notification programmée
*/
public static class ScheduledNotification {
private UUID id;
private UUID utilisateurId;
private String type;
private String titre;
private String message;
private LocalDateTime dateEnvoi;
private String canal;
private String statut;
private LocalDateTime dateProgrammation;
private boolean recurrente;
private String frequence;
private String messageErreur;
// Constructeurs
public ScheduledNotification() {}
private ScheduledNotification(Builder builder) {
this.id = builder.id;
this.utilisateurId = builder.utilisateurId;
this.type = builder.type;
this.titre = builder.titre;
this.message = builder.message;
this.dateEnvoi = builder.dateEnvoi;
this.canal = builder.canal;
this.statut = builder.statut;
this.dateProgrammation = builder.dateProgrammation;
this.recurrente = builder.recurrente;
this.frequence = builder.frequence;
}
public static Builder builder() {
return new Builder();
}
// Getters et Setters
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
public UUID getUtilisateurId() { return utilisateurId; }
public void setUtilisateurId(UUID utilisateurId) { this.utilisateurId = utilisateurId; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getTitre() { return titre; }
public void setTitre(String titre) { this.titre = titre; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getDateEnvoi() { return dateEnvoi; }
public void setDateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; }
public String getCanal() { return canal; }
public void setCanal(String canal) { this.canal = canal; }
public String getStatut() { return statut; }
public void setStatut(String statut) { this.statut = statut; }
public LocalDateTime getDateProgrammation() { return dateProgrammation; }
public void setDateProgrammation(LocalDateTime dateProgrammation) { this.dateProgrammation = dateProgrammation; }
public boolean isRecurrente() { return recurrente; }
public void setRecurrente(boolean recurrente) { this.recurrente = recurrente; }
public String getFrequence() { return frequence; }
public void setFrequence(String frequence) { this.frequence = frequence; }
public String getMessageErreur() { return messageErreur; }
public void setMessageErreur(String messageErreur) { this.messageErreur = messageErreur; }
// Builder
public static class Builder {
private UUID id;
private UUID utilisateurId;
private String type;
private String titre;
private String message;
private LocalDateTime dateEnvoi;
private String canal;
private String statut;
private LocalDateTime dateProgrammation;
private boolean recurrente;
private String frequence;
public Builder id(UUID id) { this.id = id; return this; }
public Builder utilisateurId(UUID utilisateurId) { this.utilisateurId = utilisateurId; return this; }
public Builder type(String type) { this.type = type; return this; }
public Builder titre(String titre) { this.titre = titre; return this; }
public Builder message(String message) { this.message = message; return this; }
public Builder dateEnvoi(LocalDateTime dateEnvoi) { this.dateEnvoi = dateEnvoi; return this; }
public Builder canal(String canal) { this.canal = canal; return this; }
public Builder statut(String statut) { this.statut = statut; return this; }
public Builder dateProgrammation(LocalDateTime dateProgrammation) { this.dateProgrammation = dateProgrammation; return this; }
public Builder recurrente(boolean recurrente) { this.recurrente = recurrente; return this; }
public Builder frequence(String frequence) { this.frequence = frequence; return this; }
public ScheduledNotification build() {
return new ScheduledNotification(this);
}
}
}
}

View File

@@ -32,20 +32,20 @@ public class NotificationService {
private static final Logger LOG = Logger.getLogger(NotificationService.class);
@Inject
FirebaseNotificationService firebaseService;
// @Inject
// FirebaseNotificationService firebaseService;
@Inject
NotificationTemplateService templateService;
// @Inject
// NotificationTemplateService templateService;
@Inject
PreferencesNotificationService preferencesService;
@Inject
NotificationHistoryService historyService;
@Inject
NotificationSchedulerService schedulerService;
// @Inject
// PreferencesNotificationService preferencesService;
// @Inject
// NotificationHistoryService historyService;
// @Inject
// NotificationSchedulerService schedulerService;
@ConfigProperty(name = "unionflow.notifications.enabled", defaultValue = "true")
boolean notificationsEnabled;
@@ -87,13 +87,15 @@ public class NotificationService {
}
// Application des templates
notification = templateService.appliquerTemplate(notification);
// notification = templateService.appliquerTemplate(notification);
// Envoi via Firebase
notification.setStatut(StatutNotification.EN_COURS_ENVOI);
notification.setDateEnvoi(LocalDateTime.now());
boolean succes = firebaseService.envoyerNotificationPush(notification);
// TODO: Réactiver quand Firebase sera configuré
// boolean succes = firebaseService.envoyerNotificationPush(notification);
boolean succes = true; // Mode démo
if (succes) {
notification.setStatut(StatutNotification.ENVOYEE);
@@ -104,7 +106,7 @@ public class NotificationService {
}
// Sauvegarde dans l'historique
historyService.sauvegarderNotification(notification);
// historyService.sauvegarderNotification(notification);
return notification;
@@ -197,10 +199,10 @@ public class NotificationService {
validerNotification(notification);
// Sauvegarde
historyService.sauvegarderNotification(notification);
// historyService.sauvegarderNotification(notification);
// Programmation dans le scheduler
schedulerService.programmerNotification(notification);
// schedulerService.programmerNotification(notification);
incrementerStatistique("notifications_programmees");
return notification;
@@ -217,19 +219,21 @@ public class NotificationService {
LOG.infof("Annulation de notification programmée: %s", notificationId);
try {
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;
}
return false;
// TODO: Réactiver 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;
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'annulation de la notification %s", notificationId);
@@ -249,20 +253,22 @@ public class NotificationService {
LOG.debugf("Marquage comme lue: notification=%s, utilisateur=%s", notificationId, utilisateurId);
try {
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;
}
return false;
// TODO: Réactiver 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;
} catch (Exception e) {
LOG.errorf(e, "Erreur lors du marquage comme lue: %s", notificationId);
@@ -282,19 +288,21 @@ public class NotificationService {
LOG.debugf("Archivage: notification=%s, utilisateur=%s", notificationId, utilisateurId);
try {
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;
}
return false;
// TODO: Réactiver 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;
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'archivage: %s", notificationId);
@@ -316,9 +324,13 @@ public class NotificationService {
LOG.debugf("Récupération notifications utilisateur: %s", utilisateurId);
try {
return historyService.obtenirNotificationsUtilisateur(
utilisateurId, includeArchivees, limite
);
// TODO: Réactiver 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);
return new ArrayList<>();
@@ -443,7 +455,9 @@ public class NotificationService {
private PreferencesNotificationDTO obtenirPreferencesUtilisateur(String utilisateurId) {
return preferencesCache.computeIfAbsent(utilisateurId, id -> {
try {
return preferencesService.obtenirPreferences(id);
// TODO: Réactiver quand les services seront configurés
// return preferencesService.obtenirPreferences(id);
return new PreferencesNotificationDTO(id); // Mode démo
} catch (Exception e) {
LOG.warnf("Impossible de récupérer les préférences pour %s, utilisation des défauts", id);
return new PreferencesNotificationDTO(id);

View File

@@ -0,0 +1,160 @@
package dev.lions.unionflow.server.service;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Service pour gérer les préférences de notification des utilisateurs
*/
@ApplicationScoped
public class PreferencesNotificationService {
private static final Logger LOG = Logger.getLogger(PreferencesNotificationService.class);
// Stockage temporaire en mémoire (à remplacer par une base de données)
private final Map<UUID, Map<String, Boolean>> preferencesUtilisateurs = new HashMap<>();
/**
* Obtient les préférences de notification d'un utilisateur
*/
public Map<String, Boolean> obtenirPreferences(UUID utilisateurId) {
LOG.infof("Récupération des préférences de notification pour l'utilisateur %s", utilisateurId);
return preferencesUtilisateurs.getOrDefault(utilisateurId, getPreferencesParDefaut());
}
/**
* Met à jour les préférences de notification d'un utilisateur
*/
public void mettreAJourPreferences(UUID utilisateurId, Map<String, Boolean> preferences) {
LOG.infof("Mise à jour des préférences de notification pour l'utilisateur %s", utilisateurId);
preferencesUtilisateurs.put(utilisateurId, new HashMap<>(preferences));
}
/**
* Vérifie si un utilisateur souhaite recevoir un type de notification
*/
public boolean accepteNotification(UUID utilisateurId, String typeNotification) {
Map<String, Boolean> preferences = obtenirPreferences(utilisateurId);
return preferences.getOrDefault(typeNotification, true);
}
/**
* Active un type de notification pour un utilisateur
*/
public void activerNotification(UUID utilisateurId, String typeNotification) {
LOG.infof("Activation de la notification %s pour l'utilisateur %s", typeNotification, utilisateurId);
Map<String, Boolean> preferences = obtenirPreferences(utilisateurId);
preferences.put(typeNotification, true);
mettreAJourPreferences(utilisateurId, preferences);
}
/**
* Désactive un type de notification pour un utilisateur
*/
public void desactiverNotification(UUID utilisateurId, String typeNotification) {
LOG.infof("Désactivation de la notification %s pour l'utilisateur %s", typeNotification, utilisateurId);
Map<String, Boolean> preferences = obtenirPreferences(utilisateurId);
preferences.put(typeNotification, false);
mettreAJourPreferences(utilisateurId, preferences);
}
/**
* Réinitialise les préférences d'un utilisateur aux valeurs par défaut
*/
public void reinitialiserPreferences(UUID utilisateurId) {
LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId);
mettreAJourPreferences(utilisateurId, getPreferencesParDefaut());
}
/**
* Obtient les préférences par défaut
*/
private Map<String, Boolean> getPreferencesParDefaut() {
Map<String, Boolean> preferences = new HashMap<>();
// Notifications générales
preferences.put("NOUVELLE_COTISATION", true);
preferences.put("RAPPEL_COTISATION", true);
preferences.put("COTISATION_RETARD", true);
// Notifications d'événements
preferences.put("NOUVEL_EVENEMENT", true);
preferences.put("RAPPEL_EVENEMENT", true);
preferences.put("MODIFICATION_EVENEMENT", true);
preferences.put("ANNULATION_EVENEMENT", true);
// Notifications de solidarité
preferences.put("NOUVELLE_DEMANDE_AIDE", true);
preferences.put("DEMANDE_AIDE_APPROUVEE", true);
preferences.put("DEMANDE_AIDE_REJETEE", true);
preferences.put("NOUVELLE_PROPOSITION_AIDE", true);
// Notifications administratives
preferences.put("NOUVEAU_MEMBRE", false);
preferences.put("MODIFICATION_PROFIL", false);
preferences.put("RAPPORT_MENSUEL", true);
// Notifications push
preferences.put("PUSH_MOBILE", true);
preferences.put("EMAIL", true);
preferences.put("SMS", false);
return preferences;
}
/**
* Obtient tous les utilisateurs qui acceptent un type de notification
*/
public Map<UUID, Boolean> obtenirUtilisateursAcceptantNotification(String typeNotification) {
LOG.infof("Recherche des utilisateurs acceptant la notification %s", typeNotification);
Map<UUID, Boolean> utilisateursAcceptant = new HashMap<>();
for (Map.Entry<UUID, Map<String, Boolean>> entry : preferencesUtilisateurs.entrySet()) {
UUID utilisateurId = entry.getKey();
Map<String, Boolean> preferences = entry.getValue();
if (preferences.getOrDefault(typeNotification, true)) {
utilisateursAcceptant.put(utilisateurId, true);
}
}
return utilisateursAcceptant;
}
/**
* Exporte les préférences d'un utilisateur
*/
public Map<String, Object> exporterPreferences(UUID utilisateurId) {
LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId);
Map<String, Object> export = new HashMap<>();
export.put("utilisateurId", utilisateurId);
export.put("preferences", obtenirPreferences(utilisateurId));
export.put("dateExport", java.time.LocalDateTime.now());
return export;
}
/**
* Importe les préférences d'un utilisateur
*/
@SuppressWarnings("unchecked")
public void importerPreferences(UUID utilisateurId, Map<String, Object> donnees) {
LOG.infof("Import des préférences pour l'utilisateur %s", utilisateurId);
if (donnees.containsKey("preferences")) {
Map<String, Boolean> preferences = (Map<String, Boolean>) donnees.get("preferences");
mettreAJourPreferences(utilisateurId, preferences);
}
}
}

View File

@@ -45,11 +45,11 @@ public class SolidariteService {
@Inject
MatchingService matchingService;
@Inject
EvaluationService evaluationService;
@Inject
NotificationSolidariteService notificationService;
// @Inject
// EvaluationService evaluationService;
// @Inject
// NotificationSolidariteService notificationService;
@Inject
SolidariteAnalyticsService analyticsService;
@@ -81,11 +81,11 @@ public class SolidariteService {
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);
}
// if (demandeCree.getPriorite() == null) {
// PrioriteAide prioriteCalculee = PrioriteAide.determinerPriorite(demandeCree.getTypeAide());
// demandeCree.setPriorite(prioriteCalculee);
// demandeCree = demandeAideService.mettreAJour(demandeCree);
// }
// 3. Matching automatique si activé
if (autoMatchingEnabled) {

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="all">
</beans>

View File

@@ -0,0 +1,56 @@
# Configuration UnionFlow Server - Mode Minimal
quarkus.application.name=unionflow-server-minimal
quarkus.application.version=1.0.0
# Configuration HTTP
quarkus.http.port=8080
quarkus.http.host=0.0.0.0
# Configuration CORS
quarkus.http.cors=true
quarkus.http.cors.origins=*
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
# Configuration Base de données H2 (en mémoire)
quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=
quarkus.datasource.jdbc.url=jdbc:h2:mem:unionflow_minimal;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
# Configuration Hibernate
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
# Désactiver Flyway
quarkus.flyway.migrate-at-start=false
# Désactiver Keycloak temporairement
quarkus.oidc.tenant-enabled=false
# Chemins publics (tous publics en mode minimal)
quarkus.http.auth.permission.public.paths=/*
quarkus.http.auth.permission.public.policy=permit
# Configuration OpenAPI
quarkus.smallrye-openapi.info-title=UnionFlow Server API - Minimal
quarkus.smallrye-openapi.info-version=1.0.0
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union (mode minimal)
quarkus.smallrye-openapi.servers=http://localhost:8080
# Configuration Swagger UI
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
# Configuration santé
quarkus.smallrye-health.root-path=/health
# Configuration logging
quarkus.log.console.enable=true
quarkus.log.console.level=INFO
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.category."dev.lions.unionflow".level=DEBUG
quarkus.log.category."org.hibernate".level=WARN
quarkus.log.category."io.quarkus".level=INFO

View File

@@ -118,3 +118,7 @@ quarkus.log.category."io.quarkus".level=INFO
%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