Version propre - Dashboard enhanced
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité Cotisation avec Lombok
|
||||
* Représente une cotisation d'un membre à son organisation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cotisations", indexes = {
|
||||
@Index(name = "idx_cotisation_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
|
||||
@Index(name = "idx_cotisation_statut", columnList = "statut"),
|
||||
@Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
|
||||
@Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
|
||||
@Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class Cotisation extends PanacheEntity {
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
|
||||
private String numeroReference;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "type_cotisation", nullable = false, length = 50)
|
||||
private String typeCotisation;
|
||||
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal montantDu;
|
||||
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal montantPaye = BigDecimal.ZERO;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
@Column(name = "code_devise", nullable = false, length = 3)
|
||||
private String codeDevise;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
|
||||
@Column(name = "statut", nullable = false, length = 30)
|
||||
private String statut;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_echeance", nullable = false)
|
||||
private LocalDate dateEcheance;
|
||||
|
||||
@Column(name = "date_paiement")
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
@Size(max = 500)
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "periode", length = 20)
|
||||
private String periode;
|
||||
|
||||
@NotNull
|
||||
@Min(value = 2020, message = "L'année doit être supérieure à 2020")
|
||||
@Max(value = 2100, message = "L'année doit être inférieure à 2100")
|
||||
@Column(name = "annee", nullable = false)
|
||||
private Integer annee;
|
||||
|
||||
@Min(value = 1, message = "Le mois doit être entre 1 et 12")
|
||||
@Max(value = 12, message = "Le mois doit être entre 1 et 12")
|
||||
@Column(name = "mois")
|
||||
private Integer mois;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "observations", length = 1000)
|
||||
private String observations;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "recurrente", nullable = false)
|
||||
private Boolean recurrente = false;
|
||||
|
||||
@Builder.Default
|
||||
@Min(value = 0, message = "Le nombre de rappels doit être positif")
|
||||
@Column(name = "nombre_rappels", nullable = false)
|
||||
private Integer nombreRappels = 0;
|
||||
|
||||
@Column(name = "date_dernier_rappel")
|
||||
private LocalDateTime dateDernierRappel;
|
||||
|
||||
@Column(name = "valide_par_id")
|
||||
private Long valideParId;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "nom_validateur", length = 100)
|
||||
private String nomValidateur;
|
||||
|
||||
@Column(name = "date_validation")
|
||||
private LocalDateTime dateValidation;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "methode_paiement", length = 50)
|
||||
private String methodePaiement;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "reference_paiement", length = 100)
|
||||
private String referencePaiement;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "date_creation", nullable = false)
|
||||
private LocalDateTime dateCreation = LocalDateTime.now();
|
||||
|
||||
@Column(name = "date_modification")
|
||||
private LocalDateTime dateModification;
|
||||
|
||||
/**
|
||||
* Méthode métier pour calculer le montant restant à payer
|
||||
*/
|
||||
public BigDecimal getMontantRestant() {
|
||||
if (montantDu == null || montantPaye == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return montantDu.subtract(montantPaye);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode métier pour vérifier si la cotisation est entièrement payée
|
||||
*/
|
||||
public boolean isEntierementPayee() {
|
||||
return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode métier pour vérifier si la cotisation est en retard
|
||||
*/
|
||||
public boolean isEnRetard() {
|
||||
return dateEcheance != null &&
|
||||
dateEcheance.isBefore(LocalDate.now()) &&
|
||||
!isEntierementPayee();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode métier pour générer un numéro de référence unique
|
||||
*/
|
||||
public static String genererNumeroReference() {
|
||||
return "COT-" + LocalDate.now().getYear() + "-" +
|
||||
String.format("%08d", System.currentTimeMillis() % 100000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback JPA avant la persistance
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (numeroReference == null || numeroReference.isEmpty()) {
|
||||
numeroReference = genererNumeroReference();
|
||||
}
|
||||
if (dateCreation == null) {
|
||||
dateCreation = LocalDateTime.now();
|
||||
}
|
||||
if (codeDevise == null) {
|
||||
codeDevise = "XOF";
|
||||
}
|
||||
if (statut == null) {
|
||||
statut = "EN_ATTENTE";
|
||||
}
|
||||
if (montantPaye == null) {
|
||||
montantPaye = BigDecimal.ZERO;
|
||||
}
|
||||
if (nombreRappels == null) {
|
||||
nombreRappels = 0;
|
||||
}
|
||||
if (recurrente == null) {
|
||||
recurrente = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback JPA avant la mise à jour
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
dateModification = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Repository pour la gestion des cotisations
|
||||
* Utilise Panache pour simplifier les opérations JPA
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class CotisationRepository implements PanacheRepository<Cotisation> {
|
||||
|
||||
/**
|
||||
* Trouve une cotisation par son numéro de référence
|
||||
*
|
||||
* @param numeroReference le numéro de référence unique
|
||||
* @return Optional contenant la cotisation si trouvée
|
||||
*/
|
||||
public Optional<Cotisation> findByNumeroReference(String numeroReference) {
|
||||
return find("numeroReference = ?1", numeroReference).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les cotisations d'un membre
|
||||
*
|
||||
* @param membreId l'identifiant du membre
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des cotisations
|
||||
*/
|
||||
public List<Cotisation> findByMembreId(Long membreId, Page page, Sort sort) {
|
||||
return find("membre.id = ?1", membreId)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par statut
|
||||
*
|
||||
* @param statut le statut recherché
|
||||
* @param page pagination
|
||||
* @return liste paginée des cotisations
|
||||
*/
|
||||
public List<Cotisation> findByStatut(String statut, Page page) {
|
||||
return find("statut = ?1", Sort.by("dateEcheance").descending(), statut)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations en retard
|
||||
*
|
||||
* @param dateReference date de référence (généralement aujourd'hui)
|
||||
* @param page pagination
|
||||
* @return liste des cotisations en retard
|
||||
*/
|
||||
public List<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
|
||||
return find("dateEcheance < ?1 and statut != 'PAYEE' and statut != 'ANNULEE'",
|
||||
Sort.by("dateEcheance").ascending(), dateReference)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par période (année/mois)
|
||||
*
|
||||
* @param annee l'année
|
||||
* @param mois le mois (optionnel)
|
||||
* @param page pagination
|
||||
* @return liste des cotisations de la période
|
||||
*/
|
||||
public List<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
|
||||
if (mois != null) {
|
||||
return find("annee = ?1 and mois = ?2", Sort.by("dateEcheance").descending(), annee, mois)
|
||||
.page(page)
|
||||
.list();
|
||||
} else {
|
||||
return find("annee = ?1", Sort.by("mois", "dateEcheance").descending(), annee)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par type
|
||||
*
|
||||
* @param typeCotisation le type de cotisation
|
||||
* @param page pagination
|
||||
* @return liste des cotisations du type spécifié
|
||||
*/
|
||||
public List<Cotisation> findByType(String typeCotisation, Page page) {
|
||||
return find("typeCotisation = ?1", Sort.by("dateEcheance").descending(), typeCotisation)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée avec filtres multiples
|
||||
*
|
||||
* @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 pagination
|
||||
* @return liste filtrée des cotisations
|
||||
*/
|
||||
public List<Cotisation> rechercheAvancee(Long membreId, String statut, String typeCotisation,
|
||||
Integer annee, Integer mois, Page page) {
|
||||
StringBuilder query = new StringBuilder("1=1");
|
||||
Map<String, Object> params = new java.util.HashMap<>();
|
||||
|
||||
if (membreId != null) {
|
||||
query.append(" and membre.id = :membreId");
|
||||
params.put("membreId", membreId);
|
||||
}
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
query.append(" and statut = :statut");
|
||||
params.put("statut", statut);
|
||||
}
|
||||
|
||||
if (typeCotisation != null && !typeCotisation.isEmpty()) {
|
||||
query.append(" and typeCotisation = :typeCotisation");
|
||||
params.put("typeCotisation", typeCotisation);
|
||||
}
|
||||
|
||||
if (annee != null) {
|
||||
query.append(" and annee = :annee");
|
||||
params.put("annee", annee);
|
||||
}
|
||||
|
||||
if (mois != null) {
|
||||
query.append(" and mois = :mois");
|
||||
params.put("mois", mois);
|
||||
}
|
||||
|
||||
return find(query.toString(), Sort.by("dateEcheance").descending(), params)
|
||||
.page(page)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le total des montants dus pour un membre
|
||||
*
|
||||
* @param membreId identifiant du membre
|
||||
* @return montant total dû
|
||||
*/
|
||||
public BigDecimal calculerTotalMontantDu(Long membreId) {
|
||||
return find("select sum(c.montantDu) from Cotisation c where c.membre.id = ?1", membreId)
|
||||
.project(BigDecimal.class)
|
||||
.firstResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le total des montants payés pour un membre
|
||||
*
|
||||
* @param membreId identifiant du membre
|
||||
* @return montant total payé
|
||||
*/
|
||||
public BigDecimal calculerTotalMontantPaye(Long membreId) {
|
||||
return find("select sum(c.montantPaye) from Cotisation c where c.membre.id = ?1", membreId)
|
||||
.project(BigDecimal.class)
|
||||
.firstResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les cotisations par statut
|
||||
*
|
||||
* @param statut le statut
|
||||
* @return nombre de cotisations
|
||||
*/
|
||||
public long compterParStatut(String statut) {
|
||||
return count("statut = ?1", statut);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return liste des cotisations à rappeler
|
||||
*/
|
||||
public List<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
|
||||
LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
|
||||
return find("dateEcheance <= ?1 and statut != 'PAYEE' and statut != 'ANNULEE' and nombreRappels < ?2",
|
||||
Sort.by("dateEcheance").ascending(), dateRappel, nombreMaxRappels)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le nombre de rappels pour une cotisation
|
||||
*
|
||||
* @param cotisationId identifiant de la cotisation
|
||||
* @return nombre de lignes mises à jour
|
||||
*/
|
||||
public int incrementerNombreRappels(Long cotisationId) {
|
||||
return update("nombreRappels = nombreRappels + 1, dateDernierRappel = ?1 where id = ?2",
|
||||
LocalDateTime.now(), cotisationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiques des cotisations par période
|
||||
*
|
||||
* @param annee l'année
|
||||
* @param mois le mois (optionnel)
|
||||
* @return map avec les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesPeriode(Integer annee, Integer mois) {
|
||||
String baseQuery = mois != null ?
|
||||
"from Cotisation c where c.annee = ?1 and c.mois = ?2" :
|
||||
"from Cotisation c where c.annee = ?1";
|
||||
|
||||
Object[] params = mois != null ? new Object[]{annee, mois} : new Object[]{annee};
|
||||
|
||||
Long totalCotisations = mois != null ?
|
||||
count("annee = ?1 and mois = ?2", params) :
|
||||
count("annee = ?1", params);
|
||||
|
||||
BigDecimal montantTotal = find("select sum(c.montantDu) " + baseQuery, params)
|
||||
.project(BigDecimal.class)
|
||||
.firstResult();
|
||||
|
||||
BigDecimal montantPaye = find("select sum(c.montantPaye) " + baseQuery, params)
|
||||
.project(BigDecimal.class)
|
||||
.firstResult();
|
||||
|
||||
Long cotisationsPayees = mois != null ?
|
||||
count("annee = ?1 and mois = ?2 and statut = 'PAYEE'", annee, mois) :
|
||||
count("annee = ?1 and statut = 'PAYEE'", annee);
|
||||
|
||||
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 != null && totalCotisations > 0 ?
|
||||
(cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations : 0.0
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@ package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -45,7 +48,80 @@ public class MembreRepository implements PanacheRepository<Membre> {
|
||||
* Trouve les membres par nom ou prénom (recherche partielle)
|
||||
*/
|
||||
public List<Membre> findByNomOrPrenom(String recherche) {
|
||||
return find("lower(nom) like ?1 or lower(prenom) like ?1",
|
||||
return find("lower(nom) like ?1 or lower(prenom) like ?1",
|
||||
"%" + recherche.toLowerCase() + "%").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les membres actifs avec pagination et tri
|
||||
*/
|
||||
public List<Membre> findAllActifs(Page page, Sort sort) {
|
||||
return find("actif", sort, true).page(page).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les membres par nom ou prénom avec pagination et tri
|
||||
*/
|
||||
public List<Membre> findByNomOrPrenom(String recherche, Page page, Sort sort) {
|
||||
return find("lower(nom) like ?1 or lower(prenom) like ?1",
|
||||
sort, "%" + recherche.toLowerCase() + "%")
|
||||
.page(page).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les nouveaux membres depuis une date donnée
|
||||
*/
|
||||
public long countNouveauxMembres(LocalDate depuis) {
|
||||
return count("dateAdhesion >= ?1", depuis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les membres par statut avec pagination
|
||||
*/
|
||||
public List<Membre> findByStatut(boolean actif, Page page, Sort sort) {
|
||||
return find("actif", sort, actif).page(page).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les membres par tranche d'âge
|
||||
*/
|
||||
public List<Membre> findByTrancheAge(int ageMin, int ageMax, Page page, Sort sort) {
|
||||
LocalDate dateNaissanceMax = LocalDate.now().minusYears(ageMin);
|
||||
LocalDate dateNaissanceMin = LocalDate.now().minusYears(ageMax + 1);
|
||||
|
||||
return find("dateNaissance between ?1 and ?2", sort, dateNaissanceMin, dateNaissanceMax)
|
||||
.page(page).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée avec filtres multiples
|
||||
*/
|
||||
public List<Membre> rechercheAvancee(String recherche, Boolean actif,
|
||||
LocalDate dateAdhesionMin, LocalDate dateAdhesionMax,
|
||||
Page page, Sort sort) {
|
||||
StringBuilder query = new StringBuilder("1=1");
|
||||
java.util.Map<String, Object> params = new java.util.HashMap<>();
|
||||
|
||||
if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
query.append(" and (lower(nom) like :recherche or lower(prenom) like :recherche or lower(email) like :recherche)");
|
||||
params.put("recherche", "%" + recherche.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
if (actif != null) {
|
||||
query.append(" and actif = :actif");
|
||||
params.put("actif", actif);
|
||||
}
|
||||
|
||||
if (dateAdhesionMin != null) {
|
||||
query.append(" and dateAdhesion >= :dateAdhesionMin");
|
||||
params.put("dateAdhesionMin", dateAdhesionMin);
|
||||
}
|
||||
|
||||
if (dateAdhesionMax != null) {
|
||||
query.append(" and dateAdhesion <= :dateAdhesionMax");
|
||||
params.put("dateAdhesionMax", dateAdhesionMax);
|
||||
}
|
||||
|
||||
return find(query.toString(), sort, params).page(page).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.service.CotisationService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
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.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des cotisations
|
||||
* Expose les endpoints API pour les opérations CRUD sur les cotisations
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Path("/api/cotisations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Cotisations", description = "Gestion des cotisations des membres")
|
||||
@Slf4j
|
||||
public class CotisationResource {
|
||||
|
||||
@Inject
|
||||
CotisationService cotisationService;
|
||||
|
||||
/**
|
||||
* Récupère toutes les cotisations avec pagination
|
||||
*/
|
||||
@GET
|
||||
@Operation(summary = "Lister toutes les cotisations",
|
||||
description = "Récupère la liste paginée de toutes les cotisations")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations récupérée avec succès",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAllCotisations(
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations - page: {}, size: {}", page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des cotisations",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer une cotisation par ID",
|
||||
description = "Récupère les détails d'une cotisation spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Cotisation trouvée",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationById(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id") @NotNull Long id) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/{}", id);
|
||||
|
||||
CotisationDTO cotisation = cotisationService.getCotisationById(id);
|
||||
|
||||
log.info("Cotisation récupérée avec succès - ID: {}", id);
|
||||
return Response.ok(cotisation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération de la cotisation",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son numéro de référence
|
||||
*/
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
@Operation(summary = "Récupérer une cotisation par référence",
|
||||
description = "Récupère une cotisation par son numéro de référence unique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Cotisation trouvée"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationByReference(
|
||||
@Parameter(description = "Numéro de référence de la cotisation", required = true)
|
||||
@PathParam("numeroReference") @NotNull String numeroReference) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/reference/{}", numeroReference);
|
||||
|
||||
CotisationDTO cotisation = cotisationService.getCotisationByReference(numeroReference);
|
||||
|
||||
log.info("Cotisation récupérée avec succès - Référence: {}", numeroReference);
|
||||
return Response.ok(cotisation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée - Référence: {}", numeroReference);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "reference", numeroReference))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération de la cotisation - Référence: " + numeroReference, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération de la cotisation",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle cotisation
|
||||
*/
|
||||
@POST
|
||||
@Operation(summary = "Créer une nouvelle cotisation",
|
||||
description = "Crée une nouvelle cotisation pour un membre")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "201", description = "Cotisation créée avec succès",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response createCotisation(
|
||||
@Parameter(description = "Données de la cotisation à créer", required = true)
|
||||
@Valid CotisationDTO cotisationDTO) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/cotisations - Création cotisation pour membre: {}",
|
||||
cotisationDTO.getMembreId());
|
||||
|
||||
CotisationDTO nouvelleCotisation = cotisationService.createCotisation(cotisationDTO);
|
||||
|
||||
log.info("Cotisation créée avec succès - ID: {}, Référence: {}",
|
||||
nouvelleCotisation.getId(), nouvelleCotisation.getNumeroReference());
|
||||
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(nouvelleCotisation)
|
||||
.build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre non trouvé lors de la création de cotisation: {}", cotisationDTO.getMembreId());
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre non trouvé", "membreId", cotisationDTO.getMembreId()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides pour la création de cotisation: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la création de la cotisation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la création de la cotisation",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une cotisation existante
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour une cotisation",
|
||||
description = "Met à jour les données d'une cotisation existante")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Cotisation mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response updateCotisation(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id") @NotNull Long id,
|
||||
|
||||
@Parameter(description = "Nouvelles données de la cotisation", required = true)
|
||||
@Valid CotisationDTO cotisationDTO) {
|
||||
|
||||
try {
|
||||
log.info("PUT /api/cotisations/{}", id);
|
||||
|
||||
CotisationDTO cotisationMiseAJour = cotisationService.updateCotisation(id, cotisationDTO);
|
||||
|
||||
log.info("Cotisation mise à jour avec succès - ID: {}", id);
|
||||
return Response.ok(cotisationMiseAJour).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée pour mise à jour - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides pour la mise à jour de cotisation - ID: {}, Erreur: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la mise à jour de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la mise à jour de la cotisation",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une cotisation
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer une cotisation",
|
||||
description = "Supprime (désactive) une cotisation")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Cotisation supprimée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "409", description = "Impossible de supprimer une cotisation payée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response deleteCotisation(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id") @NotNull Long id) {
|
||||
|
||||
try {
|
||||
log.info("DELETE /api/cotisations/{}", id);
|
||||
|
||||
cotisationService.deleteCotisation(id);
|
||||
|
||||
log.info("Cotisation supprimée avec succès - ID: {}", id);
|
||||
return Response.noContent().build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée pour suppression - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de supprimer la cotisation - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", "Impossible de supprimer la cotisation", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la suppression de la cotisation",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations d'un membre
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
@Operation(summary = "Lister les cotisations d'un membre",
|
||||
description = "Récupère toutes les cotisations d'un membre spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations du membre"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsByMembre(
|
||||
@Parameter(description = "Identifiant du membre", required = true)
|
||||
@PathParam("membreId") @NotNull Long membreId,
|
||||
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/membre/{} - page: {}, size: {}", membreId, page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getCotisationsByMembre(membreId, page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations pour le membre {}", cotisations.size(), membreId);
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre non trouvé - ID: {}", membreId);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre non trouvé", "membreId", membreId))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations du membre - ID: " + membreId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des cotisations",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations par statut
|
||||
*/
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(summary = "Lister les cotisations par statut",
|
||||
description = "Récupère toutes les cotisations ayant un statut spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations avec le statut spécifié"),
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsByStatut(
|
||||
@Parameter(description = "Statut des cotisations", required = true,
|
||||
example = "EN_ATTENTE")
|
||||
@PathParam("statut") @NotNull String statut,
|
||||
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/statut/{} - page: {}, size: {}", statut, page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getCotisationsByStatut(statut, page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations avec statut {}", cotisations.size(), statut);
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations par statut - Statut: " + statut, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des cotisations",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations en retard
|
||||
*/
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
@Operation(summary = "Lister les cotisations en retard",
|
||||
description = "Récupère toutes les cotisations dont la date d'échéance est dépassée")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations en retard"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsEnRetard(
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/en-retard - page: {}, size: {}", page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getCotisationsEnRetard(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations en retard", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des cotisations en retard",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée de cotisations
|
||||
*/
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(summary = "Recherche avancée de cotisations",
|
||||
description = "Recherche de cotisations avec filtres multiples")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de recherche invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response rechercherCotisations(
|
||||
@Parameter(description = "Identifiant du membre")
|
||||
@QueryParam("membreId") Long membreId,
|
||||
|
||||
@Parameter(description = "Statut de la cotisation")
|
||||
@QueryParam("statut") String statut,
|
||||
|
||||
@Parameter(description = "Type de cotisation")
|
||||
@QueryParam("typeCotisation") String typeCotisation,
|
||||
|
||||
@Parameter(description = "Année")
|
||||
@QueryParam("annee") Integer annee,
|
||||
|
||||
@Parameter(description = "Mois")
|
||||
@QueryParam("mois") Integer mois,
|
||||
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/recherche - Filtres: membreId={}, statut={}, type={}, annee={}, mois={}",
|
||||
membreId, statut, typeCotisation, annee, mois);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.rechercherCotisations(
|
||||
membreId, statut, typeCotisation, annee, mois, page, size);
|
||||
|
||||
log.info("Recherche réussie - {} cotisations trouvées", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la recherche de cotisations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche de cotisations",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des cotisations
|
||||
*/
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Statistiques des cotisations",
|
||||
description = "Récupère les statistiques globales des cotisations")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getStatistiquesCotisations() {
|
||||
try {
|
||||
log.info("GET /api/cotisations/stats");
|
||||
|
||||
Map<String, Object> statistiques = cotisationService.getStatistiquesCotisations();
|
||||
|
||||
log.info("Statistiques récupérées avec succès");
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des statistiques",
|
||||
"message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.service.MembreService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -35,10 +38,21 @@ public class MembreResource {
|
||||
@GET
|
||||
@Operation(summary = "Lister tous les membres actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
|
||||
public Response listerMembres() {
|
||||
LOG.info("Récupération de la liste des membres actifs");
|
||||
List<Membre> membres = membreService.listerMembresActifs();
|
||||
return Response.ok(membres).build();
|
||||
public Response listerMembres(
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)") @QueryParam("direction") @DefaultValue("asc") String sortDirection) {
|
||||
|
||||
LOG.infof("Récupération de la liste des membres actifs - page: %d, size: %d", page, size);
|
||||
|
||||
Sort sort = "desc".equalsIgnoreCase(sortDirection) ?
|
||||
Sort.by(sortField).descending() : Sort.by(sortField).ascending();
|
||||
|
||||
List<Membre> membres = membreService.listerMembresActifs(Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -49,7 +63,10 @@ public class MembreResource {
|
||||
public Response obtenirMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id) {
|
||||
LOG.infof("Récupération du membre ID: %d", id);
|
||||
return membreService.trouverParId(id)
|
||||
.map(membre -> Response.ok(membre).build())
|
||||
.map(membre -> {
|
||||
MembreDTO membreDTO = membreService.convertToDTO(membre);
|
||||
return Response.ok(membreDTO).build();
|
||||
})
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("message", "Membre non trouvé")).build());
|
||||
}
|
||||
@@ -58,11 +75,25 @@ public class MembreResource {
|
||||
@Operation(summary = "Créer un nouveau membre")
|
||||
@APIResponse(responseCode = "201", description = "Membre créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response creerMembre(@Valid Membre membre) {
|
||||
LOG.infof("Création d'un nouveau membre: %s", membre.getEmail());
|
||||
public Response creerMembre(@Valid MembreDTO membreDTO) {
|
||||
LOG.infof("Création d'un nouveau membre: %s", membreDTO.getEmail());
|
||||
try {
|
||||
// Validation des données DTO
|
||||
if (!membreDTO.isDataValid()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Données du membre invalides")).build();
|
||||
}
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Membre membre = membreService.convertFromDTO(membreDTO);
|
||||
|
||||
// Création du membre
|
||||
Membre nouveauMembre = membreService.creerMembre(membre);
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauMembre).build();
|
||||
|
||||
// Conversion de retour vers DTO
|
||||
MembreDTO nouveauMembreDTO = membreService.convertToDTO(nouveauMembre);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauMembreDTO).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", e.getMessage())).build();
|
||||
@@ -76,11 +107,25 @@ public class MembreResource {
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response mettreAJourMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id,
|
||||
@Valid Membre membre) {
|
||||
@Valid MembreDTO membreDTO) {
|
||||
LOG.infof("Mise à jour du membre ID: %d", id);
|
||||
try {
|
||||
// Validation des données DTO
|
||||
if (!membreDTO.isDataValid()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Données du membre invalides")).build();
|
||||
}
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Membre membre = membreService.convertFromDTO(membreDTO);
|
||||
|
||||
// Mise à jour du membre
|
||||
Membre membreMisAJour = membreService.mettreAJourMembre(id, membre);
|
||||
return Response.ok(membreMisAJour).build();
|
||||
|
||||
// Conversion de retour vers DTO
|
||||
MembreDTO membreMisAJourDTO = membreService.convertToDTO(membreMisAJour);
|
||||
|
||||
return Response.ok(membreMisAJourDTO).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", e.getMessage())).build();
|
||||
@@ -107,26 +152,74 @@ public class MembreResource {
|
||||
@Path("/recherche")
|
||||
@Operation(summary = "Rechercher des membres par nom ou prénom")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
|
||||
public Response rechercherMembres(@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche) {
|
||||
public Response rechercherMembres(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)") @QueryParam("direction") @DefaultValue("asc") String sortDirection) {
|
||||
|
||||
LOG.infof("Recherche de membres avec le terme: %s", recherche);
|
||||
if (recherche == null || recherche.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Le terme de recherche est requis")).build();
|
||||
}
|
||||
List<Membre> membres = membreService.rechercherMembres(recherche.trim());
|
||||
return Response.ok(membres).build();
|
||||
|
||||
Sort sort = "desc".equalsIgnoreCase(sortDirection) ?
|
||||
Sort.by(sortField).descending() : Sort.by(sortField).ascending();
|
||||
|
||||
List<Membre> membres = membreService.rechercherMembres(recherche.trim(), Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques des membres")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques des membres")
|
||||
@Operation(summary = "Obtenir les statistiques avancées des membres")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques complètes des membres")
|
||||
public Response obtenirStatistiques() {
|
||||
LOG.info("Récupération des statistiques des membres");
|
||||
long nombreMembresActifs = membreService.compterMembresActifs();
|
||||
return Response.ok(Map.of(
|
||||
"nombreMembresActifs", nombreMembresActifs,
|
||||
"timestamp", java.time.LocalDateTime.now()
|
||||
)).build();
|
||||
LOG.info("Récupération des statistiques avancées des membres");
|
||||
Map<String, Object> statistiques = membreService.obtenirStatistiquesAvancees();
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recherche-avancee")
|
||||
@Operation(summary = "Recherche avancée de membres avec filtres multiples")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche avancée")
|
||||
public Response rechercheAvancee(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
|
||||
@Parameter(description = "Statut actif (true/false)") @QueryParam("actif") Boolean actif,
|
||||
@Parameter(description = "Date d'adhésion minimum (YYYY-MM-DD)") @QueryParam("dateAdhesionMin") String dateAdhesionMin,
|
||||
@Parameter(description = "Date d'adhésion maximum (YYYY-MM-DD)") @QueryParam("dateAdhesionMax") String dateAdhesionMax,
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)") @QueryParam("direction") @DefaultValue("asc") String sortDirection) {
|
||||
|
||||
LOG.infof("Recherche avancée de membres - recherche: %s, actif: %s", recherche, actif);
|
||||
|
||||
try {
|
||||
Sort sort = "desc".equalsIgnoreCase(sortDirection) ?
|
||||
Sort.by(sortField).descending() : Sort.by(sortField).ascending();
|
||||
|
||||
// Conversion des dates si fournies
|
||||
java.time.LocalDate dateMin = dateAdhesionMin != null ?
|
||||
java.time.LocalDate.parse(dateAdhesionMin) : null;
|
||||
java.time.LocalDate dateMax = dateAdhesionMax != null ?
|
||||
java.time.LocalDate.parse(dateAdhesionMax) : null;
|
||||
|
||||
List<Membre> membres = membreService.rechercheAvancee(
|
||||
recherche, actif, dateMin, dateMax, Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Erreur dans les paramètres de recherche: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.findAll(Sort.by("dateEcheance").descending())
|
||||
.page(Page.of(page, size))
|
||||
.list();
|
||||
|
||||
return cotisations.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son ID
|
||||
*
|
||||
* @param id identifiant de la cotisation
|
||||
* @return DTO de la cotisation
|
||||
* @throws NotFoundException si la cotisation n'existe pas
|
||||
*/
|
||||
public CotisationDTO getCotisationById(@NotNull Long 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
|
||||
Membre membre = membreRepository.findByIdOptional(Long.valueOf(cotisationDTO.getMembreId().toString()))
|
||||
.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.id, cotisation.getNumeroReference());
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une cotisation existante
|
||||
*
|
||||
* @param id identifiant de la cotisation
|
||||
* @param cotisationDTO nouvelles données
|
||||
* @return DTO de la cotisation mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public CotisationDTO updateCotisation(@NotNull Long 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 de la cotisation
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteCotisation(@NotNull Long 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 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 Long 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(Long 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) {
|
||||
CotisationDTO dto = new CotisationDTO();
|
||||
|
||||
// Copie des propriétés de base
|
||||
// Génération d'UUID basé sur l'ID numérique pour compatibilité
|
||||
dto.setId(UUID.nameUUIDFromBytes(("cotisation-" + cotisation.id).getBytes()));
|
||||
dto.setNumeroReference(cotisation.getNumeroReference());
|
||||
dto.setMembreId(UUID.nameUUIDFromBytes(("membre-" + cotisation.getMembre().id).getBytes()));
|
||||
dto.setNomMembre(cotisation.getMembre().getNom() + " " + cotisation.getMembre().getPrenom());
|
||||
dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre());
|
||||
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());
|
||||
dto.setValidePar(cotisation.getValideParId() != null ?
|
||||
UUID.nameUUIDFromBytes(("user-" + cotisation.getValideParId()).getBytes()) : 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û");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
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 org.jboss.logging.Logger;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service métier pour les membres
|
||||
@@ -140,4 +146,153 @@ public class MembreService {
|
||||
public long compterMembresActifs() {
|
||||
return membreRepository.countActifs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les membres actifs avec pagination
|
||||
*/
|
||||
public List<Membre> listerMembresActifs(Page page, Sort sort) {
|
||||
return membreRepository.findAllActifs(page, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche des membres avec pagination
|
||||
*/
|
||||
public List<Membre> rechercherMembres(String recherche, Page page, Sort sort) {
|
||||
return membreRepository.findByNomOrPrenom(recherche, page, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les statistiques avancées des membres
|
||||
*/
|
||||
public Map<String, Object> obtenirStatistiquesAvancees() {
|
||||
LOG.info("Calcul des statistiques avancées des membres");
|
||||
|
||||
long totalMembres = membreRepository.count();
|
||||
long membresActifs = membreRepository.countActifs();
|
||||
long membresInactifs = totalMembres - membresActifs;
|
||||
long nouveauxMembres30Jours = membreRepository.countNouveauxMembres(LocalDate.now().minusDays(30));
|
||||
|
||||
return Map.of(
|
||||
"totalMembres", totalMembres,
|
||||
"membresActifs", membresActifs,
|
||||
"membresInactifs", membresInactifs,
|
||||
"nouveauxMembres30Jours", nouveauxMembres30Jours,
|
||||
"tauxActivite", totalMembres > 0 ? (membresActifs * 100.0 / totalMembres) : 0.0,
|
||||
"timestamp", LocalDateTime.now()
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES DE CONVERSION DTO
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Convertit une entité Membre en MembreDTO
|
||||
*/
|
||||
public MembreDTO convertToDTO(Membre membre) {
|
||||
if (membre == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MembreDTO dto = new MembreDTO();
|
||||
|
||||
// Génération d'UUID basé sur l'ID numérique pour compatibilité
|
||||
dto.setId(UUID.nameUUIDFromBytes(("membre-" + membre.id).getBytes()));
|
||||
|
||||
// Copie des champs de base
|
||||
dto.setNumeroMembre(membre.getNumeroMembre());
|
||||
dto.setNom(membre.getNom());
|
||||
dto.setPrenom(membre.getPrenom());
|
||||
dto.setEmail(membre.getEmail());
|
||||
dto.setTelephone(membre.getTelephone());
|
||||
dto.setDateNaissance(membre.getDateNaissance());
|
||||
dto.setDateAdhesion(membre.getDateAdhesion());
|
||||
|
||||
// Conversion du statut boolean vers string
|
||||
dto.setStatut(membre.getActif() ? "ACTIF" : "INACTIF");
|
||||
|
||||
// Champs de base DTO
|
||||
dto.setDateCreation(membre.getDateCreation());
|
||||
dto.setDateModification(membre.getDateModification());
|
||||
dto.setVersion(0L); // Version par défaut
|
||||
|
||||
// Champs par défaut pour les champs manquants dans l'entité
|
||||
dto.setAssociationId(1L); // Association par défaut
|
||||
dto.setMembreBureau(false);
|
||||
dto.setResponsable(false);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un MembreDTO en entité Membre
|
||||
*/
|
||||
public Membre convertFromDTO(MembreDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Membre membre = new Membre();
|
||||
|
||||
// Copie des champs
|
||||
membre.setNumeroMembre(dto.getNumeroMembre());
|
||||
membre.setNom(dto.getNom());
|
||||
membre.setPrenom(dto.getPrenom());
|
||||
membre.setEmail(dto.getEmail());
|
||||
membre.setTelephone(dto.getTelephone());
|
||||
membre.setDateNaissance(dto.getDateNaissance());
|
||||
membre.setDateAdhesion(dto.getDateAdhesion());
|
||||
|
||||
// Conversion du statut string vers boolean
|
||||
membre.setActif("ACTIF".equals(dto.getStatut()));
|
||||
|
||||
// Champs de base
|
||||
if (dto.getDateCreation() != null) {
|
||||
membre.setDateCreation(dto.getDateCreation());
|
||||
}
|
||||
if (dto.getDateModification() != null) {
|
||||
membre.setDateModification(dto.getDateModification());
|
||||
}
|
||||
|
||||
return membre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une liste d'entités en liste de DTOs
|
||||
*/
|
||||
public List<MembreDTO> convertToDTOList(List<Membre> membres) {
|
||||
return membres.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une entité Membre à partir d'un MembreDTO
|
||||
*/
|
||||
public void updateFromDTO(Membre membre, MembreDTO dto) {
|
||||
if (membre == null || dto == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
membre.setPrenom(dto.getPrenom());
|
||||
membre.setNom(dto.getNom());
|
||||
membre.setEmail(dto.getEmail());
|
||||
membre.setTelephone(dto.getTelephone());
|
||||
membre.setDateNaissance(dto.getDateNaissance());
|
||||
membre.setActif("ACTIF".equals(dto.getStatut()));
|
||||
membre.setDateModification(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée de membres avec filtres multiples
|
||||
*/
|
||||
public List<Membre> rechercheAvancee(String recherche, Boolean actif,
|
||||
LocalDate dateAdhesionMin, LocalDate dateAdhesionMax,
|
||||
Page page, Sort sort) {
|
||||
LOG.infof("Recherche avancée - recherche: %s, actif: %s, dateMin: %s, dateMax: %s",
|
||||
recherche, actif, dateAdhesionMin, dateAdhesionMax);
|
||||
|
||||
return membreRepository.rechercheAvancee(recherche, actif, dateAdhesionMin, dateAdhesionMax, page, sort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,12 @@ quarkus:
|
||||
level: INFO
|
||||
format: "%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n"
|
||||
category:
|
||||
"dev.lions.unionflow": INFO
|
||||
"org.hibernate": WARN
|
||||
"io.quarkus": INFO
|
||||
"dev.lions.unionflow":
|
||||
level: INFO
|
||||
"org.hibernate":
|
||||
level: WARN
|
||||
"io.quarkus":
|
||||
level: INFO
|
||||
|
||||
---
|
||||
# Profil de développement
|
||||
@@ -94,8 +97,10 @@ quarkus:
|
||||
migrate-at-start: false
|
||||
log:
|
||||
category:
|
||||
"dev.lions.unionflow": DEBUG
|
||||
"org.hibernate.SQL": DEBUG
|
||||
"dev.lions.unionflow":
|
||||
level: DEBUG
|
||||
"org.hibernate.SQL":
|
||||
level: DEBUG
|
||||
|
||||
---
|
||||
# Profil de test
|
||||
@@ -126,5 +131,7 @@ quarkus:
|
||||
console:
|
||||
level: WARN
|
||||
category:
|
||||
"dev.lions.unionflow": INFO
|
||||
root: WARN
|
||||
"dev.lions.unionflow":
|
||||
level: INFO
|
||||
root:
|
||||
level: WARN
|
||||
@@ -0,0 +1,44 @@
|
||||
-- Script d'insertion de données de test pour UnionFlow
|
||||
-- Ce fichier sera exécuté automatiquement par Quarkus au démarrage en mode dev
|
||||
|
||||
-- Insertion de membres de test
|
||||
INSERT INTO membre (id, nom, prenom, email, telephone, date_naissance, adresse, profession, statut, date_adhesion, numero_membre, created_at, updated_at) VALUES
|
||||
('550e8400-e29b-41d4-a716-446655440001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225 07 12 34 56 78', '1985-03-15', 'Cocody, Abidjan', 'Ingénieur Informatique', 'ACTIF', '2023-01-15', 'MBR001', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225 05 98 76 54 32', '1990-07-22', 'Plateau, Abidjan', 'Comptable', 'ACTIF', '2023-02-10', 'MBR002', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225 01 23 45 67 89', '1988-11-08', 'Yopougon, Abidjan', 'Commerçant', 'ACTIF', '2023-03-05', 'MBR003', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225 07 87 65 43 21', '1992-05-18', 'Adjamé, Abidjan', 'Enseignante', 'ACTIF', '2023-04-12', 'MBR004', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225 05 11 22 33 44', '1987-09-30', 'Marcory, Abidjan', 'Médecin', 'ACTIF', '2023-05-20', 'MBR005', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225 01 55 66 77 88', '1991-12-03', 'Treichville, Abidjan', 'Avocate', 'SUSPENDU', '2023-06-08', 'MBR006', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225 07 99 88 77 66', '1989-04-25', 'Koumassi, Abidjan', 'Pharmacien', 'ACTIF', '2023-07-15', 'MBR007', NOW(), NOW()),
|
||||
('550e8400-e29b-41d4-a716-446655440008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225 05 44 33 22 11', '1993-08-14', 'Port-Bouët, Abidjan', 'Architecte', 'ACTIF', '2023-08-22', 'MBR008', NOW(), NOW());
|
||||
|
||||
-- Insertion de cotisations de test avec différents statuts
|
||||
INSERT INTO cotisation (id, numero_reference, membre_id, nom_membre, type_cotisation, montant_du, montant_paye, statut, date_echeance, date_creation, periode, description, created_at, updated_at) VALUES
|
||||
-- Cotisations payées
|
||||
('660e8400-e29b-41d4-a716-446655440001', 'COT-2024-001', '550e8400-e29b-41d4-a716-446655440001', 'Jean-Baptiste Kouassi', 'MENSUELLE', 25000, 25000, 'PAYEE', '2024-01-31', '2024-01-01', 'Janvier 2024', 'Cotisation mensuelle janvier', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440002', 'COT-2024-002', '550e8400-e29b-41d4-a716-446655440002', 'Aminata Traoré', 'MENSUELLE', 25000, 25000, 'PAYEE', '2024-01-31', '2024-01-01', 'Janvier 2024', 'Cotisation mensuelle janvier', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440003', 'COT-2024-003', '550e8400-e29b-41d4-a716-446655440003', 'Seydou Bamba', 'MENSUELLE', 25000, 25000, 'PAYEE', '2024-02-29', '2024-02-01', 'Février 2024', 'Cotisation mensuelle février', NOW(), NOW()),
|
||||
|
||||
-- Cotisations en attente
|
||||
('660e8400-e29b-41d4-a716-446655440004', 'COT-2024-004', '550e8400-e29b-41d4-a716-446655440004', 'Fatoumata Ouattara', 'MENSUELLE', 25000, 0, 'EN_ATTENTE', '2024-12-31', '2024-12-01', 'Décembre 2024', 'Cotisation mensuelle décembre', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440005', 'COT-2024-005', '550e8400-e29b-41d4-a716-446655440005', 'Ibrahim Koné', 'MENSUELLE', 25000, 0, 'EN_ATTENTE', '2024-12-31', '2024-12-01', 'Décembre 2024', 'Cotisation mensuelle décembre', NOW(), NOW()),
|
||||
|
||||
-- Cotisations en retard
|
||||
('660e8400-e29b-41d4-a716-446655440006', 'COT-2024-006', '550e8400-e29b-41d4-a716-446655440006', 'Mariam Diabaté', 'MENSUELLE', 25000, 0, 'EN_RETARD', '2024-11-30', '2024-11-01', 'Novembre 2024', 'Cotisation mensuelle novembre', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440007', 'COT-2024-007', '550e8400-e29b-41d4-a716-446655440007', 'Moussa Sangaré', 'MENSUELLE', 25000, 0, 'EN_RETARD', '2024-10-31', '2024-10-01', 'Octobre 2024', 'Cotisation mensuelle octobre', NOW(), NOW()),
|
||||
|
||||
-- Cotisations partiellement payées
|
||||
('660e8400-e29b-41d4-a716-446655440008', 'COT-2024-008', '550e8400-e29b-41d4-a716-446655440008', 'Awa Coulibaly', 'MENSUELLE', 25000, 15000, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-12-01', 'Décembre 2024', 'Cotisation mensuelle décembre', NOW(), NOW()),
|
||||
|
||||
-- Cotisations spéciales (adhésion, événements)
|
||||
('660e8400-e29b-41d4-a716-446655440009', 'COT-2024-009', '550e8400-e29b-41d4-a716-446655440001', 'Jean-Baptiste Kouassi', 'ADHESION', 50000, 50000, 'PAYEE', '2024-01-15', '2024-01-01', 'Adhésion 2024', 'Frais d''adhésion annuelle', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440010', 'COT-2024-010', '550e8400-e29b-41d4-a716-446655440002', 'Aminata Traoré', 'EVENEMENT', 15000, 0, 'EN_ATTENTE', '2024-12-25', '2024-12-01', 'Fête de fin d''année', 'Participation à la fête de fin d''année', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440011', 'COT-2024-011', '550e8400-e29b-41d4-a716-446655440003', 'Seydou Bamba', 'SOLIDARITE', 10000, 10000, 'PAYEE', '2024-11-15', '2024-11-01', 'Aide mutuelle', 'Contribution solidarité membre en difficulté', NOW(), NOW()),
|
||||
|
||||
-- Cotisations annuelles
|
||||
('660e8400-e29b-41d4-a716-446655440012', 'COT-2024-012', '550e8400-e29b-41d4-a716-446655440004', 'Fatoumata Ouattara', 'ANNUELLE', 300000, 150000, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-01-01', 'Cotisation annuelle 2024', 'Cotisation annuelle avec paiement échelonné', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440013', 'COT-2024-013', '550e8400-e29b-41d4-a716-446655440005', 'Ibrahim Koné', 'ANNUELLE', 300000, 0, 'EN_RETARD', '2024-06-30', '2024-01-01', 'Cotisation annuelle 2024', 'Cotisation annuelle en retard', NOW(), NOW()),
|
||||
|
||||
-- Cotisations diverses montants
|
||||
('660e8400-e29b-41d4-a716-446655440014', 'COT-2024-014', '550e8400-e29b-41d4-a716-446655440007', 'Moussa Sangaré', 'FORMATION', 75000, 75000, 'PAYEE', '2024-09-30', '2024-09-01', 'Formation professionnelle', 'Participation formation développement personnel', NOW(), NOW()),
|
||||
('660e8400-e29b-41d4-a716-446655440015', 'COT-2024-015', '550e8400-e29b-41d4-a716-446655440008', 'Awa Coulibaly', 'PROJET', 100000, 25000, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-11-01', 'Projet communautaire', 'Financement projet construction école', NOW(), NOW());
|
||||
44
unionflow-server-impl-quarkus/src/main/resources/import.sql
Normal file
44
unionflow-server-impl-quarkus/src/main/resources/import.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Script d'insertion de données de test pour UnionFlow
|
||||
-- Ce fichier sera exécuté automatiquement par Quarkus au démarrage
|
||||
|
||||
-- Insertion de membres de test
|
||||
INSERT INTO membres (id, numero_membre, nom, prenom, email, telephone, date_naissance, date_adhesion, actif, date_creation) VALUES
|
||||
(1, 'MBR001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225071234567', '1985-03-15', '2023-01-15', true, '2024-01-01 10:00:00'),
|
||||
(2, 'MBR002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225059876543', '1990-07-22', '2023-02-10', true, '2024-01-01 10:00:00'),
|
||||
(3, 'MBR003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225012345678', '1988-11-08', '2023-03-05', true, '2024-01-01 10:00:00'),
|
||||
(4, 'MBR004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225078765432', '1992-05-18', '2023-04-12', true, '2024-01-01 10:00:00'),
|
||||
(5, 'MBR005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225051122334', '1987-09-30', '2023-05-20', true, '2024-01-01 10:00:00'),
|
||||
(6, 'MBR006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225015566778', '1991-12-03', '2023-06-08', false, '2024-01-01 10:00:00'),
|
||||
(7, 'MBR007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225079988776', '1989-04-25', '2023-07-15', true, '2024-01-01 10:00:00'),
|
||||
(8, 'MBR008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225054433221', '1993-08-14', '2023-08-22', true, '2024-01-01 10:00:00');
|
||||
|
||||
-- Insertion de cotisations de test avec différents statuts
|
||||
INSERT INTO cotisations (id, numero_reference, membre_id, type_cotisation, montant_du, montant_paye, statut, date_echeance, date_creation, periode, description, annee, mois, code_devise, recurrente, nombre_rappels) VALUES
|
||||
-- Cotisations payées
|
||||
(1, 'COT-2024-001', 1, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-01-31', '2024-01-01 10:00:00', 'Janvier 2024', 'Cotisation mensuelle janvier', 2024, 1, 'XOF', true, 0),
|
||||
(2, 'COT-2024-002', 2, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-01-31', '2024-01-01 10:00:00', 'Janvier 2024', 'Cotisation mensuelle janvier', 2024, 1, 'XOF', true, 0),
|
||||
(3, 'COT-2024-003', 3, 'MENSUELLE', 25000.00, 25000.00, 'PAYEE', '2024-02-29', '2024-02-01 10:00:00', 'Février 2024', 'Cotisation mensuelle février', 2024, 2, 'XOF', true, 0),
|
||||
|
||||
-- Cotisations en attente
|
||||
(4, 'COT-2024-004', 4, 'MENSUELLE', 25000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 0),
|
||||
(5, 'COT-2024-005', 5, 'MENSUELLE', 25000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 0),
|
||||
|
||||
-- Cotisations en retard
|
||||
(6, 'COT-2024-006', 6, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-11-30', '2024-11-01 10:00:00', 'Novembre 2024', 'Cotisation mensuelle novembre', 2024, 11, 'XOF', true, 2),
|
||||
(7, 'COT-2024-007', 7, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-10-31', '2024-10-01 10:00:00', 'Octobre 2024', 'Cotisation mensuelle octobre', 2024, 10, 'XOF', true, 3),
|
||||
|
||||
-- Cotisations partiellement payées
|
||||
(8, 'COT-2024-008', 8, 'MENSUELLE', 25000.00, 15000.00, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-12-01 10:00:00', 'Décembre 2024', 'Cotisation mensuelle décembre', 2024, 12, 'XOF', true, 1),
|
||||
|
||||
-- Cotisations spéciales
|
||||
(9, 'COT-2024-009', 1, 'ADHESION', 50000.00, 50000.00, 'PAYEE', '2024-01-15', '2024-01-01 10:00:00', 'Adhésion 2024', 'Frais d''adhésion annuelle', 2024, null, 'XOF', false, 0),
|
||||
(10, 'COT-2024-010', 2, 'EVENEMENT', 15000.00, 15000.00, 'PAYEE', '2024-06-15', '2024-06-01 10:00:00', 'Assemblée Générale', 'Participation assemblée générale', 2024, 6, 'XOF', false, 0),
|
||||
(11, 'COT-2024-011', 3, 'SOLIDARITE', 10000.00, 10000.00, 'PAYEE', '2024-03-31', '2024-03-01 10:00:00', 'Aide Solidarité', 'Contribution solidarité membre', 2024, 3, 'XOF', false, 0),
|
||||
(12, 'COT-2024-012', 4, 'ANNUELLE', 300000.00, 0.00, 'EN_ATTENTE', '2024-12-31', '2024-01-01 10:00:00', 'Annuelle 2024', 'Cotisation annuelle complète', 2024, null, 'XOF', false, 0),
|
||||
(13, 'COT-2024-013', 5, 'FORMATION', 75000.00, 75000.00, 'PAYEE', '2024-09-30', '2024-09-01 10:00:00', 'Formation 2024', 'Formation en leadership associatif', 2024, 9, 'XOF', false, 0),
|
||||
(14, 'COT-2024-014', 6, 'PROJET', 100000.00, 50000.00, 'PARTIELLEMENT_PAYEE', '2024-12-31', '2024-11-01 10:00:00', 'Projet Communauté', 'Contribution projet développement', 2024, 11, 'XOF', false, 1),
|
||||
(15, 'COT-2024-015', 7, 'MENSUELLE', 25000.00, 0.00, 'EN_RETARD', '2024-09-30', '2024-09-01 10:00:00', 'Septembre 2024', 'Cotisation mensuelle septembre', 2024, 9, 'XOF', true, 4);
|
||||
|
||||
-- Mise à jour des séquences pour éviter les conflits
|
||||
ALTER SEQUENCE membres_SEQ RESTART WITH 50;
|
||||
ALTER SEQUENCE cotisations_SEQ RESTART WITH 50;
|
||||
@@ -0,0 +1,329 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* Tests d'intégration pour CotisationResource
|
||||
* Teste tous les endpoints REST de l'API cotisations
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@QuarkusTest
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@DisplayName("Tests d'intégration - API Cotisations")
|
||||
class CotisationResourceTest {
|
||||
|
||||
private static Long membreTestId;
|
||||
private static Long cotisationTestId;
|
||||
private static String numeroReferenceTest;
|
||||
|
||||
@BeforeEach
|
||||
@Transactional
|
||||
void setUp() {
|
||||
// Nettoyage et création des données de test
|
||||
Cotisation.deleteAll();
|
||||
Membre.deleteAll();
|
||||
|
||||
// Création d'un membre de test
|
||||
Membre membreTest = new Membre();
|
||||
membreTest.setNumeroMembre("MBR-TEST-001");
|
||||
membreTest.setNom("Dupont");
|
||||
membreTest.setPrenom("Jean");
|
||||
membreTest.setEmail("jean.dupont@test.com");
|
||||
membreTest.setTelephone("+225070123456");
|
||||
membreTest.setDateNaissance(LocalDate.of(1985, 5, 15));
|
||||
membreTest.setActif(true);
|
||||
membreTest.persist();
|
||||
|
||||
membreTestId = membreTest.id;
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(1)
|
||||
@DisplayName("POST /api/cotisations - Création d'une cotisation")
|
||||
void testCreateCotisation() {
|
||||
CotisationDTO nouvelleCotisation = new CotisationDTO();
|
||||
nouvelleCotisation.setMembreId(UUID.fromString(membreTestId.toString()));
|
||||
nouvelleCotisation.setTypeCotisation("MENSUELLE");
|
||||
nouvelleCotisation.setMontantDu(new BigDecimal("25000.00"));
|
||||
nouvelleCotisation.setDateEcheance(LocalDate.now().plusDays(30));
|
||||
nouvelleCotisation.setDescription("Cotisation mensuelle janvier 2025");
|
||||
nouvelleCotisation.setPeriode("Janvier 2025");
|
||||
nouvelleCotisation.setAnnee(2025);
|
||||
nouvelleCotisation.setMois(1);
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(nouvelleCotisation)
|
||||
.when()
|
||||
.post("/api/cotisations")
|
||||
.then()
|
||||
.statusCode(201)
|
||||
.body("numeroReference", notNullValue())
|
||||
.body("membreId", equalTo(membreTestId.toString()))
|
||||
.body("typeCotisation", equalTo("MENSUELLE"))
|
||||
.body("montantDu", equalTo(25000.00f))
|
||||
.body("montantPaye", equalTo(0.0f))
|
||||
.body("statut", equalTo("EN_ATTENTE"))
|
||||
.body("codeDevise", equalTo("XOF"))
|
||||
.body("annee", equalTo(2025))
|
||||
.body("mois", equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(2)
|
||||
@DisplayName("GET /api/cotisations - Liste des cotisations")
|
||||
void testGetAllCotisations() {
|
||||
given()
|
||||
.queryParam("page", 0)
|
||||
.queryParam("size", 10)
|
||||
.when()
|
||||
.get("/api/cotisations")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("size()", greaterThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(3)
|
||||
@DisplayName("GET /api/cotisations/{id} - Récupération par ID")
|
||||
void testGetCotisationById() {
|
||||
// Créer d'abord une cotisation
|
||||
CotisationDTO cotisation = createTestCotisation();
|
||||
cotisationTestId = Long.valueOf(cotisation.getId().toString());
|
||||
|
||||
given()
|
||||
.pathParam("id", cotisationTestId)
|
||||
.when()
|
||||
.get("/api/cotisations/{id}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("id", equalTo(cotisationTestId.toString()))
|
||||
.body("typeCotisation", equalTo("MENSUELLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(4)
|
||||
@DisplayName("GET /api/cotisations/reference/{numeroReference} - Récupération par référence")
|
||||
void testGetCotisationByReference() {
|
||||
// Utiliser la cotisation créée précédemment
|
||||
if (numeroReferenceTest == null) {
|
||||
CotisationDTO cotisation = createTestCotisation();
|
||||
numeroReferenceTest = cotisation.getNumeroReference();
|
||||
}
|
||||
|
||||
given()
|
||||
.pathParam("numeroReference", numeroReferenceTest)
|
||||
.when()
|
||||
.get("/api/cotisations/reference/{numeroReference}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("numeroReference", equalTo(numeroReferenceTest))
|
||||
.body("typeCotisation", equalTo("MENSUELLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(5)
|
||||
@DisplayName("PUT /api/cotisations/{id} - Mise à jour d'une cotisation")
|
||||
void testUpdateCotisation() {
|
||||
// Créer une cotisation si nécessaire
|
||||
if (cotisationTestId == null) {
|
||||
CotisationDTO cotisation = createTestCotisation();
|
||||
cotisationTestId = Long.valueOf(cotisation.getId().toString());
|
||||
}
|
||||
|
||||
CotisationDTO cotisationMiseAJour = new CotisationDTO();
|
||||
cotisationMiseAJour.setTypeCotisation("TRIMESTRIELLE");
|
||||
cotisationMiseAJour.setMontantDu(new BigDecimal("75000.00"));
|
||||
cotisationMiseAJour.setDescription("Cotisation trimestrielle Q1 2025");
|
||||
cotisationMiseAJour.setObservations("Mise à jour du type de cotisation");
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.pathParam("id", cotisationTestId)
|
||||
.body(cotisationMiseAJour)
|
||||
.when()
|
||||
.put("/api/cotisations/{id}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("typeCotisation", equalTo("TRIMESTRIELLE"))
|
||||
.body("montantDu", equalTo(75000.00f))
|
||||
.body("observations", equalTo("Mise à jour du type de cotisation"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(6)
|
||||
@DisplayName("GET /api/cotisations/membre/{membreId} - Cotisations d'un membre")
|
||||
void testGetCotisationsByMembre() {
|
||||
given()
|
||||
.pathParam("membreId", membreTestId)
|
||||
.queryParam("page", 0)
|
||||
.queryParam("size", 10)
|
||||
.when()
|
||||
.get("/api/cotisations/membre/{membreId}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("size()", greaterThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(7)
|
||||
@DisplayName("GET /api/cotisations/statut/{statut} - Cotisations par statut")
|
||||
void testGetCotisationsByStatut() {
|
||||
given()
|
||||
.pathParam("statut", "EN_ATTENTE")
|
||||
.queryParam("page", 0)
|
||||
.queryParam("size", 10)
|
||||
.when()
|
||||
.get("/api/cotisations/statut/{statut}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("size()", greaterThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(8)
|
||||
@DisplayName("GET /api/cotisations/en-retard - Cotisations en retard")
|
||||
void testGetCotisationsEnRetard() {
|
||||
given()
|
||||
.queryParam("page", 0)
|
||||
.queryParam("size", 10)
|
||||
.when()
|
||||
.get("/api/cotisations/en-retard")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("size()", greaterThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(9)
|
||||
@DisplayName("GET /api/cotisations/recherche - Recherche avancée")
|
||||
void testRechercherCotisations() {
|
||||
given()
|
||||
.queryParam("membreId", membreTestId)
|
||||
.queryParam("statut", "EN_ATTENTE")
|
||||
.queryParam("annee", 2025)
|
||||
.queryParam("page", 0)
|
||||
.queryParam("size", 10)
|
||||
.when()
|
||||
.get("/api/cotisations/recherche")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("size()", greaterThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(10)
|
||||
@DisplayName("GET /api/cotisations/stats - Statistiques des cotisations")
|
||||
void testGetStatistiquesCotisations() {
|
||||
given()
|
||||
.when()
|
||||
.get("/api/cotisations/stats")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("totalCotisations", notNullValue())
|
||||
.body("cotisationsPayees", notNullValue())
|
||||
.body("cotisationsEnRetard", notNullValue())
|
||||
.body("tauxPaiement", notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(11)
|
||||
@DisplayName("DELETE /api/cotisations/{id} - Suppression d'une cotisation")
|
||||
void testDeleteCotisation() {
|
||||
// Créer une cotisation si nécessaire
|
||||
if (cotisationTestId == null) {
|
||||
CotisationDTO cotisation = createTestCotisation();
|
||||
cotisationTestId = Long.valueOf(cotisation.getId().toString());
|
||||
}
|
||||
|
||||
given()
|
||||
.pathParam("id", cotisationTestId)
|
||||
.when()
|
||||
.delete("/api/cotisations/{id}")
|
||||
.then()
|
||||
.statusCode(204);
|
||||
|
||||
// Vérifier que la cotisation est marquée comme annulée
|
||||
given()
|
||||
.pathParam("id", cotisationTestId)
|
||||
.when()
|
||||
.get("/api/cotisations/{id}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("statut", equalTo("ANNULEE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET /api/cotisations/{id} - Cotisation inexistante")
|
||||
void testGetCotisationByIdNotFound() {
|
||||
given()
|
||||
.pathParam("id", 99999L)
|
||||
.when()
|
||||
.get("/api/cotisations/{id}")
|
||||
.then()
|
||||
.statusCode(404)
|
||||
.body("error", equalTo("Cotisation non trouvée"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POST /api/cotisations - Données invalides")
|
||||
void testCreateCotisationInvalidData() {
|
||||
CotisationDTO cotisationInvalide = new CotisationDTO();
|
||||
// Données manquantes ou invalides
|
||||
cotisationInvalide.setTypeCotisation("");
|
||||
cotisationInvalide.setMontantDu(new BigDecimal("-100"));
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(cotisationInvalide)
|
||||
.when()
|
||||
.post("/api/cotisations")
|
||||
.then()
|
||||
.statusCode(400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode utilitaire pour créer une cotisation de test
|
||||
*/
|
||||
private CotisationDTO createTestCotisation() {
|
||||
CotisationDTO cotisation = new CotisationDTO();
|
||||
cotisation.setMembreId(UUID.fromString(membreTestId.toString()));
|
||||
cotisation.setTypeCotisation("MENSUELLE");
|
||||
cotisation.setMontantDu(new BigDecimal("25000.00"));
|
||||
cotisation.setDateEcheance(LocalDate.now().plusDays(30));
|
||||
cotisation.setDescription("Cotisation de test");
|
||||
cotisation.setPeriode("Test 2025");
|
||||
cotisation.setAnnee(2025);
|
||||
cotisation.setMois(1);
|
||||
|
||||
return given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(cotisation)
|
||||
.when()
|
||||
.post("/api/cotisations")
|
||||
.then()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.as(CotisationDTO.class);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.service.MembreService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -17,9 +20,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -115,14 +116,20 @@ class MembreResourceTest {
|
||||
createTestMembre("Jean", "Dupont"),
|
||||
createTestMembre("Marie", "Martin")
|
||||
);
|
||||
when(membreService.listerMembresActifs()).thenReturn(membres);
|
||||
List<MembreDTO> membresDTO = Arrays.asList(
|
||||
createTestMembreDTO("Jean", "Dupont"),
|
||||
createTestMembreDTO("Marie", "Martin")
|
||||
);
|
||||
|
||||
when(membreService.listerMembresActifs(any(Page.class), any(Sort.class))).thenReturn(membres);
|
||||
when(membreService.convertToDTOList(membres)).thenReturn(membresDTO);
|
||||
|
||||
// When
|
||||
Response response = membreResource.listerMembres();
|
||||
Response response = membreResource.listerMembres(0, 20, "nom", "asc");
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getEntity()).isEqualTo(membres);
|
||||
assertThat(response.getEntity()).isEqualTo(membresDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,17 +166,22 @@ class MembreResourceTest {
|
||||
@DisplayName("Test creerMembre")
|
||||
void testCreerMembre() {
|
||||
// Given
|
||||
MembreDTO membreDTO = createTestMembreDTO("Jean", "Dupont");
|
||||
Membre membre = createTestMembre("Jean", "Dupont");
|
||||
Membre membreCreated = createTestMembre("Jean", "Dupont");
|
||||
membreCreated.id = 1L;
|
||||
MembreDTO membreCreatedDTO = createTestMembreDTO("Jean", "Dupont");
|
||||
|
||||
when(membreService.convertFromDTO(any(MembreDTO.class))).thenReturn(membre);
|
||||
when(membreService.creerMembre(any(Membre.class))).thenReturn(membreCreated);
|
||||
when(membreService.convertToDTO(any(Membre.class))).thenReturn(membreCreatedDTO);
|
||||
|
||||
// When
|
||||
Response response = membreResource.creerMembre(membre);
|
||||
Response response = membreResource.creerMembre(membreDTO);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatus()).isEqualTo(201);
|
||||
assertThat(response.getEntity()).isEqualTo(membreCreated);
|
||||
assertThat(response.getEntity()).isEqualTo(membreCreatedDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -177,17 +189,22 @@ class MembreResourceTest {
|
||||
void testMettreAJourMembre() {
|
||||
// Given
|
||||
Long id = 1L;
|
||||
MembreDTO membreDTO = createTestMembreDTO("Jean", "Dupont");
|
||||
Membre membre = createTestMembre("Jean", "Dupont");
|
||||
Membre membreUpdated = createTestMembre("Jean", "Martin");
|
||||
membreUpdated.id = id;
|
||||
MembreDTO membreUpdatedDTO = createTestMembreDTO("Jean", "Martin");
|
||||
|
||||
when(membreService.convertFromDTO(any(MembreDTO.class))).thenReturn(membre);
|
||||
when(membreService.mettreAJourMembre(anyLong(), any(Membre.class))).thenReturn(membreUpdated);
|
||||
when(membreService.convertToDTO(any(Membre.class))).thenReturn(membreUpdatedDTO);
|
||||
|
||||
// When
|
||||
Response response = membreResource.mettreAJourMembre(id, membre);
|
||||
Response response = membreResource.mettreAJourMembre(id, membreDTO);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getEntity()).isEqualTo(membreUpdated);
|
||||
assertThat(response.getEntity()).isEqualTo(membreUpdatedDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,14 +226,16 @@ class MembreResourceTest {
|
||||
// Given
|
||||
String recherche = "Jean";
|
||||
List<Membre> membres = Arrays.asList(createTestMembre("Jean", "Dupont"));
|
||||
when(membreService.rechercherMembres(anyString())).thenReturn(membres);
|
||||
List<MembreDTO> membresDTO = Arrays.asList(createTestMembreDTO("Jean", "Dupont"));
|
||||
when(membreService.rechercherMembres(anyString(), any(Page.class), any(Sort.class))).thenReturn(membres);
|
||||
when(membreService.convertToDTOList(membres)).thenReturn(membresDTO);
|
||||
|
||||
// When
|
||||
Response response = membreResource.rechercherMembres(recherche);
|
||||
Response response = membreResource.rechercherMembres(recherche, 0, 20, "nom", "asc");
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getEntity()).isEqualTo(membres);
|
||||
assertThat(response.getEntity()).isEqualTo(membresDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -235,14 +254,29 @@ class MembreResourceTest {
|
||||
}
|
||||
|
||||
private Membre createTestMembre(String prenom, String nom) {
|
||||
return Membre.builder()
|
||||
.prenom(prenom)
|
||||
.nom(nom)
|
||||
.email(prenom.toLowerCase() + "." + nom.toLowerCase() + "@test.com")
|
||||
.telephone("221701234567")
|
||||
.dateNaissance(LocalDate.of(1990, 1, 1))
|
||||
.dateAdhesion(LocalDate.now())
|
||||
.actif(true)
|
||||
.build();
|
||||
Membre membre = new Membre();
|
||||
membre.setPrenom(prenom);
|
||||
membre.setNom(nom);
|
||||
membre.setEmail(prenom.toLowerCase() + "." + nom.toLowerCase() + "@test.com");
|
||||
membre.setTelephone("221701234567");
|
||||
membre.setDateNaissance(LocalDate.of(1990, 1, 1));
|
||||
membre.setDateAdhesion(LocalDate.now());
|
||||
membre.setActif(true);
|
||||
membre.setNumeroMembre("UF-2025-TEST01");
|
||||
return membre;
|
||||
}
|
||||
|
||||
private MembreDTO createTestMembreDTO(String prenom, String nom) {
|
||||
MembreDTO dto = new MembreDTO();
|
||||
dto.setPrenom(prenom);
|
||||
dto.setNom(nom);
|
||||
dto.setEmail(prenom.toLowerCase() + "." + nom.toLowerCase() + "@test.com");
|
||||
dto.setTelephone("221701234567");
|
||||
dto.setDateNaissance(LocalDate.of(1990, 1, 1));
|
||||
dto.setDateAdhesion(LocalDate.now());
|
||||
dto.setStatut("ACTIF");
|
||||
dto.setNumeroMembre("UF-2025-TEST01");
|
||||
dto.setAssociationId(1L);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user