Version propre - Dashboard enhanced

This commit is contained in:
DahoudG
2025-09-13 19:05:06 +00:00
parent 3df010add7
commit 73459b3092
70 changed files with 15317 additions and 1498 deletions

View File

@@ -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();
}
}

View File

@@ -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
);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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û");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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

View 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 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());

View 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;