package dev.lions.unionflow.server.service; import dev.lions.unionflow.server.api.dto.organisation.request.CreateOrganisationRequest; import dev.lions.unionflow.server.api.dto.organisation.request.UpdateOrganisationRequest; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse; import dev.lions.unionflow.server.api.enums.membre.StatutMembre; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.MembreOrganisation; import dev.lions.unionflow.server.repository.AdresseRepository; import dev.lions.unionflow.server.repository.EvenementRepository; import dev.lions.unionflow.server.repository.MembreOrganisationRepository; import dev.lions.unionflow.server.repository.MembreRepository; import dev.lions.unionflow.server.repository.MembreRoleRepository; import dev.lions.unionflow.server.repository.TypeReferenceRepository; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.repository.OrganisationRepository; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.NotFoundException; import java.time.LocalDate; import java.time.LocalDateTime; 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 la gestion des organisations * * @author UnionFlow Team * @version 1.0 * @since 2025-01-15 */ @ApplicationScoped public class OrganisationService { private static final Logger LOG = Logger.getLogger(OrganisationService.class); @Inject OrganisationRepository organisationRepository; @Inject MembreRepository membreRepository; @Inject DefaultsService defaultsService; @Inject TypeReferenceRepository typeReferenceRepository; @Inject MembreOrganisationRepository membreOrganisationRepository; @Inject AdresseRepository adresseRepository; @Inject EvenementRepository evenementRepository; @Inject MembreRoleRepository membreRoleRepository; /** * Crée une nouvelle organisation * * @param organisation l'organisation à créer * @param utilisateur identifiant de l'utilisateur effectuant la création * (email ou "system") * @return l'organisation créée */ @Transactional public Organisation creerOrganisation(Organisation organisation, String utilisateur) { LOG.infof("Création d'une nouvelle organisation: %s", organisation.getNom()); // Vérifier l'unicité de l'email if (organisationRepository.findByEmail(organisation.getEmail()).isPresent()) { throw new IllegalStateException("Une organisation avec cet email existe déjà"); } // Vérifier l'unicité du nom if (organisationRepository.findByNom(organisation.getNom()).isPresent()) { throw new IllegalArgumentException("Une organisation avec ce nom existe déjà"); } // Vérifier l'unicité du numéro d'enregistrement si fourni if (organisation.getNumeroEnregistrement() != null && !organisation.getNumeroEnregistrement().isEmpty()) { if (organisationRepository .findByNumeroEnregistrement(organisation.getNumeroEnregistrement()) .isPresent()) { throw new IllegalArgumentException( "Une organisation avec ce numéro d'enregistrement existe déjà"); } } // Définir les valeurs par défaut if (organisation.getStatut() == null) { organisation.setStatut("ACTIVE"); } if (organisation.getTypeOrganisation() == null) { organisation.setTypeOrganisation("ASSOCIATION"); } // Initialiser modulesActifs et categorieType depuis types_reference // Cela permet aux types créés via CRUD (ex: "TANTANPION") d'hériter // automatiquement de leurs modules_requis sans modifier le code Java. if (organisation.getModulesActifs() == null || organisation.getModulesActifs().isBlank()) { typeReferenceRepository .findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation()) .ifPresentOrElse( tr -> { if (tr.getModulesRequis() != null && !tr.getModulesRequis().isBlank()) { organisation.setModulesActifs(tr.getModulesRequis()); LOG.infof("Modules initialisés depuis types_reference pour le type '%s': %s", organisation.getTypeOrganisation(), tr.getModulesRequis()); } if (tr.getCategorie() != null && organisation.getCategorieType() == null) { organisation.setCategorieType(tr.getCategorie()); } }, () -> LOG.warnf( "Type d'organisation '%s' absent de types_reference — modules non initialisés. " + "Ajoutez ce type via l'administration pour activer les modules métier.", organisation.getTypeOrganisation())); } // Audit : créé par / modifié par (BaseEntity n'initialise pas creePar dans // @PrePersist) String auditUser = utilisateur != null && !utilisateur.isBlank() ? utilisateur : "system"; organisation.setCreePar(auditUser); organisation.setModifiePar(auditUser); if (organisation.getDateCreation() == null) { organisation.setDateCreation(LocalDateTime.now()); } organisation.setDateModification(organisation.getDateCreation()); organisationRepository.persist(organisation); LOG.infof( "Organisation créée avec succès: ID=%s, Nom=%s", organisation.getId(), organisation.getNom()); return organisation; } /** * Met à jour une organisation existante * * @param id l'ID de l'organisation * @param organisationMiseAJour les données de mise à jour * @param utilisateur l'utilisateur effectuant la modification * @return l'organisation mise à jour */ @Transactional public Organisation mettreAJourOrganisation( UUID id, Organisation organisationMiseAJour, String utilisateur) { LOG.infof("Mise à jour de l'organisation ID: %s", id); Organisation organisation = organisationRepository .findByIdOptional(id) .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); // Vérifier l'unicité de l'email si modifié if (!organisation.getEmail().equals(organisationMiseAJour.getEmail())) { if (organisationRepository.findByEmail(organisationMiseAJour.getEmail()).isPresent()) { throw new IllegalStateException("Une organisation avec cet email existe déjà"); } organisation.setEmail(organisationMiseAJour.getEmail()); } // Vérifier l'unicité du nom si modifié if (!organisation.getNom().equals(organisationMiseAJour.getNom())) { if (organisationRepository.findByNom(organisationMiseAJour.getNom()).isPresent()) { throw new IllegalArgumentException("Une organisation avec ce nom existe déjà"); } organisation.setNom(organisationMiseAJour.getNom()); } // Mettre à jour tous les champs métier (alignés sur detail.xhtml et // organisation-form) organisation.setNomCourt(organisationMiseAJour.getNomCourt()); organisation.setDescription(organisationMiseAJour.getDescription()); organisation.setDateFondation(organisationMiseAJour.getDateFondation()); organisation.setNumeroEnregistrement(organisationMiseAJour.getNumeroEnregistrement()); organisation.setTelephone(organisationMiseAJour.getTelephone()); organisation.setTelephoneSecondaire(organisationMiseAJour.getTelephoneSecondaire()); organisation.setEmailSecondaire(organisationMiseAJour.getEmailSecondaire()); organisation.setAdresse(organisationMiseAJour.getAdresse()); organisation.setVille(organisationMiseAJour.getVille()); organisation.setRegion(organisationMiseAJour.getRegion()); organisation.setPays(organisationMiseAJour.getPays()); organisation.setCodePostal(organisationMiseAJour.getCodePostal()); organisation.setLatitude(organisationMiseAJour.getLatitude()); organisation.setLongitude(organisationMiseAJour.getLongitude()); organisation.setSiteWeb(organisationMiseAJour.getSiteWeb()); organisation.setLogo(organisationMiseAJour.getLogo()); organisation.setReseauxSociaux(organisationMiseAJour.getReseauxSociaux()); organisation.setObjectifs(organisationMiseAJour.getObjectifs()); organisation.setActivitesPrincipales(organisationMiseAJour.getActivitesPrincipales()); organisation.setCertifications(organisationMiseAJour.getCertifications()); organisation.setPartenaires(organisationMiseAJour.getPartenaires()); organisation.setNotes(organisationMiseAJour.getNotes()); if (organisationMiseAJour.getStatut() != null) { organisation.setStatut(organisationMiseAJour.getStatut()); } organisation.setTypeOrganisation(organisationMiseAJour.getTypeOrganisation()); organisation.setNiveauHierarchique( organisationMiseAJour.getNiveauHierarchique() != null ? organisationMiseAJour.getNiveauHierarchique() : 0); organisation.setNombreMembres( organisationMiseAJour.getNombreMembres() != null ? organisationMiseAJour.getNombreMembres() : 0); organisation.setNombreAdministrateurs( organisationMiseAJour.getNombreAdministrateurs() != null ? organisationMiseAJour.getNombreAdministrateurs() : 0); // Budget & Finances organisation.setBudgetAnnuel(organisationMiseAJour.getBudgetAnnuel()); organisation.setDevise( organisationMiseAJour.getDevise() != null ? organisationMiseAJour.getDevise() : defaultsService.getDevise()); organisation.setCotisationObligatoire( organisationMiseAJour.getCotisationObligatoire() != null ? organisationMiseAJour.getCotisationObligatoire() : false); organisation.setMontantCotisationAnnuelle(organisationMiseAJour.getMontantCotisationAnnuelle()); organisation.setOrganisationPublique( organisationMiseAJour.getOrganisationPublique() != null ? organisationMiseAJour.getOrganisationPublique() : true); organisation.setAccepteNouveauxMembres( organisationMiseAJour.getAccepteNouveauxMembres() != null ? organisationMiseAJour.getAccepteNouveauxMembres() : true); // Hiérarchie organisation.setOrganisationParente(organisationMiseAJour.getOrganisationParente()); organisation.marquerCommeModifie(utilisateur); LOG.infof("Organisation mise à jour avec succès: ID=%s", id); return organisation; } /** * Supprime une organisation * * @param id l'UUID de l'organisation * @param utilisateur l'utilisateur effectuant la suppression */ @Transactional public void supprimerOrganisation(UUID id, String utilisateur) { LOG.infof("Suppression de l'organisation ID: %s", id); Organisation organisation = organisationRepository .findByIdOptional(id) .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); // Vérifier qu'il n'y a pas de membres actifs if (organisation.getNombreMembres() > 0) { throw new IllegalStateException( "Impossible de supprimer une organisation avec des membres actifs"); } // Soft delete - marquer comme inactive organisation.setActif(false); organisation.setStatut("DISSOUTE"); organisation.marquerCommeModifie(utilisateur); LOG.infof("Organisation supprimée (soft delete) avec succès: ID=%s", id); } /** * Trouve une organisation par son ID * * @param id l'UUID de l'organisation * @return Optional contenant l'organisation si trouvée */ public Optional trouverParId(UUID id) { return organisationRepository.findByIdOptional(id); } /** * Trouve une organisation par son email * * @param email l'email de l'organisation * @return Optional contenant l'organisation si trouvée */ public Optional trouverParEmail(String email) { return organisationRepository.findByEmail(email); } /** * Liste les organisations auxquelles l'utilisateur connecté (membre) appartient. * Utilisé pour un administrateur d'organisation qui ne doit voir que son/ses organisation(s). * * @param emailUtilisateur email du principal (SecurityIdentity) * @return liste des organisations du membre, ou liste vide si membre non trouvé */ @Transactional public List listerOrganisationsPourUtilisateur(String emailUtilisateur) { if (emailUtilisateur == null || emailUtilisateur.isBlank()) { return List.of(); } return membreRepository.findByEmail(emailUtilisateur) .map(m -> m.getMembresOrganisations().stream() .map(mo -> mo.getOrganisation()) .distinct() .collect(Collectors.toList())) .orElse(List.of()); } /** * Associe un utilisateur (par email) à une organisation. * Réservé au SUPER_ADMIN. Crée un Membre minimal si aucun n'existe pour cet email, * puis crée le lien MembreOrganisation (idempotent si déjà associé). * * @param email email de l'utilisateur (doit correspondre à un compte Keycloak / Membre) * @param organisationId UUID de l'organisation * @throws NotFoundException si l'organisation n'existe pas */ @Transactional public void associerUtilisateurAOrganisation(String email, UUID organisationId) { if (email == null || email.isBlank()) { throw new IllegalArgumentException("L'email est obligatoire"); } if (organisationId == null) { throw new IllegalArgumentException("L'organisation est obligatoire"); } Organisation organisation = organisationRepository.findByIdOptional(organisationId) .orElseThrow(() -> new NotFoundException("Organisation non trouvée: " + organisationId)); String emailNorm = email.trim().toLowerCase(); Membre membre = membreRepository.findByEmail(emailNorm).orElseGet(() -> { Membre nouveau = creerMembreMinimalPourEmail(emailNorm); membreRepository.persist(nouveau); LOG.infof("Membre minimal créé pour associer l'utilisateur %s à l'organisation %s", emailNorm, organisation.getNom()); return nouveau; }); if (membreOrganisationRepository.findByMembreIdAndOrganisationId(membre.getId(), organisationId).isPresent()) { LOG.infof("L'utilisateur %s est déjà associé à l'organisation %s", emailNorm, organisation.getNom()); return; } MembreOrganisation mo = MembreOrganisation.builder() .membre(membre) .organisation(organisation) .statutMembre(StatutMembre.ACTIF) .dateAdhesion(LocalDate.now()) .build(); membreOrganisationRepository.persist(mo); organisation.ajouterMembre(); organisationRepository.persist(organisation); LOG.infof("Utilisateur %s associé à l'organisation %s (MembreOrganisation créé)", emailNorm, organisation.getNom()); } /** * Crée un Membre minimal à partir d'un email (pour associer un compte Keycloak sans fiche membre). */ private Membre creerMembreMinimalPourEmail(String email) { String partieLocale = email.contains("@") ? email.substring(0, email.indexOf('@')) : email; String prenom = partieLocale.contains(".") ? partieLocale.substring(0, partieLocale.indexOf('.')) : "Admin"; String nom = partieLocale.contains(".") ? partieLocale.substring(partieLocale.indexOf('.') + 1) : partieLocale; if (nom.isBlank()) nom = "Utilisateur"; if (prenom.isBlank()) prenom = "Admin"; prenom = prenom.substring(0, 1).toUpperCase() + (prenom.length() > 1 ? prenom.substring(1).toLowerCase() : ""); nom = nom.substring(0, 1).toUpperCase() + (nom.length() > 1 ? nom.substring(1).toLowerCase() : ""); String numeroMembre = "UF-ADM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); Membre m = Membre.builder() .email(email) .numeroMembre(numeroMembre) .prenom(prenom) .nom(nom) .dateNaissance(LocalDate.now().minusYears(25)) .statutCompte("ACTIF") .build(); m.setActif(Boolean.TRUE); // actif est dans BaseEntity, pas dans MembreBuilder return m; } /** * Liste toutes les organisations actives * * @return liste des organisations actives */ public List listerOrganisationsActives() { return organisationRepository.findAllActives(); } /** * Liste toutes les organisations actives avec pagination * * @param page numéro de page * @param size taille de la page * @return liste paginée des organisations actives */ public List listerOrganisationsActives(int page, int size) { return organisationRepository.findAllActives(Page.of(page, size), Sort.by("nom").ascending()); } /** * Compte le nombre d'organisations actives * * @return nombre total d'organisations actives */ public long compterOrganisationsActives() { return organisationRepository.countActives(); } /** * Recherche d'organisations par nom * * @param recherche terme de recherche * @param page numéro de page * @param size taille de la page * @return liste paginée des organisations correspondantes */ public List rechercherOrganisations(String recherche, int page, int size) { return organisationRepository.findByNomOrNomCourt( recherche, Page.of(page, size), Sort.by("nom").ascending()); } public long rechercherOrganisationsCount(String recherche) { if (recherche == null || recherche.trim().isEmpty()) { return organisationRepository.count(); } String pattern = "%" + recherche.trim().toLowerCase() + "%"; return organisationRepository.getEntityManager() .createQuery( "SELECT COUNT(o) FROM Organisation o WHERE LOWER(o.nom) LIKE :p OR LOWER(o.description) LIKE :p", Long.class) .setParameter("p", pattern) .getSingleResult(); } /** * Recherche avancée d'organisations * * @param nom nom (optionnel) * @param typeOrganisation type (optionnel) * @param statut statut (optionnel) * @param ville ville (optionnel) * @param region région (optionnel) * @param pays pays (optionnel) * @param page numéro de page * @param size taille de la page * @return liste filtrée des organisations */ public List rechercheAvancee( String nom, String typeOrganisation, String statut, String ville, String region, String pays, int page, int size) { return organisationRepository.rechercheAvancee( nom, typeOrganisation, statut, ville, region, pays, Page.of(page, size)); } /** * Active une organisation * * @param id l'ID de l'organisation * @param utilisateur l'utilisateur effectuant l'activation * @return l'organisation activée */ @Transactional public Organisation activerOrganisation(UUID id, String utilisateur) { LOG.infof("Activation de l'organisation ID: %s", id); Organisation organisation = organisationRepository .findByIdOptional(id) .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); organisation.activer(utilisateur); LOG.infof("Organisation activée avec succès: ID=%s", id); return organisation; } /** * Suspend une organisation * * @param id l'UUID de l'organisation * @param utilisateur l'utilisateur effectuant la suspension * @return l'organisation suspendue */ @Transactional public Organisation suspendreOrganisation(UUID id, String utilisateur) { LOG.infof("Suspension de l'organisation ID: %s", id); Organisation organisation = organisationRepository .findByIdOptional(id) .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); organisation.suspendre(utilisateur); LOG.infof("Organisation suspendue avec succès: ID=%s", id); return organisation; } /** * Obtient les statistiques des organisations (clés compatibles client DTO * StatistiquesAssociationDTO). * * @return map contenant les statistiques */ public Map obtenirStatistiques() { LOG.info("Calcul des statistiques des organisations"); long total = organisationRepository.count(); long actives = organisationRepository.countActives(); long inactives = total - actives; long suspendues = organisationRepository.countByStatut("SUSPENDUE"); long dissoutes = organisationRepository.countByStatut("DISSOLUE"); long nouvelles30Jours = organisationRepository.countNouvellesOrganisations(LocalDate.now().minusDays(30)); double tauxActivite = total > 0 ? (actives * 100.0 / total) : 0.0; List all = organisationRepository.listAll(); Map repartitionType = all.stream() .collect(Collectors.groupingBy( o -> o.getTypeOrganisation() != null ? o.getTypeOrganisation() : "NON_DEFINI", Collectors.counting())); Map repartitionRegion = adresseRepository .find("organisation IS NOT NULL AND region IS NOT NULL") .list() .stream() .collect(Collectors.groupingBy( a -> a.getRegion(), Collectors.counting())); Map map = new HashMap<>(); map.put("totalAssociations", total); map.put("associationsActives", actives); map.put("associationsInactives", inactives); map.put("associationsSuspendues", suspendues); map.put("associationsDissoutes", dissoutes); map.put("nouvellesAssociations30Jours", nouvelles30Jours); map.put("tauxActivite", tauxActivite); map.put("repartitionParType", repartitionType); map.put("repartitionParRegion", repartitionRegion); return map; } /** * Convertit une entité Organisation en DTO complet */ public OrganisationResponse convertToResponse(Organisation organisation) { if (organisation == null) { return null; } OrganisationResponse dto = new OrganisationResponse(); dto.setId(organisation.getId()); dto.setNom(organisation.getNom()); dto.setNomCourt(organisation.getNomCourt()); dto.setDescription(organisation.getDescription()); dto.setEmail(organisation.getEmail()); dto.setTelephone(organisation.getTelephone()); dto.setTelephoneSecondaire(organisation.getTelephoneSecondaire()); dto.setEmailSecondaire(organisation.getEmailSecondaire()); dto.setAdresse(organisation.getAdresse()); dto.setVille(organisation.getVille()); dto.setRegion(organisation.getRegion()); dto.setPays(organisation.getPays()); dto.setCodePostal(organisation.getCodePostal()); dto.setLatitude(organisation.getLatitude()); dto.setLongitude(organisation.getLongitude()); dto.setSiteWeb(organisation.getSiteWeb()); dto.setLogo(organisation.getLogo()); dto.setReseauxSociaux(organisation.getReseauxSociaux()); dto.setObjectifs(organisation.getObjectifs()); dto.setActivitesPrincipales(organisation.getActivitesPrincipales()); dto.setNombreMembres(organisation.getNombreMembres()); if (organisation.getId() != null) { // Compte dynamique des administrateurs (rôle ADMIN_ORGANISATION actif) // — le champ Organisation.nombreAdministrateurs n'est pas tenu à jour. long countAdmins = membreRoleRepository.countAdminsByOrganisationId(organisation.getId()); dto.setNombreAdministrateurs((int) countAdmins); long countEvenements = evenementRepository.countActifsByOrganisationId(organisation.getId()); dto.setNombreEvenements((int) countEvenements); } else { dto.setNombreAdministrateurs(0); dto.setNombreEvenements(0); } dto.setBudgetAnnuel(organisation.getBudgetAnnuel()); dto.setDevise(organisation.getDevise()); dto.setDateFondation(organisation.getDateFondation()); dto.setNumeroEnregistrement(organisation.getNumeroEnregistrement()); dto.setNiveauHierarchique(organisation.getNiveauHierarchique()); if (organisation.getOrganisationParente() != null) { dto.setOrganisationParenteId(organisation.getOrganisationParente().getId()); dto.setOrganisationParenteNom(organisation.getOrganisationParente().getNom()); } dto.setTypeOrganisation(organisation.getTypeOrganisation()); dto.setTypeAssociation(organisation.getTypeOrganisation()); dto.setStatut(organisation.getStatut()); // Résolution des libellés if (organisation.getTypeOrganisation() != null) { typeReferenceRepository.findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation()) .ifPresent(ref -> { dto.setTypeOrganisationLibelle(ref.getLibelle()); dto.setTypeLibelle(ref.getLibelle()); }); if (dto.getTypeLibelle() == null) { dto.setTypeLibelle(organisation.getTypeOrganisation()); } } if (organisation.getStatut() != null) { typeReferenceRepository.findByDomaineAndCode("STATUT_ORGANISATION", organisation.getStatut()) .ifPresent(ref -> { dto.setStatutLibelle(ref.getLibelle()); dto.setStatutSeverity(ref.getCouleur()); // ou severity si dispo }); } dto.setDateCreation(organisation.getDateCreation()); dto.setDateModification(organisation.getDateModification()); dto.setCreePar(organisation.getCreePar()); dto.setModifiePar(organisation.getModifiePar()); dto.setActif(organisation.getActif()); dto.setVersion(organisation.getVersion()); dto.setOrganisationPublique(organisation.getOrganisationPublique()); dto.setAccepteNouveauxMembres(organisation.getAccepteNouveauxMembres()); dto.setCotisationObligatoire(organisation.getCotisationObligatoire()); dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle()); return dto; } /** * Convertit une entité Organisation en Summary DTO */ public OrganisationSummaryResponse convertToSummaryResponse(Organisation organisation) { if (organisation == null) return null; String typeLibelle = organisation.getTypeOrganisation(); if (organisation.getTypeOrganisation() != null) { typeLibelle = typeReferenceRepository .findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation()) .map(dev.lions.unionflow.server.entity.TypeReference::getLibelle) .orElse(organisation.getTypeOrganisation()); } String statutLibelle = organisation.getStatut(); String statutSeverity = null; if (organisation.getStatut() != null) { var refOpt = typeReferenceRepository.findByDomaineAndCode("STATUT_ORGANISATION", organisation.getStatut()); if (refOpt.isPresent()) { statutLibelle = refOpt.get().getLibelle(); statutSeverity = refOpt.get().getCouleur(); } } return new OrganisationSummaryResponse( organisation.getId(), organisation.getNom(), organisation.getNomCourt(), organisation.getTypeOrganisation(), typeLibelle, organisation.getStatut(), statutLibelle, statutSeverity, organisation.getNombreMembres(), organisation.getActif()); } /** * Crée une entité Organisation depuis CreateOrganisationRequest */ public Organisation convertFromCreateRequest(CreateOrganisationRequest req) { if (req == null) return null; return Organisation.builder() .nom(req.nom()) .nomCourt(req.nomCourt()) .description(req.description()) .email(req.email()) .telephone(req.telephone()) .telephoneSecondaire(req.telephoneSecondaire()) .emailSecondaire(req.emailSecondaire()) .latitude(req.latitude()) .longitude(req.longitude()) .siteWeb(req.siteWeb()) .logo(req.logo()) .reseauxSociaux(req.reseauxSociaux()) .objectifs(req.objectifs()) .activitesPrincipales(req.activitesPrincipales()) .certifications(req.certifications()) .partenaires(req.partenaires()) .notes(req.notes()) .dateFondation(req.dateFondation()) .numeroEnregistrement(req.numeroEnregistrement()) .typeOrganisation(req.typeOrganisation() != null ? req.typeOrganisation() : "ASSOCIATION") .statut(req.statut() != null ? req.statut() : "ACTIVE") .budgetAnnuel(req.budgetAnnuel()) .devise(req.devise() != null ? req.devise() : defaultsService.getDevise()) .cotisationObligatoire(req.cotisationObligatoire() != null ? req.cotisationObligatoire() : false) .montantCotisationAnnuelle(req.montantCotisationAnnuelle()) .adresse(req.adresse()) .ville(req.ville()) .region(req.region()) .pays(req.pays()) .codePostal(req.codePostal()) .organisationPublique(req.organisationPublique() != null ? req.organisationPublique() : true) .accepteNouveauxMembres(req.accepteNouveauxMembres() != null ? req.accepteNouveauxMembres() : true) .build(); } /** * Crée une entité Organisation depuis UpdateOrganisationRequest */ public Organisation convertFromUpdateRequest(UpdateOrganisationRequest req) { if (req == null) return null; return Organisation.builder() .nom(req.nom()) .nomCourt(req.nomCourt()) .description(req.description()) .email(req.email()) .telephone(req.telephone()) .telephoneSecondaire(req.telephoneSecondaire()) .emailSecondaire(req.emailSecondaire()) .latitude(req.latitude()) .longitude(req.longitude()) .siteWeb(req.siteWeb()) .logo(req.logo()) .reseauxSociaux(req.reseauxSociaux()) .objectifs(req.objectifs()) .activitesPrincipales(req.activitesPrincipales()) .certifications(req.certifications()) .partenaires(req.partenaires()) .notes(req.notes()) .dateFondation(req.dateFondation()) .numeroEnregistrement(req.numeroEnregistrement()) .typeOrganisation(req.typeOrganisation()) .statut(req.statut()) .budgetAnnuel(req.budgetAnnuel()) .devise(req.devise() != null ? req.devise() : defaultsService.getDevise()) .cotisationObligatoire(req.cotisationObligatoire() != null ? req.cotisationObligatoire() : false) .montantCotisationAnnuelle(req.montantCotisationAnnuelle()) .adresse(req.adresse()) .ville(req.ville()) .region(req.region()) .pays(req.pays()) .codePostal(req.codePostal()) .organisationPublique(req.organisationPublique() != null ? req.organisationPublique() : true) .accepteNouveauxMembres(req.accepteNouveauxMembres() != null ? req.accepteNouveauxMembres() : true) .build(); } /** * Retourne la liste des organisations d'un membre (pour le sélecteur multi-org). * Inclut les infos nécessaires au sélecteur : id, nom, type, catégorie, modules, rôle du membre. */ public java.util.List> listerOrganisationsParMembre(java.util.UUID membreId) { java.util.List liens = membreOrganisationRepository.findOrganisationsActivesParMembre(membreId); return liens.stream().map(lien -> { Organisation org = lien.getOrganisation(); java.util.Map entry = new java.util.LinkedHashMap<>(); entry.put("organisationId", org.getId()); entry.put("nom", org.getNom()); entry.put("nomCourt", org.getNomCourt()); entry.put("typeOrganisation", org.getTypeOrganisation()); entry.put("categorieType", org.getCategorieType()); entry.put("modulesActifs", org.getModulesActifs()); entry.put("statut", org.getStatut()); entry.put("statutMembre", lien.getStatutMembre() != null ? lien.getStatutMembre().name() : null); entry.put("roleOrg", lien.getRoleOrg()); entry.put("dateAdhesion", lien.getDateAdhesion()); return entry; }).toList(); } }