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

@@ -77,6 +77,39 @@ public class FormuleAbonnement extends BaseEntity {
@Column(name = "ordre_affichage", nullable = false)
private Integer ordreAffichage = 0;
// ── Champs Option C (ajoutés en V19) ──────────────────────────────────────
/** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */
@Column(name = "plan_commercial", length = 30)
private String planCommercial;
/** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */
@Column(name = "niveau_reporting", length = 20)
private String niveauReporting;
/** Accès à l'API REST (false pour les plans de base) */
@Builder.Default
@Column(name = "api_access", nullable = false)
private Boolean apiAccess = false;
/** Accès au module de fédération multi-org (ENTERPRISE uniquement) */
@Builder.Default
@Column(name = "federation_access", nullable = false)
private Boolean federationAccess = false;
/** Support prioritaire inclus */
@Builder.Default
@Column(name = "support_prioritaire", nullable = false)
private Boolean supportPrioritaire = false;
/** SLA garanti (ex: "99.0%", "99.9%") */
@Column(name = "sla_garanti", length = 10)
private String slaGaranti;
/** Nombre maximum d'administrateurs. NULL = illimité */
@Column(name = "max_admins")
private Integer maxAdmins;
public boolean isIllimitee() {
return maxMembres == null;
}

View File

@@ -5,8 +5,10 @@ import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.*;
/**
@@ -77,6 +79,34 @@ public class MembreOrganisation extends BaseEntity {
@JoinColumn(name = "approuve_par_id")
private Membre approuvePar;
// ── Champs d'invitation (StatutMembre.INVITE) ──────────────────────────────
/** Date à laquelle l'invitation a été envoyée. */
@Column(name = "date_invitation")
private LocalDateTime dateInvitation;
/** Date d'expiration de l'invitation (null = pas d'expiration). */
@Column(name = "date_expiration_invitation")
private LocalDateTime dateExpirationInvitation;
/** Token opaque utilisé dans le lien d'invitation envoyé par email. */
@Column(name = "token_invitation", length = 64)
private String tokenInvitation;
/** ID de l'administrateur qui a envoyé l'invitation. */
@Column(name = "invite_par")
private UUID invitePar;
/** Motif d'archivage (pour StatutMembre.ARCHIVE). */
@Column(name = "motif_archivage", length = 500)
private String motifArchivage;
// ── Rôle fonctionnel dans l'organisation ─────────────────────────────────
/** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */
@Column(name = "role_org", length = 50)
private String roleOrg;
// ── Relations ─────────────────────────────────────────────────────────────
/** Rôles de ce membre dans cette organisation */

View File

@@ -197,6 +197,14 @@ public class Organisation extends BaseEntity {
@Column(name = "accepte_nouveaux_membres", nullable = false)
private Boolean accepteNouveauxMembres = true;
/** Catégorie du type d'organisation (ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION) */
@Column(name = "categorie_type", length = 50)
private String categorieType;
/** Modules activés pour cette organisation (liste CSV, ex: "MEMBRES,COTISATIONS,TONTINE") */
@Column(name = "modules_actifs", length = 1000)
private String modulesActifs;
// Relations
/** Adhésions des membres à cette organisation */

View File

@@ -56,6 +56,11 @@ public class Role extends BaseEntity {
@Column(name = "type_role", nullable = false, length = 50)
private String typeRole;
/** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */
@Column(name = "categorie", length = 30)
@Builder.Default
private String categorie = "FONCTIONNEL";
/** Organisation propriétaire (null pour rôles système) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")