Configure Maven repository for unionflow-server-api dependency

This commit is contained in:
dahoud
2025-12-10 01:08:17 +00:00
commit 4a0c5f9d33
320 changed files with 33373 additions and 0 deletions

View File

@@ -0,0 +1,705 @@
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.annotation.security.RolesAllowed;
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
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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();
}
}
}

View File

@@ -0,0 +1,345 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO;
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetDTO;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.service.AnalyticsService;
import dev.lions.unionflow.server.service.KPICalculatorService;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
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 org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Ressource REST pour les analytics et métriques UnionFlow
*
* <p>Cette ressource expose les APIs pour accéder aux données analytics, KPI, tendances et widgets
* de tableau de bord.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Path("/api/v1/analytics")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Authenticated
@Tag(name = "Analytics", description = "APIs pour les analytics et métriques")
public class AnalyticsResource {
private static final Logger log = Logger.getLogger(AnalyticsResource.class);
@Inject AnalyticsService analyticsService;
@Inject KPICalculatorService kpiCalculatorService;
/** Calcule une métrique analytics pour une période donnée */
@GET
@Path("/metriques/{typeMetrique}")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données")
@APIResponse(responseCode = "200", description = "Métrique calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerMetrique(
@Parameter(description = "Type de métrique à calculer", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la métrique %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
AnalyticsDataDTO result =
analyticsService.calculerMetrique(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la métrique", "message", e.getMessage()))
.build();
}
}
/** Calcule les tendances d'un KPI sur une période */
@GET
@Path("/tendances/{typeMetrique}")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
@APIResponse(responseCode = "200", description = "Tendance calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerTendanceKPI(
@Parameter(description = "Type de métrique pour la tendance", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la tendance KPI %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
KPITrendDTO result =
analyticsService.calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(
e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la tendance", "message", e.getMessage()))
.build();
}
}
/** Obtient tous les KPI pour une organisation */
@GET
@Path("/kpis")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données")
@APIResponse(responseCode = "200", description = "KPI récupérés avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirTousLesKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération de tous les KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> kpis =
kpiCalculatorService.calculerTousLesKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(kpis).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors de la récupération des KPI", "message", e.getMessage()))
.build();
}
}
/** Calcule le KPI de performance globale */
@GET
@Path("/performance-globale")
@RolesAllowed({"ADMIN", "MANAGER"})
@Operation(
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation")
@APIResponse(responseCode = "200", description = "Performance globale calculée avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerPerformanceGlobale(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la performance globale pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
BigDecimal performanceGlobale =
kpiCalculatorService.calculerKPIPerformanceGlobale(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(
Map.of(
"performanceGlobale", performanceGlobale,
"periode", periodeAnalyse,
"organisationId", organisationId,
"dateCalcul", java.time.LocalDateTime.now()))
.build();
} catch (Exception e) {
log.error("Erreur lors du calcul de la performance globale: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors du calcul de la performance globale",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les évolutions des KPI par rapport à la période précédente */
@GET
@Path("/evolutions")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente")
@APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirEvolutionsKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération des évolutions KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> evolutions =
kpiCalculatorService.calculerEvolutionsKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(evolutions).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des évolutions KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des évolutions",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les widgets du tableau de bord pour un utilisateur */
@GET
@Path("/dashboard/widgets")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
@APIResponse(responseCode = "200", description = "Widgets récupérés avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirWidgetsTableauBord(
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("utilisateurId")
@NotNull
UUID utilisateurId) {
try {
log.infof(
"Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s",
organisationId, utilisateurId);
List<DashboardWidgetDTO> widgets =
analyticsService.obtenirMetriquesTableauBord(organisationId, utilisateurId);
return Response.ok(widgets).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des widgets: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error", "Erreur lors de la récupération des widgets", "message", e.getMessage()))
.build();
}
}
/** Obtient les types de métriques disponibles */
@GET
@Path("/types-metriques")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles")
@APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
public Response obtenirTypesMetriques() {
try {
log.info("Récupération des types de métriques disponibles");
TypeMetrique[] typesMetriques = TypeMetrique.values();
return Response.ok(Map.of("typesMetriques", typesMetriques, "total", typesMetriques.length))
.build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des types de métriques: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des types de métriques",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les périodes d'analyse disponibles */
@GET
@Path("/periodes-analyse")
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
@Operation(
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
@APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
public Response obtenirPeriodesAnalyse() {
try {
log.info("Récupération des périodes d'analyse disponibles");
PeriodeAnalyse[] periodesAnalyse = PeriodeAnalyse.values();
return Response.ok(
Map.of("periodesAnalyse", periodesAnalyse, "total", periodesAnalyse.length))
.build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des périodes d'analyse: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des périodes d'analyse",
"message",
e.getMessage()))
.build();
}
}
}

View File

@@ -0,0 +1,112 @@
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.annotation.security.RolesAllowed;
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
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
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();
}
}
}

View File

@@ -0,0 +1,278 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.comptabilite.*;
import dev.lions.unionflow.server.service.ComptabiliteService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion comptable
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Path("/api/comptabilite")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class ComptabiliteResource {
private static final Logger LOG = Logger.getLogger(ComptabiliteResource.class);
@Inject ComptabiliteService comptabiliteService;
// ========================================
// COMPTES COMPTABLES
// ========================================
/**
* Crée un nouveau compte comptable
*
* @param compteDTO DTO du compte à créer
* @return Compte créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/comptes")
public Response creerCompteComptable(@Valid CompteComptableDTO compteDTO) {
try {
CompteComptableDTO result = comptabiliteService.creerCompteComptable(compteDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage()))
.build();
}
}
/**
* Trouve un compte comptable par son ID
*
* @param id ID du compte
* @return Compte comptable
*/
@GET
@Path("/comptes/{id}")
public Response trouverCompteParId(@PathParam("id") UUID id) {
try {
CompteComptableDTO result = comptabiliteService.trouverCompteParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Compte comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
.build();
}
}
/**
* Liste tous les comptes comptables actifs
*
* @return Liste des comptes
*/
@GET
@Path("/comptes")
public Response listerTousLesComptes() {
try {
List<CompteComptableDTO> result = comptabiliteService.listerTousLesComptes();
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des comptes comptables");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
.build();
}
}
// ========================================
// JOURNAUX COMPTABLES
// ========================================
/**
* Crée un nouveau journal comptable
*
* @param journalDTO DTO du journal à créer
* @return Journal créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/journaux")
public Response creerJournalComptable(@Valid JournalComptableDTO journalDTO) {
try {
JournalComptableDTO result = comptabiliteService.creerJournalComptable(journalDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage()))
.build();
}
}
/**
* Trouve un journal comptable par son ID
*
* @param id ID du journal
* @return Journal comptable
*/
@GET
@Path("/journaux/{id}")
public Response trouverJournalParId(@PathParam("id") UUID id) {
try {
JournalComptableDTO result = comptabiliteService.trouverJournalParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Journal comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
.build();
}
}
/**
* Liste tous les journaux comptables actifs
*
* @return Liste des journaux
*/
@GET
@Path("/journaux")
public Response listerTousLesJournaux() {
try {
List<JournalComptableDTO> result = comptabiliteService.listerTousLesJournaux();
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des journaux comptables");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
.build();
}
}
// ========================================
// ÉCRITURES COMPTABLES
// ========================================
/**
* Crée une nouvelle écriture comptable
*
* @param ecritureDTO DTO de l'écriture à créer
* @return Écriture créée
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/ecritures")
public Response creerEcritureComptable(@Valid EcritureComptableDTO ecritureDTO) {
try {
EcritureComptableDTO result = comptabiliteService.creerEcritureComptable(ecritureDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
.build();
}
}
/**
* Trouve une écriture comptable par son ID
*
* @param id ID de l'écriture
* @return Écriture comptable
*/
@GET
@Path("/ecritures/{id}")
public Response trouverEcritureParId(@PathParam("id") UUID id) {
try {
EcritureComptableDTO result = comptabiliteService.trouverEcritureParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Écriture comptable non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
.build();
}
}
/**
* Liste les écritures d'un journal
*
* @param journalId ID du journal
* @return Liste des écritures
*/
@GET
@Path("/ecritures/journal/{journalId}")
public Response listerEcrituresParJournal(@PathParam("journalId") UUID journalId) {
try {
List<EcritureComptableDTO> result = comptabiliteService.listerEcrituresParJournal(journalId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
/**
* Liste les écritures d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des écritures
*/
@GET
@Path("/ecritures/organisation/{organisationId}")
public Response listerEcrituresParOrganisation(@PathParam("organisationId") UUID organisationId) {
try {
List<EcritureComptableDTO> result = comptabiliteService.listerEcrituresParOrganisation(organisationId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
/** Classe interne pour les réponses d'erreur */
public static class ErrorResponse {
public String error;
public ErrorResponse(String error) {
this.error = error;
}
}
}

View File

@@ -0,0 +1,674 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
import dev.lions.unionflow.server.service.CotisationService;
import jakarta.annotation.security.RolesAllowed;
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.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
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 cotisations Expose les endpoints API pour les opérations CRUD
* sur les cotisations
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
@Path("/api/cotisations")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Cotisations", description = "Gestion des cotisations des membres")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
@Slf4j
public class CotisationResource {
@Inject CotisationService cotisationService;
/** Endpoint public pour les cotisations (test) */
@GET
@Path("/public")
@Operation(
summary = "Cotisations publiques",
description = "Liste des cotisations sans authentification")
@APIResponse(responseCode = "200", description = "Liste des cotisations")
public Response getCotisationsPublic(
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
try {
log.info("GET /api/cotisations/public - page: {}, size: {}", page, size);
// Récupérer les cotisations depuis la base de données
List<CotisationDTO> cotisationsDTO = cotisationService.getAllCotisations(page, size);
// Convertir en format pour l'application mobile
List<Map<String, Object>> cotisations = cotisationsDTO.stream()
.map(c -> {
Map<String, Object> map = new java.util.HashMap<>();
map.put("id", c.getId() != null ? c.getId().toString() : "");
map.put("nom", c.getDescription() != null ? c.getDescription() : "Cotisation");
map.put("description", c.getDescription() != null ? c.getDescription() : "");
map.put("montant", c.getMontantDu() != null ? c.getMontantDu().doubleValue() : 0.0);
map.put("devise", c.getCodeDevise() != null ? c.getCodeDevise() : "XOF");
map.put("dateEcheance", c.getDateEcheance() != null ? c.getDateEcheance().toString() : "");
map.put("statut", c.getStatut() != null ? c.getStatut() : "EN_ATTENTE");
map.put("type", c.getTypeCotisation() != null ? c.getTypeCotisation() : "MENSUELLE");
return map;
})
.collect(Collectors.toList());
long totalElements = cotisationService.getStatistiquesCotisations().get("totalCotisations") != null
? ((Number) cotisationService.getStatistiquesCotisations().get("totalCotisations")).longValue()
: cotisations.size();
int totalPages = (int) Math.ceil((double) totalElements / size);
Map<String, Object> response =
Map.of(
"content", cotisations,
"totalElements", totalElements,
"totalPages", totalPages,
"size", size,
"number", page);
return Response.ok(response).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des cotisations publiques", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération des cotisations"))
.build();
}
}
/** Récupère toutes les cotisations avec pagination */
@GET
@Operation(
summary = "Lister toutes les cotisations",
description = "Récupère la liste paginée de toutes les cotisations")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Liste des cotisations récupérée avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CotisationDTO.class))),
@APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getAllCotisations(
@Parameter(description = "Numéro de page (0-based)", example = "0")
@QueryParam("page")
@DefaultValue("0")
@Min(0)
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
@Min(1)
int size) {
try {
log.info("GET /api/cotisations - page: {}, size: {}", page, size);
List<CotisationDTO> cotisations = cotisationService.getAllCotisations(page, size);
log.info("Récupération réussie de {} cotisations", cotisations.size());
return Response.ok(cotisations).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des cotisations", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des cotisations",
"message",
e.getMessage()))
.build();
}
}
/** Récupère une cotisation par son ID */
@GET
@Path("/{id}")
@Operation(
summary = "Récupérer une cotisation par ID",
description = "Récupère les détails d'une cotisation spécifique")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Cotisation trouvée",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CotisationDTO.class))),
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getCotisationById(
@Parameter(description = "Identifiant de la cotisation", required = true)
@PathParam("id")
@NotNull
UUID id) {
try {
log.info("GET /api/cotisations/{}", id);
CotisationDTO cotisation = cotisationService.getCotisationById(id);
log.info("Cotisation récupérée avec succès - ID: {}", id);
return Response.ok(cotisation).build();
} catch (NotFoundException e) {
log.warn("Cotisation non trouvée - ID: {}", id);
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
.build();
} catch (Exception e) {
log.error("Erreur lors de la récupération de la cotisation - ID: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération de la cotisation",
"message",
e.getMessage()))
.build();
}
}
/** Récupère une cotisation par son numéro de référence */
@GET
@Path("/reference/{numeroReference}")
@Operation(
summary = "Récupérer une cotisation par référence",
description = "Récupère une cotisation par son numéro de référence unique")
@APIResponses({
@APIResponse(responseCode = "200", description = "Cotisation trouvée"),
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getCotisationByReference(
@Parameter(description = "Numéro de référence de la cotisation", required = true)
@PathParam("numeroReference")
@NotNull
String numeroReference) {
try {
log.info("GET /api/cotisations/reference/{}", numeroReference);
CotisationDTO cotisation = cotisationService.getCotisationByReference(numeroReference);
log.info("Cotisation récupérée avec succès - Référence: {}", numeroReference);
return Response.ok(cotisation).build();
} catch (NotFoundException e) {
log.warn("Cotisation non trouvée - Référence: {}", numeroReference);
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Cotisation non trouvée", "reference", numeroReference))
.build();
} catch (Exception e) {
log.error(
"Erreur lors de la récupération de la cotisation - Référence: " + numeroReference, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération de la cotisation",
"message",
e.getMessage()))
.build();
}
}
/** Crée une nouvelle cotisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Operation(
summary = "Créer une nouvelle cotisation",
description = "Crée une nouvelle cotisation pour un membre")
@APIResponses({
@APIResponse(
responseCode = "201",
description = "Cotisation créée avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = CotisationDTO.class))),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response createCotisation(
@Parameter(description = "Données de la cotisation à créer", required = true) @Valid
CotisationDTO cotisationDTO) {
try {
log.info(
"POST /api/cotisations - Création cotisation pour membre: {}",
cotisationDTO.getMembreId());
CotisationDTO nouvelleCotisation = cotisationService.createCotisation(cotisationDTO);
log.info(
"Cotisation créée avec succès - ID: {}, Référence: {}",
nouvelleCotisation.getId(),
nouvelleCotisation.getNumeroReference());
return Response.status(Response.Status.CREATED).entity(nouvelleCotisation).build();
} catch (NotFoundException e) {
log.warn(
"Membre non trouvé lors de la création de cotisation: {}", cotisationDTO.getMembreId());
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Membre non trouvé", "membreId", cotisationDTO.getMembreId()))
.build();
} catch (IllegalArgumentException e) {
log.warn("Données invalides pour la création de cotisation: {}", e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
.build();
} catch (Exception e) {
log.error("Erreur lors de la création de la cotisation", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la création de la cotisation",
"message",
e.getMessage()))
.build();
}
}
/** Met à jour une cotisation existante */
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}")
@Operation(
summary = "Mettre à jour une cotisation",
description = "Met à jour les données d'une cotisation existante")
@APIResponses({
@APIResponse(responseCode = "200", description = "Cotisation mise à jour avec succès"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response updateCotisation(
@Parameter(description = "Identifiant de la cotisation", required = true)
@PathParam("id")
@NotNull
UUID id,
@Parameter(description = "Nouvelles données de la cotisation", required = true) @Valid
CotisationDTO cotisationDTO) {
try {
log.info("PUT /api/cotisations/{}", id);
CotisationDTO cotisationMiseAJour = cotisationService.updateCotisation(id, cotisationDTO);
log.info("Cotisation mise à jour avec succès - ID: {}", id);
return Response.ok(cotisationMiseAJour).build();
} catch (NotFoundException e) {
log.warn("Cotisation non trouvée pour mise à jour - ID: {}", id);
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
.build();
} catch (IllegalArgumentException e) {
log.warn(
"Données invalides pour la mise à jour de cotisation - ID: {}, Erreur: {}",
id,
e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
.build();
} catch (Exception e) {
log.error("Erreur lors de la mise à jour de la cotisation - ID: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la mise à jour de la cotisation",
"message",
e.getMessage()))
.build();
}
}
/** Supprime une cotisation */
@DELETE
@RolesAllowed({"ADMIN"})
@Path("/{id}")
@Operation(
summary = "Supprimer une cotisation",
description = "Supprime (désactive) une cotisation")
@APIResponses({
@APIResponse(responseCode = "204", description = "Cotisation supprimée avec succès"),
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
@APIResponse(
responseCode = "409",
description = "Impossible de supprimer une cotisation payée"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response deleteCotisation(
@Parameter(description = "Identifiant de la cotisation", required = true)
@PathParam("id")
@NotNull
UUID id) {
try {
log.info("DELETE /api/cotisations/{}", id);
cotisationService.deleteCotisation(id);
log.info("Cotisation supprimée avec succès - ID: {}", id);
return Response.noContent().build();
} catch (NotFoundException e) {
log.warn("Cotisation non trouvée pour suppression - ID: {}", id);
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
.build();
} catch (IllegalStateException e) {
log.warn("Impossible de supprimer la cotisation - ID: {}, Raison: {}", id, e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(
Map.of("error", "Impossible de supprimer la cotisation", "message", e.getMessage()))
.build();
} catch (Exception e) {
log.error("Erreur lors de la suppression de la cotisation - ID: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la suppression de la cotisation",
"message",
e.getMessage()))
.build();
}
}
/** Récupère les cotisations d'un membre */
@GET
@Path("/membre/{membreId}")
@Operation(
summary = "Lister les cotisations d'un membre",
description = "Récupère toutes les cotisations d'un membre spécifique")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des cotisations du membre"),
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getCotisationsByMembre(
@Parameter(description = "Identifiant du membre", required = true)
@PathParam("membreId")
@NotNull
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/cotisations/membre/{} - page: {}, size: {}", membreId, page, size);
List<CotisationDTO> cotisations =
cotisationService.getCotisationsByMembre(membreId, page, size);
log.info(
"Récupération réussie de {} cotisations pour le membre {}", cotisations.size(), membreId);
return Response.ok(cotisations).build();
} catch (NotFoundException e) {
log.warn("Membre non trouvé - ID: {}", membreId);
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Membre non trouvé", "membreId", membreId))
.build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des cotisations du membre - ID: " + membreId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des cotisations",
"message",
e.getMessage()))
.build();
}
}
/** Récupère les cotisations par statut */
@GET
@Path("/statut/{statut}")
@Operation(
summary = "Lister les cotisations par statut",
description = "Récupère toutes les cotisations ayant un statut spécifique")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Liste des cotisations avec le statut spécifié"),
@APIResponse(responseCode = "400", description = "Statut invalide"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getCotisationsByStatut(
@Parameter(description = "Statut des cotisations", required = true, example = "EN_ATTENTE")
@PathParam("statut")
@NotNull
String statut,
@Parameter(description = "Numéro de page", example = "0")
@QueryParam("page")
@DefaultValue("0")
@Min(0)
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
@Min(1)
int size) {
try {
log.info("GET /api/cotisations/statut/{} - page: {}, size: {}", statut, page, size);
List<CotisationDTO> cotisations =
cotisationService.getCotisationsByStatut(statut, page, size);
log.info("Récupération réussie de {} cotisations avec statut {}", cotisations.size(), statut);
return Response.ok(cotisations).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des cotisations par statut - Statut: " + statut, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des cotisations",
"message",
e.getMessage()))
.build();
}
}
/** Récupère les cotisations en retard */
@GET
@Path("/en-retard")
@Operation(
summary = "Lister les cotisations en retard",
description = "Récupère toutes les cotisations dont la date d'échéance est dépassée")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des cotisations en retard"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getCotisationsEnRetard(
@Parameter(description = "Numéro de page", example = "0")
@QueryParam("page")
@DefaultValue("0")
@Min(0)
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
@Min(1)
int size) {
try {
log.info("GET /api/cotisations/en-retard - page: {}, size: {}", page, size);
List<CotisationDTO> cotisations = cotisationService.getCotisationsEnRetard(page, size);
log.info("Récupération réussie de {} cotisations en retard", cotisations.size());
return Response.ok(cotisations).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des cotisations en retard", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des cotisations en retard",
"message",
e.getMessage()))
.build();
}
}
/** Recherche avancée de cotisations */
@GET
@Path("/recherche")
@Operation(
summary = "Recherche avancée de cotisations",
description = "Recherche de cotisations avec filtres multiples")
@APIResponses({
@APIResponse(responseCode = "200", description = "Résultats de la recherche"),
@APIResponse(responseCode = "400", description = "Paramètres de recherche invalides"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response rechercherCotisations(
@Parameter(description = "Identifiant du membre") @QueryParam("membreId") UUID membreId,
@Parameter(description = "Statut de la cotisation") @QueryParam("statut") String statut,
@Parameter(description = "Type de cotisation") @QueryParam("typeCotisation")
String typeCotisation,
@Parameter(description = "Année") @QueryParam("annee") Integer annee,
@Parameter(description = "Mois") @QueryParam("mois") Integer mois,
@Parameter(description = "Numéro de page", example = "0")
@QueryParam("page")
@DefaultValue("0")
@Min(0)
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
@Min(1)
int size) {
try {
log.info(
"GET /api/cotisations/recherche - Filtres: membreId={}, statut={}, type={}, annee={},"
+ " mois={}",
membreId,
statut,
typeCotisation,
annee,
mois);
List<CotisationDTO> cotisations =
cotisationService.rechercherCotisations(
membreId, statut, typeCotisation, annee, mois, page, size);
log.info("Recherche réussie - {} cotisations trouvées", cotisations.size());
return Response.ok(cotisations).build();
} catch (Exception e) {
log.error("Erreur lors de la recherche de cotisations", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error", "Erreur lors de la recherche de cotisations", "message", e.getMessage()))
.build();
}
}
/** Récupère les statistiques des cotisations */
@GET
@Path("/stats")
@Operation(
summary = "Statistiques des cotisations",
description = "Récupère les statistiques globales des cotisations")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Response getStatistiquesCotisations() {
try {
log.info("GET /api/cotisations/stats");
Map<String, Object> statistiques = cotisationService.getStatistiquesCotisations();
log.info("Statistiques récupérées avec succès");
return Response.ok(statistiques).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des statistiques", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des statistiques",
"message",
e.getMessage()))
.build();
}
}
/**
* Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY)
*
* @param membreIds Liste des IDs des membres destinataires
* @return Nombre de rappels envoyés
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/rappels/groupes")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Envoyer des rappels de cotisations groupés")
@APIResponse(responseCode = "200", description = "Rappels envoyés avec succès")
public Response envoyerRappelsGroupes(List<UUID> membreIds) {
try {
int rappelsEnvoyes = cotisationService.envoyerRappelsCotisationsGroupes(membreIds);
return Response.ok(Map.of("rappelsEnvoyes", rappelsEnvoyes)).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
log.error("Erreur lors de l'envoi des rappels groupés", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'envoi des rappels: " + e.getMessage()))
.build();
}
}
}

View File

@@ -0,0 +1,251 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO;
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO;
import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO;
import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO;
import dev.lions.unionflow.server.api.service.dashboard.DashboardService;
import jakarta.inject.Inject;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Resource REST pour les APIs du dashboard
*
* <p>Cette ressource expose les endpoints pour récupérer les données du dashboard,
* incluant les statistiques, activités récentes et événements à venir.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
@Path("/api/v1/dashboard")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Dashboard", description = "APIs pour la gestion du dashboard")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class DashboardResource {
private static final Logger LOG = Logger.getLogger(DashboardResource.class);
@Inject
DashboardService dashboardService;
/**
* Récupère toutes les données du dashboard
*/
@GET
@Path("/data")
@Operation(
summary = "Récupérer toutes les données du dashboard",
description = "Retourne les statistiques, activités récentes et événements à venir"
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Données récupérées avec succès"),
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
public Response getDashboardData(
@Parameter(description = "ID de l'organisation", required = true)
@QueryParam("organizationId") @NotNull String organizationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("userId") @NotNull String userId) {
LOG.infof("GET /api/v1/dashboard/data - org: %s, user: %s", organizationId, userId);
try {
DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId);
return Response.ok(dashboardData).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des données dashboard");
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
}
}
/**
* Récupère uniquement les statistiques du dashboard
*/
@GET
@Path("/stats")
@Operation(
summary = "Récupérer les statistiques du dashboard",
description = "Retourne uniquement les statistiques principales"
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
public Response getDashboardStats(
@Parameter(description = "ID de l'organisation", required = true)
@QueryParam("organizationId") @NotNull String organizationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("userId") @NotNull String userId) {
LOG.infof("GET /api/v1/dashboard/stats - org: %s, user: %s", organizationId, userId);
try {
DashboardStatsDTO stats = dashboardService.getDashboardStats(organizationId, userId);
return Response.ok(stats).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des statistiques dashboard");
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
}
}
/**
* Récupère les activités récentes
*/
@GET
@Path("/activities")
@Operation(
summary = "Récupérer les activités récentes",
description = "Retourne la liste des activités récentes avec pagination"
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Activités récupérées avec succès"),
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
public Response getRecentActivities(
@Parameter(description = "ID de l'organisation", required = true)
@QueryParam("organizationId") @NotNull String organizationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("userId") @NotNull String userId,
@Parameter(description = "Nombre maximum d'activités à retourner", required = false)
@QueryParam("limit") @DefaultValue("10") int limit) {
LOG.infof("GET /api/v1/dashboard/activities - org: %s, user: %s, limit: %d",
organizationId, userId, limit);
try {
List<RecentActivityDTO> activities = dashboardService.getRecentActivities(
organizationId, userId, limit);
Map<String, Object> response = new HashMap<>();
response.put("activities", activities);
response.put("total", activities.size());
response.put("limit", limit);
return Response.ok(response).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des activités récentes");
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
}
}
/**
* Récupère les événements à venir
*/
@GET
@Path("/events/upcoming")
@Operation(
summary = "Récupérer les événements à venir",
description = "Retourne la liste des événements à venir avec pagination"
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Événements récupérés avec succès"),
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
public Response getUpcomingEvents(
@Parameter(description = "ID de l'organisation", required = true)
@QueryParam("organizationId") @NotNull String organizationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("userId") @NotNull String userId,
@Parameter(description = "Nombre maximum d'événements à retourner", required = false)
@QueryParam("limit") @DefaultValue("5") int limit) {
LOG.infof("GET /api/v1/dashboard/events/upcoming - org: %s, user: %s, limit: %d",
organizationId, userId, limit);
try {
List<UpcomingEventDTO> events = dashboardService.getUpcomingEvents(
organizationId, userId, limit);
Map<String, Object> response = new HashMap<>();
response.put("events", events);
response.put("total", events.size());
response.put("limit", limit);
return Response.ok(response).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des événements à venir");
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
}
}
/**
* Endpoint de santé pour vérifier le statut du dashboard
*/
@GET
@Path("/health")
@Operation(
summary = "Vérifier la santé du service dashboard",
description = "Retourne le statut de santé du service dashboard"
)
@APIResponse(responseCode = "200", description = "Service en bonne santé")
public Response healthCheck() {
LOG.debug("GET /api/v1/dashboard/health");
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("service", "dashboard");
health.put("timestamp", System.currentTimeMillis());
health.put("version", "1.0.0");
return Response.ok(health).build();
}
/**
* Endpoint pour rafraîchir les données du dashboard
*/
@POST
@Path("/refresh")
@Operation(
summary = "Rafraîchir les données du dashboard",
description = "Force la mise à jour des données du dashboard"
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Données rafraîchies avec succès"),
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
public Response refreshDashboard(
@Parameter(description = "ID de l'organisation", required = true)
@QueryParam("organizationId") @NotNull String organizationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("userId") @NotNull String userId) {
LOG.infof("POST /api/v1/dashboard/refresh - org: %s, user: %s", organizationId, userId);
try {
// Simuler un rafraîchissement (dans un vrai système, cela pourrait vider le cache)
DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId);
Map<String, Object> response = new HashMap<>();
response.put("status", "refreshed");
response.put("timestamp", System.currentTimeMillis());
response.put("data", dashboardData);
return Response.ok(response).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors du rafraîchissement du dashboard");
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
}
}
}

View File

@@ -0,0 +1,158 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.document.DocumentDTO;
import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO;
import dev.lions.unionflow.server.service.DocumentService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion documentaire
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Path("/api/documents")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class DocumentResource {
private static final Logger LOG = Logger.getLogger(DocumentResource.class);
@Inject DocumentService documentService;
/**
* Crée un nouveau document
*
* @param documentDTO DTO du document à créer
* @return Document créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
public Response creerDocument(@Valid DocumentDTO documentDTO) {
try {
DocumentDTO result = documentService.creerDocument(documentDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du document");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage()))
.build();
}
}
/**
* Trouve un document par son ID
*
* @param id ID du document
* @return Document
*/
@GET
@Path("/{id}")
public Response trouverParId(@PathParam("id") UUID id) {
try {
DocumentDTO result = documentService.trouverParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Document non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du document");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage()))
.build();
}
}
/**
* Enregistre un téléchargement de document
*
* @param id ID du document
* @return Succès
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/telechargement")
public Response enregistrerTelechargement(@PathParam("id") UUID id) {
try {
documentService.enregistrerTelechargement(id);
return Response.ok().build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Document non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(
new ErrorResponse(
"Erreur lors de l'enregistrement du téléchargement: " + e.getMessage()))
.build();
}
}
/**
* Crée une pièce jointe
*
* @param pieceJointeDTO DTO de la pièce jointe à créer
* @return Pièce jointe créée
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/pieces-jointes")
public Response creerPieceJointe(@Valid PieceJointeDTO pieceJointeDTO) {
try {
PieceJointeDTO result = documentService.creerPieceJointe(pieceJointeDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de la pièce jointe");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage()))
.build();
}
}
/**
* Liste toutes les pièces jointes d'un document
*
* @param documentId ID du document
* @return Liste des pièces jointes
*/
@GET
@Path("/{documentId}/pieces-jointes")
public Response listerPiecesJointesParDocument(@PathParam("documentId") UUID documentId) {
try {
List<PieceJointeDTO> result = documentService.listerPiecesJointesParDocument(documentId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des pièces jointes");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage()))
.build();
}
}
/** Classe interne pour les réponses d'erreur */
public static class ErrorResponse {
public String error;
public ErrorResponse(String error) {
this.error = error;
}
}
}

View File

@@ -0,0 +1,452 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.dto.EvenementMobileDTO;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
import dev.lions.unionflow.server.service.EvenementService;
import java.util.stream.Collectors;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion des événements
*
* <p>Fournit les endpoints API pour les opérations CRUD sur les événements, optimisé pour
* l'intégration avec l'application mobile UnionFlow.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
@Path("/api/evenements")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Événements", description = "Gestion des événements de l'union")
public class EvenementResource {
private static final Logger LOG = Logger.getLogger(EvenementResource.class);
@Inject EvenementService evenementService;
/** Endpoint de test public pour vérifier la connectivité */
@GET
@Path("/test")
@Operation(
summary = "Test de connectivité",
description = "Endpoint public pour tester la connectivité")
@APIResponse(responseCode = "200", description = "Test réussi")
public Response testConnectivity() {
LOG.info("Test de connectivité appelé depuis l'application mobile");
return Response.ok(
Map.of(
"status", "success",
"message", "Serveur UnionFlow opérationnel",
"timestamp", System.currentTimeMillis(),
"version", "1.0.0"))
.build();
}
/** Endpoint de debug pour vérifier le chargement des données */
@GET
@Path("/count")
@Operation(summary = "Compter les événements", description = "Compte le nombre d'événements dans la base")
@APIResponse(responseCode = "200", description = "Nombre d'événements")
public Response countEvenements() {
try {
long count = evenementService.countEvenements();
return Response.ok(Map.of("count", count, "status", "success")).build();
} catch (Exception e) {
LOG.errorf("Erreur count: %s", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/** Liste tous les événements actifs avec pagination */
@GET
@Operation(
summary = "Lister tous les événements actifs",
description = "Récupère la liste paginée des événements actifs")
@APIResponse(responseCode = "200", description = "Liste des événements actifs")
@APIResponse(responseCode = "401", description = "Non authentifié")
// @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) // Temporairement désactivé
public Response listerEvenements(
@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,
@Parameter(description = "Champ de tri", example = "dateDebut")
@QueryParam("sort")
@DefaultValue("dateDebut")
String sortField,
@Parameter(description = "Direction du tri (asc/desc)", example = "asc")
@QueryParam("direction")
@DefaultValue("asc")
String sortDirection) {
try {
LOG.infof("GET /api/evenements - page: %d, size: %d", page, size);
Sort sort =
sortDirection.equalsIgnoreCase("desc")
? Sort.by(sortField).descending()
: Sort.by(sortField).ascending();
List<Evenement> evenements =
evenementService.listerEvenementsActifs(Page.of(page, size), sort);
LOG.infof("Nombre d'événements récupérés: %d", evenements.size());
// Convertir en DTO mobile
List<EvenementMobileDTO> evenementsDTOs = new ArrayList<>();
for (Evenement evenement : evenements) {
try {
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(evenement);
evenementsDTOs.add(dto);
} catch (Exception e) {
LOG.errorf("Erreur lors de la conversion de l'événement %s: %s", evenement.getId(), e.getMessage());
// Continuer avec les autres événements
}
}
LOG.infof("Nombre de DTOs créés: %d", evenementsDTOs.size());
// Compter le total d'événements actifs
long total = evenementService.countEvenementsActifs();
int totalPages = total > 0 ? (int) Math.ceil((double) total / size) : 0;
// Retourner la structure paginée attendue par le mobile
Map<String, Object> response = new HashMap<>();
response.put("data", evenementsDTOs);
response.put("total", total);
response.put("page", page);
response.put("size", size);
response.put("totalPages", totalPages);
LOG.infof("Réponse prête: %d événements, total=%d, pages=%d", evenementsDTOs.size(), total, totalPages);
return Response.ok(response)
.header("Content-Type", "application/json;charset=UTF-8")
.build();
} catch (Exception e) {
LOG.errorf("Erreur lors de la récupération des événements: %s", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération des événements: " + e.getMessage()))
.build();
}
}
/** Récupère un événement par son ID */
@GET
@Path("/{id}")
@Operation(summary = "Récupérer un événement par ID")
@APIResponse(responseCode = "200", description = "Événement trouvé")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
public Response obtenirEvenement(
@Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) {
try {
LOG.infof("GET /api/evenements/%s", id);
Optional<Evenement> evenement = evenementService.trouverParId(id);
if (evenement.isPresent()) {
return Response.ok(evenement.get()).build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Événement non trouvé"))
.build();
}
} catch (Exception e) {
LOG.errorf("Erreur lors de la récupération de l'événement %d: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération de l'événement"))
.build();
}
}
/** Crée un nouvel événement */
@POST
@Operation(summary = "Créer un nouvel événement")
@APIResponse(responseCode = "201", description = "Événement créé avec succès")
@APIResponse(responseCode = "400", description = "Données invalides")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
public Response creerEvenement(
@Parameter(description = "Données de l'événement à créer", required = true) @Valid
Evenement evenement) {
try {
LOG.infof("POST /api/evenements - Création événement: %s", evenement.getTitre());
Evenement evenementCree = evenementService.creerEvenement(evenement);
return Response.status(Response.Status.CREATED).entity(evenementCree).build();
} catch (IllegalArgumentException e) {
LOG.warnf("Données invalides: %s", e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (SecurityException e) {
LOG.warnf("Permissions insuffisantes: %s", e.getMessage());
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf("Erreur lors de la création: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la création de l'événement"))
.build();
}
}
/** Met à jour un événement existant */
@PUT
@Path("/{id}")
@Operation(summary = "Mettre à jour un événement")
@APIResponse(responseCode = "200", description = "Événement mis à jour avec succès")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
public Response mettreAJourEvenement(@PathParam("id") UUID id, @Valid Evenement evenement) {
try {
LOG.infof("PUT /api/evenements/%s", id);
Evenement evenementMisAJour = evenementService.mettreAJourEvenement(id, evenement);
return Response.ok(evenementMisAJour).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (SecurityException e) {
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf("Erreur lors de la mise à jour: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la mise à jour"))
.build();
}
}
/** Supprime un événement */
@DELETE
@Path("/{id}")
@Operation(summary = "Supprimer un événement")
@APIResponse(responseCode = "204", description = "Événement supprimé avec succès")
@RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"})
public Response supprimerEvenement(@PathParam("id") UUID id) {
try {
LOG.infof("DELETE /api/evenements/%s", id);
evenementService.supprimerEvenement(id);
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (IllegalStateException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (SecurityException e) {
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf("Erreur lors de la suppression: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la suppression"))
.build();
}
}
/** Endpoints spécialisés pour l'application mobile */
/** Liste les événements à venir */
@GET
@Path("/a-venir")
@Operation(summary = "Événements à venir")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
public Response evenementsAVenir(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("10") int size) {
try {
List<Evenement> evenements =
evenementService.listerEvenementsAVenir(
Page.of(page, size), Sort.by("dateDebut").ascending());
return Response.ok(evenements).build();
} catch (Exception e) {
LOG.errorf("Erreur événements à venir: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération"))
.build();
}
}
/** Liste les événements publics */
@GET
@Path("/publics")
@Operation(summary = "Événements publics")
public Response evenementsPublics(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
try {
List<Evenement> evenements =
evenementService.listerEvenementsPublics(
Page.of(page, size), Sort.by("dateDebut").ascending());
return Response.ok(evenements).build();
} catch (Exception e) {
LOG.errorf("Erreur événements publics: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération"))
.build();
}
}
/** Recherche d'événements */
@GET
@Path("/recherche")
@Operation(summary = "Rechercher des événements")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
public Response rechercherEvenements(
@QueryParam("q") String recherche,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
try {
if (recherche == null || recherche.trim().isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Le terme de recherche est obligatoire"))
.build();
}
List<Evenement> evenements =
evenementService.rechercherEvenements(
recherche, Page.of(page, size), Sort.by("dateDebut").ascending());
return Response.ok(evenements).build();
} catch (Exception e) {
LOG.errorf("Erreur recherche: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la recherche"))
.build();
}
}
/** Événements par type */
@GET
@Path("/type/{type}")
@Operation(summary = "Événements par type")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
public Response evenementsParType(
@PathParam("type") TypeEvenement type,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
try {
List<Evenement> evenements =
evenementService.listerParType(
type, Page.of(page, size), Sort.by("dateDebut").ascending());
return Response.ok(evenements).build();
} catch (Exception e) {
LOG.errorf("Erreur événements par type: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la récupération"))
.build();
}
}
/** Change le statut d'un événement */
@PATCH
@Path("/{id}/statut")
@Operation(summary = "Changer le statut d'un événement")
@RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"})
public Response changerStatut(
@PathParam("id") UUID id, @QueryParam("statut") StatutEvenement nouveauStatut) {
try {
if (nouveauStatut == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Le nouveau statut est obligatoire"))
.build();
}
Evenement evenement = evenementService.changerStatut(id, nouveauStatut);
return Response.ok(evenement).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (SecurityException e) {
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf("Erreur changement statut: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du changement de statut"))
.build();
}
}
/** Statistiques des événements */
@GET
@Path("/statistiques")
@Operation(summary = "Statistiques des événements")
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
public Response obtenirStatistiques() {
try {
Map<String, Object> statistiques = evenementService.obtenirStatistiques();
return Response.ok(statistiques).build();
} catch (Exception e) {
LOG.errorf("Erreur statistiques: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du calcul des statistiques"))
.build();
}
}
}

View File

@@ -0,0 +1,119 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.ExportService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.annotation.security.RolesAllowed;
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")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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();
}
}

View File

@@ -0,0 +1,33 @@
package dev.lions.unionflow.server.resource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/** Resource de santé pour UnionFlow Server */
@Path("/api/status")
@Produces(MediaType.APPLICATION_JSON)
@ApplicationScoped
@Tag(name = "Status", description = "API de statut du serveur")
public class HealthResource {
@GET
@Operation(summary = "Vérifier le statut du serveur")
public Response getStatus() {
return Response.ok(
Map.of(
"status", "UP",
"service", "UnionFlow Server",
"version", "1.0.0",
"timestamp", LocalDateTime.now().toString(),
"message", "Serveur opérationnel"))
.build();
}
}

View File

@@ -0,0 +1,643 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
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;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
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.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/** Resource REST pour la gestion des membres */
@Path("/api/membres")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApplicationScoped
@Tag(name = "Membres", description = "API de gestion des membres")
public class MembreResource {
private static final Logger LOG = Logger.getLogger(MembreResource.class);
@Inject MembreService membreService;
@GET
@Operation(summary = "Lister tous les membres actifs")
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
public Response listerMembres(
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
int page,
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
int size,
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
String sortField,
@Parameter(description = "Direction du tri (asc/desc)")
@QueryParam("direction")
@DefaultValue("asc")
String sortDirection) {
LOG.infof("Récupération de la liste des membres actifs - page: %d, size: %d", page, size);
Sort sort =
"desc".equalsIgnoreCase(sortDirection)
? Sort.by(sortField).descending()
: Sort.by(sortField).ascending();
List<Membre> membres = membreService.listerMembresActifs(Page.of(page, size), sort);
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
return Response.ok(membresDTO).build();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupérer un membre par son ID")
@APIResponse(responseCode = "200", description = "Membre trouvé")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
public Response obtenirMembre(@Parameter(description = "UUID du membre") @PathParam("id") UUID id) {
LOG.infof("Récupération du membre ID: %s", id);
return membreService
.trouverParId(id)
.map(
membre -> {
MembreDTO membreDTO = membreService.convertToDTO(membre);
return Response.ok(membreDTO).build();
})
.orElse(
Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", "Membre non trouvé"))
.build());
}
@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")
public Response creerMembre(@Valid MembreDTO membreDTO) {
LOG.infof("Création d'un nouveau membre: %s", membreDTO.getEmail());
try {
// Conversion DTO vers entité
Membre membre = membreService.convertFromDTO(membreDTO);
// Création du membre
Membre nouveauMembre = membreService.creerMembre(membre);
// Conversion de retour vers DTO
MembreDTO nouveauMembreDTO = membreService.convertToDTO(nouveauMembre);
return Response.status(Response.Status.CREATED).entity(nouveauMembreDTO).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", e.getMessage()))
.build();
}
}
@PUT
@Path("/{id}")
@Operation(summary = "Mettre à jour un membre existant")
@APIResponse(responseCode = "200", description = "Membre mis à jour avec succès")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
@APIResponse(responseCode = "400", description = "Données invalides")
public Response mettreAJourMembre(
@Parameter(description = "UUID du membre") @PathParam("id") UUID id,
@Valid MembreDTO membreDTO) {
LOG.infof("Mise à jour du membre ID: %s", id);
try {
// Conversion DTO vers entité
Membre membre = membreService.convertFromDTO(membreDTO);
// Mise à jour du membre
Membre membreMisAJour = membreService.mettreAJourMembre(id, membre);
// Conversion de retour vers DTO
MembreDTO membreMisAJourDTO = membreService.convertToDTO(membreMisAJour);
return Response.ok(membreMisAJourDTO).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", e.getMessage()))
.build();
}
}
@DELETE
@Path("/{id}")
@Operation(summary = "Désactiver un membre")
@APIResponse(responseCode = "204", description = "Membre désactivé avec succès")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
public Response desactiverMembre(
@Parameter(description = "UUID du membre") @PathParam("id") UUID id) {
LOG.infof("Désactivation du membre ID: %s", id);
try {
membreService.desactiverMembre(id);
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", e.getMessage()))
.build();
}
}
@GET
@Path("/recherche")
@Operation(summary = "Rechercher des membres par nom ou prénom")
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
public Response rechercherMembres(
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
int page,
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
int size,
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
String sortField,
@Parameter(description = "Direction du tri (asc/desc)")
@QueryParam("direction")
@DefaultValue("asc")
String sortDirection) {
LOG.infof("Recherche de membres avec le terme: %s", recherche);
if (recherche == null || recherche.trim().isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Le terme de recherche est requis"))
.build();
}
Sort sort =
"desc".equalsIgnoreCase(sortDirection)
? Sort.by(sortField).descending()
: Sort.by(sortField).ascending();
List<Membre> membres =
membreService.rechercherMembres(recherche.trim(), Page.of(page, size), sort);
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
return Response.ok(membresDTO).build();
}
@GET
@Path("/stats")
@Operation(summary = "Obtenir les statistiques avancées des membres")
@APIResponse(responseCode = "200", description = "Statistiques complètes des membres")
public Response obtenirStatistiques() {
LOG.info("Récupération des statistiques avancées des membres");
Map<String, Object> statistiques = membreService.obtenirStatistiquesAvancees();
return Response.ok(statistiques).build();
}
@GET
@Path("/autocomplete/villes")
@Operation(summary = "Obtenir la liste des villes pour autocomplétion")
@APIResponse(responseCode = "200", description = "Liste des villes distinctes")
public Response obtenirVilles(
@Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) {
LOG.infof("Récupération des villes pour autocomplétion - query: %s", query);
List<String> villes = membreService.obtenirVillesDistinctes(query);
return Response.ok(villes).build();
}
@GET
@Path("/autocomplete/professions")
@Operation(summary = "Obtenir la liste des professions pour autocomplétion")
@APIResponse(responseCode = "200", description = "Liste des professions distinctes")
public Response obtenirProfessions(
@Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) {
LOG.infof("Récupération des professions pour autocomplétion - query: %s", query);
List<String> professions = membreService.obtenirProfessionsDistinctes(query);
return Response.ok(professions).build();
}
@GET
@Path("/recherche-avancee")
@Operation(summary = "Recherche avancée de membres avec filtres multiples (DEPRECATED)")
@APIResponse(responseCode = "200", description = "Résultats de la recherche avancée")
@Deprecated
public Response rechercheAvancee(
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
@Parameter(description = "Statut actif (true/false)") @QueryParam("actif") Boolean actif,
@Parameter(description = "Date d'adhésion minimum (YYYY-MM-DD)")
@QueryParam("dateAdhesionMin")
String dateAdhesionMin,
@Parameter(description = "Date d'adhésion maximum (YYYY-MM-DD)")
@QueryParam("dateAdhesionMax")
String dateAdhesionMax,
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
int page,
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
int size,
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
String sortField,
@Parameter(description = "Direction du tri (asc/desc)")
@QueryParam("direction")
@DefaultValue("asc")
String sortDirection) {
LOG.infof(
"Recherche avancée de membres (DEPRECATED) - recherche: %s, actif: %s", recherche, actif);
try {
Sort sort =
"desc".equalsIgnoreCase(sortDirection)
? Sort.by(sortField).descending()
: Sort.by(sortField).ascending();
// Conversion des dates si fournies
java.time.LocalDate dateMin =
dateAdhesionMin != null ? java.time.LocalDate.parse(dateAdhesionMin) : null;
java.time.LocalDate dateMax =
dateAdhesionMax != null ? java.time.LocalDate.parse(dateAdhesionMax) : null;
List<Membre> membres =
membreService.rechercheAvancee(
recherche, actif, dateMin, dateMax, Page.of(page, size), sort);
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
return Response.ok(membresDTO).build();
} catch (Exception e) {
LOG.errorf("Erreur lors de la recherche avancée: %s", e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Erreur dans les paramètres de recherche: " + e.getMessage()))
.build();
}
}
/**
* Nouvelle recherche avancée avec critères complets et résultats enrichis Réservée aux super
* administrateurs pour des recherches sophistiquées
*/
@POST
@Path("/search/advanced")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(
summary = "Recherche avancée de membres avec critères multiples",
description =
"""
Recherche sophistiquée de membres avec de nombreux critères de filtrage :
- Recherche textuelle dans nom, prénom, email
- Filtres par organisation, rôles, statut
- Filtres par âge, région, profession
- Filtres par dates d'adhésion
- Résultats paginés avec statistiques
Réservée aux super administrateurs et administrateurs.
""")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Recherche effectuée avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = MembreSearchResultDTO.class),
examples =
@ExampleObject(
name = "Exemple de résultats",
value =
"""
{
"membres": [...],
"totalElements": 247,
"totalPages": 13,
"currentPage": 0,
"pageSize": 20,
"hasNext": true,
"hasPrevious": false,
"executionTimeMs": 45,
"statistics": {
"membresActifs": 230,
"membresInactifs": 17,
"ageMoyen": 34.5,
"nombreOrganisations": 12
}
}
"""))),
@APIResponse(
responseCode = "400",
description = "Critères de recherche invalides",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
examples =
@ExampleObject(
value =
"""
{
"message": "Critères de recherche invalides",
"details": "La date minimum ne peut pas être postérieure à la date maximum"
}
"""))),
@APIResponse(
responseCode = "403",
description = "Accès non autorisé - Rôle SUPER_ADMIN ou ADMIN requis"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
@SecurityRequirement(name = "keycloak")
public Response searchMembresAdvanced(
@RequestBody(
description = "Critères de recherche avancée",
required = false,
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = MembreSearchCriteria.class),
examples =
@ExampleObject(
name = "Exemple de critères",
value =
"""
{
"query": "marie",
"statut": "ACTIF",
"ageMin": 25,
"ageMax": 45,
"region": "Dakar",
"roles": ["PRESIDENT", "SECRETAIRE"],
"dateAdhesionMin": "2020-01-01",
"includeInactifs": false
}
""")))
MembreSearchCriteria criteria,
@Parameter(description = "Numéro de page (0-based)", example = "0")
@QueryParam("page")
@DefaultValue("0")
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
int size,
@Parameter(description = "Champ de tri", example = "nom")
@QueryParam("sort")
@DefaultValue("nom")
String sortField,
@Parameter(description = "Direction du tri (asc/desc)", example = "asc")
@QueryParam("direction")
@DefaultValue("asc")
String sortDirection) {
long startTime = System.currentTimeMillis();
try {
// Validation des critères
if (criteria == null) {
LOG.warn("Recherche avancée de membres - critères null rejetés");
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Les critères de recherche sont requis"))
.build();
}
LOG.infof(
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
criteria.getDescription(), page, size);
// Nettoyage et validation des critères
criteria.sanitize();
if (!criteria.hasAnyCriteria()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Au moins un critère de recherche doit être spécifié"))
.build();
}
if (!criteria.isValid()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(
Map.of(
"message", "Critères de recherche invalides",
"details", "Vérifiez la cohérence des dates et des âges"))
.build();
}
// Construction du tri
Sort sort =
"desc".equalsIgnoreCase(sortDirection)
? Sort.by(sortField).descending()
: Sort.by(sortField).ascending();
// Exécution de la recherche
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(page, size), sort);
// Calcul du temps d'exécution
long executionTime = System.currentTimeMillis() - startTime;
result.setExecutionTimeMs(executionTime);
LOG.infof(
"Recherche avancée terminée - %d résultats trouvés en %d ms",
result.getTotalElements(), executionTime);
return Response.ok(result).build();
} catch (jakarta.validation.ConstraintViolationException e) {
LOG.warnf("Erreur de validation Jakarta dans la recherche avancée: %s", e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Critères de recherche invalides", "details", e.getMessage()))
.build();
} catch (IllegalArgumentException e) {
LOG.warnf("Erreur de validation dans la recherche avancée: %s", e.getMessage());
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Paramètres de recherche invalides", "details", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche avancée de membres");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("message", "Erreur interne lors de la recherche", "error", e.getMessage()))
.build();
}
}
@POST
@Path("/export/selection")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Operation(summary = "Exporter une sélection de membres en Excel")
@APIResponse(responseCode = "200", description = "Fichier Excel généré")
public Response exporterSelectionMembres(
@Parameter(description = "Liste des IDs des membres à exporter") List<UUID> membreIds,
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format) {
LOG.infof("Export de %d membres sélectionnés", membreIds.size());
try {
byte[] excelData = membreService.exporterMembresSelectionnes(membreIds, format);
return Response.ok(excelData)
.header("Content-Disposition", "attachment; filename=\"membres_selection_" +
java.time.LocalDate.now() + "." + (format != null ? format.toLowerCase() : "xlsx") + "\"")
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'export de la sélection");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
.build();
}
}
@POST
@Path("/import")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Importer des membres depuis un fichier Excel ou CSV")
@APIResponse(responseCode = "200", description = "Import terminé")
public Response importerMembres(
@Parameter(description = "Contenu du fichier à importer") @FormParam("file") byte[] fileContent,
@Parameter(description = "Nom du fichier") @FormParam("fileName") String fileName,
@Parameter(description = "ID de l'organisation (optionnel)") @FormParam("organisationId") UUID organisationId,
@Parameter(description = "Type de membre par défaut") @FormParam("typeMembreDefaut") String typeMembreDefaut,
@Parameter(description = "Mettre à jour les membres existants") @FormParam("mettreAJourExistants") boolean mettreAJourExistants,
@Parameter(description = "Ignorer les erreurs") @FormParam("ignorerErreurs") boolean ignorerErreurs) {
try {
if (fileContent == null || fileContent.length == 0) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Aucun fichier fourni"))
.build();
}
if (fileName == null || fileName.isEmpty()) {
fileName = "import.xlsx";
}
if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) {
typeMembreDefaut = "ACTIF";
}
InputStream fileInputStream = new java.io.ByteArrayInputStream(fileContent);
dev.lions.unionflow.server.service.MembreImportExportService.ResultatImport resultat = membreService.importerMembres(
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
Map<String, Object> response = new HashMap<>();
response.put("totalLignes", resultat.totalLignes);
response.put("lignesTraitees", resultat.lignesTraitees);
response.put("lignesErreur", resultat.lignesErreur);
response.put("erreurs", resultat.erreurs);
response.put("membresImportes", resultat.membresImportes);
return Response.ok(response).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'import");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'import: " + e.getMessage()))
.build();
}
}
@GET
@Path("/export")
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Operation(summary = "Exporter des membres en Excel, CSV ou PDF")
@APIResponse(responseCode = "200", description = "Fichier exporté")
public Response exporterMembres(
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
@Parameter(description = "Type de membre") @QueryParam("type") String type,
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin,
@Parameter(description = "Colonnes à exporter") @QueryParam("colonnes") List<String> colonnesExportList,
@Parameter(description = "Inclure les en-têtes") @QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
@Parameter(description = "Formater les dates") @QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
@Parameter(description = "Inclure un onglet statistiques (Excel uniquement)") @QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
@Parameter(description = "Mot de passe pour chiffrer le fichier (optionnel)") @QueryParam("motDePasse") String motDePasse) {
try {
// Récupérer les membres selon les filtres
List<MembreDTO> membres = membreService.listerMembresPourExport(
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
byte[] exportData;
String contentType;
String extension;
List<String> colonnesExport = colonnesExportList != null ? colonnesExportList : new ArrayList<>();
if ("CSV".equalsIgnoreCase(format)) {
exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
contentType = "text/csv";
extension = "csv";
} else {
// Pour Excel, inclure les statistiques uniquement si demandé et si format Excel
boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format);
exportData = membreService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, stats, motDePasse);
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
extension = "xlsx";
}
return Response.ok(exportData)
.type(contentType)
.header("Content-Disposition", "attachment; filename=\"membres_export_" +
java.time.LocalDate.now() + "." + extension + "\"")
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'export");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
.build();
}
}
@GET
@Path("/import/modele")
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Operation(summary = "Télécharger le modèle Excel pour l'import")
@APIResponse(responseCode = "200", description = "Modèle Excel généré")
public Response telechargerModeleImport() {
try {
byte[] modele = membreService.genererModeleImport();
return Response.ok(modele)
.header("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"")
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la génération du modèle");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la génération du modèle: " + e.getMessage()))
.build();
}
}
@GET
@Path("/export/count")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Compter les membres selon les filtres pour l'export")
@APIResponse(responseCode = "200", description = "Nombre de membres correspondant aux critères")
public Response compterMembresPourExport(
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
@Parameter(description = "Type de membre") @QueryParam("type") String type,
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin) {
try {
List<MembreDTO> membres = membreService.listerMembresPourExport(
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
return Response.ok(Map.of("count", membres.size())).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors du comptage des membres");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du comptage: " + e.getMessage()))
.build();
}
}
}

