feat: BackupService real pg_dump, OrganisationService region stats, SystemConfigService overrides

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dahoud
2026-04-04 16:14:30 +00:00
parent 9c66909eff
commit e00a9301d8
98 changed files with 5571 additions and 636 deletions

View File

@@ -38,7 +38,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Adhésions", description = "Gestion des demandes d'adhésion des membres")
@Slf4j
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
public class AdhesionResource {
@Inject
@@ -99,7 +99,7 @@ public class AdhesionResource {
}
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Operation(summary = "Créer une nouvelle adhésion", description = "Crée une nouvelle demande d'adhésion pour un membre")
@APIResponses({
@APIResponse(responseCode = "201", description = "Adhésion créée avec succès", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = AdhesionResponse.class))),
@@ -127,7 +127,7 @@ public class AdhesionResource {
/** Met à jour une adhésion existante */
@PUT
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}")
@Operation(summary = "Mettre à jour une adhésion", description = "Met à jour les données d'une adhésion existante")
@APIResponses({
@@ -168,7 +168,7 @@ public class AdhesionResource {
/** Approuve une adhésion */
@POST
@RolesAllowed({ "SUPER_ADMIN", "ADMIN" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN", "ADMIN_ORGANISATION" })
@Path("/{id}/approuver")
@Operation(summary = "Approuver une adhésion", description = "Approuve une demande d'adhésion en attente")
@APIResponses({
@@ -189,7 +189,7 @@ public class AdhesionResource {
/** Rejette une adhésion */
@POST
@RolesAllowed({ "SUPER_ADMIN", "ADMIN" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN", "ADMIN_ORGANISATION" })
@Path("/{id}/rejeter")
@Operation(summary = "Rejeter une adhésion", description = "Rejette une demande d'adhésion en attente")
@APIResponses({
@@ -210,7 +210,7 @@ public class AdhesionResource {
/** Enregistre un paiement pour une adhésion */
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/paiement")
@Operation(summary = "Enregistrer un paiement", description = "Enregistre un paiement pour une adhésion approuvée")
@APIResponses({

View File

@@ -38,7 +38,7 @@ public class AlerteLcbFtResource {
* Récupère les alertes LCB-FT avec filtres et pagination.
*/
@GET
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Liste des alertes LCB-FT", description = "Récupère les alertes avec filtrage et pagination")
public Response getAlertes(
@QueryParam("organisationId") String organisationId,
@@ -84,7 +84,7 @@ public class AlerteLcbFtResource {
*/
@GET
@Path("/{id}")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Détails d'une alerte", description = "Récupère une alerte par son ID")
public Response getAlerteById(@PathParam("id") String id) {
AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id));
@@ -100,7 +100,7 @@ public class AlerteLcbFtResource {
*/
@POST
@Path("/{id}/traiter")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Traiter une alerte", description = "Marque une alerte comme traitée avec un commentaire")
public Response traiterAlerte(
@PathParam("id") String id,
@@ -133,7 +133,7 @@ public class AlerteLcbFtResource {
*/
@GET
@Path("/stats/non-traitees")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques alertes", description = "Nombre d'alertes non traitées")
public Response getStatsNonTraitees(@QueryParam("organisationId") String organisationId) {
UUID orgId = organisationId != null && !organisationId.isBlank() ? UUID.fromString(organisationId) : null;

View File

@@ -38,7 +38,7 @@ public class ApprovalResource {
ApprovalService approvalService;
@POST
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN", "MEMBRE"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN", "MEMBRE"})
@Operation(summary = "Demande une approbation de transaction",
description = "Crée une demande d'approbation pour une transaction financière")
public Response requestApproval(Map<String, Object> request) {
@@ -76,7 +76,7 @@ public class ApprovalResource {
@GET
@Path("/pending")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les approbations en attente",
description = "Liste toutes les approbations de transactions en attente pour une organisation")
public Response getPendingApprovals(@QueryParam("organizationId") UUID organizationId) {
@@ -95,7 +95,7 @@ public class ApprovalResource {
@GET
@Path("/{approvalId}")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère une approbation par ID",
description = "Retourne les détails d'une approbation spécifique")
public Response getApprovalById(@PathParam("approvalId") UUID approvalId) {
@@ -118,7 +118,7 @@ public class ApprovalResource {
@POST
@Path("/{approvalId}/approve")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Approuve une transaction",
description = "Approuve une demande de transaction avec un commentaire optionnel")
public Response approveTransaction(
@@ -147,7 +147,7 @@ public class ApprovalResource {
@POST
@Path("/{approvalId}/reject")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Rejette une transaction",
description = "Rejette une demande de transaction avec une raison obligatoire")
public Response rejectTransaction(
@@ -176,7 +176,7 @@ public class ApprovalResource {
@GET
@Path("/history")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère l'historique des approbations",
description = "Liste l'historique des approbations avec filtres optionnels")
public Response getApprovalsHistory(
@@ -209,7 +209,7 @@ public class ApprovalResource {
@GET
@Path("/count/pending")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Compte les approbations en attente",
description = "Retourne le nombre d'approbations en attente pour une organisation")
public Response countPendingApprovals(@QueryParam("organizationId") UUID organizationId) {

View File

@@ -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", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
public class AuditResource {
@Inject

View File

@@ -36,7 +36,7 @@ public class BudgetResource {
BudgetService budgetService;
@GET
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les budgets",
description = "Liste tous les budgets d'une organisation avec filtres optionnels")
public Response getBudgets(
@@ -63,7 +63,7 @@ public class BudgetResource {
@GET
@Path("/{budgetId}")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère un budget par ID",
description = "Retourne les détails complets d'un budget")
public Response getBudgetById(@PathParam("budgetId") UUID budgetId) {
@@ -85,7 +85,7 @@ public class BudgetResource {
}
@POST
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Crée un nouveau budget",
description = "Crée un budget avec ses lignes budgétaires")
public Response createBudget(@Valid CreateBudgetRequest request) {
@@ -114,7 +114,7 @@ public class BudgetResource {
@GET
@Path("/{budgetId}/tracking")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère le suivi budgétaire",
description = "Retourne les statistiques de suivi et réalisation du budget")
public Response getBudgetTracking(@PathParam("budgetId") UUID budgetId) {
@@ -137,7 +137,7 @@ public class BudgetResource {
@PUT
@Path("/{budgetId}")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Met à jour un budget",
description = "Modifie un budget existant (nom, description, lignes, statut)")
public Response updateBudget(
@@ -166,7 +166,7 @@ public class BudgetResource {
@DELETE
@Path("/{budgetId}")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Supprime un budget",
description = "Supprime logiquement un budget (soft delete)")
public Response deleteBudget(@PathParam("budgetId") UUID budgetId) {

View File

@@ -24,7 +24,7 @@ import org.jboss.logging.Logger;
@Path("/api/comptabilite")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Tag(name = "Comptabilité", description = "Gestion comptable : comptes, journaux et écritures comptables")
public class ComptabiliteResource {
@@ -44,7 +44,7 @@ public class ComptabiliteResource {
* @return Compte créé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/comptes")
public Response creerCompteComptable(@Valid CreateCompteComptableRequest request) {
try {
@@ -116,7 +116,7 @@ public class ComptabiliteResource {
* @return Journal créé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/journaux")
public Response creerJournalComptable(@Valid CreateJournalComptableRequest request) {
try {
@@ -188,7 +188,7 @@ public class ComptabiliteResource {
* @return Écriture créée
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/ecritures")
public Response creerEcritureComptable(@Valid CreateEcritureComptableRequest request) {
try {

View File

@@ -1,7 +1,17 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.membre.CompteAdherentResponse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.SouscriptionOrganisation;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.SouscriptionOrganisationRepository;
import dev.lions.unionflow.server.service.CompteAdherentService;
import dev.lions.unionflow.server.service.MembreKeycloakSyncService;
import dev.lions.unionflow.server.service.MembreService;
import dev.lions.unionflow.server.service.support.SecuriteHelper;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
@@ -9,6 +19,12 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Endpoint REST pour le compte adhérent du membre connecté.
@@ -28,16 +44,36 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Tag(name = "Compte Adhérent", description = "Vue financière unifiée du membre connecté")
public class CompteAdherentResource {
private static final Logger LOG = Logger.getLogger(CompteAdherentResource.class);
@Inject
CompteAdherentService compteAdherentService;
@Inject
SecuriteHelper securiteHelper;
@Inject
MembreRepository membreRepository;
@Inject
MembreOrganisationRepository membreOrganisationRepo;
@Inject
SouscriptionOrganisationRepository souscriptionRepo;
@Inject
MembreKeycloakSyncService membreKeycloakSyncService;
@Inject
MembreService membreService;
/**
* Retourne le compte adhérent complet du membre connecté :
* numéro de membre, soldes (cotisations + épargne), capacité d'emprunt, taux d'engagement.
*/
@GET
@Path("/mon-compte")
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "SUPER_ADMIN" })
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN" })
@Operation(
summary = "Compte adhérent du membre connecté",
description = "Agrège cotisations, épargne et crédit en une vue financière unifiée."
@@ -46,4 +82,144 @@ public class CompteAdherentResource {
CompteAdherentResponse compte = compteAdherentService.getMonCompte();
return Response.ok(compte).build();
}
/**
* Retourne le statut du compte du membre connecté.
*
* <p>Endpoint léger appelé par le mobile juste après le login Keycloak
* pour détecter les comptes en attente/suspendus/désactivés avant d'accorder l'accès.
*
* <p>Si aucun enregistrement membre n'existe (ex. : administrateur créé directement
* dans Keycloak sans fiche membre), retourne {@code ACTIF} pour ne pas bloquer les admins.
*/
@GET
@Path("/mon-statut")
@Authenticated
@Operation(
summary = "Statut du compte du membre connecté",
description = "Retourne statutCompte : ACTIF, EN_ATTENTE_VALIDATION, SUSPENDU ou DESACTIVE."
)
public Response getMonStatut() {
String email = securiteHelper.resolveEmail();
if (email == null || email.isBlank()) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
.or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));
// Pas de fiche membre → administrateur pur → accès autorisé
String statutCompte = membreOpt
.map(Membre::getStatutCompte)
.filter(s -> s != null && !s.isBlank())
.orElse("ACTIF");
// Auto-activer si le membre a été créé par un admin dont l'org a une souscription active.
// Couvre les membres créés avant l'auto-activation à la création ET les cas limites futurs.
if ("EN_ATTENTE_VALIDATION".equals(statutCompte) && membreOpt.isPresent()) {
Membre m = membreOpt.get();
UUID orgId = membreOrganisationRepo.findFirstByMembreId(m.getId())
.map(mo -> mo.getOrganisation().getId())
.orElse(null);
if (membreService.orgHasActiveSubscription(orgId)) {
LOG.infof("Auto-activation au login de %s (org %s a souscription active)", m.getEmail(), orgId);
membreService.activerMembre(m.getId());
try {
membreKeycloakSyncService.activerMembreDansKeycloak(m.getId());
} catch (Exception e) {
LOG.warnf("Activation Keycloak au login échouée pour %s (non bloquant): %s",
m.getEmail(), e.getMessage());
}
statutCompte = "ACTIF";
}
}
Map<String, Object> response = new HashMap<>();
response.put("statutCompte", statutCompte);
// Signaler si le membre doit changer son mot de passe (premier login)
boolean changerMotDePasseRequis = membreOpt
.map(m -> Boolean.TRUE.equals(m.getPremiereConnexion()))
.orElse(false);
response.put("changerMotDePasseRequis", changerMotDePasseRequis);
// Enrichir avec l'état d'onboarding pour les comptes en attente
if ("EN_ATTENTE_VALIDATION".equals(statutCompte)) {
membreOpt.flatMap(m -> membreOrganisationRepo.findFirstByMembreId(m.getId())
.map(MembreOrganisation::getOrganisation))
.ifPresent(org -> {
response.put("organisationId", org.getId().toString());
Optional<SouscriptionOrganisation> souscOpt =
souscriptionRepo.findLatestByOrganisationId(org.getId());
if (souscOpt.isEmpty()) {
response.put("onboardingState", "NO_SUBSCRIPTION");
} else {
SouscriptionOrganisation sosc = souscOpt.get();
String valState = sosc.getStatutValidation() != null
? sosc.getStatutValidation().name()
: "EN_ATTENTE_PAIEMENT";
String onboardingState = switch (valState) {
case "EN_ATTENTE_PAIEMENT" -> "AWAITING_PAYMENT";
case "PAIEMENT_INITIE" -> "PAYMENT_INITIATED";
case "PAIEMENT_CONFIRME" -> "AWAITING_VALIDATION";
case "VALIDEE" -> "VALIDATED";
case "REJETEE" -> "REJECTED";
default -> "AWAITING_PAYMENT";
};
response.put("onboardingState", onboardingState);
response.put("souscriptionId", sosc.getId().toString());
if (sosc.getWaveSessionId() != null) {
response.put("waveSessionId", sosc.getWaveSessionId());
}
}
response.put("typeOrganisation", org.getTypeOrganisation() != null
? org.getTypeOrganisation() : "ASSOCIATION");
});
if (!response.containsKey("onboardingState")) {
response.put("onboardingState", "NO_SUBSCRIPTION");
}
}
return Response.ok(response).build();
}
/**
* Permet au membre connecté de changer son mot de passe lors du premier login.
* Appelle Keycloak via lions-user-manager et marque {@code premiereConnexion = false}.
*
* <p>Body attendu : {@code { "nouveauMotDePasse": "..." }}
*/
@PUT
@Path("/mon-compte/mot-de-passe")
@Authenticated
@Operation(
summary = "Changer le mot de passe au premier login",
description = "Met à jour le mot de passe Keycloak et lève le flag premiereConnexion."
)
public Response changerMotDePasse(Map<String, String> body) {
String email = securiteHelper.resolveEmail();
if (email == null || email.isBlank()) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
String nouveauMotDePasse = body == null ? null : body.get("nouveauMotDePasse");
if (nouveauMotDePasse == null || nouveauMotDePasse.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Le champ 'nouveauMotDePasse' est requis."))
.build();
}
Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
.or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));
if (membreOpt.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
.build();
}
membreKeycloakSyncService.changerMotDePassePremierLogin(membreOpt.get().getId(), nouveauMotDePasse);
return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
}
}

View File

@@ -39,7 +39,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Cotisations", description = "Gestion des cotisations des membres")
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Slf4j
public class CotisationResource {
@@ -165,7 +165,7 @@ public class CotisationResource {
* Crée une nouvelle cotisation.
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Operation(summary = "Créer une cotisation")
@APIResponses({
@APIResponse(responseCode = "201", description = "Créée", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = CotisationResponse.class))),
@@ -189,7 +189,7 @@ public class CotisationResource {
*/
@PUT
@Path("/{id}")
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Operation(summary = "Mettre à jour une cotisation")
public Response updateCotisation(@PathParam("id") @NotNull UUID id, @Valid UpdateCotisationRequest request) {
try {
@@ -344,7 +344,7 @@ public class CotisationResource {
* Enregistrer le paiement.
*/
@PUT
@RolesAllowed({ "ADMIN", "MEMBRE", "TRESORIER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "TRESORIER" })
@Path("/{id}/payer")
@Operation(summary = "Payer une cotisation")
public Response enregistrerPaiement(@PathParam("id") UUID id, Map<String, Object> paiementData) {
@@ -373,7 +373,7 @@ public class CotisationResource {
* Envoyer rappels groupés.
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/rappels/groupes")
@Operation(summary = "Rappels groupés")
public Response envoyerRappelsGroupes(List<UUID> membreIds) {

View File

@@ -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", "MEMBRE", "USER"})
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"})
public class DashboardResource {
private static final Logger LOG = Logger.getLogger(DashboardResource.class);

View File

@@ -32,7 +32,7 @@ import org.jboss.logging.Logger;
@Path("/api/documents")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Tag(name = "Documents", description = "Gestion documentaire : documents et pièces jointes")
public class DocumentResource {
@@ -54,7 +54,7 @@ public class DocumentResource {
* @return Document créé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public Response creerDocument(@Valid CreateDocumentRequest request) {
try {
DocumentResponse result = documentService.creerDocument(request);
@@ -77,7 +77,7 @@ public class DocumentResource {
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@jakarta.transaction.Transactional
public Response uploadFile(
@org.jboss.resteasy.reactive.RestForm("file") FileUpload file,
@@ -176,7 +176,7 @@ public class DocumentResource {
* @return Succès
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/telechargement")
public Response enregistrerTelechargement(@PathParam("id") UUID id) {
try {
@@ -203,7 +203,7 @@ public class DocumentResource {
* @return Pièce jointe créée
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/pieces-jointes")
public Response creerPieceJointe(@Valid CreatePieceJointeRequest request) {
try {

View File

@@ -81,7 +81,7 @@ public class EvenementResource {
@Operation(summary = "Lister tous les événements actifs", description = "Récupère la liste paginée des événements actifs")
@APIResponse(responseCode = "200", description = "Liste des événements actifs")
@APIResponse(responseCode = "401", description = "Non authentifié")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
public PagedResponse<EvenementMobileDTO> listerEvenements(
@Parameter(description = "Numéro de page (0-based)", example = "0") @QueryParam("page") @DefaultValue("0") @Min(0) int page,
@Parameter(description = "Taille de la page", example = "20") @QueryParam("size") @DefaultValue("20") @Min(1) int size,
@@ -121,7 +121,7 @@ public class EvenementResource {
@Operation(summary = "Récupérer un événement par ID")
@APIResponse(responseCode = "200", description = "Événement trouvé")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
public Response obtenirEvenement(
@Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) {
@@ -138,7 +138,7 @@ public class EvenementResource {
@Operation(summary = "Créer un nouvel événement")
@APIResponse(responseCode = "201", description = "Événement créé avec succès")
@APIResponse(responseCode = "400", description = "Données invalides")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
public Response creerEvenement(
@Parameter(description = "Données de l'événement à créer", required = true) @Valid Evenement evenement) {
@@ -153,7 +153,7 @@ public class EvenementResource {
@Operation(summary = "Mettre à jour un événement")
@APIResponse(responseCode = "200", description = "Événement mis à jour avec succès")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
public Response mettreAJourEvenement(@PathParam("id") UUID id, @Valid Evenement evenement) {
LOG.infof("PUT /api/evenements/%s", id);
@@ -166,7 +166,7 @@ public class EvenementResource {
@Path("/{id}")
@Operation(summary = "Supprimer un événement")
@APIResponse(responseCode = "204", description = "Événement supprimé avec succès")
@RolesAllowed({ "ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "ORGANISATEUR_EVENEMENT" })
public Response supprimerEvenement(@PathParam("id") UUID id) {
LOG.infof("DELETE /api/evenements/%s", id);
@@ -180,7 +180,7 @@ public class EvenementResource {
@GET
@Path("/a-venir")
@Operation(summary = "Événements à venir")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
public Response evenementsAVenir(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("10") int size) {
@@ -209,7 +209,7 @@ public class EvenementResource {
@GET
@Path("/recherche")
@Operation(summary = "Rechercher des événements")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
public Response rechercherEvenements(
@QueryParam("q") String recherche,
@QueryParam("page") @DefaultValue("0") int page,
@@ -231,7 +231,7 @@ public class EvenementResource {
@GET
@Path("/type/{type}")
@Operation(summary = "Événements par type")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
public Response evenementsParType(
@PathParam("type") String type,
@QueryParam("page") @DefaultValue("0") int page,
@@ -247,7 +247,7 @@ public class EvenementResource {
@PATCH
@Path("/{id}/statut")
@Operation(summary = "Changer le statut d'un événement")
@RolesAllowed({ "ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "ORGANISATEUR_EVENEMENT" })
public Response changerStatut(
@PathParam("id") UUID id, @QueryParam("statut") String nouveauStatut) {
@@ -266,7 +266,7 @@ public class EvenementResource {
@GET
@Path("/statistiques")
@Operation(summary = "Statistiques des événements")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT" })
public Response obtenirStatistiques() {
Map<String, Object> statistiques = evenementService.obtenirStatistiques();
@@ -278,7 +278,7 @@ public class EvenementResource {
@Path("/{id}/me/inscrit")
@Operation(summary = "Statut d'inscription de l'utilisateur connecté")
@APIResponse(responseCode = "200", description = "Statut d'inscription")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
public Response meInscrit(
@Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) {
boolean inscrit = evenementService.isUserInscrit(id);
@@ -294,7 +294,7 @@ public class EvenementResource {
@APIResponse(responseCode = "201", description = "Inscription créée")
@APIResponse(responseCode = "400", description = "Déjà inscrit ou événement complet")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@Transactional
public Response inscrireEvenement(@PathParam("id") UUID evenementId) {
try {
@@ -313,7 +313,7 @@ public class EvenementResource {
@Operation(summary = "Se désinscrire d'un événement")
@APIResponse(responseCode = "204", description = "Désinscription effectuée")
@APIResponse(responseCode = "404", description = "Inscription non trouvée")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@Transactional
public Response desinscrireEvenement(@PathParam("id") UUID evenementId) {
evenementService.desinscrireEvenement(evenementId);
@@ -325,7 +325,7 @@ public class EvenementResource {
@Path("/{id}/participants")
@Operation(summary = "Liste des participants")
@APIResponse(responseCode = "200", description = "Liste des participants")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
public Response getParticipants(@PathParam("id") UUID evenementId) {
List<InscriptionEvenement> participants = evenementService.getParticipants(evenementId);
return Response.ok(participants).build();
@@ -336,7 +336,7 @@ public class EvenementResource {
@Path("/mes-inscriptions")
@Operation(summary = "Mes inscriptions aux événements")
@APIResponse(responseCode = "200", description = "Liste de mes inscriptions")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
public Response getMesInscriptions() {
List<InscriptionEvenement> inscriptions = evenementService.getMesInscriptions();
return Response.ok(inscriptions).build();
@@ -351,7 +351,7 @@ public class EvenementResource {
@APIResponse(responseCode = "201", description = "Feedback créé")
@APIResponse(responseCode = "400", description = "Données invalides ou feedback déjà soumis")
@APIResponse(responseCode = "404", description = "Événement non trouvé")
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
@Transactional
public Response soumetteFeedback(
@PathParam("id") UUID evenementId, Map<String, Object> requestBody) {

View File

@@ -18,7 +18,7 @@ import org.jboss.logging.Logger;
@Path("/api/export")
@ApplicationScoped
@Tag(name = "Export", description = "API d'export des données")
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"})
public class ExportResource {
private static final Logger LOG = Logger.getLogger(ExportResource.class);
@@ -45,7 +45,7 @@ public class ExportResource {
}
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Path("/cotisations/csv")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("text/csv")
@@ -79,7 +79,7 @@ public class ExportResource {
}
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Path("/cotisations/recus")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("text/plain")

View File

@@ -29,7 +29,7 @@ import java.util.UUID;
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Favoris", description = "Gestion des favoris utilisateur")
@Slf4j
@RolesAllowed({ "USER", "ADMIN", "MEMBRE" })
@RolesAllowed({ "USER", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public class FavorisResource {
@Inject

View File

@@ -24,7 +24,7 @@ import java.util.UUID;
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Feedback", description = "Commentaires et suggestions utilisateur")
@RolesAllowed({ "USER", "ADMIN", "MEMBRE" })
@RolesAllowed({ "USER", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public class FeedbackResource {
private static final Logger LOG = Logger.getLogger(FeedbackResource.class);

View File

@@ -31,7 +31,7 @@ public class FinanceWorkflowResource {
@GET
@Path("/stats")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques du workflow financier",
description = "Retourne les statistiques globales du workflow financier")
public Response getWorkflowStats(
@@ -57,7 +57,7 @@ public class FinanceWorkflowResource {
@GET
@Path("/audit-logs")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les logs d'audit financier",
description = "Liste les logs d'audit avec filtres optionnels")
public Response getAuditLogs(
@@ -76,7 +76,7 @@ public class FinanceWorkflowResource {
@GET
@Path("/audit-logs/anomalies")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les anomalies financières détectées",
description = "Liste les anomalies et transactions suspectes")
public Response getAnomalies(
@@ -91,7 +91,7 @@ public class FinanceWorkflowResource {
@POST
@Path("/audit-logs/export")
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Exporte les logs d'audit",
description = "Génère un export des logs d'audit au format spécifié (CSV/PDF)")
public Response exportAuditLogs(Map<String, Object> request) {

View File

@@ -24,7 +24,7 @@ public class MembreDashboardResource {
@GET
@Path("/me")
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "SUPER_ADMIN" })
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN" })
@Operation(summary = "Récupérer la synthèse du dashboard pour le membre connecté")
public Response getMonDashboard() {
MembreDashboardSyntheseResponse data = dashboardService.getDashboardData();

View File

@@ -25,11 +25,13 @@ import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
@@ -69,6 +71,9 @@ public class MembreResource {
@Inject
io.quarkus.security.identity.SecurityIdentity securityIdentity;
@Inject
JsonWebToken jwt;
@GET
@Operation(summary = "Lister les membres")
@APIResponse(responseCode = "200", description = "Liste des membres avec pagination")
@@ -110,8 +115,7 @@ public class MembreResource {
LOG.infof("ADMIN_ORGANISATION %s : accès à %d organisations", email, orgIds.size());
membres = membreService.listerMembresParOrganisations(orgIds, Page.of(page, size), sort);
// TODO: compter total membres pour ces organisations (approximation pour l'instant)
totalElements = membres.size();
totalElements = membreService.compterMembresParOrganisations(orgIds);
}
} else {
// ADMIN / SUPER_ADMIN : accès à tous les membres
@@ -151,19 +155,80 @@ public class MembreResource {
@GET
@Path("/me")
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "SUPER_ADMIN" })
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN" })
@Operation(summary = "Récupérer le membre connecté")
@APIResponse(responseCode = "200", description = "Membre connecté trouvé")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
@APIResponse(responseCode = "200", description = "Membre connecté trouvé ou auto-provisionné")
public Response obtenirMembreConnecte() {
String email = securityIdentity.getPrincipal().getName();
LOG.infof("Récupération du membre connecté: %s", email);
Membre membre = membreService.trouverParEmail(email)
.filter(m -> m.getActif() == null || m.getActif())
.orElseThrow(() -> new NotFoundException("Membre non trouvé pour l'email: " + email));
.orElseGet(() -> {
LOG.infof("Fiche membre inexistante pour %s — auto-provisionnement depuis JWT", email);
return autoProvisionnerMembre(email);
});
return Response.ok(membreService.convertToResponse(membre)).build();
// Si la fiche existe mais est inactive (provisionnement précédent incomplet), on l'active
if (membre.getActif() != null && !membre.getActif()) {
LOG.infof("Fiche inactive pour %s — activation automatique", email);
membre = membreService.activerMembre(membre.getId());
}
MembreResponse response = membreService.convertToResponse(membre);
// Enrichir avec les rôles Keycloak si la table membres_roles est vide
if (response.getRoles() == null || response.getRoles().isEmpty()) {
java.util.Set<String> keycloakRoles = securityIdentity.getRoles();
// Filtrer les rôles internes Keycloak (offline_access, uma_authorization, etc.)
java.util.List<String> rolesFiltres = keycloakRoles.stream()
.filter(r -> !r.equals("offline_access") && !r.equals("uma_authorization")
&& !r.startsWith("default-roles-"))
.collect(java.util.stream.Collectors.toList());
response.setRoles(rolesFiltres);
}
return Response.ok(response).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";
String nom = "UnionFlow";
UUID keycloakId = null;
if (jwt != null) {
String givenName = jwt.getClaim("given_name");
String familyName = jwt.getClaim("family_name");
String sub = jwt.getSubject();
if (givenName != null && !givenName.isBlank()) prenom = givenName;
if (familyName != null && !familyName.isBlank()) nom = familyName;
if (sub != null) {
try { keycloakId = UUID.fromString(sub); } catch (Exception ignored) {}
}
}
CreateMembreRequest req = CreateMembreRequest.builder()
.prenom(prenom)
.nom(nom)
.email(email)
.dateNaissance(LocalDate.of(1900, 1, 1))
.build();
Membre nouveau = membreService.convertFromCreateRequest(req);
if (keycloakId != null) nouveau.setKeycloakId(keycloakId);
// creerMembre() force actif=false + EN_ATTENTE_VALIDATION.
// On active immédiatement car l'utilisateur est déjà authentifié via Keycloak.
try {
Membre cree = membreService.creerMembre(nouveau);
return membreService.activerMembre(cree.getId());
} catch (IllegalArgumentException e) {
// Fiche déjà présente mais inactive — on l'active directement
LOG.infof("Fiche existante pour %s — activation directe", email);
Membre existant = membreService.trouverParEmail(email)
.orElseThrow(() -> new jakarta.ws.rs.NotFoundException("Membre introuvable : " + email));
return membreService.activerMembre(existant.getId());
}
}
@POST
@@ -180,48 +245,62 @@ public class MembreResource {
Membre nouveauMembre = membreService.creerMembre(membre);
// Provisionner le compte Keycloak (non bloquant — l'admin peut activer manuellement)
String motDePasseTemporaire = null;
try {
keycloakSyncService.provisionKeycloakUser(nouveauMembre.getId());
motDePasseTemporaire = keycloakSyncService.provisionKeycloakUser(nouveauMembre.getId());
} catch (Exception e) {
LOG.warnf("Provisionnement Keycloak échoué pour %s (non bloquant): %s", nouveauMembre.getEmail(), e.getMessage());
}
// Validation périmètre ADMIN_ORGANISATION - lier le membre à l'organisation
// Lier le membre à l'organisation si un organisationId est fourni
java.util.Set<String> roles = securityIdentity.getRoles();
boolean onlyOrgAdmin = roles != null && roles.contains("ADMIN_ORGANISATION")
&& !roles.contains("ADMIN")
&& !roles.contains("SUPER_ADMIN");
if (onlyOrgAdmin) {
if (membreDTO.organisationId() == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "organisationId obligatoire pour ADMIN_ORGANISATION"))
.build();
if (membreDTO.organisationId() != null) {
if (onlyOrgAdmin) {
// Vérifier que l'ADMIN_ORGANISATION a accès à cette organisation
String email = securityIdentity.getPrincipal().getName();
List<UUID> userOrgIds = organisationService.listerOrganisationsPourUtilisateur(email)
.stream()
.map(org -> org.getId())
.collect(java.util.stream.Collectors.toList());
if (!userOrgIds.contains(membreDTO.organisationId())) {
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", "Vous n'avez pas accès à cette organisation"))
.build();
}
}
// Vérifier que l'utilisateur a accès à cette organisation
String email = securityIdentity.getPrincipal().getName();
// Auto-activer si l'organisation a une souscription active (l'admin a déjà payé)
boolean orgActif = membreService.orgHasActiveSubscription(membreDTO.organisationId());
List<UUID> userOrgIds = organisationService.listerOrganisationsPourUtilisateur(email)
.stream()
.map(org -> org.getId())
.collect(java.util.stream.Collectors.toList());
if (!userOrgIds.contains(membreDTO.organisationId())) {
return Response.status(Response.Status.FORBIDDEN)
.entity(Map.of("error", "Vous n'avez pas accès à cette organisation"))
.build();
}
// Lier le membre à l'organisation et incrémenter le quota
// Lier le membre à l'organisation (SUPER_ADMIN ou ADMIN_ORGANISATION)
membreService.lierMembreOrganisationEtIncrementerQuota(
nouveauMembre,
membreDTO.organisationId(),
"EN_ATTENTE_VALIDATION");
orgActif ? "ACTIF" : "EN_ATTENTE_VALIDATION");
if (orgActif) {
nouveauMembre = membreService.activerMembre(nouveauMembre.getId());
try {
keycloakSyncService.activerMembreDansKeycloak(nouveauMembre.getId());
} catch (Exception e) {
LOG.warnf("Activation Keycloak échouée pour %s (non bloquant): %s",
nouveauMembre.getEmail(), e.getMessage());
}
}
} else if (onlyOrgAdmin) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "organisationId obligatoire pour ADMIN_ORGANISATION"))
.build();
}
// Conversion de retour vers DTO
MembreResponse nouveauMembreDTO = membreService.convertToResponse(nouveauMembre);
nouveauMembreDTO.setMotDePasseTemporaire(motDePasseTemporaire);
return Response.status(Response.Status.CREATED).entity(nouveauMembreDTO).build();
}
@@ -674,6 +753,30 @@ public class MembreResource {
.build();
}
@PUT
@Path("/{id}/affecter-organisation")
@RolesAllowed({"ADMIN", "SUPER_ADMIN"})
@Operation(
summary = "Affecter un membre à une organisation",
description = "Crée le lien MembreOrganisation (statut EN_ATTENTE_VALIDATION) si le membre n'est pas encore rattaché à une organisation. Idempotent.")
@APIResponse(responseCode = "200", description = "Membre affecté à l'organisation")
@APIResponse(responseCode = "404", description = "Membre ou organisation non trouvé")
@APIResponse(responseCode = "403", description = "Accès réservé aux ADMIN / SUPER_ADMIN")
public Response affecterOrganisation(
@Parameter(description = "UUID du membre") @PathParam("id") UUID id,
@Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId) {
LOG.infof("Affectation du membre %s à l'organisation %s", id, organisationId);
if (organisationId == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "organisationId est obligatoire"))
.build();
}
Membre membre = membreService.affecterOrganisation(id, organisationId);
return Response.ok(membreService.convertToResponse(membre)).build();
}
@PUT
@Path("/{id}/promouvoir-admin-organisation")
@RolesAllowed({"ADMIN", "SUPER_ADMIN"})
@@ -722,6 +825,37 @@ public class MembreResource {
return Response.ok(membreService.convertToResponse(membreActive)).build();
}
@PUT
@Path("/{id}/reinitialiser-mot-de-passe")
@RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"})
@Operation(summary = "Réinitialiser le mot de passe d'un membre",
description = "Génère un nouveau mot de passe temporaire et le définit dans Keycloak. Le nouveau mot de passe est retourné une seule fois dans la réponse.")
@APIResponse(responseCode = "200", description = "Mot de passe réinitialisé, retourné dans motDePasseTemporaire")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
@APIResponse(responseCode = "400", description = "Le membre n'a pas de compte Keycloak")
public Response reinitialiserMotDePasse(
@Parameter(description = "UUID du membre") @PathParam("id") UUID id) {
LOG.infof("Réinitialisation mot de passe pour membre ID: %s", id);
Membre membre = membreService.trouverParId(id)
.orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + id));
String newPassword;
try {
newPassword = keycloakSyncService.reinitialiserMotDePasse(id);
} catch (IllegalStateException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage())).build();
}
dev.lions.unionflow.server.api.dto.membre.response.MembreResponse response =
membreService.convertToResponse(membre);
response.setMotDePasseTemporaire(newPassword);
LOG.infof("Mot de passe réinitialisé pour %s", membre.getEmail());
return Response.ok(response).build();
}
@GET
@Path("/export/count")
@Produces(MediaType.APPLICATION_JSON)

View File

@@ -29,7 +29,7 @@ import dev.lions.unionflow.server.repository.MembreRepository;
@Path("/api/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "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", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/templates")
public Response creerTemplate(@Valid CreateTemplateNotificationRequest request) {
try {
@@ -128,7 +128,7 @@ public class NotificationResource {
* @return Notification créée
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
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", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@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", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/groupees")
public Response envoyerNotificationsGroupees(NotificationGroupeeRequest request) {
try {

View File

@@ -86,7 +86,7 @@ public class OrganisationResource {
/** Crée une nouvelle organisation */
@POST
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MEMBRE"})
@RolesAllowed({"SUPER_ADMIN"})
@Operation(
summary = "Créer une nouvelle organisation",
@@ -242,7 +242,7 @@ public class OrganisationResource {
/** Met à jour une organisation */
@PUT
@RolesAllowed({"ADMIN", "MEMBRE"})
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Path("/{id}")
@Operation(
@@ -294,7 +294,7 @@ public class OrganisationResource {
/** Supprime une organisation */
@DELETE
@RolesAllowed({"ADMIN"})
@RolesAllowed({"SUPER_ADMIN"})
@Path("/{id}")
@Operation(
@@ -384,7 +384,7 @@ public class OrganisationResource {
/** Active une organisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Path("/{id}/activer")
@Operation(
@@ -419,7 +419,7 @@ public class OrganisationResource {
/** Suspend une organisation */
@POST
@RolesAllowed({"ADMIN", "MEMBRE"})
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Path("/{id}/suspendre")
@Operation(
@@ -454,6 +454,7 @@ public class OrganisationResource {
/** Obtient les statistiques des organisations */
@GET
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "ADMIN_ORGANISATION"})
@Path("/statistiques")
@Operation(

View File

@@ -25,7 +25,7 @@ import org.jboss.logging.Logger;
@Path("/api/paiements")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Tag(name = "Paiements", description = "Gestion des paiements : création, validation et suivi")
public class PaiementResource {
@@ -41,7 +41,7 @@ public class PaiementResource {
* @return Paiement créé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public Response creerPaiement(@Valid CreatePaiementRequest request) {
LOG.infof("POST /api/paiements - Création paiement: %s", request.numeroReference());
PaiementResponse result = paiementService.creerPaiement(request);
@@ -55,7 +55,7 @@ public class PaiementResource {
* @return Paiement validé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/valider")
public Response validerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/valider", id);
@@ -70,7 +70,7 @@ public class PaiementResource {
* @return Paiement annulé
*/
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/annuler")
public Response annulerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/annuler", id);
@@ -129,7 +129,7 @@ public class PaiementResource {
*/
@GET
@Path("/mes-paiements/historique")
@RolesAllowed({ "MEMBRE", "ADMIN" })
@RolesAllowed({ "MEMBRE", "ADMIN", "ADMIN_ORGANISATION" })
public Response getMonHistoriquePaiements(
@QueryParam("limit") @DefaultValue("5") int limit) {
LOG.infof("GET /api/paiements/mes-paiements/historique?limit=%d", limit);
@@ -146,7 +146,7 @@ public class PaiementResource {
*/
@POST
@Path("/initier-paiement-en-ligne")
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "USER" })
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
public Response initierPaiementEnLigne(@Valid dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest request) {
LOG.infof("POST /api/paiements/initier-paiement-en-ligne - cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());
@@ -161,7 +161,7 @@ public class PaiementResource {
*/
@POST
@Path("/initier-depot-epargne-en-ligne")
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "USER" })
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
public Response initierDepotEpargneEnLigne(@Valid dev.lions.unionflow.server.api.dto.paiement.request.InitierDepotEpargneRequest request) {
LOG.infof("POST /api/paiements/initier-depot-epargne-en-ligne - compte: %s, montant: %s",
request.compteId(), request.montant());
@@ -180,7 +180,7 @@ public class PaiementResource {
*/
@POST
@Path("/declarer-paiement-manuel")
@RolesAllowed({ "MEMBRE", "ADMIN" })
@RolesAllowed({ "MEMBRE", "ADMIN", "ADMIN_ORGANISATION" })
public Response declarerPaiementManuel(@Valid dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest request) {
LOG.infof("POST /api/paiements/declarer-paiement-manuel - cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());

View File

@@ -0,0 +1,209 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.souscription.FormuleAbonnementResponse;
import dev.lions.unionflow.server.api.dto.souscription.SouscriptionDemandeRequest;
import dev.lions.unionflow.server.api.dto.souscription.SouscriptionStatutResponse;
import dev.lions.unionflow.server.service.SouscriptionService;
import dev.lions.unionflow.server.service.support.SecuriteHelper;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Ressource REST pour le workflow de souscription/onboarding UnionFlow.
*
* <p>Endpoints publics :
* <ul>
* <li>{@code GET /api/souscriptions/formules} — catalogue des formules (PermitAll)</li>
* <li>{@code POST /api/souscriptions/confirmer-paiement} — callback deep link Wave (PermitAll)</li>
* </ul>
*
* <p>Endpoints ADMIN_ORGANISATION :
* <ul>
* <li>{@code GET /api/souscriptions/ma-souscription}</li>
* <li>{@code POST /api/souscriptions/demande}</li>
* <li>{@code POST /api/souscriptions/{id}/initier-paiement}</li>
* </ul>
*
* <p>Endpoints SUPER_ADMIN :
* <ul>
* <li>{@code GET /api/souscriptions/admin/en-attente}</li>
* <li>{@code POST /api/souscriptions/admin/{id}/approuver}</li>
* <li>{@code POST /api/souscriptions/admin/{id}/rejeter}</li>
* </ul>
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-30
*/
@Path("/api/souscriptions")
@ApplicationScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SouscriptionResource {
private static final Logger LOG = Logger.getLogger(SouscriptionResource.class);
@Inject
SouscriptionService souscriptionService;
@Inject
SecuriteHelper securiteHelper;
// ── Catalogue (public) ────────────────────────────────────────────────────
/**
* Retourne le catalogue complet des formules d'abonnement.
*
* <p>Endpoint public — PermitAll. Utilisé par l'écran d'onboarding mobile
* avant authentification.
*/
@GET
@Path("/formules")
@PermitAll
public Response getFormules() {
LOG.debug("GET /api/souscriptions/formules");
List<FormuleAbonnementResponse> formules = souscriptionService.getFormules();
return Response.ok(formules).build();
}
// ── ADMIN_ORGANISATION ────────────────────────────────────────────────────
/**
* Retourne la souscription de l'organisation du membre connecté.
*/
@GET
@Path("/ma-souscription")
@Authenticated
public Response getMaSouscription() {
LOG.debug("GET /api/souscriptions/ma-souscription");
SouscriptionStatutResponse response = souscriptionService.getMaSouscription();
return Response.ok(response).build();
}
/**
* Crée une demande de souscription.
*
* <p>Le corps de la requête doit contenir les champs :
* {@code typeFormule}, {@code plageMembres}, {@code typePeriode},
* {@code typeOrganisation}, {@code organisationId}.
*/
@POST
@Path("/demande")
@Authenticated
public Response creerDemande(@Valid SouscriptionDemandeRequest request) {
LOG.infof("POST /api/souscriptions/demande — formule=%s plage=%s",
request.getTypeFormule(), request.getPlageMembres());
SouscriptionStatutResponse response = souscriptionService.creerDemande(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
/**
* Initie une session de paiement Wave pour la souscription identifiée.
*
* <p>Retourne le {@code waveLaunchUrl} à ouvrir dans le navigateur ou WebView.
*
* @param id UUID de la souscription
*/
@POST
@Path("/{id}/initier-paiement")
@Authenticated
public Response initierPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/souscriptions/%s/initier-paiement", id);
SouscriptionStatutResponse response = souscriptionService.initierPaiementWave(id);
return Response.ok(response).build();
}
// ── Callback Wave (public) ────────────────────────────────────────────────
/**
* Confirme le paiement Wave (appelé depuis le deep link ou un webhook).
*
* <p>Endpoint public car appelé par le système Wave en dehors de toute session
* utilisateur. La souscription est identifiée par son UUID.
*
* @param id UUID de la souscription (query param {@code id})
* @param waveId identifiant de la transaction Wave (query param {@code wave_id})
*/
@POST
@Path("/confirmer-paiement")
@PermitAll
public Response confirmerPaiement(@QueryParam("id") UUID id,
@QueryParam("wave_id") String waveId) {
if (id == null) {
throw new BadRequestException("Le paramètre 'id' est obligatoire");
}
LOG.infof("POST /api/souscriptions/confirmer-paiement — id=%s waveId=%s", id, waveId);
souscriptionService.confirmerPaiement(id, waveId);
return Response.ok(Map.of("message", "Paiement confirmé — compte activé")).build();
}
// ── SuperAdmin ────────────────────────────────────────────────────────────
/**
* Liste les souscriptions en attente de validation SuperAdmin (statut PAIEMENT_CONFIRME).
*/
@GET
@Path("/admin/en-attente")
@RolesAllowed({"SUPER_ADMIN"})
public Response getSouscriptionsEnAttente() {
LOG.debug("GET /api/souscriptions/admin/en-attente");
List<SouscriptionStatutResponse> liste = souscriptionService.getSouscriptionsEnAttenteValidation();
return Response.ok(liste).build();
}
/**
* Approuve une souscription et active le compte de l'administrateur d'organisation.
*
* @param id UUID de la souscription
*/
@POST
@Path("/admin/{id}/approuver")
@RolesAllowed({"SUPER_ADMIN"})
public Response approuver(@PathParam("id") UUID id) {
LOG.infof("POST /api/souscriptions/admin/%s/approuver", id);
UUID superAdminId = securiteHelper.resolveMembreId();
souscriptionService.approuver(id, superAdminId);
return Response.ok(Map.of("message", "Souscription approuvée — compte activé")).build();
}
/**
* Rejette une souscription avec un commentaire obligatoire.
*
* <p>Le corps de la requête doit contenir {@code {"commentaire": "..."}}.
*
* @param id UUID de la souscription
* @param body JSON avec le champ {@code commentaire}
*/
@POST
@Path("/admin/{id}/rejeter")
@RolesAllowed({"SUPER_ADMIN"})
public Response rejeter(@PathParam("id") UUID id, Map<String, String> body) {
LOG.infof("POST /api/souscriptions/admin/%s/rejeter", id);
String commentaire = body != null ? body.get("commentaire") : null;
if (commentaire == null || commentaire.isBlank()) {
throw new BadRequestException("Le champ 'commentaire' est obligatoire pour un rejet");
}
UUID superAdminId = securiteHelper.resolveMembreId();
souscriptionService.rejeter(id, superAdminId, commentaire);
return Response.ok(Map.of("message", "Souscription rejetée")).build();
}
}

View File

@@ -29,7 +29,7 @@ import java.util.UUID;
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Suggestions", description = "Gestion des suggestions utilisateur")
@Slf4j
@RolesAllowed({ "USER", "ADMIN", "MEMBRE" })
@RolesAllowed({ "USER", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public class SuggestionResource {
@Inject

View File

@@ -29,7 +29,7 @@ import java.util.UUID;
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Tickets", description = "Gestion des tickets support")
@Slf4j
@RolesAllowed({ "USER", "ADMIN", "MEMBRE" })
@RolesAllowed({ "USER", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public class TicketResource {
@Inject

View File

@@ -36,7 +36,7 @@ import io.quarkus.security.identity.SecurityIdentity;
@Path("/api/references/types-organisation")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
public class TypeOrganisationReferenceResource {
private static final String DOMAINE_TYPE_ORGANISATION = "TYPE_ORGANISATION";
@@ -56,7 +56,7 @@ public class TypeOrganisationReferenceResource {
}
@POST
@RolesAllowed({ "SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN" })
public Response create(@Valid CreateTypeReferenceRequest request) {
CreateTypeReferenceRequest withDomaine = CreateTypeReferenceRequest.builder()
.domaine(DOMAINE_TYPE_ORGANISATION)
@@ -83,7 +83,7 @@ public class TypeOrganisationReferenceResource {
@PUT
@Path("/{id}")
@RolesAllowed({ "SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN" })
public Response update(@PathParam("id") UUID id, @Valid UpdateTypeReferenceRequest request) {
try {
TypeReferenceResponse updated = typeReferenceService.modifier(id, request);
@@ -97,10 +97,10 @@ public class TypeOrganisationReferenceResource {
@DELETE
@Path("/{id}")
@RolesAllowed({ "SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN" })
@RolesAllowed({ "SUPER_ADMIN", "ADMIN" })
public Response supprimer(@PathParam("id") UUID id) {
try {
if (securityIdentity.hasRole("SUPER_ADMIN") || securityIdentity.hasRole("SUPER_ADMINISTRATEUR")) {
if (securityIdentity.hasRole("SUPER_ADMIN")) {
typeReferenceService.supprimerPourSuperAdmin(id);
} else {
typeReferenceService.supprimer(id);

View File

@@ -52,7 +52,7 @@ import org.jboss.logging.Logger;
@Tag(name = "Types de référence", description = "Gestion des données de référence"
+ " paramétrables")
@RolesAllowed({
"SUPER_ADMIN", "SUPER_ADMINISTRATEUR",
"SUPER_ADMIN",
"ADMIN", "MEMBRE", "USER"
})
public class TypeReferenceResource {
@@ -174,7 +174,7 @@ public class TypeReferenceResource {
*/
@POST
@RolesAllowed({
"SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN"
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Créer une référence", description = "Ajoute une nouvelle valeur dans"
+ " un domaine")
@@ -212,7 +212,7 @@ public class TypeReferenceResource {
@PUT
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN", "SUPER_ADMINISTRATEUR", "ADMIN"
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Modifier une référence", description = "Met à jour une valeur"
+ " existante")
@@ -251,7 +251,7 @@ public class TypeReferenceResource {
@DELETE
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN", "SUPER_ADMINISTRATEUR"
"SUPER_ADMIN"
})
@Operation(summary = "Supprimer une référence", description = "Supprime une valeur non"
+ " système")

View File

@@ -29,7 +29,7 @@ import org.jboss.logging.Logger;
@Path("/api/wave")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "MEMBRE", "USER" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Tag(name = "Wave Mobile Money", description = "Gestion des comptes et transactions Wave Mobile Money")
public class WaveResource {
@@ -43,7 +43,7 @@ public class WaveResource {
// ========================================
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/comptes")
@Operation(summary = "Créer un compte Wave", description = "Crée un nouveau compte Wave pour un membre ou une organisation")
@APIResponses({
@@ -67,7 +67,7 @@ public class WaveResource {
}
@PUT
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/comptes/{id}")
@Operation(summary = "Mettre à jour un compte Wave", description = "Met à jour les informations d'un compte Wave existant")
@APIResponses({
@@ -94,7 +94,7 @@ public class WaveResource {
}
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/comptes/{id}/verifier")
@Operation(summary = "Vérifier un compte Wave", description = "Vérifie la validité d'un compte Wave")
@APIResponses({
@@ -193,7 +193,7 @@ public class WaveResource {
// ========================================
@POST
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/transactions")
@Operation(summary = "Créer une transaction Wave", description = "Initie une nouvelle transaction de paiement Wave")
@APIResponses({
@@ -213,7 +213,7 @@ public class WaveResource {
}
@PUT
@RolesAllowed({ "ADMIN", "MEMBRE" })
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/transactions/{waveTransactionId}/statut")
@Operation(summary = "Mettre à jour le statut d'une transaction", description = "Met à jour le statut d'une transaction Wave (ex: COMPLETED, FAILED)")
@APIResponses({