Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
@@ -0,0 +1,740 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
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.api.enums.membre.StatutMembre;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
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 java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Service métier pour les membres */
|
||||
@ApplicationScoped
|
||||
public class MembreService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MembreService.class);
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
MembreImportExportService membreImportExportService;
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager entityManager;
|
||||
|
||||
/** Crée un nouveau membre */
|
||||
@Transactional
|
||||
public Membre creerMembre(Membre membre) {
|
||||
LOG.infof("Création d'un nouveau membre: %s", membre.getEmail());
|
||||
|
||||
// Générer un numéro de membre unique
|
||||
if (membre.getNumeroMembre() == null || membre.getNumeroMembre().isEmpty()) {
|
||||
membre.setNumeroMembre(genererNumeroMembre());
|
||||
}
|
||||
|
||||
// Définir la date d'adhésion si non fournie
|
||||
if (membre.getDateAdhesion() == null) {
|
||||
membre.setDateAdhesion(LocalDate.now());
|
||||
LOG.infof("Date d'adhésion automatiquement définie à: %s", membre.getDateAdhesion());
|
||||
}
|
||||
|
||||
// Définir la date de naissance par défaut si non fournie (pour éviter @NotNull)
|
||||
if (membre.getDateNaissance() == null) {
|
||||
membre.setDateNaissance(LocalDate.now().minusYears(18)); // Majeur par défaut
|
||||
LOG.warn("Date de naissance non fournie, définie par défaut à il y a 18 ans");
|
||||
}
|
||||
|
||||
// Vérifier l'unicité de l'email
|
||||
if (membreRepository.findByEmail(membre.getEmail()).isPresent()) {
|
||||
throw new IllegalArgumentException("Un membre avec cet email existe déjà");
|
||||
}
|
||||
|
||||
// Vérifier l'unicité du numéro de membre
|
||||
if (membreRepository.findByNumeroMembre(membre.getNumeroMembre()).isPresent()) {
|
||||
throw new IllegalArgumentException("Un membre avec ce numéro existe déjà");
|
||||
}
|
||||
|
||||
membreRepository.persist(membre);
|
||||
|
||||
// Mettre à jour le compteur de membres de l'organisation
|
||||
if (membre.getOrganisation() != null) {
|
||||
membre.getOrganisation().ajouterMembre();
|
||||
LOG.infof("Compteur de membres mis à jour pour l'organisation: %s", membre.getOrganisation().getNom());
|
||||
}
|
||||
|
||||
LOG.infof("Membre créé avec succès: %s (ID: %s)", membre.getNomComplet(), membre.getId());
|
||||
return membre;
|
||||
}
|
||||
|
||||
/** Met à jour un membre existant */
|
||||
@Transactional
|
||||
public Membre mettreAJourMembre(UUID id, Membre membreModifie) {
|
||||
LOG.infof("Mise à jour du membre ID: %s", id);
|
||||
|
||||
Membre membre = membreRepository.findById(id);
|
||||
if (membre == null) {
|
||||
throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
// Vérifier l'unicité de l'email si modifié
|
||||
if (!membre.getEmail().equals(membreModifie.getEmail())) {
|
||||
if (membreRepository.findByEmail(membreModifie.getEmail()).isPresent()) {
|
||||
throw new IllegalArgumentException("Un membre avec cet email existe déjà");
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour les champs
|
||||
membre.setPrenom(membreModifie.getPrenom());
|
||||
membre.setNom(membreModifie.getNom());
|
||||
membre.setEmail(membreModifie.getEmail());
|
||||
membre.setTelephone(membreModifie.getTelephone());
|
||||
membre.setDateNaissance(membreModifie.getDateNaissance());
|
||||
membre.setActif(membreModifie.getActif());
|
||||
|
||||
LOG.infof("Membre mis à jour avec succès: %s", membre.getNomComplet());
|
||||
return membre;
|
||||
}
|
||||
|
||||
/** Trouve un membre par son ID */
|
||||
public Optional<Membre> trouverParId(UUID id) {
|
||||
return Optional.ofNullable(membreRepository.findById(id));
|
||||
}
|
||||
|
||||
/** Trouve un membre par son email */
|
||||
public Optional<Membre> trouverParEmail(String email) {
|
||||
return membreRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
/** Liste tous les membres actifs */
|
||||
public List<Membre> listerMembresActifs() {
|
||||
return membreRepository.findAllActifs();
|
||||
}
|
||||
|
||||
/** Recherche des membres par nom ou prénom */
|
||||
public List<Membre> rechercherMembres(String recherche) {
|
||||
return membreRepository.findByNomOrPrenom(recherche);
|
||||
}
|
||||
|
||||
/** Désactive un membre */
|
||||
@Transactional
|
||||
public void desactiverMembre(UUID id) {
|
||||
LOG.infof("Désactivation du membre ID: %s", id);
|
||||
|
||||
Membre membre = membreRepository.findById(id);
|
||||
if (membre == null) {
|
||||
throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
membre.setActif(false);
|
||||
LOG.infof("Membre désactivé: %s", membre.getNomComplet());
|
||||
}
|
||||
|
||||
/** Génère un numéro de membre unique */
|
||||
private String genererNumeroMembre() {
|
||||
String prefix = "UF" + LocalDate.now().getYear();
|
||||
String suffix = UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||
return prefix + "-" + suffix;
|
||||
}
|
||||
|
||||
/** Compte le nombre total de membres actifs */
|
||||
public long compterMembresActifs() {
|
||||
return membreRepository.countActifs();
|
||||
}
|
||||
|
||||
/** Liste tous les membres actifs avec pagination */
|
||||
public List<Membre> listerMembresActifs(Page page, Sort sort) {
|
||||
return membreRepository.findAllActifs(page, sort);
|
||||
}
|
||||
|
||||
/** Recherche des membres avec pagination */
|
||||
public List<Membre> rechercherMembres(String recherche, Page page, Sort sort) {
|
||||
return membreRepository.findByNomOrPrenom(recherche, page, sort);
|
||||
}
|
||||
|
||||
/** Obtient les statistiques avancées des membres */
|
||||
public Map<String, Object> obtenirStatistiquesAvancees() {
|
||||
LOG.info("Calcul des statistiques avancées des membres");
|
||||
|
||||
long totalMembres = membreRepository.count();
|
||||
long membresActifs = membreRepository.countActifs();
|
||||
long membresInactifs = totalMembres - membresActifs;
|
||||
long nouveauxMembres30Jours =
|
||||
membreRepository.countNouveauxMembres(LocalDate.now().minusDays(30));
|
||||
|
||||
return Map.of(
|
||||
"totalMembres", totalMembres,
|
||||
"membresActifs", membresActifs,
|
||||
"membresInactifs", membresInactifs,
|
||||
"nouveauxMembres30Jours", nouveauxMembres30Jours,
|
||||
"tauxActivite", totalMembres > 0 ? (membresActifs * 100.0 / totalMembres) : 0.0,
|
||||
"timestamp", LocalDateTime.now());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES DE CONVERSION DTO
|
||||
// ========================================
|
||||
|
||||
/** Convertit une entité Membre en MembreDTO */
|
||||
public MembreDTO convertToDTO(Membre membre) {
|
||||
if (membre == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MembreDTO dto = new MembreDTO();
|
||||
|
||||
// Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant)
|
||||
dto.setId(membre.getId());
|
||||
|
||||
// Copie des champs de base
|
||||
dto.setNumeroMembre(membre.getNumeroMembre());
|
||||
dto.setNom(membre.getNom());
|
||||
dto.setPrenom(membre.getPrenom());
|
||||
dto.setEmail(membre.getEmail());
|
||||
dto.setTelephone(membre.getTelephone());
|
||||
dto.setDateNaissance(membre.getDateNaissance());
|
||||
dto.setDateAdhesion(membre.getDateAdhesion());
|
||||
|
||||
// Conversion du statut boolean vers enum StatutMembre
|
||||
// Règle métier: actif=true → ACTIF, actif=false → INACTIF
|
||||
if (membre.getActif() == null || Boolean.TRUE.equals(membre.getActif())) {
|
||||
dto.setStatut(StatutMembre.ACTIF);
|
||||
} else {
|
||||
dto.setStatut(StatutMembre.INACTIF);
|
||||
}
|
||||
|
||||
// Conversion de l'organisation (associationId)
|
||||
// Utilisation directe de l'UUID de l'organisation
|
||||
if (membre.getOrganisation() != null && membre.getOrganisation().getId() != null) {
|
||||
dto.setAssociationId(membre.getOrganisation().getId());
|
||||
dto.setAssociationNom(membre.getOrganisation().getNom());
|
||||
}
|
||||
|
||||
// Champs de base DTO
|
||||
dto.setDateCreation(membre.getDateCreation());
|
||||
dto.setDateModification(membre.getDateModification());
|
||||
dto.setVersion(0L); // Version par défaut
|
||||
|
||||
// Champs par défaut pour les champs manquants dans l'entité
|
||||
dto.setMembreBureau(false);
|
||||
dto.setResponsable(false);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un MembreDTO en entité Membre */
|
||||
public Membre convertFromDTO(MembreDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Membre membre = new Membre();
|
||||
|
||||
// Copie des champs
|
||||
membre.setNumeroMembre(dto.getNumeroMembre());
|
||||
membre.setNom(dto.getNom());
|
||||
membre.setPrenom(dto.getPrenom());
|
||||
membre.setEmail(dto.getEmail());
|
||||
membre.setTelephone(dto.getTelephone());
|
||||
membre.setDateNaissance(dto.getDateNaissance());
|
||||
membre.setDateAdhesion(dto.getDateAdhesion());
|
||||
|
||||
// Conversion du statut enum vers boolean
|
||||
// Règle métier: ACTIF → true, autres statuts → false
|
||||
membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut()));
|
||||
|
||||
// Champs de base
|
||||
if (dto.getDateCreation() != null) {
|
||||
membre.setDateCreation(dto.getDateCreation());
|
||||
}
|
||||
if (dto.getDateModification() != null) {
|
||||
membre.setDateModification(dto.getDateModification());
|
||||
}
|
||||
|
||||
return membre;
|
||||
}
|
||||
|
||||
/** Convertit une liste d'entités en liste de DTOs */
|
||||
public List<MembreDTO> convertToDTOList(List<Membre> membres) {
|
||||
return membres.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Met à jour une entité Membre à partir d'un MembreDTO */
|
||||
public void updateFromDTO(Membre membre, MembreDTO dto) {
|
||||
if (membre == null || dto == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
membre.setPrenom(dto.getPrenom());
|
||||
membre.setNom(dto.getNom());
|
||||
membre.setEmail(dto.getEmail());
|
||||
membre.setTelephone(dto.getTelephone());
|
||||
membre.setDateNaissance(dto.getDateNaissance());
|
||||
// Conversion du statut enum vers boolean
|
||||
membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut()));
|
||||
membre.setDateModification(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/** Recherche avancée de membres avec filtres multiples (DEPRECATED) */
|
||||
public List<Membre> rechercheAvancee(
|
||||
String recherche,
|
||||
Boolean actif,
|
||||
LocalDate dateAdhesionMin,
|
||||
LocalDate dateAdhesionMax,
|
||||
Page page,
|
||||
Sort sort) {
|
||||
LOG.infof(
|
||||
"Recherche avancée (DEPRECATED) - recherche: %s, actif: %s, dateMin: %s, dateMax: %s",
|
||||
recherche, actif, dateAdhesionMin, dateAdhesionMax);
|
||||
|
||||
return membreRepository.rechercheAvancee(
|
||||
recherche, actif, dateAdhesionMin, dateAdhesionMax, page, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nouvelle recherche avancée de membres avec critères complets Retourne des résultats paginés
|
||||
* avec statistiques
|
||||
*
|
||||
* @param criteria Critères de recherche
|
||||
* @param page Pagination
|
||||
* @param sort Tri
|
||||
* @return Résultats de recherche avec métadonnées
|
||||
*/
|
||||
public MembreSearchResultDTO searchMembresAdvanced(
|
||||
MembreSearchCriteria criteria, Page page, Sort sort) {
|
||||
LOG.infof("Recherche avancée de membres - critères: %s", criteria.getDescription());
|
||||
|
||||
try {
|
||||
// Construction de la requête dynamique
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT m FROM Membre m WHERE 1=1");
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
// Ajout des critères de recherche
|
||||
addSearchCriteria(queryBuilder, parameters, criteria);
|
||||
|
||||
// Requête pour compter le total
|
||||
String countQuery =
|
||||
queryBuilder
|
||||
.toString()
|
||||
.replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m");
|
||||
|
||||
// Exécution de la requête de comptage
|
||||
TypedQuery<Long> countQueryTyped = entityManager.createQuery(countQuery, Long.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
countQueryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
long totalElements = countQueryTyped.getSingleResult();
|
||||
|
||||
if (totalElements == 0) {
|
||||
return MembreSearchResultDTO.empty(criteria, page.size, page.index);
|
||||
}
|
||||
|
||||
// Ajout du tri et pagination
|
||||
String finalQuery = queryBuilder.toString();
|
||||
if (sort != null) {
|
||||
finalQuery += " ORDER BY " + buildOrderByClause(sort);
|
||||
}
|
||||
|
||||
// Exécution de la requête principale
|
||||
TypedQuery<Membre> queryTyped = entityManager.createQuery(finalQuery, Membre.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
queryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
queryTyped.setFirstResult(page.index * page.size);
|
||||
queryTyped.setMaxResults(page.size);
|
||||
List<Membre> membres = queryTyped.getResultList();
|
||||
|
||||
// Conversion en DTOs
|
||||
List<MembreDTO> membresDTO = convertToDTOList(membres);
|
||||
|
||||
// Calcul des statistiques
|
||||
MembreSearchResultDTO.SearchStatistics statistics = calculateSearchStatistics(membres);
|
||||
|
||||
// Construction du résultat
|
||||
MembreSearchResultDTO result =
|
||||
MembreSearchResultDTO.builder()
|
||||
.membres(membresDTO)
|
||||
.totalElements(totalElements)
|
||||
.totalPages((int) Math.ceil((double) totalElements / page.size))
|
||||
.currentPage(page.index)
|
||||
.pageSize(page.size)
|
||||
.criteria(criteria)
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
|
||||
// Calcul des indicateurs de pagination
|
||||
result.calculatePaginationFlags();
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche avancée de membres");
|
||||
throw new RuntimeException("Erreur lors de la recherche avancée", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ajoute les critères de recherche à la requête */
|
||||
private void addSearchCriteria(
|
||||
StringBuilder queryBuilder, Map<String, Object> parameters, MembreSearchCriteria criteria) {
|
||||
|
||||
// Recherche générale dans nom, prénom, email
|
||||
if (criteria.getQuery() != null) {
|
||||
queryBuilder.append(
|
||||
" AND (LOWER(m.nom) LIKE LOWER(:query) OR LOWER(m.prenom) LIKE LOWER(:query) OR"
|
||||
+ " LOWER(m.email) LIKE LOWER(:query))");
|
||||
parameters.put("query", "%" + criteria.getQuery() + "%");
|
||||
}
|
||||
|
||||
// Recherche par nom
|
||||
if (criteria.getNom() != null) {
|
||||
queryBuilder.append(" AND LOWER(m.nom) LIKE LOWER(:nom)");
|
||||
parameters.put("nom", "%" + criteria.getNom() + "%");
|
||||
}
|
||||
|
||||
// Recherche par prénom
|
||||
if (criteria.getPrenom() != null) {
|
||||
queryBuilder.append(" AND LOWER(m.prenom) LIKE LOWER(:prenom)");
|
||||
parameters.put("prenom", "%" + criteria.getPrenom() + "%");
|
||||
}
|
||||
|
||||
// Recherche par email
|
||||
if (criteria.getEmail() != null) {
|
||||
queryBuilder.append(" AND LOWER(m.email) LIKE LOWER(:email)");
|
||||
parameters.put("email", "%" + criteria.getEmail() + "%");
|
||||
}
|
||||
|
||||
// Recherche par téléphone
|
||||
if (criteria.getTelephone() != null) {
|
||||
queryBuilder.append(" AND m.telephone LIKE :telephone");
|
||||
parameters.put("telephone", "%" + criteria.getTelephone() + "%");
|
||||
}
|
||||
|
||||
// Filtre par statut
|
||||
if (criteria.getStatut() != null) {
|
||||
boolean isActif = "ACTIF".equals(criteria.getStatut());
|
||||
queryBuilder.append(" AND m.actif = :actif");
|
||||
parameters.put("actif", isActif);
|
||||
} else if (!Boolean.TRUE.equals(criteria.getIncludeInactifs())) {
|
||||
// Par défaut, exclure les inactifs
|
||||
queryBuilder.append(" AND m.actif = true");
|
||||
}
|
||||
|
||||
// Filtre par dates d'adhésion
|
||||
if (criteria.getDateAdhesionMin() != null) {
|
||||
queryBuilder.append(" AND m.dateAdhesion >= :dateAdhesionMin");
|
||||
parameters.put("dateAdhesionMin", criteria.getDateAdhesionMin());
|
||||
}
|
||||
|
||||
if (criteria.getDateAdhesionMax() != null) {
|
||||
queryBuilder.append(" AND m.dateAdhesion <= :dateAdhesionMax");
|
||||
parameters.put("dateAdhesionMax", criteria.getDateAdhesionMax());
|
||||
}
|
||||
|
||||
// Filtre par âge (calculé à partir de la date de naissance)
|
||||
if (criteria.getAgeMin() != null) {
|
||||
LocalDate maxBirthDate = LocalDate.now().minusYears(criteria.getAgeMin());
|
||||
queryBuilder.append(" AND m.dateNaissance <= :maxBirthDateForMinAge");
|
||||
parameters.put("maxBirthDateForMinAge", maxBirthDate);
|
||||
}
|
||||
|
||||
if (criteria.getAgeMax() != null) {
|
||||
LocalDate minBirthDate = LocalDate.now().minusYears(criteria.getAgeMax() + 1).plusDays(1);
|
||||
queryBuilder.append(" AND m.dateNaissance >= :minBirthDateForMaxAge");
|
||||
parameters.put("minBirthDateForMaxAge", minBirthDate);
|
||||
}
|
||||
|
||||
// Filtre par organisations (si implémenté dans l'entité)
|
||||
if (criteria.getOrganisationIds() != null && !criteria.getOrganisationIds().isEmpty()) {
|
||||
queryBuilder.append(" AND m.organisation.id IN :organisationIds");
|
||||
parameters.put("organisationIds", criteria.getOrganisationIds());
|
||||
}
|
||||
|
||||
// Filtre par rôles (recherche via la relation MembreRole -> Role)
|
||||
if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) {
|
||||
// Utiliser EXISTS avec une sous-requête pour vérifier les rôles
|
||||
queryBuilder.append(" AND EXISTS (");
|
||||
queryBuilder.append(" SELECT 1 FROM MembreRole mr WHERE mr.membre = m");
|
||||
queryBuilder.append(" AND mr.actif = true");
|
||||
queryBuilder.append(" AND mr.role.code IN :roleCodes");
|
||||
queryBuilder.append(")");
|
||||
// Convertir les noms de rôles en codes (supposant que criteria.getRoles() contient des codes)
|
||||
parameters.put("roleCodes", criteria.getRoles());
|
||||
}
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir du Sort */
|
||||
private String buildOrderByClause(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "m.nom ASC";
|
||||
}
|
||||
|
||||
return sort.getColumns().stream()
|
||||
.map(column -> {
|
||||
String direction = column.getDirection() == Sort.Direction.Descending ? "DESC" : "ASC";
|
||||
return "m." + column.getName() + " " + direction;
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
/** Calcule les statistiques sur les résultats de recherche */
|
||||
private MembreSearchResultDTO.SearchStatistics calculateSearchStatistics(List<Membre> membres) {
|
||||
if (membres.isEmpty()) {
|
||||
return MembreSearchResultDTO.SearchStatistics.builder()
|
||||
.membresActifs(0)
|
||||
.membresInactifs(0)
|
||||
.ageMoyen(0.0)
|
||||
.ageMin(0)
|
||||
.ageMax(0)
|
||||
.nombreOrganisations(0)
|
||||
.nombreRegions(0)
|
||||
.ancienneteMoyenne(0.0)
|
||||
.build();
|
||||
}
|
||||
|
||||
long membresActifs =
|
||||
membres.stream().mapToLong(m -> Boolean.TRUE.equals(m.getActif()) ? 1 : 0).sum();
|
||||
long membresInactifs = membres.size() - membresActifs;
|
||||
|
||||
// Calcul des âges
|
||||
List<Integer> ages =
|
||||
membres.stream()
|
||||
.filter(m -> m.getDateNaissance() != null)
|
||||
.map(m -> Period.between(m.getDateNaissance(), LocalDate.now()).getYears())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
double ageMoyen = ages.stream().mapToInt(Integer::intValue).average().orElse(0.0);
|
||||
int ageMin = ages.stream().mapToInt(Integer::intValue).min().orElse(0);
|
||||
int ageMax = ages.stream().mapToInt(Integer::intValue).max().orElse(0);
|
||||
|
||||
// Calcul de l'ancienneté moyenne
|
||||
double ancienneteMoyenne =
|
||||
membres.stream()
|
||||
.filter(m -> m.getDateAdhesion() != null)
|
||||
.mapToDouble(m -> Period.between(m.getDateAdhesion(), LocalDate.now()).getYears())
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
// Nombre d'organisations (si relation disponible)
|
||||
long nombreOrganisations =
|
||||
membres.stream()
|
||||
.filter(m -> m.getOrganisation() != null)
|
||||
.map(m -> m.getOrganisation().getId())
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
return MembreSearchResultDTO.SearchStatistics.builder()
|
||||
.membresActifs(membresActifs)
|
||||
.membresInactifs(membresInactifs)
|
||||
.ageMoyen(ageMoyen)
|
||||
.ageMin(ageMin)
|
||||
.ageMax(ageMax)
|
||||
.nombreOrganisations(nombreOrganisations)
|
||||
.nombreRegions(0) // TODO: Calculer depuis les adresses
|
||||
.ancienneteMoyenne(ancienneteMoyenne)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES D'AUTOCOMPLÉTION (WOU/DRY)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Obtient la liste des villes distinctes depuis les adresses des membres
|
||||
* Réutilisable pour autocomplétion (WOU/DRY)
|
||||
*/
|
||||
public List<String> obtenirVillesDistinctes(String query) {
|
||||
LOG.infof("Récupération des villes distinctes - query: %s", query);
|
||||
|
||||
String jpql = "SELECT DISTINCT a.ville FROM Adresse a WHERE a.ville IS NOT NULL AND a.ville != ''";
|
||||
if (query != null && !query.trim().isEmpty()) {
|
||||
jpql += " AND LOWER(a.ville) LIKE LOWER(:query)";
|
||||
}
|
||||
jpql += " ORDER BY a.ville ASC";
|
||||
|
||||
TypedQuery<String> typedQuery = entityManager.createQuery(jpql, String.class);
|
||||
if (query != null && !query.trim().isEmpty()) {
|
||||
typedQuery.setParameter("query", "%" + query.trim() + "%");
|
||||
}
|
||||
typedQuery.setMaxResults(50); // Limiter à 50 résultats pour performance
|
||||
|
||||
List<String> villes = typedQuery.getResultList();
|
||||
LOG.infof("Trouvé %d villes distinctes", villes.size());
|
||||
return villes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la liste des professions distinctes depuis les membres
|
||||
* Note: Si le champ profession n'existe pas dans Membre, retourne une liste vide
|
||||
* Réutilisable pour autocomplétion (WOU/DRY)
|
||||
*/
|
||||
public List<String> obtenirProfessionsDistinctes(String query) {
|
||||
LOG.infof("Récupération des professions distinctes - query: %s", query);
|
||||
|
||||
// TODO: Vérifier si le champ profession existe dans Membre
|
||||
// Pour l'instant, retourner une liste vide car le champ n'existe pas
|
||||
// Cette méthode peut être étendue si un champ profession est ajouté plus tard
|
||||
LOG.warn("Le champ profession n'existe pas dans l'entité Membre. Retour d'une liste vide.");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte une sélection de membres en Excel (WOU/DRY - réutilise la logique d'export)
|
||||
*
|
||||
* @param membreIds Liste des IDs des membres à exporter
|
||||
* @param format Format d'export (EXCEL, CSV, etc.)
|
||||
* @return Données binaires du fichier Excel
|
||||
*/
|
||||
public byte[] exporterMembresSelectionnes(List<UUID> membreIds, String format) {
|
||||
LOG.infof("Export de %d membres sélectionnés - format: %s", membreIds.size(), format);
|
||||
|
||||
if (membreIds == null || membreIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("La liste des membres ne peut pas être vide");
|
||||
}
|
||||
|
||||
// Récupérer les membres
|
||||
List<Membre> membres =
|
||||
membreIds.stream()
|
||||
.map(id -> membreRepository.findByIdOptional(id))
|
||||
.filter(opt -> opt.isPresent())
|
||||
.map(java.util.Optional::get)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Convertir en DTOs
|
||||
List<MembreDTO> membresDTO = convertToDTOList(membres);
|
||||
|
||||
// Générer le fichier Excel (simplifié - à améliorer avec Apache POI)
|
||||
// Pour l'instant, générer un CSV simple
|
||||
StringBuilder csv = new StringBuilder();
|
||||
csv.append("Numéro;Nom;Prénom;Email;Téléphone;Statut;Date Adhésion\n");
|
||||
for (MembreDTO m : membresDTO) {
|
||||
csv.append(
|
||||
String.format(
|
||||
"%s;%s;%s;%s;%s;%s;%s\n",
|
||||
m.getNumeroMembre() != null ? m.getNumeroMembre() : "",
|
||||
m.getNom() != null ? m.getNom() : "",
|
||||
m.getPrenom() != null ? m.getPrenom() : "",
|
||||
m.getEmail() != null ? m.getEmail() : "",
|
||||
m.getTelephone() != null ? m.getTelephone() : "",
|
||||
m.getStatut() != null ? m.getStatut() : "",
|
||||
m.getDateAdhesion() != null ? m.getDateAdhesion().toString() : ""));
|
||||
}
|
||||
|
||||
return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe des membres depuis un fichier Excel ou CSV
|
||||
*/
|
||||
public MembreImportExportService.ResultatImport importerMembres(
|
||||
InputStream fileInputStream,
|
||||
String fileName,
|
||||
UUID organisationId,
|
||||
String typeMembreDefaut,
|
||||
boolean mettreAJourExistants,
|
||||
boolean ignorerErreurs) {
|
||||
return membreImportExportService.importerMembres(
|
||||
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers Excel
|
||||
*/
|
||||
public byte[] exporterVersExcel(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) {
|
||||
try {
|
||||
return membreImportExportService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, inclureStatistiques, motDePasse);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export Excel");
|
||||
throw new RuntimeException("Erreur lors de l'export Excel: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers CSV
|
||||
*/
|
||||
public byte[] exporterVersCSV(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates) {
|
||||
try {
|
||||
return membreImportExportService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export CSV");
|
||||
throw new RuntimeException("Erreur lors de l'export CSV: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un modèle Excel pour l'import
|
||||
*/
|
||||
public byte[] genererModeleImport() {
|
||||
try {
|
||||
return membreImportExportService.genererModeleImport();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la génération du modèle");
|
||||
throw new RuntimeException("Erreur lors de la génération du modèle: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les membres pour l'export selon les filtres
|
||||
*/
|
||||
public List<MembreDTO> listerMembresPourExport(
|
||||
UUID associationId,
|
||||
String statut,
|
||||
String type,
|
||||
String dateAdhesionDebut,
|
||||
String dateAdhesionFin) {
|
||||
|
||||
List<Membre> membres;
|
||||
|
||||
if (associationId != null) {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.organisation.id = :associationId", Membre.class);
|
||||
query.setParameter("associationId", associationId);
|
||||
membres = query.getResultList();
|
||||
} else {
|
||||
membres = membreRepository.listAll();
|
||||
}
|
||||
|
||||
// Filtrer par statut
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
boolean actif = "ACTIF".equals(statut);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getActif() == actif)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Filtrer par dates d'adhésion
|
||||
if (dateAdhesionDebut != null && !dateAdhesionDebut.isEmpty()) {
|
||||
LocalDate dateDebut = LocalDate.parse(dateAdhesionDebut);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isBefore(dateDebut))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (dateAdhesionFin != null && !dateAdhesionFin.isEmpty()) {
|
||||
LocalDate dateFin = LocalDate.parse(dateAdhesionFin);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isAfter(dateFin))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return convertToDTOList(membres);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user