feat(dashboard): cotisations tout temps + synthèse membre

- CotisationRepository: calculerTotalCotisationsPayeesToutTemps(membreId)
- MembreDashboardService: envoi totalCotisationsPayeesToutTemps dans la synthèse

Made-with: Cursor
This commit is contained in:
dahoud
2026-03-09 19:58:25 +00:00
parent 23528197cf
commit a1e30b51fb
2 changed files with 438 additions and 70 deletions

View File

@@ -12,6 +12,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
@@ -36,42 +37,129 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
*/
public Optional<Cotisation> findByNumeroReference(String numeroReference) {
TypedQuery<Cotisation> query = entityManager.createQuery(
"SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
Cotisation.class);
"SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
Cotisation.class);
query.setParameter("numeroReference", numeroReference);
return query.getResultStream().findFirst();
return query.getResultList().stream().findFirst();
}
/**
* Trouve toutes les cotisations d'un membre
*
* @param membreId l'UUID du membre
* @param page pagination
* @param sort tri
* @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);
"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();
}
/** Compte les cotisations d'un membre (pour dashboard). */
public long countByMembreId(UUID membreId) {
if (membreId == null) return 0L;
TypedQuery<Long> query = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.membre.id = :membreId", Long.class);
query.setParameter("membreId", membreId);
Long result = query.getSingleResult();
return result != null ? result : 0L;
}
/** Compte les cotisations payées d'un membre (statut PAYEE, pour dashboard). */
public long countPayeesByMembreId(UUID membreId) {
if (membreId == null) return 0L;
TypedQuery<Long> query = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.membre.id = :membreId AND (c.statut = 'PAYEE' OR c.statut = 'PARTIELLEMENT_PAYEE')",
Long.class);
query.setParameter("membreId", membreId);
Long result = query.getSingleResult();
return result != null ? result : 0L;
}
/**
* Trouve les cotisations dont l'organisation fait partie de la liste (pour admin / admin org).
*
* @param organisationIds ensemble des UUID d'organisations
* @param page pagination
* @param sort tri
* @return liste paginée des cotisations
*/
public List<Cotisation> findByOrganisationIdIn(Set<UUID> organisationIds, Page page, Sort sort) {
if (organisationIds == null || organisationIds.isEmpty()) {
return List.of();
}
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY c.dateEcheance DESC";
TypedQuery<Cotisation> query = entityManager.createQuery(
"SELECT c FROM Cotisation c WHERE c.organisation.id IN :organisationIds" + orderBy,
Cotisation.class);
query.setParameter("organisationIds", organisationIds);
query.setFirstResult(page.index * page.size);
query.setMaxResults(page.size);
return query.getResultList();
}
/** Compte les cotisations d'une organisation (pour dashboard par org). */
public long countByOrganisationId(UUID organisationId) {
if (organisationId == null) return 0L;
TypedQuery<Long> query = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.organisation.id = :organisationId", Long.class);
query.setParameter("organisationId", organisationId);
Long result = query.getSingleResult();
return result != null ? result : 0L;
}
/** Compte les cotisations d'une organisation avec date de paiement dans une période (pour graphiques). */
public long countByOrganisationIdAndDatePaiementBetween(UUID organisationId, java.time.LocalDateTime start, java.time.LocalDateTime end) {
if (organisationId == null) return 0L;
TypedQuery<Long> query = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.organisation.id = :organisationId AND c.datePaiement >= :start AND c.datePaiement <= :end",
Long.class);
query.setParameter("organisationId", organisationId);
query.setParameter("start", start);
query.setParameter("end", end);
Long result = query.getSingleResult();
return result != null ? result : 0L;
}
/**
* Trouve les cotisations en attente pour les organisations données (pour admin / admin org).
*
* @param organisationIds ensemble des UUID d'organisations
* @return liste des cotisations en attente, triées par date d'échéance
*/
public List<Cotisation> findEnAttenteByOrganisationIdIn(Set<UUID> organisationIds) {
if (organisationIds == null || organisationIds.isEmpty()) {
return List.of();
}
int anneeEnCours = LocalDate.now().getYear();
TypedQuery<Cotisation> query = entityManager.createQuery(
"SELECT c FROM Cotisation c WHERE c.organisation.id IN :organisationIds "
+ "AND c.statut = 'EN_ATTENTE' AND EXTRACT(YEAR FROM c.dateEcheance) = :annee "
+ "ORDER BY c.dateEcheance ASC",
Cotisation.class);
query.setParameter("organisationIds", organisationIds);
query.setParameter("annee", anneeEnCours);
return query.getResultList();
}
/**
* Trouve les cotisations par statut
*
* @param statut le statut recherché
* @param page pagination
* @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);
"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);
@@ -82,13 +170,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
* Trouve les cotisations en retard
*
* @param dateReference date de référence (généralement aujourd'hui)
* @param page pagination
* @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);
"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);
@@ -99,22 +187,22 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
* Trouve les cotisations par période (année/mois)
*
* @param annee l'année
* @param mois le mois (optionnel)
* @param page pagination
* @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);
"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);
"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);
@@ -126,13 +214,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
* Trouve les cotisations par type
*
* @param typeCotisation le type de cotisation
* @param page pagination
* @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);
"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);
@@ -142,16 +230,115 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
/**
* 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
* @param membreId UUID du membre (optionnel)
* @param statut Statut de la cotisation (optionnel)
* @param typeCotisation Type de cotisation (optionnel)
* @param annee Année (optionnel)
* @param page Pagination
* @param sort Tri
* @return liste paginée des cotisations filtrées
*/
public List<Cotisation> searchAdvanced(UUID membreId, String statut, String typeCotisation, Integer annee,
Page page, Sort sort) {
StringBuilder queryStr = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1 ");
Map<String, Object> params = new java.util.HashMap<>();
if (membreId != null) {
queryStr.append("AND c.membre.id = :membreId ");
params.put("membreId", membreId);
}
if (statut != null && !statut.isBlank()) {
queryStr.append("AND c.statut = :statut ");
params.put("statut", statut);
}
if (typeCotisation != null && !typeCotisation.isBlank()) {
queryStr.append("AND c.typeCotisation = :typeCotisation ");
params.put("typeCotisation", typeCotisation);
}
if (annee != null) {
queryStr.append("AND c.annee = :annee ");
params.put("annee", annee);
}
if (sort != null) {
queryStr.append(" ORDER BY ").append(buildOrderBy(sort));
} else {
queryStr.append(" ORDER BY c.dateEcheance DESC");
}
TypedQuery<Cotisation> query = entityManager.createQuery(queryStr.toString(), Cotisation.class);
params.forEach(query::setParameter);
query.setFirstResult(page.index * page.size);
query.setMaxResults(page.size);
return query.getResultList();
}
// --- Méthodes d'agrégation pour le dashboard membre --- //
public BigDecimal calculerTotalCotisationsPayeesCeMois(UUID membreId) {
LocalDate startOfMonth = LocalDate.now().withDayOfMonth(1);
LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth());
BigDecimal total = entityManager.createQuery(
"SELECT SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id = :membreId AND c.statut = 'PAYEE' AND c.datePaiement >= :start AND c.datePaiement <= :end",
BigDecimal.class)
.setParameter("membreId", membreId)
.setParameter("start", startOfMonth.atStartOfDay())
.setParameter("end", endOfMonth.atTime(23, 59, 59))
.getSingleResult();
return total != null ? total : BigDecimal.ZERO;
}
public long countRetardByMembreId(UUID membreId) {
Long count = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.membre.id = :membreId AND c.dateEcheance < :today AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE'",
Long.class)
.setParameter("membreId", membreId)
.setParameter("today", LocalDate.now())
.getSingleResult();
return count != null ? count : 0L;
}
public BigDecimal calculerTotalCotisationsAnneeEnCours(UUID membreId) {
int anneeCourante = LocalDate.now().getYear();
BigDecimal total = entityManager.createQuery(
"SELECT SUM(c.montantDu) FROM Cotisation c WHERE c.membre.id = :membreId AND c.annee = :annee",
BigDecimal.class)
.setParameter("membreId", membreId)
.setParameter("annee", anneeCourante)
.getSingleResult();
return total != null ? total : BigDecimal.ZERO;
}
public BigDecimal calculerTotalCotisationsPayeesAnneeEnCours(UUID membreId) {
int anneeCourante = LocalDate.now().getYear();
BigDecimal total = entityManager.createQuery(
"SELECT SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id = :membreId AND c.annee = :annee AND (c.statut = 'PAYEE' OR c.statut = 'PARTIELLEMENT_PAYEE')",
BigDecimal.class)
.setParameter("membreId", membreId)
.setParameter("annee", anneeCourante)
.getSingleResult();
return total != null ? total : BigDecimal.ZERO;
}
/** Total des cotisations payées par le membre (toutes années) pour le KPI « Contribution Totale ». */
public BigDecimal calculerTotalCotisationsPayeesToutTemps(UUID membreId) {
BigDecimal total = entityManager.createQuery(
"SELECT SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id = :membreId AND (c.statut = 'PAYEE' OR c.statut = 'PARTIELLEMENT_PAYEE')",
BigDecimal.class)
.setParameter("membreId", membreId)
.getSingleResult();
return total != null ? total : BigDecimal.ZERO;
}
/**
* Recherche avancee
*/
public List<Cotisation> rechercheAvancee(
UUID membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) {
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<>();
@@ -199,8 +386,8 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
*/
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);
"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;
@@ -214,8 +401,8 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
*/
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);
"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;
@@ -229,23 +416,46 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
*/
public long compterParStatut(String statut) {
TypedQuery<Long> query = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class);
"SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class);
query.setParameter("statut", statut);
return query.getSingleResult();
}
/**
* Somme des montants payés pour un statut donné (ex. PAYEE pour revenus
* perçus).
*/
public BigDecimal sommeMontantPayeParStatut(String statut) {
TypedQuery<BigDecimal> query = entityManager.createQuery(
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.statut = :statut",
BigDecimal.class);
query.setParameter("statut", statut);
BigDecimal result = query.getSingleResult();
return result != null ? result : BigDecimal.ZERO;
}
/**
* Somme de tous les montants dus.
*/
public BigDecimal sommeMontantDu() {
TypedQuery<BigDecimal> query = entityManager.createQuery(
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c",
BigDecimal.class);
return query.getSingleResult();
}
/**
* 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
* @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);
"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();
@@ -261,7 +471,7 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
Cotisation cotisation = findByIdOptional(cotisationId).orElse(null);
if (cotisation != null) {
cotisation.setNombreRappels(
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
cotisation.setDateDernierRappel(LocalDateTime.now());
update(cotisation);
return true;
@@ -273,13 +483,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
* Statistiques des cotisations par période
*
* @param annee l'année
* @param mois le mois (optionnel)
* @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";
? "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;
@@ -288,17 +498,17 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
if (mois != null) {
countQuery = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
Long.class);
"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);
"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);
"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);
"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);
@@ -310,16 +520,16 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
payeesQuery.setParameter("mois", mois);
} else {
countQuery = entityManager.createQuery(
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee", Long.class);
"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);
"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);
"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);
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'",
Long.class);
countQuery.setParameter("annee", annee);
montantTotalQuery.setParameter("annee", annee);
@@ -333,22 +543,22 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
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", 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);
? (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) {
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);
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.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);
@@ -358,10 +568,10 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
/** Somme des montants en attente dans une période */
public BigDecimal sumMontantsEnAttente(
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
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);
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.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);

