diff --git a/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java b/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java index 75cd57e..9531108 100644 --- a/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java +++ b/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java @@ -40,6 +40,8 @@ public class AdminServiceTokenHeadersFactory implements ClientHeadersFactory { tokens.getAccessToken().length()); } catch (Exception e) { LOG.errorf("Impossible d'obtenir le token service account 'admin-service': %s", e.getMessage()); + throw new jakarta.ws.rs.ServiceUnavailableException( + "Service d'authentification interne indisponible: " + e.getMessage()); } return result; } diff --git a/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java b/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java index bdf373d..7de9043 100644 --- a/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java +++ b/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java @@ -77,6 +77,39 @@ public class FormuleAbonnement extends BaseEntity { @Column(name = "ordre_affichage", nullable = false) private Integer ordreAffichage = 0; + // ── Champs Option C (ajoutés en V19) ────────────────────────────────────── + + /** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */ + @Column(name = "plan_commercial", length = 30) + private String planCommercial; + + /** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */ + @Column(name = "niveau_reporting", length = 20) + private String niveauReporting; + + /** Accès à l'API REST (false pour les plans de base) */ + @Builder.Default + @Column(name = "api_access", nullable = false) + private Boolean apiAccess = false; + + /** Accès au module de fédération multi-org (ENTERPRISE uniquement) */ + @Builder.Default + @Column(name = "federation_access", nullable = false) + private Boolean federationAccess = false; + + /** Support prioritaire inclus */ + @Builder.Default + @Column(name = "support_prioritaire", nullable = false) + private Boolean supportPrioritaire = false; + + /** SLA garanti (ex: "99.0%", "99.9%") */ + @Column(name = "sla_garanti", length = 10) + private String slaGaranti; + + /** Nombre maximum d'administrateurs. NULL = illimité */ + @Column(name = "max_admins") + private Integer maxAdmins; + public boolean isIllimitee() { return maxMembres == null; } diff --git a/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java index 01ea064..05809e3 100644 --- a/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java +++ b/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java @@ -5,8 +5,10 @@ import dev.lions.unionflow.server.api.enums.membre.StatutMembre; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import lombok.*; /** @@ -77,6 +79,34 @@ public class MembreOrganisation extends BaseEntity { @JoinColumn(name = "approuve_par_id") private Membre approuvePar; + // ── Champs d'invitation (StatutMembre.INVITE) ────────────────────────────── + + /** Date à laquelle l'invitation a été envoyée. */ + @Column(name = "date_invitation") + private LocalDateTime dateInvitation; + + /** Date d'expiration de l'invitation (null = pas d'expiration). */ + @Column(name = "date_expiration_invitation") + private LocalDateTime dateExpirationInvitation; + + /** Token opaque utilisé dans le lien d'invitation envoyé par email. */ + @Column(name = "token_invitation", length = 64) + private String tokenInvitation; + + /** ID de l'administrateur qui a envoyé l'invitation. */ + @Column(name = "invite_par") + private UUID invitePar; + + /** Motif d'archivage (pour StatutMembre.ARCHIVE). */ + @Column(name = "motif_archivage", length = 500) + private String motifArchivage; + + // ── Rôle fonctionnel dans l'organisation ───────────────────────────────── + + /** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */ + @Column(name = "role_org", length = 50) + private String roleOrg; + // ── Relations ───────────────────────────────────────────────────────────── /** Rôles de ce membre dans cette organisation */ diff --git a/src/main/java/dev/lions/unionflow/server/entity/Organisation.java b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java index d4db270..d22f363 100644 --- a/src/main/java/dev/lions/unionflow/server/entity/Organisation.java +++ b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java @@ -197,6 +197,14 @@ public class Organisation extends BaseEntity { @Column(name = "accepte_nouveaux_membres", nullable = false) private Boolean accepteNouveauxMembres = true; + /** Catégorie du type d'organisation (ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION) */ + @Column(name = "categorie_type", length = 50) + private String categorieType; + + /** Modules activés pour cette organisation (liste CSV, ex: "MEMBRES,COTISATIONS,TONTINE") */ + @Column(name = "modules_actifs", length = 1000) + private String modulesActifs; + // Relations /** Adhésions des membres à cette organisation */ diff --git a/src/main/java/dev/lions/unionflow/server/entity/Role.java b/src/main/java/dev/lions/unionflow/server/entity/Role.java index 5bf52c4..4b742ea 100644 --- a/src/main/java/dev/lions/unionflow/server/entity/Role.java +++ b/src/main/java/dev/lions/unionflow/server/entity/Role.java @@ -56,6 +56,11 @@ public class Role extends BaseEntity { @Column(name = "type_role", nullable = false, length = 50) private String typeRole; + /** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */ + @Column(name = "categorie", length = 30) + @Builder.Default + private String categorie = "FONCTIONNEL"; + /** Organisation propriétaire (null pour rôles système) */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "organisation_id") diff --git a/src/main/java/dev/lions/unionflow/server/repository/MembreOrganisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/MembreOrganisationRepository.java index 8042ed5..012f77e 100644 --- a/src/main/java/dev/lions/unionflow/server/repository/MembreOrganisationRepository.java +++ b/src/main/java/dev/lions/unionflow/server/repository/MembreOrganisationRepository.java @@ -1,7 +1,9 @@ package dev.lions.unionflow.server.repository; +import dev.lions.unionflow.server.api.enums.membre.StatutMembre; import dev.lions.unionflow.server.entity.MembreOrganisation; import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -36,4 +38,51 @@ public class MembreOrganisationRepository extends BaseRepository findAllByOrganisationId(UUID organisationId) { return find("organisation.id = ?1 and membre.actif = true", organisationId).list(); } + + /** + * Trouve toutes les organisations auxquelles un membre est rattaché (multi-org). + */ + public List findAllByMembreId(UUID membreId) { + return find("membre.id = ?1", membreId).list(); + } + + /** + * Trouve toutes les organisations actives d'un membre (statut ACTIF). + */ + public List findOrganisationsActivesParMembre(UUID membreId) { + return find("membre.id = ?1 and statutMembre = 'ACTIF'", membreId).list(); + } + + /** + * Trouve toutes les invitations en attente dont la date d'expiration est dépassée. + */ + public List findInvitationsExpirees(LocalDateTime now) { + return find("statutMembre = ?1 and dateExpirationInvitation is not null and dateExpirationInvitation < ?2", + StatutMembre.INVITE, now).list(); + } + + /** + * Trouve les invitations dont la date d'expiration arrive dans les prochaines 24h. + * Utile pour envoyer un rappel avant expiration. + */ + public List findInvitationsExpirantBientot(LocalDateTime from, LocalDateTime to) { + return find("statutMembre = ?1 and dateExpirationInvitation >= ?2 and dateExpirationInvitation < ?3", + StatutMembre.INVITE, from, to).list(); + } + + /** + * Trouve tous les membres actifs d'une organisation (pour les rappels de cotisation). + */ + public List findMembresActifsParOrganisation(UUID organisationId) { + return find("organisation.id = ?1 and statutMembre = ?2", + organisationId, StatutMembre.ACTIF).list(); + } + + /** + * Trouve les membres en attente de validation depuis plus de N jours. + */ + public List findMembresEnAttenteDepuis(LocalDateTime avant) { + return find("statutMembre = ?1 and dateInvitation is not null and dateInvitation < ?2", + StatutMembre.EN_ATTENTE_VALIDATION, avant).list(); + } } diff --git a/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java b/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java index 822ffa9..961c477 100644 --- a/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java +++ b/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java @@ -80,4 +80,35 @@ public class RoleRepository implements PanacheRepositoryBase { return find("typeRole = ?1 AND actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending), typeRole) .list(); } + + /** + * Trouve les rôles par catégorie (PLATEFORME, FONCTIONNEL, METIER) + * + * @param categorie Catégorie du rôle + * @return Liste des rôles actifs de cette catégorie + */ + public List findByCategorie(String categorie) { + return find("categorie = ?1 AND actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending), categorie) + .list(); + } + + /** + * Trouve les rôles plateforme (SUPERADMIN, ORGADMIN, etc.) + * + * @return Liste des rôles plateforme actifs + */ + public List findRolesPlateformeActifs() { + return findByCategorie("PLATEFORME"); + } + + /** + * Trouve les rôles fonctionnels et métier (assignables dans une org) + * + * @return Liste des rôles assignables + */ + public List findRolesAssignables() { + return find("categorie IN ('FONCTIONNEL', 'METIER') AND actif = true", + Sort.by("niveauHierarchique", Sort.Direction.Ascending)) + .list(); + } } diff --git a/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java b/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java index 7d25a2b..d6c086d 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java @@ -27,7 +27,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag; @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Audit", description = "Gestion des logs d'audit") @Slf4j -@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) +@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) public class AuditResource { @Inject diff --git a/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java b/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java index 06058b9..9c1a27a 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java @@ -36,7 +36,7 @@ import java.util.Map; @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Dashboard", description = "APIs pour la gestion du dashboard") -@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"}) +@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER", "SUPER_ADMIN"}) public class DashboardResource { private static final Logger LOG = Logger.getLogger(DashboardResource.class); diff --git a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java index 3cc7cf0..ca4126f 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java @@ -9,6 +9,9 @@ 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.entity.Organisation; +import dev.lions.unionflow.server.entity.MembreOrganisation; +import dev.lions.unionflow.server.repository.MembreOrganisationRepository; +import dev.lions.unionflow.server.service.MemberLifecycleService; import dev.lions.unionflow.server.service.MembreKeycloakSyncService; import dev.lions.unionflow.server.service.MembreService; import dev.lions.unionflow.server.service.MembreSuiviService; @@ -68,6 +71,12 @@ public class MembreResource { @Inject OrganisationService organisationService; + @Inject + MemberLifecycleService memberLifecycleService; + + @Inject + MembreOrganisationRepository membreOrgRepository; + @Inject io.quarkus.security.identity.SecurityIdentity securityIdentity; @@ -75,6 +84,7 @@ public class MembreResource { JsonWebToken jwt; @GET + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR" }) @Operation(summary = "Lister les membres") @APIResponse(responseCode = "200", description = "Liste des membres avec pagination") public PagedResponse listerMembres( @@ -130,6 +140,7 @@ public class MembreResource { @GET @Path("/{id}") + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "MEMBRE", "USER" }) @Operation(summary = "Récupérer un membre par son ID") @APIResponse(responseCode = "200", description = "Membre trouvé") @APIResponse(responseCode = "404", description = "Membre non trouvé") @@ -190,6 +201,34 @@ public class MembreResource { return Response.ok(response).build(); } + /** + * Retourne la liste des organisations du membre connecté (pour le sélecteur multi-org). + * Inclut le type, la catégorie et les modules actifs de chaque organisation. + */ + @GET + @Path("/mes-organisations") + @RolesAllowed({ "USER", "MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN", "MODERATEUR" }) + @Operation(summary = "Organisations du membre connecté", + description = "Retourne la liste des organisations auxquelles le membre connecté appartient (multi-org)") + @APIResponse(responseCode = "200", description = "Liste des organisations") + public Response getMesOrganisations() { + String email = securityIdentity.getPrincipal().getName(); + try { + var membre = membreService.trouverParEmail(email); + if (membre.isEmpty()) { + return Response.ok(java.util.List.of()).build(); + } + // Charger les liens membre-organisation avec les infos d'org + var liens = organisationService.listerOrganisationsParMembre(membre.get().getId()); + return Response.ok(liens).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des organisations du membre %s", email); + return Response.serverError() + .entity(java.util.Map.of("error", "Erreur serveur")) + .build(); + } + } + /** Crée et active une fiche membre depuis les claims JWT lors du premier accès. */ private Membre autoProvisionnerMembre(String email) { String prenom = "Utilisateur"; @@ -307,6 +346,7 @@ public class MembreResource { @PUT @Path("/{id}") + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) @Operation(summary = "Mettre à jour un membre existant") @APIResponse(responseCode = "200", description = "Membre mis à jour avec succès") @APIResponse(responseCode = "404", description = "Membre non trouvé") @@ -334,6 +374,7 @@ public class MembreResource { @DELETE @Path("/{id}") + @RolesAllowed({ "ADMIN", "SUPER_ADMIN" }) @Operation(summary = "Désactiver un membre") @APIResponse(responseCode = "204", description = "Membre désactivé avec succès") @APIResponse(responseCode = "404", description = "Membre non trouvé") @@ -583,6 +624,7 @@ public class MembreResource { @POST @Path("/export/selection") + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) @Consumes(MediaType.APPLICATION_JSON) @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") @Operation(summary = "Exporter une sélection de membres en Excel") @@ -692,6 +734,7 @@ public class MembreResource { @GET @Path("/export") + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") @Operation(summary = "Exporter des membres en Excel, CSV ou PDF") @APIResponse(responseCode = "200", description = "Fichier exporté") @@ -873,4 +916,345 @@ public class MembreResource { return Response.ok(Map.of("count", membres.size())).build(); } + + // ========================================================================= + // Endpoints cycle de vie des adhésions (MemberLifecycleService) + // ========================================================================= + + /** + * Invite un membre existant à rejoindre une organisation. + * Crée un lien MembreOrganisation au statut INVITE avec token + expiration 7j. + */ + @PUT + @Path("/{membreId}/inviter-organisation") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Inviter un membre dans une organisation", + description = "Crée une invitation (statut INVITE) pour un membre existant. Token valable 7 jours.") + @APIResponse(responseCode = "200", description = "Invitation créée") + @APIResponse(responseCode = "404", description = "Membre ou organisation introuvable") + @APIResponse(responseCode = "409", description = "Membre déjà lié à cette organisation") + public Response inviterMembre( + @Parameter(description = "UUID du membre à inviter") @PathParam("membreId") UUID membreId, + @Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId, + @Parameter(description = "Rôle proposé (optionnel)") @QueryParam("roleOrg") String roleOrg) { + + if (organisationId == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "organisationId est obligatoire")).build(); + } + + Membre membre = membreService.trouverParId(membreId) + .orElseThrow(() -> new NotFoundException("Membre introuvable : " + membreId)); + Organisation organisation = organisationService.trouverParId(organisationId) + .orElseThrow(() -> new NotFoundException("Organisation introuvable : " + organisationId)); + + UUID adminId = resolveCurrentAdminId(); + try { + var lien = memberLifecycleService.inviterMembre(membre, organisation, adminId, roleOrg); + return Response.ok(Map.of( + "membreOrgId", lien.getId(), + "statut", lien.getStatutMembre(), + "tokenInvitation", lien.getTokenInvitation(), + "expiresAt", lien.getDateExpirationInvitation() + )).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Accepte une invitation via son token (INVITE → EN_ATTENTE_VALIDATION). + * Endpoint public — le membre clique sur le lien reçu par email. + */ + @POST + @Path("/accepter-invitation/{token}") + @PermitAll + @Operation(summary = "Accepter une invitation", + description = "Valide le token d'invitation et passe l'adhésion en EN_ATTENTE_VALIDATION.") + @APIResponse(responseCode = "200", description = "Invitation acceptée") + @APIResponse(responseCode = "400", description = "Token invalide ou expiré") + public Response accepterInvitation( + @Parameter(description = "Token d'invitation") @PathParam("token") String token) { + try { + var lien = memberLifecycleService.accepterInvitation(token); + return Response.ok(Map.of( + "membreOrgId", lien.getId(), + "statut", lien.getStatutMembre(), + "organisation", lien.getOrganisation().getNom() + )).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Active une adhésion (EN_ATTENTE_VALIDATION / INVITE / SUSPENDU → ACTIF). + */ + @PUT + @Path("/{membreOrgId}/activer-adhesion") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Activer une adhésion", + description = "Transitions autorisées : EN_ATTENTE_VALIDATION, INVITE, SUSPENDU → ACTIF.") + @APIResponse(responseCode = "200", description = "Adhésion activée") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + @APIResponse(responseCode = "409", description = "Transition non autorisée depuis le statut actuel") + public Response activerAdhesion( + @Parameter(description = "UUID du lien membre-organisation") @PathParam("membreOrgId") UUID membreOrgId, + Map body) { + + String motif = body != null ? body.get("motif") : null; + UUID adminId = resolveCurrentAdminId(); + try { + var lien = memberLifecycleService.activerMembre(membreOrgId, adminId, motif); + Map result = new HashMap<>(); + result.put("membreOrgId", lien.getId()); + result.put("statut", lien.getStatutMembre()); + result.put("dateChangementStatut", lien.getDateChangementStatut()); + return Response.ok(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Suspend une adhésion (ACTIF → SUSPENDU). + */ + @PUT + @Path("/{membreOrgId}/suspendre-adhesion") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Suspendre une adhésion", description = "Transition autorisée : ACTIF → SUSPENDU.") + @APIResponse(responseCode = "200", description = "Adhésion suspendue") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + @APIResponse(responseCode = "409", description = "Transition non autorisée") + public Response suspendrAdhesion( + @Parameter(description = "UUID du lien membre-organisation") @PathParam("membreOrgId") UUID membreOrgId, + Map body) { + + String motif = body != null ? body.get("motif") : null; + UUID adminId = resolveCurrentAdminId(); + try { + var lien = memberLifecycleService.suspendreMembre(membreOrgId, adminId, motif); + Map result = new HashMap<>(); + result.put("membreOrgId", lien.getId()); + result.put("statut", lien.getStatutMembre()); + result.put("dateChangementStatut", lien.getDateChangementStatut()); + return Response.ok(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Radie un membre d'une organisation (→ RADIE). + */ + @PUT + @Path("/{membreOrgId}/radier-adhesion") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Radier un membre d'une organisation") + @APIResponse(responseCode = "200", description = "Adhésion radiée") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + public Response radierAdhesion( + @Parameter(description = "UUID du lien membre-organisation") @PathParam("membreOrgId") UUID membreOrgId, + Map body) { + + String motif = body != null ? body.get("motif") : null; + UUID adminId = resolveCurrentAdminId(); + try { + var lien = memberLifecycleService.radierMembre(membreOrgId, adminId, motif); + Map result = new HashMap<>(); + result.put("membreOrgId", lien.getId()); + result.put("statut", lien.getStatutMembre()); + result.put("dateChangementStatut", lien.getDateChangementStatut()); + return Response.ok(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Archive une adhésion (→ ARCHIVE) sans supprimer l'historique. + */ + @PUT + @Path("/{membreOrgId}/archiver-adhesion") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Archiver une adhésion", + description = "Conserve l'historique sans supprimer le lien membre-organisation.") + @APIResponse(responseCode = "200", description = "Adhésion archivée") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + public Response archiverAdhesion( + @Parameter(description = "UUID du lien membre-organisation") @PathParam("membreOrgId") UUID membreOrgId, + Map body) { + + String motif = body != null ? body.get("motif") : null; + try { + var lien = memberLifecycleService.archiverMembre(membreOrgId, motif); + Map result = new HashMap<>(); + result.put("membreOrgId", lien.getId()); + result.put("statut", lien.getStatutMembre()); + result.put("dateChangementStatut", lien.getDateChangementStatut()); + return Response.ok(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())).build(); + } + } + + // ========================================================================= + // Endpoints lifecycle par membreId + organisationId (sans membreOrgId) + // ========================================================================= + + /** + * Retourne le statut d'adhésion d'un membre dans une organisation. + * Utilisé par le profil membre pour afficher les boutons d'action contextuels. + */ + @GET + @Path("/{membreId}/adhesion") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"}) + @Operation(summary = "Statut d'adhésion d'un membre dans une organisation") + @APIResponse(responseCode = "200", description = "Statut d'adhésion") + @APIResponse(responseCode = "404", description = "Aucun lien membre-organisation trouvé") + public Response getAdhesionStatut( + @Parameter(description = "UUID du membre") @PathParam("membreId") UUID membreId, + @Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId) { + + if (organisationId == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "organisationId requis")).build(); + } + return membreOrgRepository.findByMembreIdAndOrganisationId(membreId, organisationId) + .map(lien -> Response.ok(Map.of( + "membreOrgId", lien.getId(), + "statut", lien.getStatutMembre(), + "dateInvitation", lien.getDateInvitation() != null ? lien.getDateInvitation().toString() : "", + "dateExpiration", lien.getDateExpirationInvitation() != null ? lien.getDateExpirationInvitation().toString() : "", + "roleOrg", lien.getRoleOrg() != null ? lien.getRoleOrg() : "", + "motifStatut", lien.getMotifStatut() != null ? lien.getMotifStatut() : "" + )).build()) + .orElse(Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Aucune adhésion trouvée")).build()); + } + + /** + * Active l'adhésion d'un membre (EN_ATTENTE/INVITE/SUSPENDU → ACTIF) + * en passant par membreId + organisationId plutôt que membreOrgId. + */ + @PUT + @Path("/{membreId}/adhesion/activer") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Activer l'adhésion par membreId + organisationId") + @APIResponse(responseCode = "200", description = "Adhésion activée") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + public Response activerAdhesionParMembre( + @Parameter(description = "UUID du membre") @PathParam("membreId") UUID membreId, + @Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId, + Map body) { + + if (organisationId == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "organisationId requis")).build(); + } + MembreOrganisation lien = membreOrgRepository + .findByMembreIdAndOrganisationId(membreId, organisationId) + .orElse(null); + if (lien == null) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Aucune adhésion trouvée")).build(); + } + String motif = body != null ? body.get("motif") : null; + UUID adminId = resolveCurrentAdminId(); + try { + var updated = memberLifecycleService.activerMembre(lien.getId(), adminId, motif); + return Response.ok(Map.of("statut", updated.getStatutMembre())).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.CONFLICT).entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Suspend l'adhésion d'un membre (ACTIF → SUSPENDU) par membreId + organisationId. + */ + @PUT + @Path("/{membreId}/adhesion/suspendre") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Suspendre l'adhésion par membreId + organisationId") + @APIResponse(responseCode = "200", description = "Adhésion suspendue") + @APIResponse(responseCode = "404", description = "Lien membre-organisation introuvable") + public Response suspendrAdhesionParMembre( + @Parameter(description = "UUID du membre") @PathParam("membreId") UUID membreId, + @Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId, + Map body) { + + if (organisationId == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "organisationId requis")).build(); + } + MembreOrganisation lien = membreOrgRepository + .findByMembreIdAndOrganisationId(membreId, organisationId) + .orElse(null); + if (lien == null) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Aucune adhésion trouvée")).build(); + } + String motif = body != null ? body.get("motif") : null; + UUID adminId = resolveCurrentAdminId(); + try { + var updated = memberLifecycleService.suspendreMembre(lien.getId(), adminId, motif); + return Response.ok(Map.of("statut", updated.getStatutMembre())).build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.CONFLICT).entity(Map.of("error", e.getMessage())).build(); + } + } + + /** + * Radie un membre d'une organisation par membreId + organisationId. + */ + @PUT + @Path("/{membreId}/adhesion/radier") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) + @Operation(summary = "Radier par membreId + organisationId") + @APIResponse(responseCode = "200", description = "Adhésion radiée") + public Response radierAdhesionParMembre( + @Parameter(description = "UUID du membre") @PathParam("membreId") UUID membreId, + @Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId, + Map body) { + + if (organisationId == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "organisationId requis")).build(); + } + MembreOrganisation lien = membreOrgRepository + .findByMembreIdAndOrganisationId(membreId, organisationId) + .orElse(null); + if (lien == null) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Aucune adhésion trouvée")).build(); + } + String motif = body != null ? body.get("motif") : null; + var updated = memberLifecycleService.radierMembre(lien.getId(), resolveCurrentAdminId(), motif); + return Response.ok(Map.of("statut", updated.getStatutMembre())).build(); + } + + /** Résout l'UUID de l'admin connecté depuis le JWT subject. */ + private UUID resolveCurrentAdminId() { + try { + String sub = jwt != null ? jwt.getSubject() : null; + return sub != null ? UUID.fromString(sub) : null; + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java b/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java index fcfd26f..06c72c9 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java @@ -29,7 +29,7 @@ import dev.lions.unionflow.server.repository.MembreRepository; @Path("/api/notifications") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) -@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) +@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "MEMBRE", "USER" }) @Tag(name = "Notifications", description = "Gestion des notifications : envoi, templates et notifications groupées") public class NotificationResource { @@ -99,7 +99,7 @@ public class NotificationResource { * @return Template créé */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) @Path("/templates") public Response creerTemplate(@Valid CreateTemplateNotificationRequest request) { try { @@ -128,7 +128,7 @@ public class NotificationResource { * @return Notification créée */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) public Response creerNotification(@Valid CreateNotificationRequest request) { try { NotificationResponse result = notificationService.creerNotification(request); @@ -148,7 +148,7 @@ public class NotificationResource { * @return Notification mise à jour */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "MEMBRE", "USER" }) @Path("/{id}/marquer-lue") public Response marquerCommeLue(@PathParam("id") UUID id) { try { @@ -260,7 +260,7 @@ public class NotificationResource { * @return Nombre de notifications créées */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) @Path("/groupees") public Response envoyerNotificationsGroupees(NotificationGroupeeRequest request) { try { diff --git a/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java index 6576f05..2226110 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java @@ -7,6 +7,7 @@ import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResp import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.service.KeycloakService; +import dev.lions.unionflow.server.service.OrganisationModuleService; import dev.lions.unionflow.server.service.OrganisationService; import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; @@ -53,6 +54,8 @@ public class OrganisationResource { @Inject SecurityIdentity securityIdentity; + @Inject OrganisationModuleService organisationModuleService; + @Inject dev.lions.unionflow.server.repository.MembreOrganisationRepository membreOrganisationRepository; @@ -245,7 +248,7 @@ public class OrganisationResource { /** Met à jour une organisation */ @PUT - @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"}) + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"}) @Path("/{id}") @Operation( @@ -493,4 +496,25 @@ public class OrganisationResource { .build(); } } + + /** Retourne les modules actifs pour une organisation (déterminés par son type) */ + @GET + @Path("/{id}/modules-actifs") + @Operation( + summary = "Modules actifs de l'organisation", + description = "Retourne la liste des modules disponibles selon le type de l'organisation (Option C)") + @APIResponse(responseCode = "200", description = "Liste des modules actifs") + @APIResponse(responseCode = "404", description = "Organisation non trouvée") + public Response getModulesActifs( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { + try { + OrganisationModuleService.ModulesActifsResponse result = organisationModuleService.getModulesActifsResponse(id); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des modules actifs pour l'organisation %s", id); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } } diff --git a/src/main/java/dev/lions/unionflow/server/resource/agricole/CampagneAgricoleResource.java b/src/main/java/dev/lions/unionflow/server/resource/agricole/CampagneAgricoleResource.java index 5d6330a..199ae59 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/agricole/CampagneAgricoleResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/agricole/CampagneAgricoleResource.java @@ -3,6 +3,7 @@ package dev.lions.unionflow.server.resource.agricole; import dev.lions.unionflow.server.api.dto.agricole.CampagneAgricoleDTO; import dev.lions.unionflow.server.service.agricole.CampagneAgricoleService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -16,13 +17,14 @@ import java.util.UUID; @Path("/api/v1/agricole/campagnes") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("AGRICULTURE") public class CampagneAgricoleResource { @Inject CampagneAgricoleService campagneAgricoleService; @POST - @RolesAllowed({ "admin", "admin_organisation", "coop_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "COOP_RESP" }) public Response creerCampagne(@Valid CampagneAgricoleDTO dto) { CampagneAgricoleDTO response = campagneAgricoleService.creerCampagne(dto); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -30,7 +32,7 @@ public class CampagneAgricoleResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "coop_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "COOP_RESP", "MEMBRE", "USER" }) public Response getCampagneById(@PathParam("id") UUID id) { CampagneAgricoleDTO response = campagneAgricoleService.getCampagneById(id); return Response.ok(response).build(); @@ -38,7 +40,7 @@ public class CampagneAgricoleResource { @GET @Path("/cooperative/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "coop_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "COOP_RESP" }) public Response getCampagnesByCooperative(@PathParam("organisationId") UUID organisationId) { List response = campagneAgricoleService.getCampagnesByCooperative(organisationId); return Response.ok(response).build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/collectefonds/CampagneCollecteResource.java b/src/main/java/dev/lions/unionflow/server/resource/collectefonds/CampagneCollecteResource.java index 70365bd..8e91dab 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/collectefonds/CampagneCollecteResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/collectefonds/CampagneCollecteResource.java @@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO; import dev.lions.unionflow.server.service.collectefonds.CampagneCollecteService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -17,6 +18,7 @@ import java.util.UUID; @Path("/api/v1/collectefonds/campagnes") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("COLLECTE_FONDS") public class CampagneCollecteResource { @Inject @@ -24,7 +26,7 @@ public class CampagneCollecteResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getCampagneById(@PathParam("id") UUID id) { CampagneCollecteResponse response = campagneCollecteService.getCampagneById(id); return Response.ok(response).build(); @@ -32,7 +34,7 @@ public class CampagneCollecteResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) public Response getCampagnesByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = campagneCollecteService.getCampagnesByOrganisation(organisationId); return Response.ok(response).build(); @@ -40,7 +42,7 @@ public class CampagneCollecteResource { @POST @Path("/{id}/contribuer") - @RolesAllowed({ "membre_actif" }) + @RolesAllowed({ "MEMBRE", "USER" }) public Response contribuer(@PathParam("id") UUID id, @Valid ContributionCollecteDTO dto) { ContributionCollecteDTO response = campagneCollecteService.contribuer(id, dto); return Response.status(Response.Status.CREATED).entity(response).build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/culte/DonReligieuxResource.java b/src/main/java/dev/lions/unionflow/server/resource/culte/DonReligieuxResource.java index edcabe2..82ba72b 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/culte/DonReligieuxResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/culte/DonReligieuxResource.java @@ -3,6 +3,7 @@ package dev.lions.unionflow.server.resource.culte; import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO; import dev.lions.unionflow.server.service.culte.DonReligieuxService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -16,13 +17,14 @@ import java.util.UUID; @Path("/api/v1/culte/dons") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("CULTE_DONS") public class DonReligieuxResource { @Inject DonReligieuxService donReligieuxService; @POST - @RolesAllowed({ "membre_actif", "admin", "admin_organisation" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response enregistrerDon(@Valid DonReligieuxDTO dto) { DonReligieuxDTO response = donReligieuxService.enregistrerDon(dto); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -30,7 +32,7 @@ public class DonReligieuxResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "culte_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "CULTE_RESP", "MEMBRE", "USER" }) public Response getDonById(@PathParam("id") UUID id) { DonReligieuxDTO response = donReligieuxService.getDonById(id); return Response.ok(response).build(); @@ -38,7 +40,7 @@ public class DonReligieuxResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "culte_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "CULTE_RESP" }) public Response getDonsByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = donReligieuxService.getDonsByOrganisation(organisationId); return Response.ok(response).build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/gouvernance/EchelonOrganigrammeResource.java b/src/main/java/dev/lions/unionflow/server/resource/gouvernance/EchelonOrganigrammeResource.java index f7471b1..b2a54cb 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/gouvernance/EchelonOrganigrammeResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/gouvernance/EchelonOrganigrammeResource.java @@ -22,7 +22,7 @@ public class EchelonOrganigrammeResource { EchelonOrganigrammeService echelonOrganigrammeService; @POST - @RolesAllowed({ "admin", "admin_organisation" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" }) public Response creerEchelon(@Valid EchelonOrganigrammeDTO dto) { EchelonOrganigrammeDTO response = echelonOrganigrammeService.creerEchelon(dto); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -30,7 +30,7 @@ public class EchelonOrganigrammeResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getEchelonById(@PathParam("id") UUID id) { EchelonOrganigrammeDTO response = echelonOrganigrammeService.getEchelonById(id); return Response.ok(response).build(); @@ -38,7 +38,7 @@ public class EchelonOrganigrammeResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getOrganigrammeByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = echelonOrganigrammeService .getOrganigrammeByOrganisation(organisationId); diff --git a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/credit/DemandeCreditResource.java b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/credit/DemandeCreditResource.java index 516ac5e..513f337 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/credit/DemandeCreditResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/credit/DemandeCreditResource.java @@ -5,6 +5,7 @@ import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse; import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutDemandeCredit; import dev.lions.unionflow.server.service.mutuelle.credit.DemandeCreditService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -20,13 +21,14 @@ import java.util.UUID; @Path("/api/v1/mutuelle/credits") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("CREDIT") public class DemandeCreditResource { @Inject DemandeCreditService demandeCreditService; @POST - @RolesAllowed({ "membre_actif" }) + @RolesAllowed({ "MEMBRE", "USER" }) public Response soumettreDemande(@Valid DemandeCreditRequest request) { DemandeCreditResponse response = demandeCreditService.soumettreDemande(request); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -34,7 +36,7 @@ public class DemandeCreditResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "mutuelle_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getDemandeById(@PathParam("id") UUID id) { DemandeCreditResponse response = demandeCreditService.getDemandeById(id); return Response.ok(response).build(); @@ -42,7 +44,7 @@ public class DemandeCreditResource { @GET @Path("/membre/{membreId}") - @RolesAllowed({ "admin", "admin_organisation", "mutuelle_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getDemandesByMembre(@PathParam("membreId") UUID membreId) { List response = demandeCreditService.getDemandesByMembre(membreId); return Response.ok(response).build(); @@ -50,7 +52,7 @@ public class DemandeCreditResource { @PATCH @Path("/{id}/statut") - @RolesAllowed({ "admin", "admin_organisation", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response changerStatut( @PathParam("id") UUID id, @QueryParam("statut") StatutDemandeCredit statut, @@ -64,7 +66,7 @@ public class DemandeCreditResource { @POST @Path("/{id}/approbation") - @RolesAllowed({ "admin", "admin_organisation", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response approuver( @PathParam("id") UUID id, @QueryParam("montant") BigDecimal montant, @@ -77,7 +79,7 @@ public class DemandeCreditResource { @POST @Path("/{id}/decaissement") - @RolesAllowed({ "admin", "admin_organisation", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response decaisser( @PathParam("id") UUID id, @QueryParam("datePremiereEcheance") String datePremiereEcheance) { diff --git a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/CompteEpargneResource.java b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/CompteEpargneResource.java index 8e685b8..18c06ce 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/CompteEpargneResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/CompteEpargneResource.java @@ -5,6 +5,7 @@ import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne; import dev.lions.unionflow.server.service.mutuelle.epargne.CompteEpargneService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -18,13 +19,14 @@ import java.util.UUID; @Path("/api/v1/epargne/comptes") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("EPARGNE") public class CompteEpargneResource { @Inject CompteEpargneService compteEpargneService; @POST - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response creerCompte(@Valid CompteEpargneRequest request) { CompteEpargneResponse compte = compteEpargneService.creerCompte(request); return Response.status(Response.Status.CREATED).entity(compte).build(); @@ -32,7 +34,7 @@ public class CompteEpargneResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "membre_actif", "MEMBRE", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getCompteById(@PathParam("id") UUID id) { CompteEpargneResponse compte = compteEpargneService.getCompteById(id); return Response.ok(compte).build(); @@ -40,7 +42,7 @@ public class CompteEpargneResource { @GET @Path("/mes-comptes") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "membre_actif", "MEMBRE", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getMesComptes() { List comptes = compteEpargneService.getMesComptes(); return Response.ok(comptes).build(); @@ -48,7 +50,7 @@ public class CompteEpargneResource { @GET @Path("/membre/{membreId}") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "membre_actif", "MEMBRE", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getComptesByMembre(@PathParam("membreId") UUID membreId) { List comptes = compteEpargneService.getComptesByMembre(membreId); return Response.ok(comptes).build(); @@ -56,7 +58,7 @@ public class CompteEpargneResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response getComptesByOrganisation(@PathParam("organisationId") UUID organisationId) { List comptes = compteEpargneService.getComptesByOrganisation(organisationId); return Response.ok(comptes).build(); @@ -64,7 +66,7 @@ public class CompteEpargneResource { @PATCH @Path("/{id}/statut") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP" }) public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutCompteEpargne statut) { if (statut == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/TransactionEpargneResource.java b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/TransactionEpargneResource.java index ee47d00..8150584 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/TransactionEpargneResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/mutuelle/epargne/TransactionEpargneResource.java @@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneReq import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse; import dev.lions.unionflow.server.service.mutuelle.epargne.TransactionEpargneService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -17,13 +18,14 @@ import java.util.UUID; @Path("/api/v1/epargne/transactions") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("EPARGNE") public class TransactionEpargneResource { @Inject TransactionEpargneService transactionEpargneService; @POST - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "MEMBRE", "MEMBRE_ACTIF", "membre_actif", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response executerTransaction(@Valid TransactionEpargneRequest request) { TransactionEpargneResponse transaction = transactionEpargneService.executerTransaction(request); return Response.status(Response.Status.CREATED).entity(transaction).build(); @@ -31,7 +33,7 @@ public class TransactionEpargneResource { @POST @Path("/transfert") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "membre_actif", "MEMBRE_ACTIF", "MEMBRE", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response transferer(@Valid TransactionEpargneRequest request) { TransactionEpargneResponse transaction = transactionEpargneService.transferer(request); return Response.status(Response.Status.CREATED).entity(transaction).build(); @@ -39,7 +41,7 @@ public class TransactionEpargneResource { @GET @Path("/compte/{compteId}") - @RolesAllowed({ "admin", "admin_organisation", "ADMIN", "ADMIN_ORGANISATION", "mutuelle_resp", "membre_actif", "MEMBRE_ACTIF", "MEMBRE", "USER" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" }) public Response getTransactionsByCompte(@PathParam("compteId") UUID compteId) { List transactions = transactionEpargneService.getTransactionsByCompte(compteId); return Response.ok(transactions).build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/ong/ProjetOngResource.java b/src/main/java/dev/lions/unionflow/server/resource/ong/ProjetOngResource.java index 7104fee..9b854f8 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/ong/ProjetOngResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/ong/ProjetOngResource.java @@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.dto.ong.ProjetOngDTO; import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng; import dev.lions.unionflow.server.service.ong.ProjetOngService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -17,13 +18,14 @@ import java.util.UUID; @Path("/api/v1/ong/projets") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("PROJETS_ONG") public class ProjetOngResource { @Inject ProjetOngService projetOngService; @POST - @RolesAllowed({ "admin", "admin_organisation", "ong_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "ONG_RESP" }) public Response creerProjet(@Valid ProjetOngDTO dto) { ProjetOngDTO response = projetOngService.creerProjet(dto); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -31,7 +33,7 @@ public class ProjetOngResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getProjetById(@PathParam("id") UUID id) { ProjetOngDTO response = projetOngService.getProjetById(id); return Response.ok(response).build(); @@ -39,7 +41,7 @@ public class ProjetOngResource { @GET @Path("/ong/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "ong_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "ONG_RESP" }) public Response getProjetsByOng(@PathParam("organisationId") UUID organisationId) { List response = projetOngService.getProjetsByOng(organisationId); return Response.ok(response).build(); @@ -47,7 +49,7 @@ public class ProjetOngResource { @PATCH @Path("/{id}/statut") - @RolesAllowed({ "admin", "admin_organisation", "ong_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "ONG_RESP" }) public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutProjetOng statut) { if (statut == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/registre/AgrementProfessionnelResource.java b/src/main/java/dev/lions/unionflow/server/resource/registre/AgrementProfessionnelResource.java index d82bd75..eeb10c7 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/registre/AgrementProfessionnelResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/registre/AgrementProfessionnelResource.java @@ -3,6 +3,7 @@ package dev.lions.unionflow.server.resource.registre; import dev.lions.unionflow.server.api.dto.registre.AgrementProfessionnelDTO; import dev.lions.unionflow.server.service.registre.AgrementProfessionnelService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -16,13 +17,14 @@ import java.util.UUID; @Path("/api/v1/registre/agrements") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("REGISTRE_AGREMENT") public class AgrementProfessionnelResource { @Inject AgrementProfessionnelService agrementProfessionnelService; @POST - @RolesAllowed({ "admin", "admin_organisation", "registre_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "REGISTRE_RESP" }) public Response enregistrerAgrement(@Valid AgrementProfessionnelDTO dto) { AgrementProfessionnelDTO response = agrementProfessionnelService.enregistrerAgrement(dto); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -30,7 +32,7 @@ public class AgrementProfessionnelResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getAgrementById(@PathParam("id") UUID id) { AgrementProfessionnelDTO response = agrementProfessionnelService.getAgrementById(id); return Response.ok(response).build(); @@ -38,7 +40,7 @@ public class AgrementProfessionnelResource { @GET @Path("/membre/{membreId}") - @RolesAllowed({ "admin", "admin_organisation", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) public Response getAgrementsByMembre(@PathParam("membreId") UUID membreId) { List response = agrementProfessionnelService.getAgrementsByMembre(membreId); return Response.ok(response).build(); @@ -46,7 +48,7 @@ public class AgrementProfessionnelResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "registre_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "REGISTRE_RESP" }) public Response getAgrementsByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = agrementProfessionnelService .getAgrementsByOrganisation(organisationId); diff --git a/src/main/java/dev/lions/unionflow/server/resource/tontine/TontineResource.java b/src/main/java/dev/lions/unionflow/server/resource/tontine/TontineResource.java index 5d9d5f9..abbb62a 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/tontine/TontineResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/tontine/TontineResource.java @@ -5,6 +5,7 @@ import dev.lions.unionflow.server.api.dto.tontine.TontineResponse; import dev.lions.unionflow.server.api.enums.tontine.StatutTontine; import dev.lions.unionflow.server.service.tontine.TontineService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -18,13 +19,14 @@ import java.util.UUID; @Path("/api/v1/tontines") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("TONTINE") public class TontineResource { @Inject TontineService tontineService; @POST - @RolesAllowed({ "admin", "admin_organisation", "tontine_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" }) public Response creerTontine(@Valid TontineRequest request) { TontineResponse response = tontineService.creerTontine(request); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -32,7 +34,7 @@ public class TontineResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "tontine_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP", "MEMBRE", "USER" }) public Response getTontineById(@PathParam("id") UUID id) { TontineResponse response = tontineService.getTontineById(id); return Response.ok(response).build(); @@ -40,7 +42,7 @@ public class TontineResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "tontine_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" }) public Response getTontinesByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = tontineService.getTontinesByOrganisation(organisationId); return Response.ok(response).build(); @@ -48,7 +50,7 @@ public class TontineResource { @PATCH @Path("/{id}/statut") - @RolesAllowed({ "admin", "admin_organisation", "tontine_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" }) public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutTontine statut) { if (statut == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build(); diff --git a/src/main/java/dev/lions/unionflow/server/resource/vote/CampagneVoteResource.java b/src/main/java/dev/lions/unionflow/server/resource/vote/CampagneVoteResource.java index 4de6fa9..71e7b3f 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/vote/CampagneVoteResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/vote/CampagneVoteResource.java @@ -6,6 +6,7 @@ import dev.lions.unionflow.server.api.dto.vote.CandidatDTO; import dev.lions.unionflow.server.api.enums.vote.StatutVote; import dev.lions.unionflow.server.service.vote.CampagneVoteService; +import dev.lions.unionflow.server.security.RequiresModule; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.validation.Valid; @@ -19,13 +20,14 @@ import java.util.UUID; @Path("/api/v1/vote/campagnes") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@RequiresModule("VOTES") public class CampagneVoteResource { @Inject CampagneVoteService campagneVoteService; @POST - @RolesAllowed({ "admin", "admin_organisation", "vote_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "VOTE_RESP" }) public Response creerCampagne(@Valid CampagneVoteRequest request) { CampagneVoteResponse response = campagneVoteService.creerCampagne(request); return Response.status(Response.Status.CREATED).entity(response).build(); @@ -33,7 +35,7 @@ public class CampagneVoteResource { @GET @Path("/{id}") - @RolesAllowed({ "admin", "admin_organisation", "vote_resp", "membre_actif" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "VOTE_RESP", "MEMBRE", "USER" }) public Response getCampagneById(@PathParam("id") UUID id) { CampagneVoteResponse response = campagneVoteService.getCampagneById(id); return Response.ok(response).build(); @@ -41,7 +43,7 @@ public class CampagneVoteResource { @GET @Path("/organisation/{organisationId}") - @RolesAllowed({ "admin", "admin_organisation", "vote_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "VOTE_RESP" }) public Response getCampagnesByOrganisation(@PathParam("organisationId") UUID organisationId) { List response = campagneVoteService.getCampagnesByOrganisation(organisationId); return Response.ok(response).build(); @@ -49,7 +51,7 @@ public class CampagneVoteResource { @PATCH @Path("/{id}/statut") - @RolesAllowed({ "admin", "admin_organisation", "vote_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "VOTE_RESP" }) public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutVote statut) { if (statut == null) { return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build(); @@ -60,7 +62,7 @@ public class CampagneVoteResource { @POST @Path("/{id}/candidats") - @RolesAllowed({ "admin", "admin_organisation", "vote_resp" }) + @RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "VOTE_RESP" }) public Response ajouterCandidat(@PathParam("id") UUID id, @Valid CandidatDTO dto) { CandidatDTO response = campagneVoteService.ajouterCandidat(id, dto); return Response.status(Response.Status.CREATED).entity(response).build(); diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java index 5f5272b..1b9cf7a 100644 --- a/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java +++ b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java @@ -620,7 +620,7 @@ public class MembreImportExportService { } if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { row.createCell(colNum++) - .setCellValue(membre.getAssociationNom() != null ? membre.getAssociationNom() : ""); + .setCellValue(membre.getOrganisationNom() != null ? membre.getOrganisationNom() : ""); } } @@ -701,8 +701,8 @@ public class MembreImportExportService { // Organisations distinctes long organisationsDistinctes = membres.stream() - .filter(m -> m.getAssociationNom() != null) - .map(MembreResponse::getAssociationNom) + .filter(m -> m.getOrganisationNom() != null) + .map(MembreResponse::getOrganisationNom) .distinct() .count(); @@ -862,7 +862,7 @@ public class MembreImportExportService { values.add(membre.getStatutCompte() != null ? membre.getStatutCompte() : ""); } if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { - values.add(Objects.toString(membre.getAssociationNom(), "")); + values.add(Objects.toString(membre.getOrganisationNom(), "")); } printer.printRecord(values); @@ -964,7 +964,7 @@ public class MembreImportExportService { table.addCell(createDataCell(membre.getStatutCompte() != null ? membre.getStatutCompte() : "", dataFont)); } if (inclureOrg) { - table.addCell(createDataCell(membre.getAssociationNom() != null ? membre.getAssociationNom() : "", dataFont)); + table.addCell(createDataCell(membre.getOrganisationNom() != null ? membre.getOrganisationNom() : "", dataFont)); } } @@ -1030,8 +1030,8 @@ public class MembreImportExportService { .equals(m.getStatutCompte())) .count(); long organisationsDistinctes = membres.stream() - .filter(m -> m.getAssociationNom() != null) - .map(MembreResponse::getAssociationNom) + .filter(m -> m.getOrganisationNom() != null) + .map(MembreResponse::getOrganisationNom) .distinct() .count(); diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreService.java b/src/main/java/dev/lions/unionflow/server/service/MembreService.java index 56a37b5..d934b7c 100644 --- a/src/main/java/dev/lions/unionflow/server/service/MembreService.java +++ b/src/main/java/dev/lions/unionflow/server/service/MembreService.java @@ -7,6 +7,7 @@ import dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse; import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; +import dev.lions.unionflow.server.entity.FormuleAbonnement; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.repository.MembreRepository; import io.quarkus.panache.common.Page; @@ -200,6 +201,29 @@ public class MembreService { Membre membre = membreRepository.findByIdOptional(membreId) .orElseThrow(() -> new jakarta.ws.rs.NotFoundException("Membre non trouvé avec l'ID: " + membreId)); + // Vérifier le quota d'administrateurs selon la formule souscrite + membreOrganisationRepository.findFirstByMembreId(membreId).ifPresent(mo -> { + UUID orgId = mo.getOrganisation().getId(); + entityManager.createQuery( + "SELECT s FROM SouscriptionOrganisation s WHERE s.organisation.id = :orgId AND s.statut = 'ACTIVE'", + dev.lions.unionflow.server.entity.SouscriptionOrganisation.class) + .setParameter("orgId", orgId) + .getResultStream().findFirst().ifPresent(souscription -> { + FormuleAbonnement formule = souscription.getFormule(); + if (formule != null && formule.getMaxAdmins() != null) { + long adminCount = entityManager.createQuery( + "SELECT COUNT(mr) FROM MembreRole mr WHERE mr.organisation.id = :orgId " + + "AND mr.role.code = 'ORGADMIN' AND mr.actif = true", Long.class) + .setParameter("orgId", orgId).getSingleResult(); + if (adminCount >= formule.getMaxAdmins()) { + throw new jakarta.ws.rs.ForbiddenException( + "Le quota d'administrateurs de votre plan (" + formule.getMaxAdmins() + + ") est atteint. Mettez à niveau votre abonnement pour ajouter plus d'administrateurs."); + } + } + }); + }); + membre.setStatutCompte("ACTIF"); membre.setActif(true); membreRepository.persist(membre); @@ -432,7 +456,7 @@ public class MembreService { dev.lions.unionflow.server.entity.MembreOrganisation mo = membre.getMembresOrganisations().get(0); if (mo.getOrganisation() != null) { dto.setOrganisationId(mo.getOrganisation().getId()); - dto.setAssociationNom(mo.getOrganisation().getNom()); + dto.setOrganisationNom(mo.getOrganisation().getNom()); } dto.setDateAdhesion(mo.getDateAdhesion()); } else if (membre.getDateCreation() != null) { @@ -498,12 +522,12 @@ public class MembreService { } UUID organisationId = null; - String associationNom = null; + String organisationNom = null; if (membre.getMembresOrganisations() != null && !membre.getMembresOrganisations().isEmpty()) { dev.lions.unionflow.server.entity.MembreOrganisation mo = membre.getMembresOrganisations().get(0); if (mo.getOrganisation() != null) { organisationId = mo.getOrganisation().getId(); - associationNom = mo.getOrganisation().getNom(); + organisationNom = mo.getOrganisation().getNom(); } } @@ -521,7 +545,7 @@ public class MembreService { membre.getActif(), rolesNames, organisationId, - associationNom); + organisationNom); } /** Convertit un CreateMembreRequest en entité Membre */ @@ -1183,16 +1207,33 @@ public class MembreService { // Assigner le rôle SIMPLEMEMBER par défaut assignerRoleDefaut(membreOrganisation, "SIMPLEMEMBER"); - // Incrémenter quota si souscription existe + // Vérifier quota et expiration avant d'incrémenter if (souscriptionOpt.isPresent()) { dev.lions.unionflow.server.entity.SouscriptionOrganisation souscription = souscriptionOpt.get(); + + // Vérifier que la souscription n'est pas expirée + if (!souscription.isActive()) { + throw new jakarta.ws.rs.ForbiddenException( + "La souscription de l'organisation est expirée ou inactive. " + + "Veuillez renouveler votre abonnement avant d'ajouter de nouveaux membres."); + } + + // Vérifier que le quota n'est pas dépassé + if (souscription.isQuotaDepasse()) { + Integer max = souscription.getQuotaMax(); + throw new jakarta.ws.rs.ForbiddenException( + "Le quota de membres de votre plan est atteint (" + max + "/" + max + "). " + + "Veuillez mettre à niveau votre formule d'abonnement."); + } + souscription.incrementerQuota(); entityManager.persist(souscription); LOG.infof("Quota souscription incrémenté (utilise: %d/%s)", souscription.getQuotaUtilise(), souscription.getQuotaMax() != null ? souscription.getQuotaMax().toString() : "∞"); } else { - LOG.warn("Aucune souscription active trouvée pour organisation " + organisationId); + LOG.warn("Aucune souscription active trouvée pour organisation " + organisationId + + " — ajout du membre sans vérification de quota"); } } diff --git a/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java b/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java index 61250ad..8d8c734 100644 --- a/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java +++ b/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java @@ -719,4 +719,27 @@ public class OrganisationService { .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(); + } } diff --git a/src/main/java/dev/lions/unionflow/server/service/RoleService.java b/src/main/java/dev/lions/unionflow/server/service/RoleService.java index 306580c..d1910d4 100644 --- a/src/main/java/dev/lions/unionflow/server/service/RoleService.java +++ b/src/main/java/dev/lions/unionflow/server/service/RoleService.java @@ -1,14 +1,19 @@ package dev.lions.unionflow.server.service; +import dev.lions.unionflow.server.entity.Permission; import dev.lions.unionflow.server.entity.Role; +import dev.lions.unionflow.server.entity.RolePermission; import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.repository.PermissionRepository; import dev.lions.unionflow.server.repository.RoleRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.NotFoundException; import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.jboss.logging.Logger; /** @@ -32,6 +37,9 @@ public class RoleService { @Inject KeycloakService keycloakService; + @Inject + PermissionRepository permissionRepository; + /** * Crée un nouveau rôle * @@ -141,6 +149,56 @@ public class RoleService { return roleRepository.findAllActifs(); } + /** + * Liste les rôles par catégorie (PLATEFORME, FONCTIONNEL, METIER) + */ + public List listerParCategorie(String categorie) { + return roleRepository.findByCategorie(categorie); + } + + /** + * Liste les rôles assignables (FONCTIONNEL + METIER) — pour UI d'assignation. + */ + public List listerRolesAssignables() { + return roleRepository.findRolesAssignables(); + } + + /** + * Retourne les codes de permissions attribuées à un rôle. + */ + public Set getPermissionsDuRole(UUID roleId) { + Role role = roleRepository.findRoleById(roleId) + .orElseThrow(() -> new NotFoundException("Rôle non trouvé : " + roleId)); + return role.getPermissions().stream() + .map(rp -> rp.getPermission().getCode()) + .collect(Collectors.toSet()); + } + + /** + * Assigne une permission à un rôle (si pas déjà assignée). + */ + @Transactional + public void assignerPermission(UUID roleId, UUID permissionId) { + Role role = roleRepository.findRoleById(roleId) + .orElseThrow(() -> new NotFoundException("Rôle non trouvé : " + roleId)); + Permission permission = permissionRepository.findByIdOptional(permissionId) + .orElseThrow(() -> new NotFoundException("Permission non trouvée : " + permissionId)); + + boolean dejaAssignee = role.getPermissions().stream() + .anyMatch(rp -> rp.getPermission().getId().equals(permissionId)); + if (dejaAssignee) { + return; + } + + RolePermission rp = new RolePermission(); + rp.setRole(role); + rp.setPermission(permission); + rp.setCreePar(keycloakService.getCurrentUserEmail()); + role.getPermissions().add(rp); + roleRepository.persist(role); + LOG.infof("Permission %s assignée au rôle %s", permission.getCode(), role.getCode()); + } + /** * Supprime (désactive) un rôle * diff --git a/src/main/java/dev/lions/unionflow/server/service/SouscriptionService.java b/src/main/java/dev/lions/unionflow/server/service/SouscriptionService.java index e3d9002..9f784e0 100644 --- a/src/main/java/dev/lions/unionflow/server/service/SouscriptionService.java +++ b/src/main/java/dev/lions/unionflow/server/service/SouscriptionService.java @@ -503,6 +503,26 @@ public class SouscriptionService { r.setOrganisationId(s.getOrganisation().getId().toString()); r.setOrganisationNom(s.getOrganisation().getNom()); } + // ── Quota & Option C ────────────────────────────────────────────────────── + r.setQuotaMax(s.getQuotaMax()); + r.setQuotaUtilise(s.getQuotaUtilise() != null ? s.getQuotaUtilise() : 0); + r.setQuotaDepasse(s.isQuotaDepasse()); + if (s.getQuotaMax() != null && s.getQuotaUtilise() != null) { + r.setQuotaRestant(Math.max(0, s.getQuotaMax() - s.getQuotaUtilise())); + } + if (s.getDateFin() != null) { + r.setJoursAvantExpiration(java.time.LocalDate.now().until(s.getDateFin(), java.time.temporal.ChronoUnit.DAYS)); + } + if (s.getFormule() != null) { + FormuleAbonnement f = s.getFormule(); + r.setPlanCommercial(f.getPlanCommercial()); + r.setApiAccess(Boolean.TRUE.equals(f.getApiAccess())); + r.setFederationAccess(Boolean.TRUE.equals(f.getFederationAccess())); + r.setSupportPrioritaire(Boolean.TRUE.equals(f.getSupportPrioritaire())); + r.setSlaGaranti(f.getSlaGaranti()); + r.setMaxAdmins(f.getMaxAdmins()); + r.setNiveauReporting(f.getNiveauReporting()); + } return r; } @@ -518,6 +538,14 @@ public class SouscriptionService { r.setPrixMensuel(f.getPrixMensuel()); r.setPrixAnnuel(f.getPrixAnnuel()); r.setOrdreAffichage(f.getOrdreAffichage() != null ? f.getOrdreAffichage() : 0); + // Champs Option C (V19) + r.setPlanCommercial(f.getPlanCommercial()); + r.setNiveauReporting(f.getNiveauReporting()); + r.setApiAccess(Boolean.TRUE.equals(f.getApiAccess())); + r.setFederationAccess(Boolean.TRUE.equals(f.getFederationAccess())); + r.setSupportPrioritaire(Boolean.TRUE.equals(f.getSupportPrioritaire())); + r.setSlaGaranti(f.getSlaGaranti()); + r.setMaxAdmins(f.getMaxAdmins() != null ? f.getMaxAdmins() : -1); return r; } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 887b327..698a38d 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -56,3 +56,4 @@ quarkus.oidc-client.admin-service.client-id=unionflow-server quarkus.oidc-client.admin-service.credentials.secret=unionflow-secret-2025 quarkus.oidc-client.admin-service.grant.type=client quarkus.oidc-client.admin-service.tls.verification=none +quarkus.oidc-client.admin-service.early-tokens-acquisition=true diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 2240b44..52403a1 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -4,7 +4,7 @@ # Configuration Base de données H2 pour tests quarkus.datasource.db-kind=h2 quarkus.datasource.username=sa -quarkus.datasource.password= +quarkus.datasource.password=sa quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;NON_KEYWORDS=MONTH,YEAR # Configuration Hibernate pour tests diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java index b552af5..ee4cf49 100644 --- a/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java @@ -1547,7 +1547,7 @@ class MembreImportExportServiceTest { membreSansDate.setEmail(null); membreSansDate.setTelephone(null); membreSansDate.setStatutCompte(null); - membreSansDate.setAssociationNom(null); + membreSansDate.setOrganisationNom(null); membreSansDate.setDateNaissance(null); // null → branche else → createCell("") byte[] excelData = importExportService.exporterVersExcel( @@ -1567,14 +1567,14 @@ class MembreImportExportServiceTest { m1.setNom("A"); m1.setPrenom("B"); m1.setEmail("a@test.com"); m1.setStatutCompte("ACTIF"); - m1.setAssociationNom("Org1"); // non-null pour lambda dans creerOngletStatistiques + m1.setOrganisationNom("Org1"); // non-null pour lambda dans creerOngletStatistiques (organisationNom) MembreResponse m2 = new MembreResponse(); m2.setId(UUID.randomUUID()); m2.setNom("C"); m2.setPrenom("D"); m2.setEmail("c@test.com"); m2.setStatutCompte("INACTIF"); - m2.setAssociationNom(null); // null → lambda branche false + m2.setOrganisationNom(null); // null → lambda branche false (organisationNom) byte[] excelData = importExportService.exporterVersExcel( List.of(m1, m2), @@ -1695,15 +1695,15 @@ class MembreImportExportServiceTest { @Test @Order(205) - @DisplayName("exporterVersPDF avec membres ayant associationNom non-null → lambda stats associationNom != null") - void exporterVersPDF_avecAssociationNom_couvreStatistiques() throws Exception { - // Créer membres avec associationNom non-null pour la lambda dans ajouterStatistiquesPDF + @DisplayName("exporterVersPDF avec membres ayant organisationNom non-null → lambda stats organisationNom != null") + void exporterVersPDF_avecOrganisationNom_couvreStatistiques() throws Exception { + // Créer membres avec organisationNom non-null pour la lambda dans ajouterStatistiquesPDF MembreResponse m1 = new MembreResponse(); m1.setId(UUID.randomUUID()); m1.setNom("Alpha"); m1.setPrenom("Beta"); m1.setEmail("alpha@test.com"); - m1.setAssociationNom("Union Test"); // non-null → lambda filter true + m1.setOrganisationNom("Union Test"); // non-null → organisationNom lambda filter true m1.setStatutCompte("ACTIF"); // couvre ACTIF count MembreResponse m2 = new MembreResponse(); @@ -1711,7 +1711,7 @@ class MembreImportExportServiceTest { m2.setNom("Gamma"); m2.setPrenom("Delta"); m2.setEmail("gamma@test.com"); - m2.setAssociationNom(null); // null → lambda filter false + m2.setOrganisationNom(null); // null → organisationNom lambda filter false m2.setStatutCompte("INACTIF"); // couvre INACTIF count MembreResponse m3 = new MembreResponse(); @@ -1719,7 +1719,7 @@ class MembreImportExportServiceTest { m3.setNom("Epsilon"); m3.setPrenom("Zeta"); m3.setEmail("epsilon@test.com"); - m3.setAssociationNom("Union Test"); + m3.setOrganisationNom("Union Test"); m3.setStatutCompte("SUSPENDU"); // couvre SUSPENDU count byte[] pdfData = importExportService.exporterVersPDF( @@ -2068,7 +2068,7 @@ class MembreImportExportServiceTest { membreNull.setEmail(null); // null → ternaire → "" membreNull.setTelephone(null); // null → ternaire → "" membreNull.setStatutCompte(null); // null → ternaire → "" - membreNull.setAssociationNom(null); + membreNull.setOrganisationNom(null); byte[] csvData = importExportService.exporterVersCSV( List.of(membreNull), @@ -2119,7 +2119,7 @@ class MembreImportExportServiceTest { membreNulls.setEmail(null); // null → L959 false branch membreNulls.setTelephone(null); membreNulls.setStatutCompte(null); - membreNulls.setAssociationNom(null); + membreNulls.setOrganisationNom(null); membreNulls.setDateNaissance(null); // Toutes colonnes pour passer par L948, L949, L959 diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreServiceExportAndSummaryTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreServiceExportAndSummaryTest.java index 033c3d1..1ae0a03 100644 --- a/src/test/java/dev/lions/unionflow/server/service/MembreServiceExportAndSummaryTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/MembreServiceExportAndSummaryTest.java @@ -286,9 +286,9 @@ class MembreServiceExportAndSummaryTest { membreService.convertToSummaryResponse(membre); assertThat(resp).isNotNull(); - // Branche mo.getOrganisation() != null → true → organisationId et associationNom remplis + // Branche mo.getOrganisation() != null → true → organisationId et organisationNom remplis assertThat(resp.organisationId()).isEqualTo(orgId); - assertThat(resp.associationNom()).isEqualTo("Lions Club Dakar"); + assertThat(resp.organisationNom()).isEqualTo("Lions Club Dakar"); } @Test @@ -306,9 +306,9 @@ class MembreServiceExportAndSummaryTest { membreService.convertToSummaryResponse(membre); assertThat(resp).isNotNull(); - // Branche false → organisationId et associationNom restent null + // Branche false → organisationId et organisationNom restent null assertThat(resp.organisationId()).isNull(); - assertThat(resp.associationNom()).isNull(); + assertThat(resp.organisationNom()).isNull(); } @Test @@ -324,7 +324,7 @@ class MembreServiceExportAndSummaryTest { assertThat(resp).isNotNull(); assertThat(resp.organisationId()).isNull(); - assertThat(resp.associationNom()).isNull(); + assertThat(resp.organisationNom()).isNull(); } @Test diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreServiceFinalBranchesTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreServiceFinalBranchesTest.java index 143a839..3590526 100644 --- a/src/test/java/dev/lions/unionflow/server/service/MembreServiceFinalBranchesTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/MembreServiceFinalBranchesTest.java @@ -105,7 +105,7 @@ class MembreServiceFinalBranchesTest { assertThat(result).isNotNull().isInstanceOf(MembreResponse.class); MembreResponse response = (MembreResponse) result; assertThat(response.getOrganisationId()).isNull(); - assertThat(response.getAssociationNom()).isNull(); + assertThat(response.getOrganisationNom()).isNull(); assertThat(response.getDateAdhesion()).isEqualTo(LocalDate.of(2024, 6, 1)); } diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java index 97b4f3a..94dd341 100644 --- a/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java @@ -817,7 +817,7 @@ class MembreServiceTest { MembreResponse resp = membreService.convertToResponse(m); assertThat(resp.getOrganisationId()).isEqualTo(orgId); - assertThat(resp.getAssociationNom()).isEqualTo("Lions Club"); + assertThat(resp.getOrganisationNom()).isEqualTo("Lions Club"); assertThat(resp.getDateAdhesion()).isEqualTo(LocalDate.of(2022, 6, 1)); } @@ -836,7 +836,7 @@ class MembreServiceTest { MembreResponse resp = membreService.convertToResponse(m); assertThat(resp.getOrganisationId()).isNull(); - assertThat(resp.getAssociationNom()).isNull(); + assertThat(resp.getOrganisationNom()).isNull(); } @Test @@ -1050,7 +1050,7 @@ class MembreServiceTest { MembreSummaryResponse resp = membreService.convertToSummaryResponse(m); assertThat(resp.organisationId()).isEqualTo(orgId); - assertThat(resp.associationNom()).isEqualTo("Assoc Test"); + assertThat(resp.organisationNom()).isEqualTo("Assoc Test"); } @Test