Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
@@ -0,0 +1,493 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des cotisations Contient la logique métier et les règles de
|
||||
* validation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class CotisationService {
|
||||
|
||||
@Inject CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
/**
|
||||
* Récupère toutes les cotisations avec pagination
|
||||
*
|
||||
* @param page numéro de page (0-based)
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations converties en DTO
|
||||
*/
|
||||
public List<CotisationDTO> getAllCotisations(int page, int size) {
|
||||
log.debug("Récupération des cotisations - page: {}, size: {}", page, size);
|
||||
|
||||
// Utilisation de EntityManager pour la pagination
|
||||
jakarta.persistence.TypedQuery<Cotisation> query =
|
||||
cotisationRepository.getEntityManager().createQuery(
|
||||
"SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
List<Cotisation> cotisations = query.getResultList();
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son ID
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
* @return DTO de la cotisation
|
||||
* @throws NotFoundException si la cotisation n'existe pas
|
||||
*/
|
||||
public CotisationDTO getCotisationById(@NotNull UUID id) {
|
||||
log.debug("Récupération de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return DTO de la cotisation
|
||||
* @throws NotFoundException si la cotisation n'existe pas
|
||||
*/
|
||||
public CotisationDTO getCotisationByReference(@NotNull String numeroReference) {
|
||||
log.debug("Récupération de la cotisation avec référence: {}", numeroReference);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByNumeroReference(numeroReference)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Cotisation non trouvée avec la référence: " + numeroReference));
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle cotisation
|
||||
*
|
||||
* @param cotisationDTO données de la cotisation à créer
|
||||
* @return DTO de la cotisation créée
|
||||
*/
|
||||
@Transactional
|
||||
public CotisationDTO createCotisation(@Valid CotisationDTO cotisationDTO) {
|
||||
log.info("Création d'une nouvelle cotisation pour le membre: {}", cotisationDTO.getMembreId());
|
||||
|
||||
// Validation du membre - UUID direct maintenant
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(cotisationDTO.getMembreId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Membre non trouvé avec l'ID: " + cotisationDTO.getMembreId()));
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Cotisation cotisation = convertToEntity(cotisationDTO);
|
||||
cotisation.setMembre(membre);
|
||||
|
||||
// Génération automatique du numéro de référence si absent
|
||||
if (cotisation.getNumeroReference() == null || cotisation.getNumeroReference().isEmpty()) {
|
||||
cotisation.setNumeroReference(Cotisation.genererNumeroReference());
|
||||
}
|
||||
|
||||
// Validation des règles métier
|
||||
validateCotisationRules(cotisation);
|
||||
|
||||
// Persistance
|
||||
cotisationRepository.persist(cotisation);
|
||||
|
||||
log.info(
|
||||
"Cotisation créée avec succès - ID: {}, Référence: {}",
|
||||
cotisation.getId(),
|
||||
cotisation.getNumeroReference());
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une cotisation existante
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
* @param cotisationDTO nouvelles données
|
||||
* @return DTO de la cotisation mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public CotisationDTO updateCotisation(@NotNull UUID id, @Valid CotisationDTO cotisationDTO) {
|
||||
log.info("Mise à jour de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisationExistante =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
updateCotisationFields(cotisationExistante, cotisationDTO);
|
||||
|
||||
// Validation des règles métier
|
||||
validateCotisationRules(cotisationExistante);
|
||||
|
||||
log.info("Cotisation mise à jour avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(cotisationExistante);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime (désactive) une cotisation
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteCotisation(@NotNull UUID id) {
|
||||
log.info("Suppression de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
// Vérification si la cotisation peut être supprimée
|
||||
if ("PAYEE".equals(cotisation.getStatut())) {
|
||||
throw new IllegalStateException("Impossible de supprimer une cotisation déjà payée");
|
||||
}
|
||||
|
||||
cotisation.setStatut("ANNULEE");
|
||||
|
||||
log.info("Cotisation supprimée avec succès - ID: {}", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations d'un membre
|
||||
*
|
||||
* @param membreId identifiant UUID du membre
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations du membre
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsByMembre(@NotNull UUID membreId, int page, int size) {
|
||||
log.debug("Récupération des cotisations du membre: {}", membreId);
|
||||
|
||||
// Vérification de l'existence du membre
|
||||
if (!membreRepository.findByIdOptional(membreId).isPresent()) {
|
||||
throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
|
||||
}
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.findByMembreId(
|
||||
membreId, Page.of(page, size), Sort.by("dateEcheance").descending());
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations par statut
|
||||
*
|
||||
* @param statut statut recherché
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations avec le statut spécifié
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsByStatut(@NotNull String statut, int page, int size) {
|
||||
log.debug("Récupération des cotisations avec statut: {}", statut);
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.findByStatut(statut, Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations en retard
|
||||
*
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations en retard
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsEnRetard(int page, int size) {
|
||||
log.debug("Récupération des cotisations en retard");
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.findCotisationsEnRetard(LocalDate.now(), Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée de cotisations
|
||||
*
|
||||
* @param membreId identifiant du membre (optionnel)
|
||||
* @param statut statut (optionnel)
|
||||
* @param typeCotisation type (optionnel)
|
||||
* @param annee année (optionnel)
|
||||
* @param mois mois (optionnel)
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste filtrée des cotisations
|
||||
*/
|
||||
public List<CotisationDTO> rechercherCotisations(
|
||||
UUID membreId,
|
||||
String statut,
|
||||
String typeCotisation,
|
||||
Integer annee,
|
||||
Integer mois,
|
||||
int page,
|
||||
int size) {
|
||||
log.debug("Recherche avancée de cotisations avec filtres");
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.rechercheAvancee(
|
||||
membreId, statut, typeCotisation, annee, mois, Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des cotisations
|
||||
*
|
||||
* @return map contenant les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesCotisations() {
|
||||
log.debug("Calcul des statistiques des cotisations");
|
||||
|
||||
long totalCotisations = cotisationRepository.count();
|
||||
long cotisationsPayees = cotisationRepository.compterParStatut("PAYEE");
|
||||
long cotisationsEnRetard =
|
||||
cotisationRepository
|
||||
.findCotisationsEnRetard(LocalDate.now(), Page.of(0, Integer.MAX_VALUE))
|
||||
.size();
|
||||
|
||||
return Map.of(
|
||||
"totalCotisations", totalCotisations,
|
||||
"cotisationsPayees", cotisationsPayees,
|
||||
"cotisationsEnRetard", cotisationsEnRetard,
|
||||
"tauxPaiement",
|
||||
totalCotisations > 0 ? (cotisationsPayees * 100.0 / totalCotisations) : 0.0);
|
||||
}
|
||||
|
||||
/** Convertit une entité Cotisation en DTO */
|
||||
private CotisationDTO convertToDTO(Cotisation cotisation) {
|
||||
if (cotisation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CotisationDTO dto = new CotisationDTO();
|
||||
|
||||
// Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant)
|
||||
dto.setId(cotisation.getId());
|
||||
dto.setNumeroReference(cotisation.getNumeroReference());
|
||||
|
||||
// Conversion du membre associé
|
||||
if (cotisation.getMembre() != null) {
|
||||
dto.setMembreId(cotisation.getMembre().getId());
|
||||
dto.setNomMembre(cotisation.getMembre().getNomComplet());
|
||||
dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre());
|
||||
|
||||
// Conversion de l'organisation du membre (associationId)
|
||||
if (cotisation.getMembre().getOrganisation() != null
|
||||
&& cotisation.getMembre().getOrganisation().getId() != null) {
|
||||
dto.setAssociationId(cotisation.getMembre().getOrganisation().getId());
|
||||
dto.setNomAssociation(cotisation.getMembre().getOrganisation().getNom());
|
||||
}
|
||||
}
|
||||
|
||||
// Propriétés de la cotisation
|
||||
dto.setTypeCotisation(cotisation.getTypeCotisation());
|
||||
dto.setMontantDu(cotisation.getMontantDu());
|
||||
dto.setMontantPaye(cotisation.getMontantPaye());
|
||||
dto.setCodeDevise(cotisation.getCodeDevise());
|
||||
dto.setStatut(cotisation.getStatut());
|
||||
dto.setDateEcheance(cotisation.getDateEcheance());
|
||||
dto.setDatePaiement(cotisation.getDatePaiement());
|
||||
dto.setDescription(cotisation.getDescription());
|
||||
dto.setPeriode(cotisation.getPeriode());
|
||||
dto.setAnnee(cotisation.getAnnee());
|
||||
dto.setMois(cotisation.getMois());
|
||||
dto.setObservations(cotisation.getObservations());
|
||||
dto.setRecurrente(cotisation.getRecurrente());
|
||||
dto.setNombreRappels(cotisation.getNombreRappels());
|
||||
dto.setDateDernierRappel(cotisation.getDateDernierRappel());
|
||||
|
||||
// Conversion du validateur
|
||||
dto.setValidePar(
|
||||
cotisation.getValideParId() != null
|
||||
? cotisation.getValideParId()
|
||||
: null);
|
||||
dto.setNomValidateur(cotisation.getNomValidateur());
|
||||
|
||||
dto.setMethodePaiement(cotisation.getMethodePaiement());
|
||||
dto.setReferencePaiement(cotisation.getReferencePaiement());
|
||||
dto.setDateCreation(cotisation.getDateCreation());
|
||||
dto.setDateModification(cotisation.getDateModification());
|
||||
|
||||
// Propriétés héritées de BaseDTO
|
||||
dto.setActif(true); // Les cotisations sont toujours actives
|
||||
dto.setVersion(0L); // Version par défaut
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité Cotisation */
|
||||
private Cotisation convertToEntity(CotisationDTO dto) {
|
||||
return Cotisation.builder()
|
||||
.numeroReference(dto.getNumeroReference())
|
||||
.typeCotisation(dto.getTypeCotisation())
|
||||
.montantDu(dto.getMontantDu())
|
||||
.montantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO)
|
||||
.codeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF")
|
||||
.statut(dto.getStatut() != null ? dto.getStatut() : "EN_ATTENTE")
|
||||
.dateEcheance(dto.getDateEcheance())
|
||||
.datePaiement(dto.getDatePaiement())
|
||||
.description(dto.getDescription())
|
||||
.periode(dto.getPeriode())
|
||||
.annee(dto.getAnnee())
|
||||
.mois(dto.getMois())
|
||||
.observations(dto.getObservations())
|
||||
.recurrente(dto.getRecurrente() != null ? dto.getRecurrente() : false)
|
||||
.nombreRappels(dto.getNombreRappels() != null ? dto.getNombreRappels() : 0)
|
||||
.dateDernierRappel(dto.getDateDernierRappel())
|
||||
.methodePaiement(dto.getMethodePaiement())
|
||||
.referencePaiement(dto.getReferencePaiement())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Met à jour les champs d'une cotisation existante */
|
||||
private void updateCotisationFields(Cotisation cotisation, CotisationDTO dto) {
|
||||
if (dto.getTypeCotisation() != null) {
|
||||
cotisation.setTypeCotisation(dto.getTypeCotisation());
|
||||
}
|
||||
if (dto.getMontantDu() != null) {
|
||||
cotisation.setMontantDu(dto.getMontantDu());
|
||||
}
|
||||
if (dto.getMontantPaye() != null) {
|
||||
cotisation.setMontantPaye(dto.getMontantPaye());
|
||||
}
|
||||
if (dto.getStatut() != null) {
|
||||
cotisation.setStatut(dto.getStatut());
|
||||
}
|
||||
if (dto.getDateEcheance() != null) {
|
||||
cotisation.setDateEcheance(dto.getDateEcheance());
|
||||
}
|
||||
if (dto.getDatePaiement() != null) {
|
||||
cotisation.setDatePaiement(dto.getDatePaiement());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
cotisation.setDescription(dto.getDescription());
|
||||
}
|
||||
if (dto.getObservations() != null) {
|
||||
cotisation.setObservations(dto.getObservations());
|
||||
}
|
||||
if (dto.getMethodePaiement() != null) {
|
||||
cotisation.setMethodePaiement(dto.getMethodePaiement());
|
||||
}
|
||||
if (dto.getReferencePaiement() != null) {
|
||||
cotisation.setReferencePaiement(dto.getReferencePaiement());
|
||||
}
|
||||
}
|
||||
|
||||
/** Valide les règles métier pour une cotisation */
|
||||
private void validateCotisationRules(Cotisation cotisation) {
|
||||
// Validation du montant
|
||||
if (cotisation.getMontantDu().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("Le montant dû doit être positif");
|
||||
}
|
||||
|
||||
// Validation de la date d'échéance
|
||||
if (cotisation.getDateEcheance().isBefore(LocalDate.now().minusYears(1))) {
|
||||
throw new IllegalArgumentException("La date d'échéance ne peut pas être antérieure à un an");
|
||||
}
|
||||
|
||||
// Validation du montant payé
|
||||
if (cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) > 0) {
|
||||
throw new IllegalArgumentException("Le montant payé ne peut pas dépasser le montant dû");
|
||||
}
|
||||
|
||||
// Validation de la cohérence statut/paiement
|
||||
if ("PAYEE".equals(cotisation.getStatut())
|
||||
&& cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Une cotisation marquée comme payée doit avoir un montant payé égal au montant dû");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY)
|
||||
*
|
||||
* @param membreIds Liste des IDs des membres destinataires
|
||||
* @return Nombre de rappels envoyés
|
||||
*/
|
||||
@Transactional
|
||||
public int envoyerRappelsCotisationsGroupes(List<UUID> membreIds) {
|
||||
log.info("Envoi de rappels de cotisations groupés à {} membres", membreIds.size());
|
||||
|
||||
if (membreIds == null || membreIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("La liste des membres ne peut pas être vide");
|
||||
}
|
||||
|
||||
int rappelsEnvoyes = 0;
|
||||
for (UUID membreId : membreIds) {
|
||||
try {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(membreId)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"Membre non trouvé avec l'ID: " + membreId));
|
||||
|
||||
// Trouver les cotisations en retard pour ce membre
|
||||
List<Cotisation> cotisationsEnRetard =
|
||||
cotisationRepository.findCotisationsAuRappel(7, 3).stream()
|
||||
.filter(c -> c.getMembre() != null && c.getMembre().getId().equals(membreId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Cotisation cotisation : cotisationsEnRetard) {
|
||||
// Incrémenter le nombre de rappels
|
||||
cotisationRepository.incrementerNombreRappels(cotisation.getId());
|
||||
rappelsEnvoyes++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(
|
||||
"Erreur lors de l'envoi du rappel de cotisation pour le membre {}: {}",
|
||||
membreId,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("{} rappels envoyés sur {} membres demandés", rappelsEnvoyes, membreIds.size());
|
||||
return rappelsEnvoyes;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user