From a1e30b51fb242d0a439c4dc34e6670d7ef3d8c93 Mon Sep 17 00:00:00 2001 From: dahoud Date: Mon, 9 Mar 2026 19:58:25 +0000 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20cotisations=20tout=20temps?= =?UTF-8?q?=20+=20synth=C3=A8se=20membre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CotisationRepository: calculerTotalCotisationsPayeesToutTemps(membreId) - MembreDashboardService: envoi totalCotisationsPayeesToutTemps dans la synthèse Made-with: Cursor --- .../repository/CotisationRepository.java | 350 ++++++++++++++---- .../service/MembreDashboardService.java | 158 ++++++++ 2 files changed, 438 insertions(+), 70 deletions(-) create mode 100644 src/main/java/dev/lions/unionflow/server/service/MembreDashboardService.java diff --git a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java index 0c6863c..81400da 100644 --- a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java +++ b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java @@ -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 { */ public Optional findByNumeroReference(String numeroReference) { TypedQuery 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 findByMembreId(UUID membreId, Page page, Sort sort) { String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; TypedQuery query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy, - Cotisation.class); + "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 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 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 findByOrganisationIdIn(Set 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 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 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 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 findEnAttenteByOrganisationIdIn(Set organisationIds) { + if (organisationIds == null || organisationIds.isEmpty()) { + return List.of(); + } + int anneeEnCours = LocalDate.now().getYear(); + TypedQuery 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 findByStatut(String statut, Page page) { TypedQuery query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC", - Cotisation.class); + "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 { * 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 findCotisationsEnRetard(LocalDate dateReference, Page page) { TypedQuery query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC", - Cotisation.class); + "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 { * 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 findByPeriode(Integer annee, Integer mois, Page page) { TypedQuery query; if (mois != null) { query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC", - Cotisation.class); + "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 { * 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 findByType(String typeCotisation, Page page) { TypedQuery query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC", - Cotisation.class); + "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 { /** * 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 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 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 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 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 params = new HashMap<>(); @@ -199,8 +386,8 @@ public class CotisationRepository extends BaseRepository { */ public BigDecimal calculerTotalMontantDu(UUID membreId) { TypedQuery query = entityManager.createQuery( - "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId", - BigDecimal.class); + "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 { */ public BigDecimal calculerTotalMontantPaye(UUID membreId) { TypedQuery query = entityManager.createQuery( - "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId", - BigDecimal.class); + "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 { */ public long compterParStatut(String statut) { TypedQuery 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 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 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 findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) { LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance); TypedQuery query = entityManager.createQuery( - "SELECT c FROM Cotisation c WHERE c.dateEcheance <= :dateRappel AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' AND c.nombreRappels < :nombreMaxRappels ORDER BY c.dateEcheance ASC", - Cotisation.class); + "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 = 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 { * 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 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 countQuery; TypedQuery montantTotalQuery; @@ -288,17 +498,17 @@ public class CotisationRepository extends BaseRepository { 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 { 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 { 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 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 { /** 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 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); diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreDashboardService.java b/src/main/java/dev/lions/unionflow/server/service/MembreDashboardService.java new file mode 100644 index 0000000..d7c2106 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/MembreDashboardService.java @@ -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 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); + } +}