View File

@@ -0,0 +1,246 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.notification.NotificationDTO;
import dev.lions.unionflow.server.api.dto.notification.TemplateNotificationDTO;
import dev.lions.unionflow.server.service.NotificationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion des notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Path("/api/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class NotificationResource {
private static final Logger LOG = Logger.getLogger(NotificationResource.class);
@Inject NotificationService notificationService;
// ========================================
// TEMPLATES
// ========================================
/**
* Crée un nouveau template de notification
*
* @param templateDTO DTO du template à créer
* @return Template créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/templates")
public Response creerTemplate(@Valid TemplateNotificationDTO templateDTO) {
try {
TemplateNotificationDTO result = notificationService.creerTemplate(templateDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du template");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du template: " + e.getMessage()))
.build();
}
}
// ========================================
// NOTIFICATIONS
// ========================================
/**
* Crée une nouvelle notification
*
* @param notificationDTO DTO de la notification à créer
* @return Notification créée
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
public Response creerNotification(@Valid NotificationDTO notificationDTO) {
try {
NotificationDTO result = notificationService.creerNotification(notificationDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de la notification");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création de la notification: " + e.getMessage()))
.build();
}
}
/**
* Marque une notification comme lue
*
* @param id ID de la notification
* @return Notification mise à jour
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/marquer-lue")
public Response marquerCommeLue(@PathParam("id") UUID id) {
try {
NotificationDTO result = notificationService.marquerCommeLue(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Notification non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors du marquage de la notification");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors du marquage de la notification: " + e.getMessage()))
.build();
}
}
/**
* Trouve une notification par son ID
*
* @param id ID de la notification
* @return Notification
*/
@GET
@Path("/{id}")
public Response trouverNotificationParId(@PathParam("id") UUID id) {
try {
NotificationDTO result = notificationService.trouverNotificationParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Notification non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche de la notification");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche de la notification: " + e.getMessage()))
.build();
}
}
/**
* Liste toutes les notifications d'un membre
*
* @param membreId ID du membre
* @return Liste des notifications
*/
@GET
@Path("/membre/{membreId}")
public Response listerNotificationsParMembre(@PathParam("membreId") UUID membreId) {
try {
List<NotificationDTO> result = notificationService.listerNotificationsParMembre(membreId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des notifications");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des notifications: " + e.getMessage()))
.build();
}
}
/**
* Liste les notifications non lues d'un membre
*
* @param membreId ID du membre
* @return Liste des notifications non lues
*/
@GET
@Path("/membre/{membreId}/non-lues")
public Response listerNotificationsNonLuesParMembre(@PathParam("membreId") UUID membreId) {
try {
List<NotificationDTO> result = notificationService.listerNotificationsNonLuesParMembre(membreId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des notifications non lues");
return Response.status(Response.Status.BAD_REQUEST)
.entity(
new ErrorResponse(
"Erreur lors de la liste des notifications non lues: " + e.getMessage()))
.build();
}
}
/**
* Liste les notifications en attente d'envoi
*
* @return Liste des notifications en attente
*/
@GET
@Path("/en-attente-envoi")
public Response listerNotificationsEnAttenteEnvoi() {
try {
List<NotificationDTO> result = notificationService.listerNotificationsEnAttenteEnvoi();
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des notifications en attente");
return Response.status(Response.Status.BAD_REQUEST)
.entity(
new ErrorResponse(
"Erreur lors de la liste des notifications en attente: " + e.getMessage()))
.build();
}
}
/**
* Envoie des notifications groupées à plusieurs membres (WOU/DRY)
*
* @param request DTO contenant les IDs des membres, sujet, corps et canaux
* @return Nombre de notifications créées
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/groupees")
public Response envoyerNotificationsGroupees(NotificationGroupeeRequest request) {
try {
int notificationsCreees =
notificationService.envoyerNotificationsGroupees(
request.membreIds, request.sujet, request.corps, request.canaux);
return Response.ok(Map.of("notificationsCreees", notificationsCreees)).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'envoi des notifications groupées");
return Response.status(Response.Status.BAD_REQUEST)
.entity(
new ErrorResponse(
"Erreur lors de l'envoi des notifications groupées: " + e.getMessage()))
.build();
}
}
/** Classe interne pour les réponses d'erreur */
public static class ErrorResponse {
public String error;
public ErrorResponse(String error) {
this.error = error;
}
}
/** Classe interne pour les requêtes de notifications groupées (WOU/DRY) */
public static class NotificationGroupeeRequest {
public List<UUID> membreIds;
public String sujet;
public String corps;
public List<String> canaux;
public NotificationGroupeeRequest() {}
}
}

