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