feat(v3.0): implémentation Phases 0-8 — RBAC, lifecycle, multi-org, plans, dashboards

Phase 0 : @RolesAllowed SUPER_ADMIN sur POST/DELETE organisations ; AuthenticationFilter pages super-admin
Phase 2 : OrganisationModuleService, @RequiresModule, ModuleAccessFilter, RoleService, PermissionChecker
Phase 3 : multi-org context switching (OrganisationContextFilter, headers X-Active-Organisation-Id / X-Active-Role)
Phase 4 : feature-gating navigation par typeOrganisation (web MenuBean + mobile MorePage)
Phase 5 : MemberLifecycleService — 8 transitions (activer/suspendre/radier/archiver/inviter/accepter/expirer/rappels)
Phase 6 : FormuleAbonnement Option C (planCommercial, apiAccess, federationAccess, quotas) + SouscriptionOrganisation méthodes quota
Phase 7 : DashboardResource SUPER_ADMIN ajouté ; DashboardBean.checkAccessAndRedirect() ; dashboards distincts par rôle
Phase 8 : MembreResourceLifecycleRbacTest, SouscriptionQuotaOptionCTest, OrganisationContextHolderTest, OrganisationContextFilterMultiOrgTest, MemberLifecycleServiceTest
This commit is contained in:
dahoud
2026-04-06 16:49:47 +00:00
parent 39e98a9cb3
commit aef5548e87
34 changed files with 823 additions and 86 deletions

View File

@@ -7,6 +7,7 @@ import dev.lions.unionflow.server.api.dto.membre.response.MembreSummaryResponse;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
import dev.lions.unionflow.server.entity.FormuleAbonnement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import io.quarkus.panache.common.Page;
@@ -200,6 +201,29 @@ public class MembreService {
Membre membre = membreRepository.findByIdOptional(membreId)
.orElseThrow(() -> new jakarta.ws.rs.NotFoundException("Membre non trouvé avec l'ID: " + membreId));
// Vérifier le quota d'administrateurs selon la formule souscrite
membreOrganisationRepository.findFirstByMembreId(membreId).ifPresent(mo -> {
UUID orgId = mo.getOrganisation().getId();
entityManager.createQuery(
"SELECT s FROM SouscriptionOrganisation s WHERE s.organisation.id = :orgId AND s.statut = 'ACTIVE'",
dev.lions.unionflow.server.entity.SouscriptionOrganisation.class)
.setParameter("orgId", orgId)
.getResultStream().findFirst().ifPresent(souscription -> {
FormuleAbonnement formule = souscription.getFormule();
if (formule != null && formule.getMaxAdmins() != null) {
long adminCount = entityManager.createQuery(
"SELECT COUNT(mr) FROM MembreRole mr WHERE mr.organisation.id = :orgId " +
"AND mr.role.code = 'ORGADMIN' AND mr.actif = true", Long.class)
.setParameter("orgId", orgId).getSingleResult();
if (adminCount >= formule.getMaxAdmins()) {
throw new jakarta.ws.rs.ForbiddenException(
"Le quota d'administrateurs de votre plan (" + formule.getMaxAdmins() +
") est atteint. Mettez à niveau votre abonnement pour ajouter plus d'administrateurs.");
}
}
});
});
membre.setStatutCompte("ACTIF");
membre.setActif(true);
membreRepository.persist(membre);
@@ -432,7 +456,7 @@ public class MembreService {
dev.lions.unionflow.server.entity.MembreOrganisation mo = membre.getMembresOrganisations().get(0);
if (mo.getOrganisation() != null) {
dto.setOrganisationId(mo.getOrganisation().getId());
dto.setAssociationNom(mo.getOrganisation().getNom());
dto.setOrganisationNom(mo.getOrganisation().getNom());
}
dto.setDateAdhesion(mo.getDateAdhesion());
} else if (membre.getDateCreation() != null) {
@@ -498,12 +522,12 @@ public class MembreService {
}
UUID organisationId = null;
String associationNom = null;
String organisationNom = null;
if (membre.getMembresOrganisations() != null && !membre.getMembresOrganisations().isEmpty()) {
dev.lions.unionflow.server.entity.MembreOrganisation mo = membre.getMembresOrganisations().get(0);
if (mo.getOrganisation() != null) {
organisationId = mo.getOrganisation().getId();
associationNom = mo.getOrganisation().getNom();
organisationNom = mo.getOrganisation().getNom();
}
}
@@ -521,7 +545,7 @@ public class MembreService {
membre.getActif(),
rolesNames,
organisationId,
associationNom);
organisationNom);
}
/** Convertit un CreateMembreRequest en entité Membre */
@@ -1183,16 +1207,33 @@ public class MembreService {
// Assigner le rôle SIMPLEMEMBER par défaut
assignerRoleDefaut(membreOrganisation, "SIMPLEMEMBER");
// Incrémenter quota si souscription existe
// Vérifier quota et expiration avant d'incrémenter
if (souscriptionOpt.isPresent()) {
dev.lions.unionflow.server.entity.SouscriptionOrganisation souscription = souscriptionOpt.get();
// Vérifier que la souscription n'est pas expirée
if (!souscription.isActive()) {
throw new jakarta.ws.rs.ForbiddenException(
"La souscription de l'organisation est expirée ou inactive. " +
"Veuillez renouveler votre abonnement avant d'ajouter de nouveaux membres.");
}
// Vérifier que le quota n'est pas dépassé
if (souscription.isQuotaDepasse()) {
Integer max = souscription.getQuotaMax();
throw new jakarta.ws.rs.ForbiddenException(
"Le quota de membres de votre plan est atteint (" + max + "/" + max + "). " +
"Veuillez mettre à niveau votre formule d'abonnement.");
}
souscription.incrementerQuota();
entityManager.persist(souscription);
LOG.infof("Quota souscription incrémenté (utilise: %d/%s)",
souscription.getQuotaUtilise(),
souscription.getQuotaMax() != null ? souscription.getQuotaMax().toString() : "");
} else {
LOG.warn("Aucune souscription active trouvée pour organisation " + organisationId);
LOG.warn("Aucune souscription active trouvée pour organisation " + organisationId +
" — ajout du membre sans vérification de quota");
}
}