View File

@@ -0,0 +1,423 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.service.KeycloakService;
import dev.lions.unionflow.server.service.OrganisationService;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
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;
/**
* Resource REST pour la gestion des organisations
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-15
*/
@Path("/api/organisations")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Organisations", description = "Gestion des organisations")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class OrganisationResource {
private static final Logger LOG = Logger.getLogger(OrganisationResource.class);
@Inject OrganisationService organisationService;
@Inject KeycloakService keycloakService;
/** Crée une nouvelle organisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Operation(
summary = "Créer une nouvelle organisation",
description = "Crée une nouvelle organisation dans le système")
@APIResponses({
@APIResponse(
responseCode = "201",
description = "Organisation créée avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = OrganisationDTO.class))),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "409", description = "Organisation déjà existante"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response creerOrganisation(@Valid OrganisationDTO organisationDTO) {
LOG.infof("Création d'une nouvelle organisation: %s", organisationDTO.getNom());
try {
Organisation organisation = organisationService.convertFromDTO(organisationDTO);
Organisation organisationCreee = organisationService.creerOrganisation(organisation);
OrganisationDTO dto = organisationService.convertToDTO(organisationCreee);
return Response.created(URI.create("/api/organisations/" + organisationCreee.getId()))
.entity(dto)
.build();
} catch (IllegalArgumentException e) {
LOG.warnf("Erreur lors de la création de l'organisation: %s", e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur inattendue lors de la création de l'organisation");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** 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")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Liste des organisations récupérée avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response listerOrganisations(
@Parameter(description = "Numéro de page (commence à 0)", example = "0")
@QueryParam("page")
@DefaultValue("0")
int page,
@Parameter(description = "Taille de la page", example = "20")
@QueryParam("size")
@DefaultValue("20")
int size,
@Parameter(description = "Terme de recherche (nom ou nom court)") @QueryParam("recherche")
String recherche) {
LOG.infof(
"Récupération des organisations - page: %d, size: %d, recherche: %s",
page, size, recherche);
try {
List<Organisation> organisations;
if (recherche != null && !recherche.trim().isEmpty()) {
organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size);
} else {
organisations = organisationService.listerOrganisationsActives(page, size);
}
List<OrganisationDTO> dtos =
organisations.stream()
.map(organisationService::convertToDTO)
.collect(Collectors.toList());
return Response.ok(dtos).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des organisations");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Récupère une organisation par son ID */
@GET
@Path("/{id}")
@Operation(
summary = "Récupérer une organisation",
description = "Récupère une organisation par son ID")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Organisation trouvée",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = OrganisationDTO.class))),
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response obtenirOrganisation(
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
LOG.infof("Récupération de l'organisation ID: %d", id);
return organisationService
.trouverParId(id)
.map(
organisation -> {
OrganisationDTO dto = organisationService.convertToDTO(organisation);
return Response.ok(dto).build();
})
.orElse(
Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Organisation non trouvée"))
.build());
}
/** Met à jour une organisation */
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}")
@Operation(
summary = "Mettre à jour une organisation",
description = "Met à jour les informations d'une organisation")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Organisation mise à jour avec succès",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = OrganisationDTO.class))),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
@APIResponse(responseCode = "409", description = "Conflit de données"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response mettreAJourOrganisation(
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id,
@Valid OrganisationDTO organisationDTO) {
LOG.infof("Mise à jour de l'organisation ID: %s", id);
try {
Organisation organisationMiseAJour = organisationService.convertFromDTO(organisationDTO);
Organisation organisation =
organisationService.mettreAJourOrganisation(id, organisationMiseAJour, "system");
OrganisationDTO dto = organisationService.convertToDTO(organisation);
return Response.ok(dto).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (IllegalArgumentException e) {
LOG.warnf("Erreur lors de la mise à jour de l'organisation: %s", e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur inattendue lors de la mise à jour de l'organisation");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Supprime une organisation */
@DELETE
@RolesAllowed({"ADMIN"})
@Path("/{id}")
@Operation(
summary = "Supprimer une organisation",
description = "Supprime une organisation (soft delete)")
@APIResponses({
@APIResponse(responseCode = "204", description = "Organisation supprimée avec succès"),
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
@APIResponse(responseCode = "409", description = "Impossible de supprimer l'organisation"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response supprimerOrganisation(
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
LOG.infof("Suppression de l'organisation ID: %d", id);
try {
organisationService.supprimerOrganisation(id, "system");
return Response.noContent().build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (IllegalStateException e) {
LOG.warnf("Erreur lors de la suppression de l'organisation: %s", e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur inattendue lors de la suppression de l'organisation");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Recherche avancée d'organisations */
@GET
@Path("/recherche")
@Operation(
summary = "Recherche avancée",
description = "Recherche d'organisations avec critères multiples")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "Résultats de recherche",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response rechercheAvancee(
@Parameter(description = "Nom de l'organisation") @QueryParam("nom") String nom,
@Parameter(description = "Type d'organisation") @QueryParam("type") String typeOrganisation,
@Parameter(description = "Statut") @QueryParam("statut") String statut,
@Parameter(description = "Ville") @QueryParam("ville") String ville,
@Parameter(description = "Région") @QueryParam("region") String region,
@Parameter(description = "Pays") @QueryParam("pays") String pays,
@Parameter(description = "Numéro de page") @QueryParam("page") @DefaultValue("0") int page,
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
int size) {
LOG.infof("Recherche avancée d'organisations avec critères multiples");
try {
List<Organisation> organisations =
organisationService.rechercheAvancee(
nom, typeOrganisation, statut, ville, region, pays, page, size);
List<OrganisationDTO> dtos =
organisations.stream()
.map(organisationService::convertToDTO)
.collect(Collectors.toList());
return Response.ok(dtos).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche avancée");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Active une organisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/activer")
@Operation(
summary = "Activer une organisation",
description = "Active une organisation suspendue")
@APIResponses({
@APIResponse(responseCode = "200", description = "Organisation activée avec succès"),
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response activerOrganisation(
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
LOG.infof("Activation de l'organisation ID: %d", id);
try {
Organisation organisation = organisationService.activerOrganisation(id, "system");
OrganisationDTO dto = organisationService.convertToDTO(organisation);
return Response.ok(dto).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'activation de l'organisation");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Suspend une organisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/suspendre")
@Operation(
summary = "Suspendre une organisation",
description = "Suspend une organisation active")
@APIResponses({
@APIResponse(responseCode = "200", description = "Organisation suspendue avec succès"),
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response suspendreOrganisation(
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
LOG.infof("Suspension de l'organisation ID: %d", id);
try {
Organisation organisation = organisationService.suspendreOrganisation(id, "system");
OrganisationDTO dto = organisationService.convertToDTO(organisation);
return Response.ok(dto).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la suspension de l'organisation");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
/** Obtient les statistiques des organisations */
@GET
@Path("/statistiques")
@Operation(
summary = "Statistiques des organisations",
description = "Récupère les statistiques globales des organisations")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
@APIResponse(responseCode = "401", description = "Non authentifié"),
@APIResponse(responseCode = "403", description = "Non autorisé")
})
public Response obtenirStatistiques() {
LOG.info("Récupération des statistiques des organisations");
try {
Map<String, Object> statistiques = organisationService.obtenirStatistiques();
return Response.ok(statistiques).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la récupération des statistiques");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur interne du serveur"))
.build();
}
}
}

View File

@@ -0,0 +1,213 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO;
import dev.lions.unionflow.server.service.PaiementService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion des paiements
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Path("/api/paiements")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class PaiementResource {
private static final Logger LOG = Logger.getLogger(PaiementResource.class);
@Inject PaiementService paiementService;
/**
* Crée un nouveau paiement
*
* @param paiementDTO DTO du paiement à créer
* @return Paiement créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
public Response creerPaiement(@Valid PaiementDTO paiementDTO) {
try {
PaiementDTO result = paiementService.creerPaiement(paiementDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du paiement: " + e.getMessage()))
.build();
}
}
/**
* Met à jour un paiement
*
* @param id ID du paiement
* @param paiementDTO DTO avec les modifications
* @return Paiement mis à jour
*/
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}")
public Response mettreAJourPaiement(@PathParam("id") UUID id, @Valid PaiementDTO paiementDTO) {
try {
PaiementDTO result = paiementService.mettreAJourPaiement(id, paiementDTO);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Paiement non trouvé"))
.build();
} catch (IllegalStateException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la mise à jour du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la mise à jour du paiement: " + e.getMessage()))
.build();
}
}
/**
* Valide un paiement
*
* @param id ID du paiement
* @return Paiement validé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/valider")
public Response validerPaiement(@PathParam("id") UUID id) {
try {
PaiementDTO result = paiementService.validerPaiement(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Paiement non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la validation du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la validation du paiement: " + e.getMessage()))
.build();
}
}
/**
* Annule un paiement
*
* @param id ID du paiement
* @return Paiement annulé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/{id}/annuler")
public Response annulerPaiement(@PathParam("id") UUID id) {
try {
PaiementDTO result = paiementService.annulerPaiement(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Paiement non trouvé"))
.build();
} catch (IllegalStateException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'annulation du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de l'annulation du paiement: " + e.getMessage()))
.build();
}
}
/**
* Trouve un paiement par son ID
*
* @param id ID du paiement
* @return Paiement
*/
@GET
@Path("/{id}")
public Response trouverParId(@PathParam("id") UUID id) {
try {
PaiementDTO result = paiementService.trouverParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Paiement non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
.build();
}
}
/**
* Trouve un paiement par son numéro de référence
*
* @param numeroReference Numéro de référence
* @return Paiement
*/
@GET
@Path("/reference/{numeroReference}")
public Response trouverParNumeroReference(@PathParam("numeroReference") String numeroReference) {
try {
PaiementDTO result = paiementService.trouverParNumeroReference(numeroReference);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Paiement non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du paiement");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
.build();
}
}
/**
* Liste tous les paiements d'un membre
*
* @param membreId ID du membre
* @return Liste des paiements
*/
@GET
@Path("/membre/{membreId}")
public Response listerParMembre(@PathParam("membreId") UUID membreId) {
try {
List<PaiementDTO> result = paiementService.listerParMembre(membreId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des paiements");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des paiements: " + e.getMessage()))
.build();
}
}
/** Classe interne pour les réponses d'erreur */
public static class ErrorResponse {
public String error;
public ErrorResponse(String error) {
this.error = error;
}
}
}

View File

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

View File

@@ -0,0 +1,165 @@
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.RolesAllowed;
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")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN", "MEMBRE"})
@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
@RolesAllowed({"ADMIN"})
@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();
}
}
}

View File

@@ -0,0 +1,269 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.service.WaveService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Resource REST pour l'intégration Wave Mobile Money
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Path("/api/wave")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
public class WaveResource {
private static final Logger LOG = Logger.getLogger(WaveResource.class);
@Inject WaveService waveService;
// ========================================
// COMPTES WAVE
// ========================================
/**
* Crée un nouveau compte Wave
*
* @param compteWaveDTO DTO du compte à créer
* @return Compte créé
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/comptes")
public Response creerCompteWave(@Valid CompteWaveDTO compteWaveDTO) {
try {
CompteWaveDTO result = waveService.creerCompteWave(compteWaveDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du compte Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création du compte Wave: " + e.getMessage()))
.build();
}
}
/**
* Met à jour un compte Wave
*
* @param id ID du compte
* @param compteWaveDTO DTO avec les modifications
* @return Compte mis à jour
*/
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/comptes/{id}")
public Response mettreAJourCompteWave(@PathParam("id") UUID id, @Valid CompteWaveDTO compteWaveDTO) {
try {
CompteWaveDTO result = waveService.mettreAJourCompteWave(id, compteWaveDTO);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Compte Wave non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la mise à jour du compte Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la mise à jour du compte Wave: " + e.getMessage()))
.build();
}
}
/**
* Vérifie un compte Wave
*
* @param id ID du compte
* @return Compte vérifié
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/comptes/{id}/verifier")
public Response verifierCompteWave(@PathParam("id") UUID id) {
try {
CompteWaveDTO result = waveService.verifierCompteWave(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Compte Wave non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la vérification du compte Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la vérification du compte Wave: " + e.getMessage()))
.build();
}
}
/**
* Trouve un compte Wave par son ID
*
* @param id ID du compte
* @return Compte Wave
*/
@GET
@Path("/comptes/{id}")
public Response trouverCompteWaveParId(@PathParam("id") UUID id) {
try {
CompteWaveDTO result = waveService.trouverCompteWaveParId(id);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Compte Wave non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
.build();
}
}
/**
* Trouve un compte Wave par numéro de téléphone
*
* @param numeroTelephone Numéro de téléphone
* @return Compte Wave ou null
*/
@GET
@Path("/comptes/telephone/{numeroTelephone}")
public Response trouverCompteWaveParTelephone(@PathParam("numeroTelephone") String numeroTelephone) {
try {
CompteWaveDTO result = waveService.trouverCompteWaveParTelephone(numeroTelephone);
if (result == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Compte Wave non trouvé"))
.build();
}
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
.build();
}
}
/**
* Liste tous les comptes Wave d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des comptes Wave
*/
@GET
@Path("/comptes/organisation/{organisationId}")
public Response listerComptesWaveParOrganisation(@PathParam("organisationId") UUID organisationId) {
try {
List<CompteWaveDTO> result = waveService.listerComptesWaveParOrganisation(organisationId);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des comptes Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la liste des comptes Wave: " + e.getMessage()))
.build();
}
}
// ========================================
// TRANSACTIONS WAVE
// ========================================
/**
* Crée une nouvelle transaction Wave
*
* @param transactionWaveDTO DTO de la transaction à créer
* @return Transaction créée
*/
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/transactions")
public Response creerTransactionWave(@Valid TransactionWaveDTO transactionWaveDTO) {
try {
TransactionWaveDTO result = waveService.creerTransactionWave(transactionWaveDTO);
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de la transaction Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la création de la transaction Wave: " + e.getMessage()))
.build();
}
}
/**
* Met à jour le statut d'une transaction Wave
*
* @param waveTransactionId Identifiant Wave de la transaction
* @param statut Nouveau statut
* @return Transaction mise à jour
*/
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@Path("/transactions/{waveTransactionId}/statut")
public Response mettreAJourStatutTransaction(
@PathParam("waveTransactionId") String waveTransactionId, StatutTransactionWave statut) {
try {
TransactionWaveDTO result = waveService.mettreAJourStatutTransaction(waveTransactionId, statut);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Transaction Wave non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la mise à jour du statut de la transaction Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(
new ErrorResponse(
"Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage()))
.build();
}
}
/**
* Trouve une transaction Wave par son identifiant Wave
*
* @param waveTransactionId Identifiant Wave
* @return Transaction Wave
*/
@GET
@Path("/transactions/{waveTransactionId}")
public Response trouverTransactionWaveParId(@PathParam("waveTransactionId") String waveTransactionId) {
try {
TransactionWaveDTO result = waveService.trouverTransactionWaveParId(waveTransactionId);
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Transaction Wave non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche de la transaction Wave");
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorResponse("Erreur lors de la recherche de la transaction Wave: " + e.getMessage()))
.build();
}
}
/** Classe interne pour les réponses d'erreur */
public static class ErrorResponse {
public String error;
public ErrorResponse(String error) {
this.error = error;
}
}
}