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:
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user