Refactroring
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Adhesion avec UUID
|
||||
* Représente une demande d'adhésion d'un membre à une organisation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "adhesions",
|
||||
indexes = {
|
||||
@Index(name = "idx_adhesion_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_adhesion_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_adhesion_reference", columnList = "numero_reference", unique = true),
|
||||
@Index(name = "idx_adhesion_statut", columnList = "statut"),
|
||||
@Index(name = "idx_adhesion_date_demande", columnList = "date_demande")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Adhesion extends BaseEntity {
|
||||
|
||||
@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;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id", nullable = false)
|
||||
private Organisation organisation;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_demande", nullable = false)
|
||||
private LocalDate dateDemande;
|
||||
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant des frais d'adhésion doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal fraisAdhesion;
|
||||
|
||||
@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|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$",
|
||||
message = "Statut invalide")
|
||||
@Column(name = "statut", nullable = false, length = 30)
|
||||
private String statut;
|
||||
|
||||
@Column(name = "date_approbation")
|
||||
private LocalDate dateApprobation;
|
||||
|
||||
@Column(name = "date_paiement")
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "methode_paiement", length = 20)
|
||||
private String methodePaiement;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "reference_paiement", length = 100)
|
||||
private String referencePaiement;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "motif_rejet", length = 1000)
|
||||
private String motifRejet;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "observations", length = 1000)
|
||||
private String observations;
|
||||
|
||||
@Column(name = "approuve_par", length = 255)
|
||||
private String approuvePar;
|
||||
|
||||
@Column(name = "date_validation")
|
||||
private LocalDate dateValidation;
|
||||
|
||||
/** Méthode métier pour vérifier si l'adhésion est payée intégralement */
|
||||
public boolean isPayeeIntegralement() {
|
||||
return montantPaye != null
|
||||
&& fraisAdhesion != null
|
||||
&& montantPaye.compareTo(fraisAdhesion) >= 0;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'adhésion est en attente de paiement */
|
||||
public boolean isEnAttentePaiement() {
|
||||
return "APPROUVEE".equals(statut) && !isPayeeIntegralement();
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer le montant restant à payer */
|
||||
public BigDecimal getMontantRestant() {
|
||||
if (fraisAdhesion == null) return BigDecimal.ZERO;
|
||||
if (montantPaye == null) return fraisAdhesion;
|
||||
BigDecimal restant = fraisAdhesion.subtract(montantPaye);
|
||||
return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Entité pour les logs d'audit
|
||||
* Enregistre toutes les actions importantes du système
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "audit_logs", indexes = {
|
||||
@Index(name = "idx_audit_date_heure", columnList = "date_heure"),
|
||||
@Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
|
||||
@Index(name = "idx_audit_module", columnList = "module"),
|
||||
@Index(name = "idx_audit_type_action", columnList = "type_action"),
|
||||
@Index(name = "idx_audit_severite", columnList = "severite")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuditLog extends BaseEntity {
|
||||
|
||||
@Column(name = "type_action", nullable = false, length = 50)
|
||||
private String typeAction;
|
||||
|
||||
@Column(name = "severite", nullable = false, length = 20)
|
||||
private String severite;
|
||||
|
||||
@Column(name = "utilisateur", length = 255)
|
||||
private String utilisateur;
|
||||
|
||||
@Column(name = "role", length = 50)
|
||||
private String role;
|
||||
|
||||
@Column(name = "module", length = 50)
|
||||
private String module;
|
||||
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(name = "details", columnDefinition = "TEXT")
|
||||
private String details;
|
||||
|
||||
@Column(name = "ip_address", length = 45)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent", length = 500)
|
||||
private String userAgent;
|
||||
|
||||
@Column(name = "session_id", length = 255)
|
||||
private String sessionId;
|
||||
|
||||
@Column(name = "date_heure", nullable = false)
|
||||
private LocalDateTime dateHeure;
|
||||
|
||||
@Column(name = "donnees_avant", columnDefinition = "TEXT")
|
||||
private String donneesAvant;
|
||||
|
||||
@Column(name = "donnees_apres", columnDefinition = "TEXT")
|
||||
private String donneesApres;
|
||||
|
||||
@Column(name = "entite_id", length = 255)
|
||||
private String entiteId;
|
||||
|
||||
@Column(name = "entite_type", length = 100)
|
||||
private String entiteType;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (dateHeure == null) {
|
||||
dateHeure = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
|
||||
/**
|
||||
* Entité persistée représentant un type d'organisation.
|
||||
*
|
||||
* <p>Cette entité permet de gérer dynamiquement le catalogue des types d'organisations
|
||||
* (codes, libellés, description, ordre d'affichage, activation/désactivation).
|
||||
*
|
||||
* <p>Le champ {@code code} doit rester synchronisé avec l'enum {@link
|
||||
* dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation} pour les types
|
||||
* standards fournis par la plateforme.
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "uf_type_organisation",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_type_organisation_code",
|
||||
columnNames = {"code"})
|
||||
})
|
||||
public class TypeOrganisationEntity extends BaseEntity {
|
||||
|
||||
@Column(name = "code", length = 50, nullable = false, unique = true)
|
||||
private String code;
|
||||
|
||||
@Column(name = "libelle", length = 150, nullable = false)
|
||||
private String libelle;
|
||||
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(name = "ordre_affichage")
|
||||
private Integer ordreAffichage;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
|
||||
public void setLibelle(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Integer getOrdreAffichage() {
|
||||
return ordreAffichage;
|
||||
}
|
||||
|
||||
public void setOrdreAffichage(Integer ordreAffichage) {
|
||||
this.ordreAffichage = ordreAffichage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Adhesion;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Adhesion
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AdhesionRepository extends BaseRepository<Adhesion> {
|
||||
|
||||
public AdhesionRepository() {
|
||||
super(Adhesion.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une adhésion par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return Optional contenant l'adhésion si trouvée
|
||||
*/
|
||||
public Optional<Adhesion> findByNumeroReference(String numeroReference) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.numeroReference = :numeroReference", Adhesion.class);
|
||||
query.setParameter("numeroReference", numeroReference);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions d'un membre
|
||||
*
|
||||
* @param membreId identifiant du membre
|
||||
* @return liste des adhésions du membre
|
||||
*/
|
||||
public List<Adhesion> findByMembreId(UUID membreId) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.membre.id = :membreId", Adhesion.class);
|
||||
query.setParameter("membreId", membreId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions d'une organisation
|
||||
*
|
||||
* @param organisationId identifiant de l'organisation
|
||||
* @return liste des adhésions de l'organisation
|
||||
*/
|
||||
public List<Adhesion> findByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.organisation.id = :organisationId", Adhesion.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions par statut
|
||||
*
|
||||
* @param statut statut de l'adhésion
|
||||
* @return liste des adhésions avec le statut spécifié
|
||||
*/
|
||||
public List<Adhesion> findByStatut(String statut) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery("SELECT a FROM Adhesion a WHERE a.statut = :statut", Adhesion.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions en attente
|
||||
*
|
||||
* @return liste des adhésions en attente
|
||||
*/
|
||||
public List<Adhesion> findEnAttente() {
|
||||
return findByStatut("EN_ATTENTE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions approuvées en attente de paiement
|
||||
*
|
||||
* @return liste des adhésions approuvées non payées
|
||||
*/
|
||||
public List<Adhesion> findApprouveesEnAttentePaiement() {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.statut = :statut AND (a.montantPaye IS NULL OR a.montantPaye < a.fraisAdhesion)",
|
||||
Adhesion.class);
|
||||
query.setParameter("statut", "APPROUVEE");
|
||||
return query.getResultList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AuditLog;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour les logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AuditLogRepository extends BaseRepository<AuditLog> {
|
||||
|
||||
public AuditLogRepository() {
|
||||
super(AuditLog.class);
|
||||
}
|
||||
|
||||
// Les méthodes de recherche spécifiques peuvent être ajoutées ici si nécessaire
|
||||
// Pour l'instant, on utilise les méthodes de base et les requêtes dans le service
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.TypeOrganisationEntity;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité {@link TypeOrganisationEntity}.
|
||||
*
|
||||
* <p>Permet de gérer le catalogue des types d'organisations.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TypeOrganisationRepository extends BaseRepository<TypeOrganisationEntity> {
|
||||
|
||||
public TypeOrganisationRepository() {
|
||||
super(TypeOrganisationEntity.class);
|
||||
}
|
||||
|
||||
/** Recherche un type par son code fonctionnel. */
|
||||
public Optional<TypeOrganisationEntity> findByCode(String code) {
|
||||
TypedQuery<TypeOrganisationEntity> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT t FROM TypeOrganisationEntity t WHERE UPPER(t.code) = UPPER(:code)",
|
||||
TypeOrganisationEntity.class);
|
||||
query.setParameter("code", code);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/** Liste les types actifs, triés par ordreAffichage puis libellé. */
|
||||
public List<TypeOrganisationEntity> listActifsOrdennes() {
|
||||
return entityManager
|
||||
.createQuery(
|
||||
"SELECT t FROM TypeOrganisationEntity t "
|
||||
+ "WHERE t.actif = true "
|
||||
+ "ORDER BY COALESCE(t.ordreAffichage, 9999), t.libelle",
|
||||
TypeOrganisationEntity.class)
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO;
|
||||
import dev.lions.unionflow.server.service.AdhesionService;
|
||||
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 java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des adhésions
|
||||
* Expose les endpoints API pour les opérations CRUD sur les adhésions
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Path("/api/adhesions")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Adhésions", description = "Gestion des demandes d'adhésion des membres")
|
||||
@Slf4j
|
||||
public class AdhesionResource {
|
||||
|
||||
@Inject AdhesionService adhesionService;
|
||||
|
||||
/** Récupère toutes les adhésions avec pagination */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les adhésions",
|
||||
description = "Récupère la liste paginée de toutes les adhésions")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des adhésions récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAllAdhesions(
|
||||
@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/adhesions - page: {}, size: {}", page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAllAdhesions(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions", adhesions.size());
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une adhésion par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une adhésion par ID",
|
||||
description = "Récupère les détails d'une adhésion spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Adhésion trouvée",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionById(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/{}", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.getAdhesionById(id);
|
||||
|
||||
log.info("Adhésion récupérée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une adhésion par son numéro de référence */
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
@Operation(
|
||||
summary = "Récupérer une adhésion par référence",
|
||||
description = "Récupère une adhésion par son numéro de référence unique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion trouvée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionByReference(
|
||||
@Parameter(description = "Numéro de référence de l'adhésion", required = true)
|
||||
@PathParam("numeroReference")
|
||||
@NotNull
|
||||
String numeroReference) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/reference/{}", numeroReference);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.getAdhesionByReference(numeroReference);
|
||||
|
||||
log.info("Adhésion récupérée avec succès - Référence: {}", numeroReference);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée - Référence: {}", numeroReference);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "reference", numeroReference))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Erreur lors de la récupération de l'adhésion - 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 l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Crée une nouvelle adhésion */
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle adhésion",
|
||||
description = "Crée une nouvelle demande d'adhésion pour un membre")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Adhésion créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Membre ou organisation non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response createAdhesion(
|
||||
@Parameter(description = "Données de l'adhésion à créer", required = true) @Valid
|
||||
AdhesionDTO adhesionDTO) {
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"POST /api/adhesions - Création adhésion pour membre: {} et organisation: {}",
|
||||
adhesionDTO.getMembreId(),
|
||||
adhesionDTO.getOrganisationId());
|
||||
|
||||
AdhesionDTO nouvelleAdhesion = adhesionService.createAdhesion(adhesionDTO);
|
||||
|
||||
log.info(
|
||||
"Adhésion créée avec succès - ID: {}, Référence: {}",
|
||||
nouvelleAdhesion.getId(),
|
||||
nouvelleAdhesion.getNumeroReference());
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(nouvelleAdhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre ou organisation non trouvé lors de la création d'adhésion");
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre ou organisation non trouvé", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides pour la création d'adhésion: {}", 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 l'adhésion", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la création de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour une adhésion existante */
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour une adhésion",
|
||||
description = "Met à jour les données d'une adhésion existante")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response updateAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Nouvelles données de l'adhésion", required = true) @Valid
|
||||
AdhesionDTO adhesionDTO) {
|
||||
|
||||
try {
|
||||
log.info("PUT /api/adhesions/{}", id);
|
||||
|
||||
AdhesionDTO adhesionMiseAJour = adhesionService.updateAdhesion(id, adhesionDTO);
|
||||
|
||||
log.info("Adhésion mise à jour avec succès - ID: {}", id);
|
||||
return Response.ok(adhesionMiseAJour).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour mise à jour - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn(
|
||||
"Données invalides pour la mise à jour d'adhésion - 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 l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la mise à jour de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime une adhésion */
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une adhésion",
|
||||
description = "Supprime (annule) une adhésion")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Adhésion supprimée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(
|
||||
responseCode = "409",
|
||||
description = "Impossible de supprimer une adhésion payée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response deleteAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("DELETE /api/adhesions/{}", id);
|
||||
|
||||
adhesionService.deleteAdhesion(id);
|
||||
|
||||
log.info("Adhésion supprimée avec succès - ID: {}", id);
|
||||
return Response.noContent().build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour suppression - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de supprimer l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", "Impossible de supprimer l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la suppression de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Approuve une adhésion */
|
||||
@POST
|
||||
@Path("/{id}/approuver")
|
||||
@Operation(
|
||||
summary = "Approuver une adhésion",
|
||||
description = "Approuve une demande d'adhésion en attente")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion approuvée avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être approuvée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response approuverAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Nom de l'utilisateur qui approuve")
|
||||
@QueryParam("approuvePar")
|
||||
String approuvePar) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/approuver", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.approuverAdhesion(id, approuvePar);
|
||||
|
||||
log.info("Adhésion approuvée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour approbation - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible d'approuver l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Impossible d'approuver l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'approbation de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de l'approbation de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Rejette une adhésion */
|
||||
@POST
|
||||
@Path("/{id}/rejeter")
|
||||
@Operation(
|
||||
summary = "Rejeter une adhésion",
|
||||
description = "Rejette une demande d'adhésion en attente")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion rejetée avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être rejetée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response rejeterAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Motif du rejet", required = true) @QueryParam("motifRejet")
|
||||
@NotNull
|
||||
String motifRejet) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/rejeter", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.rejeterAdhesion(id, motifRejet);
|
||||
|
||||
log.info("Adhésion rejetée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour rejet - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de rejeter l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Impossible de rejeter l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors du rejet de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors du rejet de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Enregistre un paiement pour une adhésion */
|
||||
@POST
|
||||
@Path("/{id}/paiement")
|
||||
@Operation(
|
||||
summary = "Enregistrer un paiement",
|
||||
description = "Enregistre un paiement pour une adhésion approuvée")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Paiement enregistré avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas recevoir de paiement"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response enregistrerPaiement(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Montant payé", required = true) @QueryParam("montantPaye")
|
||||
@NotNull
|
||||
BigDecimal montantPaye,
|
||||
@Parameter(description = "Méthode de paiement") @QueryParam("methodePaiement")
|
||||
String methodePaiement,
|
||||
@Parameter(description = "Référence du paiement") @QueryParam("referencePaiement")
|
||||
String referencePaiement) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/paiement", id);
|
||||
|
||||
AdhesionDTO adhesion =
|
||||
adhesionService.enregistrerPaiement(id, montantPaye, methodePaiement, referencePaiement);
|
||||
|
||||
log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour paiement - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible d'enregistrer le paiement - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
Map.of("error", "Impossible d'enregistrer le paiement", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'enregistrement du paiement - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de l'enregistrement du paiement", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions d'un membre */
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions d'un membre",
|
||||
description = "Récupère toutes les adhésions d'un membre spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions du membre"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByMembre(
|
||||
@Parameter(description = "Identifiant du membre", required = true)
|
||||
@PathParam("membreId")
|
||||
@NotNull
|
||||
UUID 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/adhesions/membre/{} - page: {}, size: {}", membreId, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsByMembre(membreId, page, size);
|
||||
|
||||
log.info(
|
||||
"Récupération réussie de {} adhésions pour le membre {}", adhesions.size(), membreId);
|
||||
return Response.ok(adhesions).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 adhésions 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 adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions d'une organisation */
|
||||
@GET
|
||||
@Path("/organisation/{organisationId}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions d'une organisation",
|
||||
description = "Récupère toutes les adhésions d'une organisation spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions de l'organisation"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByOrganisation(
|
||||
@Parameter(description = "Identifiant de l'organisation", required = true)
|
||||
@PathParam("organisationId")
|
||||
@NotNull
|
||||
UUID organisationId,
|
||||
@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/adhesions/organisation/{} - page: {}, size: {}", organisationId, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions =
|
||||
adhesionService.getAdhesionsByOrganisation(organisationId, page, size);
|
||||
|
||||
log.info(
|
||||
"Récupération réussie de {} adhésions pour l'organisation {}",
|
||||
adhesions.size(),
|
||||
organisationId);
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Organisation non trouvée - ID: {}", organisationId);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Organisation non trouvée", "organisationId", organisationId))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Erreur lors de la récupération des adhésions de l'organisation - ID: " + organisationId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions par statut */
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions par statut",
|
||||
description = "Récupère toutes les adhésions ayant un statut spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des adhésions avec le statut spécifié"),
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByStatut(
|
||||
@Parameter(description = "Statut des adhésions", 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/adhesions/statut/{} - page: {}, size: {}", statut, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsByStatut(statut, page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions avec statut {}", adhesions.size(), statut);
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions 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 adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions en attente */
|
||||
@GET
|
||||
@Path("/en-attente")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions en attente",
|
||||
description = "Récupère toutes les adhésions en attente d'approbation")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions en attente"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsEnAttente(
|
||||
@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/adhesions/en-attente - page: {}, size: {}", page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsEnAttente(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions en attente", adhesions.size());
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions en attente", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des adhésions en attente", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les statistiques des adhésions */
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(
|
||||
summary = "Statistiques des adhésions",
|
||||
description = "Récupère les statistiques globales des adhésions")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getStatistiquesAdhesions() {
|
||||
try {
|
||||
log.info("GET /api/adhesions/stats");
|
||||
|
||||
Map<String, Object> statistiques = adhesionService.getStatistiquesAdhesions();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO;
|
||||
import dev.lions.unionflow.server.service.AuditService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Path("/api/audit")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Audit", description = "Gestion des logs d'audit")
|
||||
@Slf4j
|
||||
public class AuditResource {
|
||||
|
||||
@Inject
|
||||
AuditService auditService;
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Liste tous les logs d'audit", description = "Récupère tous les logs avec pagination")
|
||||
public Response listerTous(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size,
|
||||
@QueryParam("sortBy") @DefaultValue("dateHeure") String sortBy,
|
||||
@QueryParam("sortOrder") @DefaultValue("desc") String sortOrder) {
|
||||
|
||||
try {
|
||||
Map<String, Object> result = auditService.listerTous(page, size, sortBy, sortOrder);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des logs d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des logs: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/rechercher")
|
||||
@Operation(summary = "Recherche des logs avec filtres", description = "Recherche avancée avec filtres multiples")
|
||||
public Response rechercher(
|
||||
@QueryParam("dateDebut") String dateDebutStr,
|
||||
@QueryParam("dateFin") String dateFinStr,
|
||||
@QueryParam("typeAction") String typeAction,
|
||||
@QueryParam("severite") String severite,
|
||||
@QueryParam("utilisateur") String utilisateur,
|
||||
@QueryParam("module") String module,
|
||||
@QueryParam("ipAddress") String ipAddress,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
|
||||
try {
|
||||
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
|
||||
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
|
||||
|
||||
Map<String, Object> result = auditService.rechercher(
|
||||
dateDebut, dateFin, typeAction, severite, utilisateur, module, ipAddress, page, size);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la recherche des logs d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la recherche: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Enregistre un nouveau log d'audit", description = "Crée une nouvelle entrée dans le journal d'audit")
|
||||
public Response enregistrerLog(@Valid AuditLogDTO dto) {
|
||||
try {
|
||||
AuditLogDTO result = auditService.enregistrerLog(dto);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'enregistrement du log d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de l'enregistrement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupère les statistiques d'audit", description = "Retourne les statistiques globales des logs")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
Map<String, Object> stats = auditService.getStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des statistiques: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.ExportService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour l'export des données */
|
||||
@Path("/api/export")
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Export", description = "API d'export des données")
|
||||
public class ExportResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ExportResource.class);
|
||||
|
||||
@Inject ExportService exportService;
|
||||
|
||||
@GET
|
||||
@Path("/cotisations/csv")
|
||||
@Produces("text/csv")
|
||||
@Operation(summary = "Exporter les cotisations en CSV")
|
||||
@APIResponse(responseCode = "200", description = "Fichier CSV généré")
|
||||
public Response exporterCotisationsCSV(
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("associationId") UUID associationId) {
|
||||
LOG.info("Export CSV des cotisations");
|
||||
|
||||
byte[] csv = exportService.exporterToutesCotisationsCSV(statut, type, associationId);
|
||||
|
||||
return Response.ok(csv)
|
||||
.header("Content-Disposition", "attachment; filename=\"cotisations.csv\"")
|
||||
.header("Content-Type", "text/csv; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/cotisations/csv")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("text/csv")
|
||||
@Operation(summary = "Exporter des cotisations spécifiques en CSV")
|
||||
@APIResponse(responseCode = "200", description = "Fichier CSV généré")
|
||||
public Response exporterCotisationsSelectionneesCSV(List<UUID> cotisationIds) {
|
||||
LOG.infof("Export CSV de %d cotisations", cotisationIds.size());
|
||||
|
||||
byte[] csv = exportService.exporterCotisationsCSV(cotisationIds);
|
||||
|
||||
return Response.ok(csv)
|
||||
.header("Content-Disposition", "attachment; filename=\"cotisations.csv\"")
|
||||
.header("Content-Type", "text/csv; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/cotisations/{cotisationId}/recu")
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer un reçu de paiement")
|
||||
@APIResponse(responseCode = "200", description = "Reçu généré")
|
||||
public Response genererRecu(@PathParam("cotisationId") UUID cotisationId) {
|
||||
LOG.infof("Génération reçu pour: %s", cotisationId);
|
||||
|
||||
byte[] recu = exportService.genererRecuPaiement(cotisationId);
|
||||
|
||||
return Response.ok(recu)
|
||||
.header("Content-Disposition", "attachment; filename=\"recu-" + cotisationId + ".txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/cotisations/recus")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer des reçus groupés")
|
||||
@APIResponse(responseCode = "200", description = "Reçus générés")
|
||||
public Response genererRecusGroupes(List<UUID> cotisationIds) {
|
||||
LOG.infof("Génération de %d reçus", cotisationIds.size());
|
||||
|
||||
byte[] recus = exportService.genererRecusGroupes(cotisationIds);
|
||||
|
||||
return Response.ok(recus)
|
||||
.header("Content-Disposition", "attachment; filename=\"recus-groupes.txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/rapport/mensuel")
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer un rapport mensuel")
|
||||
@APIResponse(responseCode = "200", description = "Rapport généré")
|
||||
public Response genererRapportMensuel(
|
||||
@QueryParam("annee") int annee,
|
||||
@QueryParam("mois") int mois,
|
||||
@QueryParam("associationId") UUID associationId) {
|
||||
LOG.infof("Génération rapport mensuel: %d/%d", mois, annee);
|
||||
|
||||
byte[] rapport = exportService.genererRapportMensuel(annee, mois, associationId);
|
||||
|
||||
return Response.ok(rapport)
|
||||
.header("Content-Disposition",
|
||||
"attachment; filename=\"rapport-" + annee + "-" + String.format("%02d", mois) + ".txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.annotation.security.PermitAll;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -90,6 +91,7 @@ public class MembreResource {
|
||||
}
|
||||
|
||||
@POST
|
||||
@PermitAll
|
||||
@Operation(summary = "Créer un nouveau membre")
|
||||
@APIResponse(responseCode = "201", description = "Membre créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.notification.NotificationDTO;
|
||||
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
|
||||
import dev.lions.unionflow.server.service.NotificationService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour la gestion des notifications */
|
||||
@Path("/api/notifications")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Notifications", description = "API de gestion des notifications")
|
||||
public class NotificationResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(NotificationResource.class);
|
||||
|
||||
@Inject NotificationService notificationService;
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Envoyer une notification")
|
||||
@APIResponse(responseCode = "200", description = "Notification envoyée")
|
||||
public Response envoyerNotification(NotificationDTO notification) {
|
||||
LOG.infof("Envoi de notification: %s", notification.getTitre());
|
||||
try {
|
||||
CompletableFuture<NotificationDTO> future = notificationService.envoyerNotification(notification);
|
||||
NotificationDTO result = future.get();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'envoi de notification");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/groupe")
|
||||
@Operation(summary = "Envoyer une notification à un groupe")
|
||||
@APIResponse(responseCode = "200", description = "Notifications envoyées")
|
||||
public Response envoyerNotificationGroupe(
|
||||
@QueryParam("type") TypeNotification type,
|
||||
@QueryParam("titre") String titre,
|
||||
@QueryParam("message") String message,
|
||||
List<String> destinatairesIds) {
|
||||
LOG.infof("Envoi de notification de groupe: %d destinataires", destinatairesIds.size());
|
||||
try {
|
||||
CompletableFuture<List<NotificationDTO>> future =
|
||||
notificationService.envoyerNotificationGroupe(type, titre, message, destinatairesIds, Map.of());
|
||||
List<NotificationDTO> results = future.get();
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'envoi de notifications groupées");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/utilisateur/{utilisateurId}")
|
||||
@Operation(summary = "Obtenir les notifications d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Notifications récupérées")
|
||||
public Response obtenirNotifications(
|
||||
@PathParam("utilisateurId") String utilisateurId,
|
||||
@QueryParam("includeArchivees") @DefaultValue("false") boolean includeArchivees,
|
||||
@QueryParam("limite") @DefaultValue("50") int limite) {
|
||||
LOG.infof("Récupération notifications pour: %s", utilisateurId);
|
||||
List<NotificationDTO> notifications =
|
||||
notificationService.obtenirNotificationsUtilisateur(utilisateurId, includeArchivees, limite);
|
||||
return Response.ok(notifications).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{notificationId}/lue")
|
||||
@Operation(summary = "Marquer une notification comme lue")
|
||||
@APIResponse(responseCode = "200", description = "Notification marquée comme lue")
|
||||
public Response marquerCommeLue(
|
||||
@PathParam("notificationId") String notificationId,
|
||||
@QueryParam("utilisateurId") String utilisateurId) {
|
||||
LOG.infof("Marquage comme lue: %s", notificationId);
|
||||
boolean succes = notificationService.marquerCommeLue(notificationId, utilisateurId);
|
||||
return Response.ok(Map.of("success", succes)).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{notificationId}/archiver")
|
||||
@Operation(summary = "Archiver une notification")
|
||||
@APIResponse(responseCode = "200", description = "Notification archivée")
|
||||
public Response archiverNotification(
|
||||
@PathParam("notificationId") String notificationId,
|
||||
@QueryParam("utilisateurId") String utilisateurId) {
|
||||
LOG.infof("Archivage: %s", notificationId);
|
||||
boolean succes = notificationService.archiverNotification(notificationId, utilisateurId);
|
||||
return Response.ok(Map.of("success", succes)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques des notifications")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées")
|
||||
public Response obtenirStatistiques() {
|
||||
Map<String, Long> stats = notificationService.obtenirStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/test/{utilisateurId}")
|
||||
@Operation(summary = "Envoyer une notification de test")
|
||||
@APIResponse(responseCode = "200", description = "Notification de test envoyée")
|
||||
public Response envoyerNotificationTest(
|
||||
@PathParam("utilisateurId") String utilisateurId,
|
||||
@QueryParam("type") @DefaultValue("SYSTEME") TypeNotification type) {
|
||||
LOG.infof("Envoi notification de test: %s", utilisateurId);
|
||||
try {
|
||||
CompletableFuture<NotificationDTO> future =
|
||||
notificationService.envoyerNotificationTest(utilisateurId, type);
|
||||
NotificationDTO result = future.get();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'envoi de notification de test");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public class OrganisationResource {
|
||||
|
||||
/** Récupère toutes les organisations actives */
|
||||
@GET
|
||||
@jakarta.annotation.security.PermitAll // ✅ Accès public pour inscription
|
||||
@Operation(
|
||||
summary = "Lister les organisations",
|
||||
description = "Récupère la liste des organisations actives avec pagination")
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.PreferencesNotificationService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour la gestion des préférences utilisateur */
|
||||
@Path("/api/preferences")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Préférences", description = "API de gestion des préférences utilisateur")
|
||||
public class PreferencesResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PreferencesResource.class);
|
||||
|
||||
@Inject PreferencesNotificationService preferencesService;
|
||||
|
||||
@GET
|
||||
@Path("/{utilisateurId}")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Obtenir les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Préférences récupérées avec succès")
|
||||
public Response obtenirPreferences(
|
||||
@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Récupération des préférences pour l'utilisateur %s", utilisateurId);
|
||||
Map<String, Boolean> preferences = preferencesService.obtenirPreferences(utilisateurId);
|
||||
return Response.ok(preferences).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{utilisateurId}")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Mettre à jour les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "204", description = "Préférences mises à jour avec succès")
|
||||
public Response mettreAJourPreferences(
|
||||
@PathParam("utilisateurId") UUID utilisateurId, Map<String, Boolean> preferences) {
|
||||
LOG.infof("Mise à jour des préférences pour l'utilisateur %s", utilisateurId);
|
||||
preferencesService.mettreAJourPreferences(utilisateurId, preferences);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{utilisateurId}/reinitialiser")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Réinitialiser les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "204", description = "Préférences réinitialisées avec succès")
|
||||
public Response reinitialiserPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId);
|
||||
preferencesService.reinitialiserPreferences(utilisateurId);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{utilisateurId}/export")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Exporter les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Préférences exportées avec succès")
|
||||
public Response exporterPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId);
|
||||
Map<String, Object> export = preferencesService.exporterPreferences(utilisateurId);
|
||||
return Response.ok(export).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.organisation.TypeOrganisationDTO;
|
||||
import dev.lions.unionflow.server.service.TypeOrganisationService;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
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 org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Ressource REST pour la gestion du catalogue des types d'organisation.
|
||||
*/
|
||||
@Path("/api/types-organisations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Types d'organisation", description = "Catalogue des types d'organisation")
|
||||
@PermitAll
|
||||
public class TypeOrganisationResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TypeOrganisationResource.class);
|
||||
|
||||
@Inject TypeOrganisationService service;
|
||||
|
||||
/** Liste les types d'organisation. */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister les types d'organisation",
|
||||
description = "Récupère la liste des types d'organisation, optionnellement seulement actifs")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des types récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response listTypes(
|
||||
@Parameter(description = "Limiter aux types actifs", example = "true")
|
||||
@QueryParam("onlyActifs")
|
||||
@DefaultValue("true")
|
||||
String onlyActifs) {
|
||||
// Parsing manuel pour éviter toute erreur de conversion JAX-RS (qui peut renvoyer une 400)
|
||||
boolean actifsSeulement = !"false".equalsIgnoreCase(onlyActifs);
|
||||
List<TypeOrganisationDTO> types = service.listAll(actifsSeulement);
|
||||
return Response.ok(types).build();
|
||||
}
|
||||
|
||||
/** Crée un nouveau type d'organisation (réservé à l'administration). */
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Créer un type d'organisation",
|
||||
description = "Crée un nouveau type dans le catalogue (code doit exister dans l'enum)")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Type créé avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response create(TypeOrganisationDTO dto) {
|
||||
try {
|
||||
TypeOrganisationDTO created = service.create(dto);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la création du type d'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la création du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour un type. */
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour un type d'organisation",
|
||||
description = "Met à jour un type existant (libellé, description, ordre, actif, code)")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Type mis à jour avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Type non trouvé"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response update(@PathParam("id") UUID id, TypeOrganisationDTO dto) {
|
||||
try {
|
||||
TypeOrganisationDTO updated = service.update(id, dto);
|
||||
return Response.ok(updated).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la mise à jour du type d'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la mise à jour du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Désactive un type (soft delete). */
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Désactiver un type d'organisation",
|
||||
description = "Désactive un type dans le catalogue (soft delete)")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Type désactivé avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Type non trouvé"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response disable(@PathParam("id") UUID id) {
|
||||
try {
|
||||
service.disable(id);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la désactivation du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveBalanceDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveWebhookDTO;
|
||||
import dev.lions.unionflow.server.service.WaveService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
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.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour l'intégration Wave Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Path("/api/wave")
|
||||
@Tag(name = "Wave Money", description = "API d'intégration Wave Money pour les paiements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class WaveResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WaveResource.class);
|
||||
|
||||
@Inject WaveService waveService;
|
||||
|
||||
@POST
|
||||
@Path("/checkout/sessions")
|
||||
@Operation(
|
||||
summary = "Créer une session de paiement Wave",
|
||||
description = "Crée une nouvelle session de paiement via l'API Wave Checkout")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Session créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveCheckoutSessionDTO.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
public Response creerSessionPaiement(
|
||||
@Parameter(description = "Montant à payer", required = true) @QueryParam("montant")
|
||||
@NotNull
|
||||
@DecimalMin("0.01")
|
||||
BigDecimal montant,
|
||||
@Parameter(description = "Devise (XOF par défaut)") @QueryParam("devise") String devise,
|
||||
@Parameter(description = "URL de succès", required = true) @QueryParam("successUrl")
|
||||
@NotBlank
|
||||
String successUrl,
|
||||
@Parameter(description = "URL d'erreur", required = true) @QueryParam("errorUrl")
|
||||
@NotBlank
|
||||
String errorUrl,
|
||||
@Parameter(description = "Référence UnionFlow") @QueryParam("reference")
|
||||
String referenceUnionFlow,
|
||||
@Parameter(description = "Description du paiement") @QueryParam("description")
|
||||
String description,
|
||||
@Parameter(description = "ID de l'organisation") @QueryParam("organisationId") UUID organisationId,
|
||||
@Parameter(description = "ID du membre") @QueryParam("membreId") UUID membreId) {
|
||||
try {
|
||||
WaveCheckoutSessionDTO session =
|
||||
waveService.creerSessionPaiement(
|
||||
montant,
|
||||
devise,
|
||||
successUrl,
|
||||
errorUrl,
|
||||
referenceUnionFlow,
|
||||
description,
|
||||
organisationId,
|
||||
membreId);
|
||||
|
||||
return Response.ok(session).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la session: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la création de la session");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/checkout/sessions/{sessionId}")
|
||||
@Operation(
|
||||
summary = "Vérifier le statut d'une session",
|
||||
description = "Récupère le statut d'une session de paiement Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Statut récupéré avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveCheckoutSessionDTO.class)))
|
||||
@APIResponse(responseCode = "404", description = "Session non trouvée")
|
||||
public Response verifierStatutSession(
|
||||
@Parameter(description = "ID de la session Wave", required = true) @PathParam("sessionId")
|
||||
@NotBlank
|
||||
String sessionId) {
|
||||
try {
|
||||
WaveCheckoutSessionDTO session = waveService.verifierStatutSession(sessionId);
|
||||
return Response.ok(session).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du statut: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la vérification du statut");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/webhooks")
|
||||
@Operation(
|
||||
summary = "Recevoir un webhook Wave",
|
||||
description = "Endpoint pour recevoir les notifications webhook de Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Webhook reçu et traité",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveWebhookDTO.class)))
|
||||
@APIResponse(responseCode = "400", description = "Webhook invalide")
|
||||
@APIResponse(responseCode = "401", description = "Signature invalide")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response recevoirWebhook(
|
||||
@Parameter(description = "Payload du webhook", required = true) String payload,
|
||||
@jakarta.ws.rs.HeaderParam("X-Wave-Signature") String signature) {
|
||||
try {
|
||||
// Récupérer les headers
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (signature != null) {
|
||||
headers.put("X-Wave-Signature", signature);
|
||||
}
|
||||
|
||||
WaveWebhookDTO webhook = waveService.traiterWebhook(payload, signature, headers);
|
||||
return Response.ok(webhook).build();
|
||||
|
||||
} catch (SecurityException e) {
|
||||
LOG.warnf("Signature webhook invalide: %s", e.getMessage());
|
||||
return Response.status(Response.Status.UNAUTHORIZED).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du traitement du webhook: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors du traitement du webhook");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/balance")
|
||||
@Operation(
|
||||
summary = "Consulter le solde Wave",
|
||||
description = "Récupère le solde disponible du wallet Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Solde récupéré avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveBalanceDTO.class)))
|
||||
public Response consulterSolde() {
|
||||
try {
|
||||
WaveBalanceDTO balance = waveService.consulterSolde();
|
||||
return Response.ok(balance).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la consultation du solde: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la consultation du solde");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/test")
|
||||
@Operation(
|
||||
summary = "Tester la connexion Wave",
|
||||
description = "Teste la connexion et la configuration de l'API Wave")
|
||||
@APIResponse(responseCode = "200", description = "Test effectué")
|
||||
public Response testerConnexion() {
|
||||
Map<String, Object> resultat = waveService.testerConnexion();
|
||||
return Response.ok(resultat).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,559 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO;
|
||||
import dev.lions.unionflow.server.entity.Adhesion;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.AdhesionRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des adhésions
|
||||
* Contient la logique métier et les règles de validation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AdhesionService {
|
||||
|
||||
@Inject AdhesionRepository adhesionRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
/**
|
||||
* Récupère toutes les adhésions avec pagination
|
||||
*
|
||||
* @param page numéro de page (0-based)
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions converties en DTO
|
||||
*/
|
||||
public List<AdhesionDTO> getAllAdhesions(int page, int size) {
|
||||
log.debug("Récupération des adhésions - page: {}, size: {}", page, size);
|
||||
|
||||
jakarta.persistence.TypedQuery<Adhesion> query =
|
||||
adhesionRepository
|
||||
.getEntityManager()
|
||||
.createQuery(
|
||||
"SELECT a FROM Adhesion a ORDER BY a.dateDemande DESC", Adhesion.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
List<Adhesion> adhesions = query.getResultList();
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une adhésion par son ID
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @return DTO de l'adhésion
|
||||
* @throws NotFoundException si l'adhésion n'existe pas
|
||||
*/
|
||||
public AdhesionDTO getAdhesionById(@NotNull UUID id) {
|
||||
log.debug("Récupération de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une adhésion par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return DTO de l'adhésion
|
||||
* @throws NotFoundException si l'adhésion n'existe pas
|
||||
*/
|
||||
public AdhesionDTO getAdhesionByReference(@NotNull String numeroReference) {
|
||||
log.debug("Récupération de l'adhésion avec référence: {}", numeroReference);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByNumeroReference(numeroReference)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Adhésion non trouvée avec la référence: " + numeroReference));
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle adhésion
|
||||
*
|
||||
* @param adhesionDTO données de l'adhésion à créer
|
||||
* @return DTO de l'adhésion créée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO createAdhesion(@Valid AdhesionDTO adhesionDTO) {
|
||||
log.info(
|
||||
"Création d'une nouvelle adhésion pour le membre: {} et l'organisation: {}",
|
||||
adhesionDTO.getMembreId(),
|
||||
adhesionDTO.getOrganisationId());
|
||||
|
||||
// Validation du membre
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(adhesionDTO.getMembreId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Membre non trouvé avec l'ID: " + adhesionDTO.getMembreId()));
|
||||
|
||||
// Validation de l'organisation
|
||||
Organisation organisation =
|
||||
organisationRepository
|
||||
.findByIdOptional(adhesionDTO.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + adhesionDTO.getOrganisationId()));
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Adhesion adhesion = convertToEntity(adhesionDTO);
|
||||
adhesion.setMembre(membre);
|
||||
adhesion.setOrganisation(organisation);
|
||||
|
||||
// Génération automatique du numéro de référence si absent
|
||||
if (adhesion.getNumeroReference() == null || adhesion.getNumeroReference().isEmpty()) {
|
||||
adhesion.setNumeroReference(genererNumeroReference());
|
||||
}
|
||||
|
||||
// Initialisation par défaut
|
||||
if (adhesion.getDateDemande() == null) {
|
||||
adhesion.setDateDemande(LocalDate.now());
|
||||
}
|
||||
if (adhesion.getStatut() == null || adhesion.getStatut().isEmpty()) {
|
||||
adhesion.setStatut("EN_ATTENTE");
|
||||
}
|
||||
if (adhesion.getMontantPaye() == null) {
|
||||
adhesion.setMontantPaye(BigDecimal.ZERO);
|
||||
}
|
||||
if (adhesion.getCodeDevise() == null || adhesion.getCodeDevise().isEmpty()) {
|
||||
adhesion.setCodeDevise("XOF");
|
||||
}
|
||||
|
||||
// Persistance
|
||||
adhesionRepository.persist(adhesion);
|
||||
|
||||
log.info(
|
||||
"Adhésion créée avec succès - ID: {}, Référence: {}",
|
||||
adhesion.getId(),
|
||||
adhesion.getNumeroReference());
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une adhésion existante
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param adhesionDTO nouvelles données
|
||||
* @return DTO de l'adhésion mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO updateAdhesion(@NotNull UUID id, @Valid AdhesionDTO adhesionDTO) {
|
||||
log.info("Mise à jour de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesionExistante =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
updateAdhesionFields(adhesionExistante, adhesionDTO);
|
||||
|
||||
log.info("Adhésion mise à jour avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesionExistante);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime (désactive) une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteAdhesion(@NotNull UUID id) {
|
||||
log.info("Suppression de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
// Vérification si l'adhésion peut être supprimée
|
||||
if ("PAYEE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException("Impossible de supprimer une adhésion déjà payée");
|
||||
}
|
||||
|
||||
adhesion.setStatut("ANNULEE");
|
||||
|
||||
log.info("Adhésion supprimée avec succès - ID: {}", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approuve une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param approuvePar nom de l'utilisateur qui approuve
|
||||
* @return DTO de l'adhésion approuvée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO approuverAdhesion(@NotNull UUID id, String approuvePar) {
|
||||
log.info("Approbation de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"EN_ATTENTE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException(
|
||||
"Seules les adhésions en attente peuvent être approuvées");
|
||||
}
|
||||
|
||||
adhesion.setStatut("APPROUVEE");
|
||||
adhesion.setDateApprobation(LocalDate.now());
|
||||
adhesion.setApprouvePar(approuvePar);
|
||||
adhesion.setDateValidation(LocalDate.now());
|
||||
|
||||
log.info("Adhésion approuvée avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejette une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param motifRejet motif du rejet
|
||||
* @return DTO de l'adhésion rejetée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO rejeterAdhesion(@NotNull UUID id, String motifRejet) {
|
||||
log.info("Rejet de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"EN_ATTENTE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées");
|
||||
}
|
||||
|
||||
adhesion.setStatut("REJETEE");
|
||||
adhesion.setMotifRejet(motifRejet);
|
||||
|
||||
log.info("Adhésion rejetée avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un paiement pour une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param montantPaye montant payé
|
||||
* @param methodePaiement méthode de paiement
|
||||
* @param referencePaiement référence du paiement
|
||||
* @return DTO de l'adhésion mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO enregistrerPaiement(
|
||||
@NotNull UUID id,
|
||||
BigDecimal montantPaye,
|
||||
String methodePaiement,
|
||||
String referencePaiement) {
|
||||
log.info("Enregistrement du paiement pour l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"APPROUVEE".equals(adhesion.getStatut()) && !"EN_PAIEMENT".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException(
|
||||
"Seules les adhésions approuvées peuvent recevoir un paiement");
|
||||
}
|
||||
|
||||
BigDecimal nouveauMontantPaye =
|
||||
adhesion.getMontantPaye() != null
|
||||
? adhesion.getMontantPaye().add(montantPaye)
|
||||
: montantPaye;
|
||||
|
||||
adhesion.setMontantPaye(nouveauMontantPaye);
|
||||
adhesion.setMethodePaiement(methodePaiement);
|
||||
adhesion.setReferencePaiement(referencePaiement);
|
||||
adhesion.setDatePaiement(java.time.LocalDateTime.now());
|
||||
|
||||
// Mise à jour du statut si payée intégralement
|
||||
if (adhesion.isPayeeIntegralement()) {
|
||||
adhesion.setStatut("PAYEE");
|
||||
} else {
|
||||
adhesion.setStatut("EN_PAIEMENT");
|
||||
}
|
||||
|
||||
log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions d'un membre
|
||||
*
|
||||
* @param membreId identifiant UUID du membre
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions du membre
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByMembre(@NotNull UUID membreId, int page, int size) {
|
||||
log.debug("Récupération des adhésions du membre: {}", membreId);
|
||||
|
||||
if (!membreRepository.findByIdOptional(membreId).isPresent()) {
|
||||
throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
|
||||
}
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByMembreId(membreId).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions d'une organisation
|
||||
*
|
||||
* @param organisationId identifiant UUID de l'organisation
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions de l'organisation
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByOrganisation(
|
||||
@NotNull UUID organisationId, int page, int size) {
|
||||
log.debug("Récupération des adhésions de l'organisation: {}", organisationId);
|
||||
|
||||
if (!organisationRepository.findByIdOptional(organisationId).isPresent()) {
|
||||
throw new NotFoundException("Organisation non trouvée avec l'ID: " + organisationId);
|
||||
}
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByOrganisationId(organisationId).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions par statut
|
||||
*
|
||||
* @param statut statut recherché
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions avec le statut spécifié
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByStatut(@NotNull String statut, int page, int size) {
|
||||
log.debug("Récupération des adhésions avec statut: {}", statut);
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByStatut(statut).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions en attente
|
||||
*
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions en attente
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsEnAttente(int page, int size) {
|
||||
log.debug("Récupération des adhésions en attente");
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findEnAttente().stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des adhésions
|
||||
*
|
||||
* @return map contenant les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesAdhesions() {
|
||||
log.debug("Calcul des statistiques des adhésions");
|
||||
|
||||
long totalAdhesions = adhesionRepository.count();
|
||||
long adhesionsApprouvees = adhesionRepository.findByStatut("APPROUVEE").size();
|
||||
long adhesionsEnAttente = adhesionRepository.findEnAttente().size();
|
||||
long adhesionsPayees = adhesionRepository.findByStatut("PAYEE").size();
|
||||
|
||||
return Map.of(
|
||||
"totalAdhesions", totalAdhesions,
|
||||
"adhesionsApprouvees", adhesionsApprouvees,
|
||||
"adhesionsEnAttente", adhesionsEnAttente,
|
||||
"adhesionsPayees", adhesionsPayees,
|
||||
"tauxApprobation",
|
||||
totalAdhesions > 0 ? (adhesionsApprouvees * 100.0 / totalAdhesions) : 0.0,
|
||||
"tauxPaiement",
|
||||
adhesionsApprouvees > 0
|
||||
? (adhesionsPayees * 100.0 / adhesionsApprouvees)
|
||||
: 0.0);
|
||||
}
|
||||
|
||||
/** Génère un numéro de référence unique pour une adhésion */
|
||||
private String genererNumeroReference() {
|
||||
return "ADH-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||
}
|
||||
|
||||
/** Convertit une entité Adhesion en DTO */
|
||||
private AdhesionDTO convertToDTO(Adhesion adhesion) {
|
||||
if (adhesion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AdhesionDTO dto = new AdhesionDTO();
|
||||
|
||||
dto.setId(adhesion.getId());
|
||||
dto.setNumeroReference(adhesion.getNumeroReference());
|
||||
|
||||
// Conversion du membre associé
|
||||
if (adhesion.getMembre() != null) {
|
||||
dto.setMembreId(adhesion.getMembre().getId());
|
||||
dto.setNomMembre(adhesion.getMembre().getNomComplet());
|
||||
dto.setNumeroMembre(adhesion.getMembre().getNumeroMembre());
|
||||
dto.setEmailMembre(adhesion.getMembre().getEmail());
|
||||
}
|
||||
|
||||
// Conversion de l'organisation
|
||||
if (adhesion.getOrganisation() != null) {
|
||||
dto.setOrganisationId(adhesion.getOrganisation().getId());
|
||||
dto.setNomOrganisation(adhesion.getOrganisation().getNom());
|
||||
}
|
||||
|
||||
// Propriétés de l'adhésion
|
||||
dto.setDateDemande(adhesion.getDateDemande());
|
||||
dto.setFraisAdhesion(adhesion.getFraisAdhesion());
|
||||
dto.setMontantPaye(adhesion.getMontantPaye());
|
||||
dto.setCodeDevise(adhesion.getCodeDevise());
|
||||
dto.setStatut(adhesion.getStatut());
|
||||
dto.setDateApprobation(adhesion.getDateApprobation());
|
||||
dto.setDatePaiement(adhesion.getDatePaiement());
|
||||
dto.setMethodePaiement(adhesion.getMethodePaiement());
|
||||
dto.setReferencePaiement(adhesion.getReferencePaiement());
|
||||
dto.setMotifRejet(adhesion.getMotifRejet());
|
||||
dto.setObservations(adhesion.getObservations());
|
||||
dto.setApprouvePar(adhesion.getApprouvePar());
|
||||
dto.setDateValidation(adhesion.getDateValidation());
|
||||
|
||||
// Métadonnées de BaseEntity
|
||||
dto.setDateCreation(adhesion.getDateCreation());
|
||||
dto.setDateModification(adhesion.getDateModification());
|
||||
dto.setCreePar(adhesion.getCreePar());
|
||||
dto.setModifiePar(adhesion.getModifiePar());
|
||||
dto.setActif(adhesion.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité Adhesion */
|
||||
private Adhesion convertToEntity(AdhesionDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Adhesion adhesion = new Adhesion();
|
||||
|
||||
adhesion.setNumeroReference(dto.getNumeroReference());
|
||||
adhesion.setDateDemande(dto.getDateDemande());
|
||||
adhesion.setFraisAdhesion(dto.getFraisAdhesion());
|
||||
adhesion.setMontantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO);
|
||||
adhesion.setCodeDevise(dto.getCodeDevise());
|
||||
adhesion.setStatut(dto.getStatut());
|
||||
adhesion.setDateApprobation(dto.getDateApprobation());
|
||||
adhesion.setDatePaiement(dto.getDatePaiement());
|
||||
adhesion.setMethodePaiement(dto.getMethodePaiement());
|
||||
adhesion.setReferencePaiement(dto.getReferencePaiement());
|
||||
adhesion.setMotifRejet(dto.getMotifRejet());
|
||||
adhesion.setObservations(dto.getObservations());
|
||||
adhesion.setApprouvePar(dto.getApprouvePar());
|
||||
adhesion.setDateValidation(dto.getDateValidation());
|
||||
|
||||
return adhesion;
|
||||
}
|
||||
|
||||
/** Met à jour les champs modifiables d'une adhésion existante */
|
||||
private void updateAdhesionFields(Adhesion adhesion, AdhesionDTO dto) {
|
||||
if (dto.getFraisAdhesion() != null) {
|
||||
adhesion.setFraisAdhesion(dto.getFraisAdhesion());
|
||||
}
|
||||
if (dto.getMontantPaye() != null) {
|
||||
adhesion.setMontantPaye(dto.getMontantPaye());
|
||||
}
|
||||
if (dto.getStatut() != null) {
|
||||
adhesion.setStatut(dto.getStatut());
|
||||
}
|
||||
if (dto.getDateApprobation() != null) {
|
||||
adhesion.setDateApprobation(dto.getDateApprobation());
|
||||
}
|
||||
if (dto.getDatePaiement() != null) {
|
||||
adhesion.setDatePaiement(dto.getDatePaiement());
|
||||
}
|
||||
if (dto.getMethodePaiement() != null) {
|
||||
adhesion.setMethodePaiement(dto.getMethodePaiement());
|
||||
}
|
||||
if (dto.getReferencePaiement() != null) {
|
||||
adhesion.setReferencePaiement(dto.getReferencePaiement());
|
||||
}
|
||||
if (dto.getMotifRejet() != null) {
|
||||
adhesion.setMotifRejet(dto.getMotifRejet());
|
||||
}
|
||||
if (dto.getObservations() != null) {
|
||||
adhesion.setObservations(dto.getObservations());
|
||||
}
|
||||
if (dto.getApprouvePar() != null) {
|
||||
adhesion.setApprouvePar(dto.getApprouvePar());
|
||||
}
|
||||
if (dto.getDateValidation() != null) {
|
||||
adhesion.setDateValidation(dto.getDateValidation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO;
|
||||
import dev.lions.unionflow.server.entity.AuditLog;
|
||||
import dev.lions.unionflow.server.repository.AuditLogRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service pour la gestion des logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AuditService {
|
||||
|
||||
@Inject
|
||||
AuditLogRepository auditLogRepository;
|
||||
|
||||
/**
|
||||
* Enregistre un nouveau log d'audit
|
||||
*/
|
||||
@Transactional
|
||||
public AuditLogDTO enregistrerLog(AuditLogDTO dto) {
|
||||
log.debug("Enregistrement d'un log d'audit: {}", dto.getTypeAction());
|
||||
|
||||
AuditLog auditLog = convertToEntity(dto);
|
||||
auditLogRepository.persist(auditLog);
|
||||
|
||||
return convertToDTO(auditLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les logs avec pagination
|
||||
*/
|
||||
public Map<String, Object> listerTous(int page, int size, String sortBy, String sortOrder) {
|
||||
log.debug("Récupération des logs d'audit - page: {}, size: {}", page, size);
|
||||
|
||||
String orderBy = sortBy != null ? sortBy : "dateHeure";
|
||||
String order = "desc".equalsIgnoreCase(sortOrder) ? "DESC" : "ASC";
|
||||
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
|
||||
// Compter le total
|
||||
long total = auditLogRepository.count();
|
||||
|
||||
// Récupérer les logs avec pagination
|
||||
var query = entityManager.createQuery(
|
||||
"SELECT a FROM AuditLog a ORDER BY a." + orderBy + " " + order, AuditLog.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
|
||||
List<AuditLog> logs = query.getResultList();
|
||||
List<AuditLogDTO> dtos = logs.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Map.of(
|
||||
"data", dtos,
|
||||
"total", total,
|
||||
"page", page,
|
||||
"size", size,
|
||||
"totalPages", (int) Math.ceil((double) total / size)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche les logs avec filtres
|
||||
*/
|
||||
public Map<String, Object> rechercher(
|
||||
LocalDateTime dateDebut, LocalDateTime dateFin,
|
||||
String typeAction, String severite, String utilisateur,
|
||||
String module, String ipAddress,
|
||||
int page, int size) {
|
||||
|
||||
log.debug("Recherche de logs d'audit avec filtres");
|
||||
|
||||
// Construire la requête dynamique avec Criteria API
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
var cb = entityManager.getCriteriaBuilder();
|
||||
var query = cb.createQuery(AuditLog.class);
|
||||
var root = query.from(AuditLog.class);
|
||||
|
||||
var predicates = new ArrayList<jakarta.persistence.criteria.Predicate>();
|
||||
|
||||
if (dateDebut != null) {
|
||||
predicates.add(cb.greaterThanOrEqualTo(root.get("dateHeure"), dateDebut));
|
||||
}
|
||||
if (dateFin != null) {
|
||||
predicates.add(cb.lessThanOrEqualTo(root.get("dateHeure"), dateFin));
|
||||
}
|
||||
if (typeAction != null && !typeAction.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("typeAction"), typeAction));
|
||||
}
|
||||
if (severite != null && !severite.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("severite"), severite));
|
||||
}
|
||||
if (utilisateur != null && !utilisateur.isEmpty()) {
|
||||
predicates.add(cb.like(cb.lower(root.get("utilisateur")),
|
||||
"%" + utilisateur.toLowerCase() + "%"));
|
||||
}
|
||||
if (module != null && !module.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("module"), module));
|
||||
}
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||
predicates.add(cb.like(root.get("ipAddress"), "%" + ipAddress + "%"));
|
||||
}
|
||||
|
||||
query.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
query.orderBy(cb.desc(root.get("dateHeure")));
|
||||
|
||||
// Compter le total
|
||||
var countQuery = cb.createQuery(Long.class);
|
||||
countQuery.select(cb.count(countQuery.from(AuditLog.class)));
|
||||
countQuery.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
long total = entityManager.createQuery(countQuery).getSingleResult();
|
||||
|
||||
// Récupérer les résultats avec pagination
|
||||
var typedQuery = entityManager.createQuery(query);
|
||||
typedQuery.setFirstResult(page * size);
|
||||
typedQuery.setMaxResults(size);
|
||||
|
||||
List<AuditLog> logs = typedQuery.getResultList();
|
||||
List<AuditLogDTO> dtos = logs.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Map.of(
|
||||
"data", dtos,
|
||||
"total", total,
|
||||
"page", page,
|
||||
"size", size,
|
||||
"totalPages", (int) Math.ceil((double) total / size)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques d'audit
|
||||
*/
|
||||
public Map<String, Object> getStatistiques() {
|
||||
long total = auditLogRepository.count();
|
||||
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
|
||||
long success = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
|
||||
.setParameter("severite", "SUCCESS")
|
||||
.getSingleResult();
|
||||
|
||||
long errors = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite IN :severites", Long.class)
|
||||
.setParameter("severites", List.of("ERROR", "CRITICAL"))
|
||||
.getSingleResult();
|
||||
|
||||
long warnings = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
|
||||
.setParameter("severite", "WARNING")
|
||||
.getSingleResult();
|
||||
|
||||
return Map.of(
|
||||
"total", total,
|
||||
"success", success,
|
||||
"errors", errors,
|
||||
"warnings", warnings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une entité en DTO
|
||||
*/
|
||||
private AuditLogDTO convertToDTO(AuditLog auditLog) {
|
||||
AuditLogDTO dto = new AuditLogDTO();
|
||||
dto.setId(auditLog.getId());
|
||||
dto.setTypeAction(auditLog.getTypeAction());
|
||||
dto.setSeverite(auditLog.getSeverite());
|
||||
dto.setUtilisateur(auditLog.getUtilisateur());
|
||||
dto.setRole(auditLog.getRole());
|
||||
dto.setModule(auditLog.getModule());
|
||||
dto.setDescription(auditLog.getDescription());
|
||||
dto.setDetails(auditLog.getDetails());
|
||||
dto.setIpAddress(auditLog.getIpAddress());
|
||||
dto.setUserAgent(auditLog.getUserAgent());
|
||||
dto.setSessionId(auditLog.getSessionId());
|
||||
dto.setDateHeure(auditLog.getDateHeure());
|
||||
dto.setDonneesAvant(auditLog.getDonneesAvant());
|
||||
dto.setDonneesApres(auditLog.getDonneesApres());
|
||||
dto.setEntiteId(auditLog.getEntiteId());
|
||||
dto.setEntiteType(auditLog.getEntiteType());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un DTO en entité
|
||||
*/
|
||||
private AuditLog convertToEntity(AuditLogDTO dto) {
|
||||
AuditLog auditLog = new AuditLog();
|
||||
if (dto.getId() != null) {
|
||||
auditLog.setId(dto.getId());
|
||||
}
|
||||
auditLog.setTypeAction(dto.getTypeAction());
|
||||
auditLog.setSeverite(dto.getSeverite());
|
||||
auditLog.setUtilisateur(dto.getUtilisateur());
|
||||
auditLog.setRole(dto.getRole());
|
||||
auditLog.setModule(dto.getModule());
|
||||
auditLog.setDescription(dto.getDescription());
|
||||
auditLog.setDetails(dto.getDetails());
|
||||
auditLog.setIpAddress(dto.getIpAddress());
|
||||
auditLog.setUserAgent(dto.getUserAgent());
|
||||
auditLog.setSessionId(dto.getSessionId());
|
||||
auditLog.setDateHeure(dto.getDateHeure() != null ? dto.getDateHeure() : LocalDateTime.now());
|
||||
auditLog.setDonneesAvant(dto.getDonneesAvant());
|
||||
auditLog.setDonneesApres(dto.getDonneesApres());
|
||||
auditLog.setEntiteId(dto.getEntiteId());
|
||||
auditLog.setEntiteType(dto.getEntiteType());
|
||||
return auditLog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
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.repository.CotisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Service d'export des données en Excel et PDF
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ExportService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ExportService.class);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
|
||||
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
|
||||
|
||||
@Inject
|
||||
CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject
|
||||
CotisationService cotisationService;
|
||||
|
||||
/**
|
||||
* Exporte les cotisations en format CSV (compatible Excel)
|
||||
*/
|
||||
public byte[] exporterCotisationsCSV(List<UUID> cotisationIds) {
|
||||
LOG.infof("Export CSV de %d cotisations", cotisationIds.size());
|
||||
|
||||
StringBuilder csv = new StringBuilder();
|
||||
csv.append("Numéro Référence;Membre;Type;Montant Dû;Montant Payé;Statut;Date Échéance;Date Paiement;Méthode Paiement\n");
|
||||
|
||||
for (UUID id : cotisationIds) {
|
||||
Optional<Cotisation> cotisationOpt = cotisationRepository.findByIdOptional(id);
|
||||
if (cotisationOpt.isPresent()) {
|
||||
Cotisation c = cotisationOpt.get();
|
||||
String nomMembre = c.getMembre() != null
|
||||
? c.getMembre().getNom() + " " + c.getMembre().getPrenom()
|
||||
: "";
|
||||
csv.append(String.format("%s;%s;%s;%s;%s;%s;%s;%s;%s\n",
|
||||
c.getNumeroReference() != null ? c.getNumeroReference() : "",
|
||||
nomMembre,
|
||||
c.getTypeCotisation() != null ? c.getTypeCotisation() : "",
|
||||
c.getMontantDu() != null ? c.getMontantDu().toString() : "0",
|
||||
c.getMontantPaye() != null ? c.getMontantPaye().toString() : "0",
|
||||
c.getStatut() != null ? c.getStatut() : "",
|
||||
c.getDateEcheance() != null ? c.getDateEcheance().format(DATE_FORMATTER) : "",
|
||||
c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "",
|
||||
c.getMethodePaiement() != null ? c.getMethodePaiement() : ""
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte toutes les cotisations filtrées en CSV
|
||||
*/
|
||||
public byte[] exporterToutesCotisationsCSV(String statut, String type, UUID associationId) {
|
||||
LOG.info("Export CSV de toutes les cotisations");
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.listAll();
|
||||
|
||||
// Filtrer
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> c.getStatut() != null && c.getStatut().equals(statut))
|
||||
.toList();
|
||||
}
|
||||
if (type != null && !type.isEmpty()) {
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> c.getTypeCotisation() != null && c.getTypeCotisation().equals(type))
|
||||
.toList();
|
||||
}
|
||||
// Note: le filtrage par association n'est pas disponible car Membre n'a pas de lien direct
|
||||
// avec Association dans cette version du modèle
|
||||
|
||||
List<UUID> ids = cotisations.stream().map(Cotisation::getId).toList();
|
||||
return exporterCotisationsCSV(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un reçu de paiement en format texte (pour impression)
|
||||
*/
|
||||
public byte[] genererRecuPaiement(UUID cotisationId) {
|
||||
LOG.infof("Génération reçu pour cotisation: %s", cotisationId);
|
||||
|
||||
Optional<Cotisation> cotisationOpt = cotisationRepository.findByIdOptional(cotisationId);
|
||||
if (cotisationOpt.isEmpty()) {
|
||||
return "Cotisation non trouvée".getBytes();
|
||||
}
|
||||
|
||||
Cotisation c = cotisationOpt.get();
|
||||
|
||||
StringBuilder recu = new StringBuilder();
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n");
|
||||
recu.append(" REÇU DE PAIEMENT\n");
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n\n");
|
||||
|
||||
recu.append("Numéro de reçu : ").append(c.getNumeroReference()).append("\n");
|
||||
recu.append("Date : ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n");
|
||||
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
recu.append(" INFORMATIONS MEMBRE\n");
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
|
||||
if (c.getMembre() != null) {
|
||||
recu.append("Nom : ").append(c.getMembre().getNom()).append(" ").append(c.getMembre().getPrenom()).append("\n");
|
||||
recu.append("Numéro membre : ").append(c.getMembre().getNumeroMembre()).append("\n");
|
||||
}
|
||||
|
||||
recu.append("\n───────────────────────────────────────────────────────────────\n");
|
||||
recu.append(" DÉTAILS DU PAIEMENT\n");
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
|
||||
recu.append("Type cotisation : ").append(c.getTypeCotisation() != null ? c.getTypeCotisation() : "").append("\n");
|
||||
recu.append("Période : ").append(c.getPeriode() != null ? c.getPeriode() : "").append("\n");
|
||||
recu.append("Montant dû : ").append(formatMontant(c.getMontantDu())).append("\n");
|
||||
recu.append("Montant payé : ").append(formatMontant(c.getMontantPaye())).append("\n");
|
||||
recu.append("Mode de paiement : ").append(c.getMethodePaiement() != null ? c.getMethodePaiement() : "").append("\n");
|
||||
recu.append("Date de paiement : ").append(c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "").append("\n");
|
||||
recu.append("Statut : ").append(c.getStatut() != null ? c.getStatut() : "").append("\n");
|
||||
|
||||
recu.append("\n═══════════════════════════════════════════════════════════════\n");
|
||||
recu.append(" Ce document fait foi de paiement de cotisation\n");
|
||||
recu.append(" Merci de votre confiance !\n");
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n");
|
||||
|
||||
return recu.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère plusieurs reçus de paiement
|
||||
*/
|
||||
public byte[] genererRecusGroupes(List<UUID> cotisationIds) {
|
||||
LOG.infof("Génération de %d reçus groupés", cotisationIds.size());
|
||||
|
||||
StringBuilder allRecus = new StringBuilder();
|
||||
for (int i = 0; i < cotisationIds.size(); i++) {
|
||||
byte[] recu = genererRecuPaiement(cotisationIds.get(i));
|
||||
allRecus.append(new String(recu, java.nio.charset.StandardCharsets.UTF_8));
|
||||
if (i < cotisationIds.size() - 1) {
|
||||
allRecus.append("\n\n════════════════════════ PAGE SUIVANTE ════════════════════════\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
return allRecus.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un rapport mensuel
|
||||
*/
|
||||
public byte[] genererRapportMensuel(int annee, int mois, UUID associationId) {
|
||||
LOG.infof("Génération rapport mensuel: %d/%d", mois, annee);
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.listAll();
|
||||
|
||||
// Filtrer par mois/année et association
|
||||
LocalDate debut = LocalDate.of(annee, mois, 1);
|
||||
LocalDate fin = debut.plusMonths(1).minusDays(1);
|
||||
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> {
|
||||
if (c.getDateCreation() == null) return false;
|
||||
LocalDate dateCot = c.getDateCreation().toLocalDate();
|
||||
return !dateCot.isBefore(debut) && !dateCot.isAfter(fin);
|
||||
})
|
||||
// Note: le filtrage par association n'est pas implémenté ici
|
||||
.toList();
|
||||
|
||||
// Calculer les statistiques
|
||||
long total = cotisations.size();
|
||||
long payees = cotisations.stream().filter(c -> "PAYEE".equals(c.getStatut())).count();
|
||||
long enAttente = cotisations.stream().filter(c -> "EN_ATTENTE".equals(c.getStatut())).count();
|
||||
long enRetard = cotisations.stream().filter(c -> "EN_RETARD".equals(c.getStatut())).count();
|
||||
|
||||
BigDecimal montantTotal = cotisations.stream()
|
||||
.map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal montantCollecte = cotisations.stream()
|
||||
.filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
|
||||
.map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
double tauxRecouvrement = montantTotal.compareTo(BigDecimal.ZERO) > 0
|
||||
? montantCollecte.multiply(BigDecimal.valueOf(100)).divide(montantTotal, 2, java.math.RoundingMode.HALF_UP).doubleValue()
|
||||
: 0;
|
||||
|
||||
// Construire le rapport
|
||||
StringBuilder rapport = new StringBuilder();
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n");
|
||||
rapport.append(" RAPPORT MENSUEL DES COTISATIONS\n");
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n\n");
|
||||
|
||||
rapport.append("Période : ").append(String.format("%02d/%d", mois, annee)).append("\n");
|
||||
rapport.append("Date de génération: ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n");
|
||||
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n");
|
||||
rapport.append(" RÉSUMÉ\n");
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n\n");
|
||||
|
||||
rapport.append("Total cotisations : ").append(total).append("\n");
|
||||
rapport.append("Cotisations payées : ").append(payees).append("\n");
|
||||
rapport.append("Cotisations en attente: ").append(enAttente).append("\n");
|
||||
rapport.append("Cotisations en retard : ").append(enRetard).append("\n\n");
|
||||
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n");
|
||||
rapport.append(" FINANCIER\n");
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n\n");
|
||||
|
||||
rapport.append("Montant total attendu : ").append(formatMontant(montantTotal)).append("\n");
|
||||
rapport.append("Montant collecté : ").append(formatMontant(montantCollecte)).append("\n");
|
||||
rapport.append("Taux de recouvrement : ").append(String.format("%.1f%%", tauxRecouvrement)).append("\n\n");
|
||||
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n");
|
||||
|
||||
return rapport.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String formatMontant(BigDecimal montant) {
|
||||
if (montant == null) return "0 FCFA";
|
||||
return String.format("%,.0f FCFA", montant.doubleValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.organisation.TypeOrganisationDTO;
|
||||
import dev.lions.unionflow.server.entity.TypeOrganisationEntity;
|
||||
import dev.lions.unionflow.server.repository.TypeOrganisationRepository;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service de gestion du catalogue des types d'organisation.
|
||||
*
|
||||
* <p>Synchronise les types persistés avec l'enum {@link TypeOrganisation} pour les valeurs
|
||||
* par défaut, puis permet un CRUD entièrement dynamique (les nouveaux codes ne sont plus
|
||||
* limités aux valeurs de l'enum).
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TypeOrganisationService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TypeOrganisationService.class);
|
||||
|
||||
@Inject TypeOrganisationRepository repository;
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
// Plus d'initialisation automatique : le catalogue des types est désormais entièrement
|
||||
// géré en mode CRUD via l'UI d'administration. Aucune donnée fictive n'est injectée
|
||||
// au démarrage ; si nécessaire, utilisez des scripts de migration (Flyway) ou l'UI.
|
||||
|
||||
/** Retourne la liste de tous les types (optionnellement seulement actifs). */
|
||||
public List<TypeOrganisationDTO> listAll(boolean onlyActifs) {
|
||||
List<TypeOrganisationEntity> entities =
|
||||
onlyActifs ? repository.listActifsOrdennes() : repository.listAll();
|
||||
return entities.stream().map(this::toDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Crée un nouveau type. Le code doit être non vide et unique. */
|
||||
@Transactional
|
||||
public TypeOrganisationDTO create(TypeOrganisationDTO dto) {
|
||||
validateCode(dto.getCode());
|
||||
|
||||
// Si un type existe déjà pour ce code, on retourne simplement l'existant
|
||||
// (comportement idempotent côté API) plutôt que de remonter une 400.
|
||||
// Le CRUD complet reste possible via l'écran d'édition.
|
||||
var existingOpt = repository.findByCode(dto.getCode());
|
||||
if (existingOpt.isPresent()) {
|
||||
LOG.infof(
|
||||
"Type d'organisation déjà existant pour le code %s, retour de l'entrée existante.",
|
||||
dto.getCode());
|
||||
return toDTO(existingOpt.get());
|
||||
}
|
||||
|
||||
TypeOrganisationEntity entity = new TypeOrganisationEntity();
|
||||
// métadonnées de création
|
||||
entity.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
applyToEntity(dto, entity);
|
||||
repository.persist(entity);
|
||||
return toDTO(entity);
|
||||
}
|
||||
|
||||
/** Met à jour un type existant. L'ID est utilisé comme identifiant principal. */
|
||||
@Transactional
|
||||
public TypeOrganisationDTO update(UUID id, TypeOrganisationDTO dto) {
|
||||
TypeOrganisationEntity entity =
|
||||
repository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Type d'organisation introuvable"));
|
||||
|
||||
if (dto.getCode() != null && !dto.getCode().equalsIgnoreCase(entity.getCode())) {
|
||||
validateCode(dto.getCode());
|
||||
repository
|
||||
.findByCode(dto.getCode())
|
||||
.ifPresent(
|
||||
existing -> {
|
||||
if (!existing.getId().equals(id)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Un autre type d'organisation utilise déjà le code: " + dto.getCode());
|
||||
}
|
||||
});
|
||||
entity.setCode(dto.getCode());
|
||||
}
|
||||
|
||||
// métadonnées de modification
|
||||
entity.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
applyToEntity(dto, entity);
|
||||
repository.update(entity);
|
||||
return toDTO(entity);
|
||||
}
|
||||
|
||||
/** Désactive logiquement un type. */
|
||||
@Transactional
|
||||
public void disable(UUID id) {
|
||||
TypeOrganisationEntity entity =
|
||||
repository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Type d'organisation introuvable"));
|
||||
entity.setActif(false);
|
||||
repository.update(entity);
|
||||
}
|
||||
|
||||
private void validateCode(String code) {
|
||||
if (code == null || code.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le code du type d'organisation est obligatoire");
|
||||
}
|
||||
// Plus aucune contrainte de format technique côté backend pour éviter les 400 inutiles.
|
||||
// Le code est simplement normalisé en majuscules dans applyToEntity, ce qui suffit
|
||||
// pour garantir la cohérence métier et la clé fonctionnelle.
|
||||
}
|
||||
|
||||
private TypeOrganisationDTO toDTO(TypeOrganisationEntity entity) {
|
||||
TypeOrganisationDTO dto = new TypeOrganisationDTO();
|
||||
dto.setId(entity.getId());
|
||||
dto.setDateCreation(entity.getDateCreation());
|
||||
dto.setDateModification(entity.getDateModification());
|
||||
dto.setActif(entity.getActif());
|
||||
dto.setVersion(entity.getVersion());
|
||||
|
||||
dto.setCode(entity.getCode());
|
||||
dto.setLibelle(entity.getLibelle());
|
||||
dto.setDescription(entity.getDescription());
|
||||
dto.setOrdreAffichage(entity.getOrdreAffichage());
|
||||
return dto;
|
||||
}
|
||||
|
||||
private void applyToEntity(TypeOrganisationDTO dto, TypeOrganisationEntity entity) {
|
||||
if (dto.getCode() != null) {
|
||||
entity.setCode(dto.getCode().toUpperCase());
|
||||
}
|
||||
if (dto.getLibelle() != null) {
|
||||
entity.setLibelle(dto.getLibelle());
|
||||
}
|
||||
entity.setDescription(dto.getDescription());
|
||||
entity.setOrdreAffichage(dto.getOrdreAffichage());
|
||||
if (dto.getActif() != null) {
|
||||
entity.setActif(dto.getActif());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveBalanceDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveWebhookDTO;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutSession;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.TypeEvenement;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service pour l'intégration Wave Money
|
||||
* Gère les sessions de paiement, les webhooks et la consultation du solde
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class WaveService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WaveService.class);
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.key")
|
||||
Optional<String> waveApiKey;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.secret")
|
||||
Optional<String> waveApiSecret;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.base.url", defaultValue = "https://api.wave.com/v1")
|
||||
String waveApiBaseUrl;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.environment", defaultValue = "sandbox")
|
||||
String waveEnvironment;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.webhook.secret")
|
||||
Optional<String> waveWebhookSecret;
|
||||
|
||||
/**
|
||||
* Crée une session de paiement Wave Checkout
|
||||
*
|
||||
* @param montant Montant à payer
|
||||
* @param devise Devise (XOF par défaut)
|
||||
* @param successUrl URL de redirection en cas de succès
|
||||
* @param errorUrl URL de redirection en cas d'erreur
|
||||
* @param referenceUnionFlow Référence interne UnionFlow
|
||||
* @param description Description du paiement
|
||||
* @param organisationId ID de l'organisation
|
||||
* @param membreId ID du membre
|
||||
* @return Session de paiement créée
|
||||
*/
|
||||
@Transactional
|
||||
public WaveCheckoutSessionDTO creerSessionPaiement(
|
||||
BigDecimal montant,
|
||||
String devise,
|
||||
String successUrl,
|
||||
String errorUrl,
|
||||
String referenceUnionFlow,
|
||||
String description,
|
||||
UUID organisationId,
|
||||
UUID membreId) {
|
||||
LOG.infof(
|
||||
"Création d'une session de paiement Wave: montant=%s, devise=%s, ref=%s",
|
||||
montant, devise, referenceUnionFlow);
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave Checkout
|
||||
// Pour l'instant, simulation de la création de session
|
||||
String waveSessionId = "wave_session_" + UUID.randomUUID().toString().replace("-", "");
|
||||
String waveUrl = buildWaveCheckoutUrl(waveSessionId);
|
||||
|
||||
WaveCheckoutSessionDTO session = new WaveCheckoutSessionDTO();
|
||||
session.setId(UUID.randomUUID());
|
||||
session.setWaveSessionId(waveSessionId);
|
||||
session.setWaveUrl(waveUrl);
|
||||
session.setMontant(montant);
|
||||
session.setDevise(devise != null ? devise : "XOF");
|
||||
session.setSuccessUrl(successUrl);
|
||||
session.setErrorUrl(errorUrl);
|
||||
session.setReferenceUnionFlow(referenceUnionFlow);
|
||||
session.setDescription(description);
|
||||
session.setOrganisationId(organisationId);
|
||||
session.setMembreId(membreId);
|
||||
session.setStatut(StatutSession.PENDING);
|
||||
session.setDateCreation(LocalDateTime.now());
|
||||
session.setDateExpiration(LocalDateTime.now().plusHours(24)); // Expire dans 24h
|
||||
|
||||
LOG.infof("Session Wave créée: %s", waveSessionId);
|
||||
return session;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la session Wave: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la création de la session Wave", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie le statut d'une session de paiement
|
||||
*
|
||||
* @param waveSessionId ID de la session Wave
|
||||
* @return Statut de la session
|
||||
*/
|
||||
public WaveCheckoutSessionDTO verifierStatutSession(String waveSessionId) {
|
||||
LOG.infof("Vérification du statut de la session Wave: %s", waveSessionId);
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave pour vérifier le statut
|
||||
// Pour l'instant, simulation
|
||||
WaveCheckoutSessionDTO session = new WaveCheckoutSessionDTO();
|
||||
session.setWaveSessionId(waveSessionId);
|
||||
session.setStatut(StatutSession.PENDING);
|
||||
return session;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du statut: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la vérification du statut", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite un webhook reçu de Wave
|
||||
*
|
||||
* @param payload Payload JSON du webhook
|
||||
* @param signature Signature Wave pour vérification
|
||||
* @param headers Headers HTTP
|
||||
* @return Webhook traité
|
||||
*/
|
||||
@Transactional
|
||||
public WaveWebhookDTO traiterWebhook(String payload, String signature, Map<String, String> headers) {
|
||||
LOG.info("Traitement d'un webhook Wave");
|
||||
|
||||
try {
|
||||
// Vérifier la signature
|
||||
if (!verifierSignatureWebhook(payload, signature)) {
|
||||
LOG.warn("Signature webhook invalide");
|
||||
throw new SecurityException("Signature webhook invalide");
|
||||
}
|
||||
|
||||
// Parser le payload
|
||||
// TODO: Parser réellement le JSON du webhook
|
||||
String webhookId = "webhook_" + UUID.randomUUID().toString();
|
||||
TypeEvenement typeEvenement = TypeEvenement.CHECKOUT_COMPLETE; // À déterminer depuis le payload
|
||||
|
||||
WaveWebhookDTO webhook = new WaveWebhookDTO();
|
||||
webhook.setId(UUID.randomUUID());
|
||||
webhook.setWebhookId(webhookId);
|
||||
webhook.setTypeEvenement(typeEvenement);
|
||||
webhook.setCodeEvenement(typeEvenement.getCodeWave());
|
||||
webhook.setPayloadJson(payload);
|
||||
webhook.setSignatureWave(signature);
|
||||
webhook.setDateReception(LocalDateTime.now());
|
||||
webhook.setDateTraitement(LocalDateTime.now());
|
||||
|
||||
// Extraire les informations du payload
|
||||
// TODO: Extraire réellement les données du JSON
|
||||
// webhook.setSessionCheckoutId(...);
|
||||
// webhook.setTransactionWaveId(...);
|
||||
// webhook.setMontantTransaction(...);
|
||||
|
||||
LOG.infof("Webhook traité: %s", webhookId);
|
||||
return webhook;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du traitement du webhook: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors du traitement du webhook", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consulte le solde du wallet Wave
|
||||
*
|
||||
* @return Solde du wallet
|
||||
*/
|
||||
public WaveBalanceDTO consulterSolde() {
|
||||
LOG.info("Consultation du solde Wave");
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave Balance
|
||||
// Pour l'instant, simulation
|
||||
WaveBalanceDTO balance = new WaveBalanceDTO();
|
||||
balance.setNumeroWallet("wave_wallet_001");
|
||||
balance.setSoldeDisponible(BigDecimal.ZERO);
|
||||
balance.setSoldeEnAttente(BigDecimal.ZERO);
|
||||
balance.setSoldeTotal(BigDecimal.ZERO);
|
||||
balance.setDevise("XOF");
|
||||
balance.setStatutWallet("ACTIVE");
|
||||
balance.setDateDerniereMiseAJour(LocalDateTime.now());
|
||||
balance.setDateDerniereSynchronisation(LocalDateTime.now());
|
||||
|
||||
return balance;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la consultation du solde: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la consultation du solde", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si Wave est configuré et actif
|
||||
*
|
||||
* @return true si Wave est configuré
|
||||
*/
|
||||
public boolean estConfigure() {
|
||||
return waveApiKey.isPresent() && !waveApiKey.get().isEmpty()
|
||||
&& waveApiSecret.isPresent() && !waveApiSecret.get().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Teste la connexion à l'API Wave
|
||||
*
|
||||
* @return Résultat du test
|
||||
*/
|
||||
public Map<String, Object> testerConnexion() {
|
||||
LOG.info("Test de connexion à l'API Wave");
|
||||
|
||||
Map<String, Object> resultat = new HashMap<>();
|
||||
resultat.put("configure", estConfigure());
|
||||
resultat.put("environment", waveEnvironment);
|
||||
resultat.put("baseUrl", waveApiBaseUrl);
|
||||
resultat.put("timestamp", LocalDateTime.now().toString());
|
||||
|
||||
if (!estConfigure()) {
|
||||
resultat.put("statut", "ERREUR");
|
||||
resultat.put("message", "Wave n'est pas configuré (clés API manquantes)");
|
||||
return resultat;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Faire un appel réel à l'API Wave pour tester
|
||||
resultat.put("statut", "OK");
|
||||
resultat.put("message", "Connexion réussie (simulation)");
|
||||
return resultat;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du test de connexion: %s", e.getMessage());
|
||||
resultat.put("statut", "ERREUR");
|
||||
resultat.put("message", "Erreur: " + e.getMessage());
|
||||
return resultat;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes privées
|
||||
|
||||
private String buildWaveCheckoutUrl(String sessionId) {
|
||||
if ("sandbox".equals(waveEnvironment)) {
|
||||
return "https://checkout-sandbox.wave.com/checkout/" + sessionId;
|
||||
} else {
|
||||
return "https://checkout.wave.com/checkout/" + sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifierSignatureWebhook(String payload, String signature) {
|
||||
if (signature == null || signature.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waveWebhookSecret.isPresent() || waveWebhookSecret.get().isEmpty()) {
|
||||
LOG.warn("Secret webhook non configuré, impossible de vérifier la signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implémenter la vérification réelle de la signature HMAC SHA256
|
||||
// La signature Wave est généralement au format: sha256=<hash>
|
||||
return true; // Pour l'instant, on accepte toutes les signatures
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,3 +92,10 @@ quarkus.log.category."io.quarkus".level=INFO
|
||||
# Configuration Jandex pour résoudre les warnings de réflexion
|
||||
quarkus.index-dependency.unionflow-server-api.group-id=dev.lions.unionflow
|
||||
quarkus.index-dependency.unionflow-server-api.artifact-id=unionflow-server-api
|
||||
|
||||
# Configuration Wave Money
|
||||
wave.api.key=${WAVE_API_KEY:}
|
||||
wave.api.secret=${WAVE_API_SECRET:}
|
||||
wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1}
|
||||
wave.environment=${WAVE_ENVIRONMENT:sandbox}
|
||||
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}
|
||||
|
||||
Reference in New Issue
Block a user