View File

@@ -0,0 +1,158 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.dashboard.MembreDashboardSyntheseResponse;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
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.MembreRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ApplicationScoped
public class MembreDashboardService {
private static final Logger LOG = Logger.getLogger(MembreDashboardService.class);
@Inject
SecurityIdentity securityIdentity;
@Inject
MembreRepository membreRepository;
@Inject
CotisationRepository cotisationRepository;
@Inject
CompteEpargneRepository compteEpargneRepository;
@Inject
DemandeAideRepository demandeAideRepository;
/**
* Récupère l'email du principal : d'abord la claim JWT "email" (Keycloak envoie souvent
* preferred_username comme getName()), puis fallback sur getName().
*/
private String getEmailFromPrincipal() {
if (securityIdentity == null || securityIdentity.getPrincipal() == null) return null;
if (securityIdentity.getPrincipal() instanceof JsonWebToken jwt) {
try {
String email = jwt.getClaim("email");
if (email != null && !email.isBlank()) return email;
} catch (Exception e) {
LOG.debugf("Claim email non disponible: %s", e.getMessage());
}
}
return securityIdentity.getPrincipal().getName();
}
public MembreDashboardSyntheseResponse getDashboardData() {
String email = getEmailFromPrincipal();
if (email == null || email.isBlank()) {
throw new NotFoundException("Identité non disponible pour le dashboard membre.");
}
LOG.infof("Génération du dashboard pour le membre: %s", email);
Membre membre = membreRepository.findByEmail(email.trim())
.or(() -> membreRepository.findByEmail(email.trim().toLowerCase()))
.filter(m -> m.getActif() == null || m.getActif())
.orElseThrow(() -> new NotFoundException("Membre non trouvé pour l'email: " + email));
UUID membreId = membre.getId();
LocalDate now = LocalDate.now();
// 1. Infos membre
String prenom = membre.getPrenom();
String nom = membre.getNom();
LocalDate dateInscription = null;
if (membre.getDateCreation() != null) {
dateInscription = membre.getDateCreation().toLocalDate();
}
// 2. Cotisations
// Approximations : on somme les cotisations payées ce mois, on regarde s'il y a
// des cotisations en retard, etc.
// On utilisera des méthodes custom sur le repository si besoin, ou on le fait
// en Java (sur les petits sets test)
BigDecimal paiementsMois = cotisationRepository.calculerTotalCotisationsPayeesCeMois(membreId);
// Statut : "En retard", "À jour", "En attente"
long enRetardCount = cotisationRepository.countRetardByMembreId(membreId);
String statutCotisations = enRetardCount > 0 ? "En retard" : "À jour";
// Taux de cotisation (payé vs dû pour l'année en cours)
BigDecimal totalAnneeDu = cotisationRepository.calculerTotalCotisationsAnneeEnCours(membreId);
BigDecimal totalAnneePaye = cotisationRepository.calculerTotalCotisationsPayeesAnneeEnCours(membreId);
Integer tauxCotisations = null;
if (totalAnneeDu != null && totalAnneeDu.compareTo(BigDecimal.ZERO) > 0) {
if (totalAnneePaye == null)
totalAnneePaye = BigDecimal.ZERO;
tauxCotisations = totalAnneePaye.multiply(new BigDecimal("100"))
.divide(totalAnneeDu, 0, java.math.RoundingMode.HALF_UP)
.intValue();
}
BigDecimal totalCotisationsPayeesAnnee = (totalAnneePaye != null ? totalAnneePaye : BigDecimal.ZERO);
BigDecimal totalCotisationsPayeesToutTemps = cotisationRepository.calculerTotalCotisationsPayeesToutTemps(membreId);
int nombreCotisationsPayees = (int) cotisationRepository.countPayeesByMembreId(membreId);
// 3. Epargne (somme des soldes des comptes actifs du membre)
BigDecimal soldeEpargne = compteEpargneRepository.sumSoldeActuelByMembreId(membreId);
BigDecimal evolutionEpargneNb = BigDecimal.ZERO;
String evolutionEpargneTxt = soldeEpargne.compareTo(BigDecimal.ZERO) > 0 ? "+0%" : "0%";
Integer objectifEpargne = 0;
// 4. Événements (pas de repository InscriptionEvenement dédié pour l'instant)
Integer mesEvenements = 0;
Integer evenementsAVenir = 0;
Integer tauxParticipation = null;
// 5. Aides (demandes du membre)
List<dev.lions.unionflow.server.entity.DemandeAide> demandes = demandeAideRepository.findByDemandeurId(membreId);
int mesDemandes = demandes != null ? demandes.size() : 0;
int aidesCours = demandes != null ? (int) demandes.stream()
.filter(d -> d.getStatut() != null && d.getStatut() != StatutAide.APPROUVEE && d.getStatut() != StatutAide.REJETEE && d.getStatut() != StatutAide.ANNULEE)
.count() : 0;
Integer tauxAidesApprouvees = null;
if (mesDemandes > 0 && demandes != null) {
long acceptees = demandes.stream().filter(d -> d.getStatut() == StatutAide.APPROUVEE).count();
tauxAidesApprouvees = (int) (acceptees * 100 / mesDemandes);
}
return new MembreDashboardSyntheseResponse(
prenom,
nom,
dateInscription,
paiementsMois != null ? paiementsMois : BigDecimal.ZERO,
totalCotisationsPayeesAnnee,
totalCotisationsPayeesToutTemps != null ? totalCotisationsPayeesToutTemps : BigDecimal.ZERO,
Integer.valueOf(nombreCotisationsPayees),
statutCotisations,
tauxCotisations,
soldeEpargne,
evolutionEpargneNb,
evolutionEpargneTxt,
objectifEpargne,
mesEvenements,
evenementsAVenir,
tauxParticipation,
Integer.valueOf(mesDemandes),
Integer.valueOf(aidesCours),
tauxAidesApprouvees);
}
}