Compare commits
3 Commits
23528197cf
...
e82dc356f3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e82dc356f3 | ||
|
|
eb729bdc56 | ||
|
|
a1e30b51fb |
@@ -12,6 +12,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,42 +37,129 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
*/
|
*/
|
||||||
public Optional<Cotisation> findByNumeroReference(String numeroReference) {
|
public Optional<Cotisation> findByNumeroReference(String numeroReference) {
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
|
"SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("numeroReference", numeroReference);
|
query.setParameter("numeroReference", numeroReference);
|
||||||
return query.getResultStream().findFirst();
|
return query.getResultList().stream().findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trouve toutes les cotisations d'un membre
|
* Trouve toutes les cotisations d'un membre
|
||||||
*
|
*
|
||||||
* @param membreId l'UUID du membre
|
* @param membreId l'UUID du membre
|
||||||
* @param page pagination
|
* @param page pagination
|
||||||
* @param sort tri
|
* @param sort tri
|
||||||
* @return liste paginée des cotisations
|
* @return liste paginée des cotisations
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findByMembreId(UUID membreId, Page page, Sort sort) {
|
public List<Cotisation> findByMembreId(UUID membreId, Page page, Sort sort) {
|
||||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy,
|
"SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy,
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("membreId", membreId);
|
query.setParameter("membreId", membreId);
|
||||||
query.setFirstResult(page.index * page.size);
|
query.setFirstResult(page.index * page.size);
|
||||||
query.setMaxResults(page.size);
|
query.setMaxResults(page.size);
|
||||||
return query.getResultList();
|
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
|
* Trouve les cotisations par statut
|
||||||
*
|
*
|
||||||
* @param statut le statut recherché
|
* @param statut le statut recherché
|
||||||
* @param page pagination
|
* @param page pagination
|
||||||
* @return liste paginée des cotisations
|
* @return liste paginée des cotisations
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findByStatut(String statut, Page page) {
|
public List<Cotisation> findByStatut(String statut, Page page) {
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC",
|
"SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("statut", statut);
|
query.setParameter("statut", statut);
|
||||||
query.setFirstResult(page.index * page.size);
|
query.setFirstResult(page.index * page.size);
|
||||||
query.setMaxResults(page.size);
|
query.setMaxResults(page.size);
|
||||||
@@ -82,13 +170,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
* Trouve les cotisations en retard
|
* Trouve les cotisations en retard
|
||||||
*
|
*
|
||||||
* @param dateReference date de référence (généralement aujourd'hui)
|
* @param dateReference date de référence (généralement aujourd'hui)
|
||||||
* @param page pagination
|
* @param page pagination
|
||||||
* @return liste des cotisations en retard
|
* @return liste des cotisations en retard
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
|
public List<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
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",
|
"SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("dateReference", dateReference);
|
query.setParameter("dateReference", dateReference);
|
||||||
query.setFirstResult(page.index * page.size);
|
query.setFirstResult(page.index * page.size);
|
||||||
query.setMaxResults(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)
|
* Trouve les cotisations par période (année/mois)
|
||||||
*
|
*
|
||||||
* @param annee l'année
|
* @param annee l'année
|
||||||
* @param mois le mois (optionnel)
|
* @param mois le mois (optionnel)
|
||||||
* @param page pagination
|
* @param page pagination
|
||||||
* @return liste des cotisations de la période
|
* @return liste des cotisations de la période
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
|
public List<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
|
||||||
TypedQuery<Cotisation> query;
|
TypedQuery<Cotisation> query;
|
||||||
if (mois != null) {
|
if (mois != null) {
|
||||||
query = entityManager.createQuery(
|
query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC",
|
"SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("annee", annee);
|
query.setParameter("annee", annee);
|
||||||
query.setParameter("mois", mois);
|
query.setParameter("mois", mois);
|
||||||
} else {
|
} else {
|
||||||
query = entityManager.createQuery(
|
query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC",
|
"SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("annee", annee);
|
query.setParameter("annee", annee);
|
||||||
}
|
}
|
||||||
query.setFirstResult(page.index * page.size);
|
query.setFirstResult(page.index * page.size);
|
||||||
@@ -126,13 +214,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
* Trouve les cotisations par type
|
* Trouve les cotisations par type
|
||||||
*
|
*
|
||||||
* @param typeCotisation le type de cotisation
|
* @param typeCotisation le type de cotisation
|
||||||
* @param page pagination
|
* @param page pagination
|
||||||
* @return liste des cotisations du type spécifié
|
* @return liste des cotisations du type spécifié
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findByType(String typeCotisation, Page page) {
|
public List<Cotisation> findByType(String typeCotisation, Page page) {
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||||
"SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC",
|
"SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC",
|
||||||
Cotisation.class);
|
Cotisation.class);
|
||||||
query.setParameter("typeCotisation", typeCotisation);
|
query.setParameter("typeCotisation", typeCotisation);
|
||||||
query.setFirstResult(page.index * page.size);
|
query.setFirstResult(page.index * page.size);
|
||||||
query.setMaxResults(page.size);
|
query.setMaxResults(page.size);
|
||||||
@@ -142,16 +230,115 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
/**
|
/**
|
||||||
* Recherche avancée avec filtres multiples
|
* Recherche avancée avec filtres multiples
|
||||||
*
|
*
|
||||||
* @param membreId UUID du membre (optionnel)
|
* @param membreId UUID du membre (optionnel)
|
||||||
* @param statut statut (optionnel)
|
* @param statut Statut de la cotisation (optionnel)
|
||||||
* @param typeCotisation type (optionnel)
|
* @param typeCotisation Type de cotisation (optionnel)
|
||||||
* @param annee année (optionnel)
|
* @param annee Année (optionnel)
|
||||||
* @param mois mois (optionnel)
|
* @param page Pagination
|
||||||
* @param page pagination
|
* @param sort Tri
|
||||||
* @return liste filtrée des cotisations
|
* @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(
|
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");
|
StringBuilder jpql = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1");
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
|
||||||
@@ -199,8 +386,8 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
*/
|
*/
|
||||||
public BigDecimal calculerTotalMontantDu(UUID membreId) {
|
public BigDecimal calculerTotalMontantDu(UUID membreId) {
|
||||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
query.setParameter("membreId", membreId);
|
query.setParameter("membreId", membreId);
|
||||||
BigDecimal result = query.getSingleResult();
|
BigDecimal result = query.getSingleResult();
|
||||||
return result != null ? result : BigDecimal.ZERO;
|
return result != null ? result : BigDecimal.ZERO;
|
||||||
@@ -214,8 +401,8 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
*/
|
*/
|
||||||
public BigDecimal calculerTotalMontantPaye(UUID membreId) {
|
public BigDecimal calculerTotalMontantPaye(UUID membreId) {
|
||||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
query.setParameter("membreId", membreId);
|
query.setParameter("membreId", membreId);
|
||||||
BigDecimal result = query.getSingleResult();
|
BigDecimal result = query.getSingleResult();
|
||||||
return result != null ? result : BigDecimal.ZERO;
|
return result != null ? result : BigDecimal.ZERO;
|
||||||
@@ -229,23 +416,46 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
*/
|
*/
|
||||||
public long compterParStatut(String statut) {
|
public long compterParStatut(String statut) {
|
||||||
TypedQuery<Long> query = entityManager.createQuery(
|
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);
|
query.setParameter("statut", statut);
|
||||||
return query.getSingleResult();
|
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
|
* Trouve les cotisations nécessitant un rappel
|
||||||
*
|
*
|
||||||
* @param joursAvantEcheance nombre de jours avant échéance
|
* @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
|
* @return liste des cotisations à rappeler
|
||||||
*/
|
*/
|
||||||
public List<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
|
public List<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
|
||||||
LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
|
LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
|
||||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
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",
|
"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);
|
Cotisation.class);
|
||||||
query.setParameter("dateRappel", dateRappel);
|
query.setParameter("dateRappel", dateRappel);
|
||||||
query.setParameter("nombreMaxRappels", nombreMaxRappels);
|
query.setParameter("nombreMaxRappels", nombreMaxRappels);
|
||||||
return query.getResultList();
|
return query.getResultList();
|
||||||
@@ -261,7 +471,7 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
Cotisation cotisation = findByIdOptional(cotisationId).orElse(null);
|
Cotisation cotisation = findByIdOptional(cotisationId).orElse(null);
|
||||||
if (cotisation != null) {
|
if (cotisation != null) {
|
||||||
cotisation.setNombreRappels(
|
cotisation.setNombreRappels(
|
||||||
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
|
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
|
||||||
cotisation.setDateDernierRappel(LocalDateTime.now());
|
cotisation.setDateDernierRappel(LocalDateTime.now());
|
||||||
update(cotisation);
|
update(cotisation);
|
||||||
return true;
|
return true;
|
||||||
@@ -273,13 +483,13 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
* Statistiques des cotisations par période
|
* Statistiques des cotisations par période
|
||||||
*
|
*
|
||||||
* @param annee l'année
|
* @param annee l'année
|
||||||
* @param mois le mois (optionnel)
|
* @param mois le mois (optionnel)
|
||||||
* @return map avec les statistiques
|
* @return map avec les statistiques
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> getStatistiquesPeriode(Integer annee, Integer mois) {
|
public Map<String, Object> getStatistiquesPeriode(Integer annee, Integer mois) {
|
||||||
String baseQuery = mois != null
|
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 AND c.mois = :mois"
|
||||||
: "SELECT c FROM Cotisation c WHERE c.annee = :annee";
|
: "SELECT c FROM Cotisation c WHERE c.annee = :annee";
|
||||||
|
|
||||||
TypedQuery<Long> countQuery;
|
TypedQuery<Long> countQuery;
|
||||||
TypedQuery<BigDecimal> montantTotalQuery;
|
TypedQuery<BigDecimal> montantTotalQuery;
|
||||||
@@ -288,17 +498,17 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
|
|
||||||
if (mois != null) {
|
if (mois != null) {
|
||||||
countQuery = entityManager.createQuery(
|
countQuery = entityManager.createQuery(
|
||||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||||
Long.class);
|
Long.class);
|
||||||
montantTotalQuery = entityManager.createQuery(
|
montantTotalQuery = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
montantPayeQuery = entityManager.createQuery(
|
montantPayeQuery = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
payeesQuery = entityManager.createQuery(
|
payeesQuery = entityManager.createQuery(
|
||||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'",
|
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'",
|
||||||
Long.class);
|
Long.class);
|
||||||
|
|
||||||
countQuery.setParameter("annee", annee);
|
countQuery.setParameter("annee", annee);
|
||||||
countQuery.setParameter("mois", mois);
|
countQuery.setParameter("mois", mois);
|
||||||
@@ -310,16 +520,16 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
payeesQuery.setParameter("mois", mois);
|
payeesQuery.setParameter("mois", mois);
|
||||||
} else {
|
} else {
|
||||||
countQuery = entityManager.createQuery(
|
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(
|
montantTotalQuery = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee",
|
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
montantPayeQuery = entityManager.createQuery(
|
montantPayeQuery = entityManager.createQuery(
|
||||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee",
|
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee",
|
||||||
BigDecimal.class);
|
BigDecimal.class);
|
||||||
payeesQuery = entityManager.createQuery(
|
payeesQuery = entityManager.createQuery(
|
||||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'",
|
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'",
|
||||||
Long.class);
|
Long.class);
|
||||||
|
|
||||||
countQuery.setParameter("annee", annee);
|
countQuery.setParameter("annee", annee);
|
||||||
montantTotalQuery.setParameter("annee", annee);
|
montantTotalQuery.setParameter("annee", annee);
|
||||||
@@ -333,22 +543,22 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
Long cotisationsPayees = payeesQuery.getSingleResult();
|
Long cotisationsPayees = payeesQuery.getSingleResult();
|
||||||
|
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"totalCotisations", totalCotisations != null ? totalCotisations : 0L,
|
"totalCotisations", totalCotisations != null ? totalCotisations : 0L,
|
||||||
"montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO,
|
"montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO,
|
||||||
"montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO,
|
"montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO,
|
||||||
"cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L,
|
"cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L,
|
||||||
"tauxPaiement",
|
"tauxPaiement",
|
||||||
totalCotisations != null && totalCotisations > 0
|
totalCotisations != null && totalCotisations > 0
|
||||||
? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations
|
? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations
|
||||||
: 0.0);
|
: 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Somme des montants payés dans une période */
|
/** Somme des montants payés dans une période */
|
||||||
public BigDecimal sumMontantsPayes(
|
public BigDecimal sumMontantsPayes(
|
||||||
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
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",
|
"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);
|
BigDecimal.class);
|
||||||
query.setParameter("organisationId", organisationId);
|
query.setParameter("organisationId", organisationId);
|
||||||
query.setParameter("debut", debut);
|
query.setParameter("debut", debut);
|
||||||
query.setParameter("fin", fin);
|
query.setParameter("fin", fin);
|
||||||
@@ -358,10 +568,10 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
|||||||
|
|
||||||
/** Somme des montants en attente dans une période */
|
/** Somme des montants en attente dans une période */
|
||||||
public BigDecimal sumMontantsEnAttente(
|
public BigDecimal sumMontantsEnAttente(
|
||||||
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
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",
|
"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);
|
BigDecimal.class);
|
||||||
query.setParameter("organisationId", organisationId);
|
query.setParameter("organisationId", organisationId);
|
||||||
query.setParameter("debut", debut);
|
query.setParameter("debut", debut);
|
||||||
query.setParameter("fin", fin);
|
query.setParameter("fin", fin);
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package dev.lions.unionflow.server.resource;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest;
|
||||||
|
import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse;
|
||||||
|
import dev.lions.unionflow.server.service.ParametresLcbFtService;
|
||||||
|
import jakarta.annotation.security.PermitAll;
|
||||||
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.tags.Tag;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource REST pour les paramètres LCB-FT (seuils anti-blanchiment).
|
||||||
|
*
|
||||||
|
* @author lions dev Team
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2026-03-13
|
||||||
|
*/
|
||||||
|
@Path("/api/parametres-lcb-ft")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Tag(name = "Paramètres LCB-FT", description = "Gestion des seuils anti-blanchiment (LCB-FT)")
|
||||||
|
@Slf4j
|
||||||
|
public class ParametresLcbFtResource {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ParametresLcbFtService parametresService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les paramètres LCB-FT pour une organisation et une devise.
|
||||||
|
* Endpoint utilisé par le mobile pour valider côté client.
|
||||||
|
*
|
||||||
|
* @param organisationId ID de l'organisation (optionnel, null pour paramètres plateforme)
|
||||||
|
* @param codeDevise Code devise ISO 4217 (XOF par défaut)
|
||||||
|
* @return Paramètres LCB-FT complets
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@PermitAll
|
||||||
|
@Operation(
|
||||||
|
summary = "Récupérer les paramètres LCB-FT",
|
||||||
|
description = "Retourne les seuils anti-blanchiment pour une organisation ou la plateforme"
|
||||||
|
)
|
||||||
|
@APIResponse(responseCode = "200", description = "Paramètres récupérés")
|
||||||
|
@APIResponse(responseCode = "404", description = "Paramètres non configurés")
|
||||||
|
public Response getParametres(
|
||||||
|
@QueryParam("organisationId")
|
||||||
|
@Parameter(description = "ID de l'organisation (optionnel)")
|
||||||
|
String organisationId,
|
||||||
|
|
||||||
|
@QueryParam("codeDevise")
|
||||||
|
@DefaultValue("XOF")
|
||||||
|
@Parameter(description = "Code devise (XOF par défaut)")
|
||||||
|
String codeDevise) {
|
||||||
|
|
||||||
|
log.info("GET /api/parametres-lcb-ft?organisationId={}&codeDevise={}",
|
||||||
|
organisationId, codeDevise);
|
||||||
|
|
||||||
|
UUID orgId = organisationId != null && !organisationId.isBlank() ?
|
||||||
|
UUID.fromString(organisationId) : null;
|
||||||
|
|
||||||
|
ParametresLcbFtResponse params = parametresService.getParametres(orgId, codeDevise);
|
||||||
|
return Response.ok(params).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère uniquement le seuil de justification (endpoint léger pour mobile).
|
||||||
|
*
|
||||||
|
* @param organisationId ID de l'organisation
|
||||||
|
* @param codeDevise Code devise (XOF par défaut)
|
||||||
|
* @return Montant seuil
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/seuil-justification")
|
||||||
|
@PermitAll
|
||||||
|
@Operation(
|
||||||
|
summary = "Récupérer le seuil de justification uniquement",
|
||||||
|
description = "Endpoint léger pour récupérer juste le montant seuil (utilisé par mobile)"
|
||||||
|
)
|
||||||
|
@APIResponse(responseCode = "200", description = "Seuil récupéré")
|
||||||
|
public Response getSeuilJustification(
|
||||||
|
@QueryParam("organisationId") String organisationId,
|
||||||
|
@QueryParam("codeDevise") @DefaultValue("XOF") String codeDevise) {
|
||||||
|
|
||||||
|
log.debug("GET /api/parametres-lcb-ft/seuil-justification");
|
||||||
|
|
||||||
|
UUID orgId = organisationId != null && !organisationId.isBlank() ?
|
||||||
|
UUID.fromString(organisationId) : null;
|
||||||
|
|
||||||
|
BigDecimal seuil = parametresService.getSeuilJustification(orgId, codeDevise);
|
||||||
|
return Response.ok().entity(new SeuilResponse(seuil, codeDevise)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée ou met à jour les paramètres LCB-FT (admin uniquement).
|
||||||
|
*
|
||||||
|
* @param request Paramètres à créer/mettre à jour
|
||||||
|
* @return Paramètres créés/mis à jour
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@RolesAllowed({ "ADMIN", "SUPER_ADMIN" })
|
||||||
|
@Operation(
|
||||||
|
summary = "Créer ou mettre à jour les paramètres LCB-FT",
|
||||||
|
description = "Admin uniquement - Configure les seuils pour une organisation ou la plateforme"
|
||||||
|
)
|
||||||
|
@APIResponse(responseCode = "200", description = "Paramètres sauvegardés")
|
||||||
|
public Response saveOrUpdateParametres(@Valid ParametresLcbFtRequest request) {
|
||||||
|
log.info("POST /api/parametres-lcb-ft - org={}", request.getOrganisationId());
|
||||||
|
|
||||||
|
ParametresLcbFtResponse saved = parametresService.saveOrUpdateParametres(request);
|
||||||
|
return Response.ok(saved).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO léger pour retourner uniquement le seuil.
|
||||||
|
*/
|
||||||
|
public record SeuilResponse(
|
||||||
|
BigDecimal montantSeuil,
|
||||||
|
String codeDevise
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package dev.lions.unionflow.server.service;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest;
|
||||||
|
import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse;
|
||||||
|
import dev.lions.unionflow.server.entity.Organisation;
|
||||||
|
import dev.lions.unionflow.server.entity.ParametresLcbFt;
|
||||||
|
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||||
|
import dev.lions.unionflow.server.repository.ParametresLcbFtRepository;
|
||||||
|
import io.quarkus.cache.CacheResult;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.ws.rs.NotFoundException;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service métier pour la gestion des paramètres LCB-FT (seuils anti-blanchiment).
|
||||||
|
*
|
||||||
|
* @author lions dev Team
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2026-03-13
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ParametresLcbFtService {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(ParametresLcbFtService.class);
|
||||||
|
private static final String CODE_DEVISE_DEFAULT = "XOF";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ParametresLcbFtRepository parametresRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
OrganisationRepository organisationRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les paramètres LCB-FT pour une organisation et une devise.
|
||||||
|
* Avec cache pour performance (les seuils changent rarement).
|
||||||
|
*
|
||||||
|
* @param organisationId ID de l'organisation (null pour paramètres plateforme)
|
||||||
|
* @param codeDevise Code devise ISO 4217 (XOF par défaut)
|
||||||
|
* @return Paramètres LCB-FT ou paramètres plateforme par défaut
|
||||||
|
*/
|
||||||
|
@CacheResult(cacheName = "parametres-lcb-ft")
|
||||||
|
public ParametresLcbFtResponse getParametres(UUID organisationId, String codeDevise) {
|
||||||
|
if (codeDevise == null || codeDevise.isBlank()) {
|
||||||
|
codeDevise = CODE_DEVISE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.infof("Récupération paramètres LCB-FT pour organisation=%s, devise=%s",
|
||||||
|
organisationId, codeDevise);
|
||||||
|
|
||||||
|
Optional<ParametresLcbFt> params = parametresRepository
|
||||||
|
.findByOrganisationAndDevise(organisationId, codeDevise);
|
||||||
|
|
||||||
|
if (params.isEmpty()) {
|
||||||
|
LOG.warnf("Aucun paramètre LCB-FT trouvé pour organisation=%s, devise=%s",
|
||||||
|
organisationId, codeDevise);
|
||||||
|
throw new NotFoundException(
|
||||||
|
"Paramètres LCB-FT non configurés pour cette organisation/devise");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(params.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère uniquement le seuil de justification (pour validation rapide).
|
||||||
|
*
|
||||||
|
* @param organisationId ID de l'organisation
|
||||||
|
* @param codeDevise Code devise (XOF par défaut)
|
||||||
|
* @return Montant seuil au-dessus duquel origine des fonds est obligatoire
|
||||||
|
*/
|
||||||
|
@CacheResult(cacheName = "seuil-justification-lcb-ft")
|
||||||
|
public BigDecimal getSeuilJustification(UUID organisationId, String codeDevise) {
|
||||||
|
if (codeDevise == null || codeDevise.isBlank()) {
|
||||||
|
codeDevise = CODE_DEVISE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debugf("Récupération seuil justification LCB-FT org=%s, devise=%s",
|
||||||
|
organisationId, codeDevise);
|
||||||
|
|
||||||
|
return parametresRepository.getSeuilJustification(organisationId, codeDevise)
|
||||||
|
.orElse(BigDecimal.valueOf(500000)); // Fallback 500k XOF
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée ou met à jour les paramètres LCB-FT pour une organisation.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public ParametresLcbFtResponse saveOrUpdateParametres(ParametresLcbFtRequest request) {
|
||||||
|
LOG.infof("Sauvegarde paramètres LCB-FT pour organisation=%s",
|
||||||
|
request.getOrganisationId());
|
||||||
|
|
||||||
|
Organisation organisation = null;
|
||||||
|
if (request.getOrganisationId() != null) {
|
||||||
|
organisation = organisationRepository.findByIdOptional(
|
||||||
|
UUID.fromString(request.getOrganisationId()))
|
||||||
|
.orElseThrow(() -> new NotFoundException(
|
||||||
|
"Organisation non trouvée: " + request.getOrganisationId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chercher paramètre existant
|
||||||
|
String codeDevise = request.getCodeDevise() != null ?
|
||||||
|
request.getCodeDevise() : CODE_DEVISE_DEFAULT;
|
||||||
|
|
||||||
|
UUID orgId = organisation != null ? organisation.getId() : null;
|
||||||
|
Optional<ParametresLcbFt> existing = parametresRepository
|
||||||
|
.findByOrganisationAndDevise(orgId, codeDevise);
|
||||||
|
|
||||||
|
ParametresLcbFt params;
|
||||||
|
if (existing.isPresent()) {
|
||||||
|
// Mise à jour
|
||||||
|
params = existing.get();
|
||||||
|
params.setMontantSeuilJustification(request.getMontantSeuilJustification());
|
||||||
|
params.setMontantSeuilValidationManuelle(request.getMontantSeuilValidationManuelle());
|
||||||
|
LOG.infof("Paramètres LCB-FT mis à jour : %s", params.getId());
|
||||||
|
} else {
|
||||||
|
// Création
|
||||||
|
params = ParametresLcbFt.builder()
|
||||||
|
.organisation(organisation)
|
||||||
|
.codeDevise(codeDevise)
|
||||||
|
.montantSeuilJustification(request.getMontantSeuilJustification())
|
||||||
|
.montantSeuilValidationManuelle(request.getMontantSeuilValidationManuelle())
|
||||||
|
.build();
|
||||||
|
parametresRepository.persist(params);
|
||||||
|
LOG.infof("Nouveaux paramètres LCB-FT créés : %s", params.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit l'entité en DTO de réponse.
|
||||||
|
*/
|
||||||
|
private ParametresLcbFtResponse toDTO(ParametresLcbFt params) {
|
||||||
|
ParametresLcbFtResponse response = ParametresLcbFtResponse.builder()
|
||||||
|
.organisationId(params.getOrganisation() != null ?
|
||||||
|
params.getOrganisation().getId().toString() : null)
|
||||||
|
.organisationNom(params.getOrganisation() != null ?
|
||||||
|
params.getOrganisation().getNom() : null)
|
||||||
|
.montantSeuilJustification(params.getMontantSeuilJustification())
|
||||||
|
.montantSeuilValidationManuelle(params.getMontantSeuilValidationManuelle())
|
||||||
|
.codeDevise(params.getCodeDevise())
|
||||||
|
.estParametrePlateforme(params.getOrganisation() == null)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Set BaseResponse fields
|
||||||
|
response.setId(params.getId());
|
||||||
|
response.setDateCreation(params.getDateCreation());
|
||||||
|
response.setDateModification(params.getDateModification());
|
||||||
|
response.setActif(params.getActif());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user