Files
unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/AdhesionService.java
dahoud e00a9301d8 feat: BackupService real pg_dump, OrganisationService region stats, SystemConfigService overrides
- BackupService: DB-persisted metadata (BackupRecord/BackupConfig entities + V16 Flyway migration),
  real pg_dump execution via ProcessBuilder, soft-delete on deleteBackup, pg_restore manual guidance
- OrganisationService: repartitionRegion now queries Adresse entities (was Map.of() stub)
- SystemConfigService: in-memory config overrides via AtomicReference (no DB dependency)
- SystemMetricsService: null-guard on MemoryMXBean in getSystemStatus() (fixes test NPE)
- Souscription workflow: SouscriptionService, SouscriptionResource, FormuleAbonnementRepository,
  V11 Flyway migration, admin REST clients
- Flyway V8-V15: notes membres, types référence, type orga constraint, seed roles,
  première connexion, Wave checkout URL, Wave telephone column length fix
- .gitignore: added uploads/ and .claude/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:14:30 +00:00

396 lines
15 KiB
Java

package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.finance.request.CreateAdhesionRequest;
import dev.lions.unionflow.server.api.dto.finance.request.UpdateAdhesionRequest;
import dev.lions.unionflow.server.api.dto.finance.response.AdhesionResponse;
import dev.lions.unionflow.server.entity.DemandeAdhesion;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AdhesionRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.jwt.JsonWebToken;
/**
* Service métier pour la gestion des demandes d'adhésion.
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-02-18
*/
@ApplicationScoped
@Slf4j
public class AdhesionService {
@Inject
AdhesionRepository adhesionRepository;
@Inject
MembreRepository membreRepository;
@Inject
OrganisationRepository organisationRepository;
@Inject
MembreOrganisationRepository membreOrganisationRepository;
@Inject
MembreKeycloakSyncService keycloakSyncService;
@Inject
DefaultsService defaultsService;
@Inject
SecurityIdentity securityIdentity;
@Inject
JsonWebToken jwt;
public List<AdhesionResponse> getAllAdhesions(int page, int size) {
log.debug("Récupération des adhésions - page: {}, size: {}", page, size);
jakarta.persistence.TypedQuery<DemandeAdhesion> query = adhesionRepository
.getEntityManager()
.createQuery(
"SELECT a FROM DemandeAdhesion a ORDER BY a.dateDemande DESC",
DemandeAdhesion.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
return query.getResultList().stream().map(this::convertToDTO).collect(Collectors.toList());
}
public AdhesionResponse getAdhesionById(@NotNull UUID id) {
log.debug("Récupération de l'adhésion avec ID: {}", id);
DemandeAdhesion adhesion = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
return convertToDTO(adhesion);
}
public AdhesionResponse getAdhesionByReference(@NotNull String numeroReference) {
log.debug("Récupération de l'adhésion avec référence: {}", numeroReference);
DemandeAdhesion adhesion = adhesionRepository
.findByNumeroReference(numeroReference)
.orElseThrow(
() -> new NotFoundException(
"Adhésion non trouvée avec la référence: " + numeroReference));
return convertToDTO(adhesion);
}
@Transactional
public AdhesionResponse createAdhesion(@Valid CreateAdhesionRequest request) {
log.info(
"Création d'une nouvelle adhésion pour le membre: {} et l'organisation: {}",
request.membreId(),
request.organisationId());
Membre membre = membreRepository
.findByIdOptional(request.membreId())
.orElseThrow(
() -> new NotFoundException(
"Membre non trouvé avec l'ID: " + request.membreId()));
Organisation organisation = organisationRepository
.findByIdOptional(request.organisationId())
.orElseThrow(
() -> new NotFoundException(
"Organisation non trouvée avec l'ID: " + request.organisationId()));
DemandeAdhesion adhesion = convertToEntity(request);
adhesion.setUtilisateur(membre);
adhesion.setOrganisation(organisation);
adhesionRepository.persist(adhesion);
log.info(
"Adhésion créée avec succès - ID: {}, Référence: {}",
adhesion.getId(),
adhesion.getNumeroReference());
return convertToDTO(adhesion);
}
@Transactional
public AdhesionResponse updateAdhesion(@NotNull UUID id, @Valid UpdateAdhesionRequest request) {
log.info("Mise à jour de l'adhésion avec ID: {}", id);
DemandeAdhesion adhesionExistante = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
updateAdhesionFields(adhesionExistante, request);
log.info("Adhésion mise à jour avec succès - ID: {}", id);
return convertToDTO(adhesionExistante);
}
@Transactional
public void deleteAdhesion(@NotNull UUID id) {
log.info("Suppression de l'adhésion avec ID: {}", id);
DemandeAdhesion adhesion = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
if ("APPROUVEE".equals(adhesion.getStatut()) && adhesion.isPayeeIntegralement()) {
throw new IllegalStateException("Impossible de supprimer une adhésion déjà payée intégralement");
}
adhesion.setStatut("ANNULEE");
log.info("Adhésion annulée avec succès - ID: {}", id);
}
@Transactional
public AdhesionResponse approuverAdhesion(@NotNull UUID id, String approuvePar) {
log.info("Approbation de l'adhésion avec ID: {}", id);
DemandeAdhesion adhesion = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
verifierAccesOrganisation(adhesion);
if (!adhesion.isEnAttente()) {
throw new IllegalStateException("Seules les adhésions en attente peuvent être approuvées");
}
adhesion.setStatut("APPROUVEE");
adhesion.setDateTraitement(LocalDateTime.now());
adhesion.setObservations(
approuvePar != null ? "Approuvée par : " + approuvePar : adhesion.getObservations());
// Activer le compte membre et provisionner son accès Keycloak
Membre membre = adhesion.getUtilisateur();
if (membre != null) {
membre.setStatutCompte("ACTIF");
membre.setActif(true);
try {
keycloakSyncService.provisionKeycloakUser(membre.getId());
log.info("Compte Keycloak provisionné pour le membre: {}", membre.getEmail());
} catch (Exception e) {
log.warn("Provisionnement Keycloak non bloquant pour {} : {}", membre.getEmail(), e.getMessage());
}
}
log.info("Adhésion approuvée avec succès - ID: {}", id);
return convertToDTO(adhesion);
}
@Transactional
public AdhesionResponse rejeterAdhesion(@NotNull UUID id, String motifRejet) {
log.info("Rejet de l'adhésion avec ID: {}", id);
DemandeAdhesion adhesion = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
verifierAccesOrganisation(adhesion);
if (!adhesion.isEnAttente()) {
throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées");
}
adhesion.setStatut("REJETEE");
adhesion.setMotifRejet(motifRejet);
adhesion.setDateTraitement(LocalDateTime.now());
// Désactiver le compte membre
Membre membre = adhesion.getUtilisateur();
if (membre != null) {
membre.setStatutCompte("DESACTIVE");
membre.setActif(false);
}
log.info("Adhésion rejetée avec succès - ID: {}", id);
return convertToDTO(adhesion);
}
@Transactional
public AdhesionResponse enregistrerPaiement(
@NotNull UUID id,
BigDecimal montantPaye,
String methodePaiement,
String referencePaiement) {
log.info("Enregistrement du paiement pour l'adhésion avec ID: {}", id);
DemandeAdhesion adhesion = adhesionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
if (!"APPROUVEE".equals(adhesion.getStatut()) && !"EN_PAIEMENT".equals(adhesion.getStatut())) {
throw new IllegalStateException(
"Seules les adhésions approuvées peuvent receive un paiement");
}
adhesion.setMontantPaye(adhesion.getMontantPaye().add(montantPaye));
if (adhesion.isPayeeIntegralement()) {
adhesion.setStatut("APPROUVEE");
}
log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
return convertToDTO(adhesion);
}
public List<AdhesionResponse> getAdhesionsByMembre(@NotNull UUID membreId, int page, int size) {
log.debug("Récupération des adhésions du membre: {}", membreId);
if (!membreRepository.findByIdOptional(membreId).isPresent()) {
throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
}
return adhesionRepository.findByMembreId(membreId).stream()
.skip((long) page * size)
.limit(size)
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AdhesionResponse> getAdhesionsByOrganisation(
@NotNull UUID organisationId, int page, int size) {
log.debug("Récupération des adhésions de l'organisation: {}", organisationId);
if (!organisationRepository.findByIdOptional(organisationId).isPresent()) {
throw new NotFoundException("Organisation non trouvée avec l'ID: " + organisationId);
}
return adhesionRepository.findByOrganisationId(organisationId).stream()
.skip((long) page * size)
.limit(size)
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AdhesionResponse> getAdhesionsByStatut(@NotNull String statut, int page, int size) {
log.debug("Récupération des adhésions avec statut: {}", statut);
return adhesionRepository.findByStatut(statut).stream()
.skip((long) page * size)
.limit(size)
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public List<AdhesionResponse> getAdhesionsEnAttente(int page, int size) {
log.debug("Récupération des adhésions en attente");
return adhesionRepository.findEnAttente().stream()
.skip((long) page * size)
.limit(size)
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public Map<String, Object> getStatistiquesAdhesions() {
log.debug("Calcul des statistiques des adhésions");
long total = adhesionRepository.count();
long approuvees = adhesionRepository.findByStatut("APPROUVEE").size();
long enAttente = adhesionRepository.findEnAttente().size();
long rejetees = adhesionRepository.findByStatut("REJETEE").size();
return Map.of(
"totalAdhesions", total,
"adhesionsApprouvees", approuvees,
"adhesionsEnAttente", enAttente,
"adhesionsRejetees", rejetees,
"tauxApprobation", total > 0 ? (approuvees * 100.0 / total) : 0.0,
"tauxRejet", total > 0 ? (rejetees * 100.0 / total) : 0.0);
}
/**
* Vérifie que l'ADMIN_ORGANISATION n'agit que sur les adhésions de sa propre organisation.
* Les rôles SUPER_ADMIN et ADMIN ont accès sans restriction.
*/
private void verifierAccesOrganisation(DemandeAdhesion adhesion) {
if (!securityIdentity.hasRole("ADMIN_ORGANISATION")) {
return; // SUPER_ADMIN / ADMIN : accès libre
}
UUID adhesionOrgId = adhesion.getOrganisation() != null ? adhesion.getOrganisation().getId() : null;
if (adhesionOrgId == null) {
throw new ForbiddenException("L'adhésion n'est rattachée à aucune organisation");
}
String keycloakSubject = jwt.getSubject();
Membre adminMembre = membreRepository.findByKeycloakUserId(keycloakSubject)
.orElseThrow(() -> new ForbiddenException("Compte admin introuvable pour le sujet JWT: " + keycloakSubject));
boolean appartient = membreOrganisationRepository
.findByMembreIdAndOrganisationId(adminMembre.getId(), adhesionOrgId)
.isPresent();
if (!appartient) {
log.warn("ADMIN_ORGANISATION {} tente d'agir sur une adhésion de l'organisation {} qui n'est pas la sienne",
keycloakSubject, adhesionOrgId);
throw new ForbiddenException("Vous ne pouvez gérer que les adhésions de votre organisation");
}
}
private AdhesionResponse convertToDTO(DemandeAdhesion adhesion) {
AdhesionResponse response = new AdhesionResponse();
response.setId(adhesion.getId());
response.setNumeroReference(adhesion.getNumeroReference());
if (adhesion.getUtilisateur() != null) {
response.setMembreId(adhesion.getUtilisateur().getId());
response.setNomMembre(adhesion.getUtilisateur().getNomComplet());
response.setNumeroMembre(adhesion.getUtilisateur().getNumeroMembre());
response.setEmailMembre(adhesion.getUtilisateur().getEmail());
}
if (adhesion.getOrganisation() != null) {
response.setOrganisationId(adhesion.getOrganisation().getId());
response.setNomOrganisation(adhesion.getOrganisation().getNom());
}
response.setDateDemande(
adhesion.getDateDemande() != null ? adhesion.getDateDemande().toLocalDate() : null);
response.setFraisAdhesion(adhesion.getFraisAdhesion());
response.setMontantPaye(adhesion.getMontantPaye());
response.setCodeDevise(adhesion.getCodeDevise());
response.setStatut(adhesion.getStatut());
response.setMotifRejet(adhesion.getMotifRejet());
response.setObservations(adhesion.getObservations());
if (adhesion.getDateTraitement() != null) {
response.setDateApprobation(adhesion.getDateTraitement().toLocalDate());
}
if (adhesion.getTraitePar() != null) {
response.setApprouvePar(adhesion.getTraitePar().getNomComplet());
}
response.setDateCreation(adhesion.getDateCreation());
response.setDateModification(adhesion.getDateModification());
response.setCreePar(adhesion.getCreePar());
response.setModifiePar(adhesion.getModifiePar());
response.setActif(adhesion.getActif());
return response;
}
private DemandeAdhesion convertToEntity(CreateAdhesionRequest request) {
return DemandeAdhesion.builder()
.numeroReference(request.numeroReference())
.dateDemande(request.dateDemande().atStartOfDay())
.fraisAdhesion(request.fraisAdhesion())
.montantPaye(BigDecimal.ZERO)
.codeDevise(request.codeDevise())
.statut("EN_ATTENTE")
.observations(request.observations())
.build();
}
private void updateAdhesionFields(DemandeAdhesion adhesion, UpdateAdhesionRequest request) {
if (request.statut() != null)
adhesion.setStatut(request.statut());
if (request.montantPaye() != null)
adhesion.setMontantPaye(request.montantPaye());
if (request.motifRejet() != null)
adhesion.setMotifRejet(request.motifRejet());
if (request.observations() != null)
adhesion.setObservations(request.observations());
if (request.dateApprobation() != null) {
adhesion.setDateTraitement(request.dateApprobation().atStartOfDay());
}
if (request.dateValidation() != null) {
// Logic for validation date if needed
}
}
}