fix(security): audit RBAC complet v3.0 — rôles normalisés, lifecycle, changement mdp mobile

RBAC:
- HealthResource: @PermitAll
- RoleResource: @RolesAllowed ADMIN/SUPER_ADMIN/ADMIN_ORGANISATION class-level
- PropositionAideResource: @RolesAllowed MEMBRE/USER class-level
- AuthCallbackResource: @PermitAll
- EvenementResource: @PermitAll /publics et /test, count restreint
- BackupResource/LogsMonitoringResource/SystemResource: MODERATOR → MODERATEUR
- AnalyticsResource: MANAGER/MEMBER → ADMIN_ORGANISATION/MEMBRE
- RoleConstant.java: constantes de rôles centralisées

Cycle de vie membres:
- MemberLifecycleService: ajouterMembre()/retirerMembre() sur activation/radiation/archivage
- MembreResource: endpoint GET /numero/{numeroMembre}
- MembreService: méthode trouverParNumeroMembre()

Changement mot de passe:
- CompteAdherentResource: endpoint POST /auth/change-password (mobile)
- MembreKeycloakSyncService: changerMotDePasseDirectKeycloak() via API Admin Keycloak directe
- Fallback automatique si lions-user-manager indisponible

Workflow:
- Flyway V17-V23: rôles, types org, formules Option C, lifecycle columns, bareme cotisation
- Nouvelles classes: MemberLifecycleService, OrganisationModuleService, scheduler
- Security: OrganisationContextFilter, OrganisationContextHolder, ModuleAccessFilter
This commit is contained in:
dahoud
2026-04-07 20:52:26 +00:00
parent c74ae25ad6
commit a2dfae9a0b
78 changed files with 5637 additions and 271 deletions

View File

@@ -26,8 +26,8 @@ quarkus.http.cors.origins=*
quarkus.oidc.tenant-enabled=true
quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow
quarkus.oidc.client-id=unionflow-server
# Validation audience : seuls les tokens destinés à unionflow-server sont acceptés
# Nécessite un audience mapper dans Keycloak : unionflow-mobile client → scope → audience mapper → unionflow-server
# Audience mapper configuré sur unionflow-client et unionflow-mobile dans Keycloak
# → les tokens contiennent désormais "unionflow-server" dans le claim aud
quarkus.oidc.token.audience=unionflow-server
quarkus.oidc.credentials.secret=unionflow-secret-2025
quarkus.oidc.tls.verification=none

View File

@@ -0,0 +1,144 @@
-- ============================================================================
-- V17 — Catégorisation des rôles + seed rôles fonctionnels et métier
-- Architecture hybride RBAC : PLATEFORME + FONCTIONNEL + METIER
-- ============================================================================
-- 1. Ajouter la colonne categorie
ALTER TABLE roles ADD COLUMN IF NOT EXISTS categorie VARCHAR(30);
-- 2. Taguer les rôles plateforme existants (seedés en V13)
UPDATE roles
SET categorie = 'PLATEFORME'
WHERE code IN ('SUPERADMIN', 'ORGADMIN', 'MODERATOR', 'ACTIVEMEMBER', 'SIMPLEMEMBER', 'VISITOR')
AND categorie IS NULL;
-- 3. Contrainte NOT NULL après mise à jour (valeur par défaut)
ALTER TABLE roles ALTER COLUMN categorie SET DEFAULT 'FONCTIONNEL';
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'roles' AND column_name = 'categorie') THEN
UPDATE roles SET categorie = 'FONCTIONNEL' WHERE categorie IS NULL;
END IF;
END $$;
-- 4. Index sur categorie
CREATE INDEX IF NOT EXISTS idx_role_categorie ON roles (categorie);
-- ============================================================================
-- Rôles FONCTIONNELS — communs à toutes les organisations
-- ============================================================================
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'FONCTIONNEL', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('PRESIDENT', 'Président', 'Représentant légal de l''organisation', 15),
('VICE_PRESIDENT', 'Vice-Président', 'Représentant légal adjoint', 16),
('SECRETAIRE', 'Secrétaire', 'Gestion administrative et des archives', 25),
('SECRETAIRE_ADJOINT', 'Secrétaire Adjoint', 'Assistant du secrétaire', 26),
('TRESORIER', 'Trésorier', 'Gestion des finances et de la trésorerie', 25),
('TRESORIER_ADJOINT', 'Trésorier Adjoint', 'Assistant du trésorier', 26),
('COMMISSAIRE_COMPTES', 'Commissaire aux Comptes', 'Contrôle et audit interne des comptes', 30),
('RESPONSABLE_MEMBRES', 'Responsable des Membres', 'Gestion des adhésions et des membres', 35),
('RESPONSABLE_COMMUNICATION','Responsable Communication', 'Communication interne et externe', 35),
('RESPONSABLE_EVENEMENTS', 'Responsable Évènements', 'Organisation des évènements et activités', 35),
('RESPONSABLE_TECHNIQUE', 'Responsable Technique', 'Support et outils techniques', 35),
('RESPONSABLE_SOCIAL', 'Responsable Social', 'Activités solidaires et entraide', 35)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- ============================================================================
-- Rôles METIER — spécifiques par type d'organisation
-- ============================================================================
-- Tontine
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('TONTINE_MANAGER', 'Responsable Tontine', 'Gestion des cycles, cotisations et rotations tontine', 20),
('TONTINE_COLLECTOR', 'Collecteur Tontine', 'Collecte des cotisations périodiques de la tontine', 30)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Mutuelle / Épargne / Crédit
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('MUTUELLE_RESP', 'Responsable Mutuelle', 'Gestion des comptes épargne et demandes de crédit', 20),
('CREDIT_ANALYSTE', 'Analyste Crédit', 'Analyse et instruction des dossiers de crédit', 25),
('EPARGNE_MANAGER', 'Gestionnaire Épargne', 'Supervision des comptes et transactions épargne', 25)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Coopérative
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('COOP_RESP', 'Responsable Coopérative', 'Gestion des campagnes et productions agricoles', 20)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- ONG / Association
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('ONG_RESP', 'Responsable ONG', 'Pilotage des projets et programmes ONG', 20),
('PROJET_MANAGER', 'Chef de Projet', 'Gestion et suivi d''un projet spécifique', 25),
('DONATEUR', 'Donateur', 'Contributeur financier externe à l''ONG', 50)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Culte / Religieux
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('CULTE_RESP', 'Responsable Culte', 'Gestion des dons religieux et activités cultuelles', 20),
('PASTEUR', 'Pasteur / Imam / Prêtre', 'Leader spirituel de l''organisation religieuse', 10),
('DIACRE', 'Diacre / Responsable Groupe', 'Responsable d''un groupe ou d''une cellule', 30)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Vote / Gouvernance
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('VOTE_RESP', 'Responsable Vote', 'Administration des campagnes de vote et élections', 20),
('SCRUTATEUR', 'Scrutateur', 'Dépouillement et comptage des votes', 30)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Collecte de fonds
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('COLLECTE_RESP', 'Responsable Collecte', 'Gestion des campagnes de collecte de fonds', 20)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);
-- Registre / Professionnel
INSERT INTO roles (id, nom, code, libelle, description, niveau_hierarchique, type_role, categorie, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT
gen_random_uuid(), v.code, v.code, v.libelle, v.description, v.niveau_hierarchique, 'SYSTEME', 'METIER', true,
NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
('REGISTRE_RESP', 'Responsable Registre', 'Gestion des agréments et accréditations professionnelles', 20),
('CONSULTANT', 'Consultant', 'Accès lecture étendu pour analyse et conseil', 45),
('GESTIONNAIRE_RH', 'Gestionnaire RH', 'Gestion des ressources humaines de l''organisation', 30)
) AS v(code, libelle, description, niveau_hierarchique)
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE roles.code = v.code);

View File

@@ -0,0 +1,135 @@
-- ============================================================================
-- V18 — Colonne categorie_type sur organisations + seed des 17 types officiels
-- Colonnes NOT NULL de TypeReference : domaine, code, libelle,
-- ordre_affichage (default 0), est_defaut (default false), est_systeme (default true pour seed)
-- ============================================================================
-- 1. Ajouter les colonnes sur organisations
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS categorie_type VARCHAR(50);
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS modules_actifs TEXT;
-- 2. Index
CREATE INDEX IF NOT EXISTS idx_organisation_categorie ON organisations (categorie_type);
-- ============================================================================
-- 3. S'assurer que les colonnes nécessaires existent dans types_reference
-- ============================================================================
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'types_reference' AND column_name = 'categorie'
) THEN
ALTER TABLE types_reference ADD COLUMN categorie VARCHAR(50);
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'types_reference' AND column_name = 'modules_requis'
) THEN
ALTER TABLE types_reference ADD COLUMN modules_requis TEXT;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'types_reference' AND column_name = 'ordre_affichage'
) THEN
ALTER TABLE types_reference ADD COLUMN ordre_affichage INTEGER NOT NULL DEFAULT 0;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'types_reference' AND column_name = 'est_defaut'
) THEN
ALTER TABLE types_reference ADD COLUMN est_defaut BOOLEAN NOT NULL DEFAULT false;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'types_reference' AND column_name = 'est_systeme'
) THEN
ALTER TABLE types_reference ADD COLUMN est_systeme BOOLEAN NOT NULL DEFAULT false;
END IF;
END $$;
-- ============================================================================
-- 4. Seed des types officiels dans types_reference
-- ============================================================================
-- Catégorie ASSOCIATIF
INSERT INTO types_reference (id, domaine, libelle, code, categorie, modules_requis, actif, est_defaut, est_systeme, ordre_affichage, date_creation, date_modification, version)
SELECT gen_random_uuid(), 'TYPE_ORGANISATION', v.libelle, v.code, v.categorie, v.modules_requis, true, false, true, 0, NOW(), NOW(), 0
FROM (VALUES
('Association Générale', 'ASSOCIATION', 'ASSOCIATIF', 'MEMBRES,COTISATIONS,EVENEMENTS,COMMUNICATION,DOCUMENTS,VOTES,AIDE'),
('Club Service', 'CLUB_SERVICE', 'ASSOCIATIF', 'MEMBRES,COTISATIONS,EVENEMENTS,COMMUNICATION,DOCUMENTS,VOTES,PROJETS_SOLIDAIRES'),
('Club Sportif', 'CLUB_SPORTIF', 'ASSOCIATIF', 'MEMBRES,COTISATIONS,EVENEMENTS,COMMUNICATION,DOCUMENTS'),
('Club Culturel', 'CLUB_CULTUREL','ASSOCIATIF', 'MEMBRES,COTISATIONS,EVENEMENTS,COMMUNICATION,DOCUMENTS')
) AS v(libelle, code, categorie, modules_requis)
WHERE NOT EXISTS (
SELECT 1 FROM types_reference WHERE types_reference.code = v.code AND domaine = 'TYPE_ORGANISATION'
);
-- Catégorie FINANCIER_SOLIDAIRE
INSERT INTO types_reference (id, domaine, libelle, code, categorie, modules_requis, actif, est_defaut, est_systeme, ordre_affichage, date_creation, date_modification, version)
SELECT gen_random_uuid(), 'TYPE_ORGANISATION', v.libelle, v.code, v.categorie, v.modules_requis, true, false, true, 0, NOW(), NOW(), 0
FROM (VALUES
('Tontine', 'TONTINE', 'FINANCIER_SOLIDAIRE', 'MEMBRES,COTISATIONS,TONTINE,COMMUNICATION,DOCUMENTS,FINANCE'),
('Mutuelle d''Épargne', 'MUTUELLE_EPARGNE', 'FINANCIER_SOLIDAIRE', 'MEMBRES,COTISATIONS,EPARGNE,COMMUNICATION,DOCUMENTS,FINANCE,LCB_FT'),
('Mutuelle de Crédit', 'MUTUELLE_CREDIT', 'FINANCIER_SOLIDAIRE', 'MEMBRES,COTISATIONS,EPARGNE,CREDIT,COMMUNICATION,DOCUMENTS,FINANCE,LCB_FT'),
('Coopérative', 'COOPERATIVE', 'FINANCIER_SOLIDAIRE', 'MEMBRES,COTISATIONS,AGRICULTURE,COMMUNICATION,DOCUMENTS,FINANCE')
) AS v(libelle, code, categorie, modules_requis)
WHERE NOT EXISTS (
SELECT 1 FROM types_reference WHERE types_reference.code = v.code AND domaine = 'TYPE_ORGANISATION'
);
-- Catégorie RELIGIEUX
INSERT INTO types_reference (id, domaine, libelle, code, categorie, modules_requis, actif, est_defaut, est_systeme, ordre_affichage, date_creation, date_modification, version)
SELECT gen_random_uuid(), 'TYPE_ORGANISATION', v.libelle, v.code, v.categorie, v.modules_requis, true, false, true, 0, NOW(), NOW(), 0
FROM (VALUES
('Église / Communauté Religieuse', 'EGLISE', 'RELIGIEUX', 'MEMBRES,COTISATIONS,CULTE_DONS,EVENEMENTS,COMMUNICATION,DOCUMENTS'),
('Groupe de Prière / Cellule', 'GROUPE_PRIERE', 'RELIGIEUX', 'MEMBRES,EVENEMENTS,COMMUNICATION,DOCUMENTS')
) AS v(libelle, code, categorie, modules_requis)
WHERE NOT EXISTS (
SELECT 1 FROM types_reference WHERE types_reference.code = v.code AND domaine = 'TYPE_ORGANISATION'
);
-- Catégorie PROFESSIONNEL
INSERT INTO types_reference (id, domaine, libelle, code, categorie, modules_requis, actif, est_defaut, est_systeme, ordre_affichage, date_creation, date_modification, version)
SELECT gen_random_uuid(), 'TYPE_ORGANISATION', v.libelle, v.code, v.categorie, v.modules_requis, true, false, true, 0, NOW(), NOW(), 0
FROM (VALUES
('ONG', 'ONG', 'PROFESSIONNEL', 'MEMBRES,COTISATIONS,PROJETS_ONG,COLLECTE_FONDS,COMMUNICATION,DOCUMENTS,FINANCE'),
('Fondation', 'FONDATION', 'PROFESSIONNEL', 'MEMBRES,COLLECTE_FONDS,PROJETS_ONG,COMMUNICATION,DOCUMENTS,FINANCE'),
('Syndicat', 'SYNDICAT', 'PROFESSIONNEL', 'MEMBRES,COTISATIONS,VOTES,COMMUNICATION,DOCUMENTS,AIDE'),
('Ordre Professionnel / Chambre', 'ORDRE_PROFESSIONNEL', 'PROFESSIONNEL', 'MEMBRES,COTISATIONS,REGISTRE_AGREMENT,COMMUNICATION,DOCUMENTS,VOTES'),
('GIE', 'GIE', 'PROFESSIONNEL', 'MEMBRES,COTISATIONS,FINANCE,COMMUNICATION,DOCUMENTS')
) AS v(libelle, code, categorie, modules_requis)
WHERE NOT EXISTS (
SELECT 1 FROM types_reference WHERE types_reference.code = v.code AND domaine = 'TYPE_ORGANISATION'
);
-- Catégorie RESEAU_FEDERATION
INSERT INTO types_reference (id, domaine, libelle, code, categorie, modules_requis, actif, est_defaut, est_systeme, ordre_affichage, date_creation, date_modification, version)
SELECT gen_random_uuid(), 'TYPE_ORGANISATION', v.libelle, v.code, v.categorie, v.modules_requis, true, false, true, 0, NOW(), NOW(), 0
FROM (VALUES
('Fédération d''Organisations', 'FEDERATION', 'RESEAU_FEDERATION', 'MEMBRES,COTISATIONS,VOTES,COMMUNICATION,DOCUMENTS,FINANCE'),
('Réseau / Plateforme', 'RESEAU', 'RESEAU_FEDERATION', 'MEMBRES,EVENEMENTS,COMMUNICATION,DOCUMENTS')
) AS v(libelle, code, categorie, modules_requis)
WHERE NOT EXISTS (
SELECT 1 FROM types_reference WHERE types_reference.code = v.code AND domaine = 'TYPE_ORGANISATION'
);
-- ============================================================================
-- 5. Migrer les valeurs existantes dans organisations.categorie_type
-- ============================================================================
UPDATE organisations o
SET categorie_type = tr.categorie,
modules_actifs = tr.modules_requis
FROM types_reference tr
WHERE tr.code = o.type_organisation
AND tr.domaine = 'TYPE_ORGANISATION'
AND o.categorie_type IS NULL;
-- Valeur par défaut pour les organisations sans correspondance
UPDATE organisations
SET categorie_type = 'ASSOCIATIF'
WHERE categorie_type IS NULL;

View File

@@ -0,0 +1,180 @@
-- ============================================================================
-- V19 — Formules Option C : plan commercial + features + statuts org
-- Option C : prix = f(taille), modules = f(type org), features = f(plan)
-- ============================================================================
-- ============================================================================
-- 1. Ajouter les colonnes Option C sur formules_abonnement
-- ============================================================================
-- Nom commercial affiché à l'utilisateur (MICRO / DECOUVERTE / ESSENTIEL / AVANCE / PROFESSIONNEL / ENTERPRISE)
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS plan_commercial VARCHAR(30);
-- Niveau de reporting inclus dans ce plan
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS niveau_reporting VARCHAR(20) DEFAULT 'BASIQUE';
-- Accès API REST (pour intégrations externes)
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS api_access BOOLEAN DEFAULT FALSE;
-- Accès module fédération (gestion multi-org hiérarchique)
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS federation_access BOOLEAN DEFAULT FALSE;
-- Support prioritaire
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS support_prioritaire BOOLEAN DEFAULT FALSE;
-- SLA garanti (ex: '99.5%', '99.9%')
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS sla_garanti VARCHAR(10);
-- Nombre max d'administrateurs (NULL = illimité)
ALTER TABLE formules_abonnement ADD COLUMN IF NOT EXISTS max_admins INTEGER;
-- ============================================================================
-- 2. Mapper les 12 formules avec leur plan_commercial et features
-- ============================================================================
-- PETITE + BASIC → MICRO
UPDATE formules_abonnement SET
plan_commercial = 'MICRO',
niveau_reporting = 'BASIQUE',
api_access = FALSE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.0%',
max_admins = 2
WHERE code = 'BASIC' AND plage = 'PETITE';
-- PETITE + STANDARD → DECOUVERTE
UPDATE formules_abonnement SET
plan_commercial = 'DECOUVERTE',
niveau_reporting = 'STANDARD',
api_access = FALSE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.0%',
max_admins = 5
WHERE code = 'STANDARD' AND plage = 'PETITE';
-- PETITE + PREMIUM → ESSENTIEL
UPDATE formules_abonnement SET
plan_commercial = 'ESSENTIEL',
niveau_reporting = 'STANDARD',
api_access = TRUE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.5%',
max_admins = 10
WHERE code = 'PREMIUM' AND plage = 'PETITE';
-- MOYENNE + BASIC → ESSENTIEL
UPDATE formules_abonnement SET
plan_commercial = 'ESSENTIEL',
niveau_reporting = 'STANDARD',
api_access = FALSE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.5%',
max_admins = 5
WHERE code = 'BASIC' AND plage = 'MOYENNE';
-- MOYENNE + STANDARD → AVANCE
UPDATE formules_abonnement SET
plan_commercial = 'AVANCE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.5%',
max_admins = 15
WHERE code = 'STANDARD' AND plage = 'MOYENNE';
-- MOYENNE + PREMIUM → PROFESSIONNEL
UPDATE formules_abonnement SET
plan_commercial = 'PROFESSIONNEL',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = FALSE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'PREMIUM' AND plage = 'MOYENNE';
-- GRANDE + BASIC → AVANCE
UPDATE formules_abonnement SET
plan_commercial = 'AVANCE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = FALSE,
support_prioritaire = FALSE,
sla_garanti = '99.5%',
max_admins = 10
WHERE code = 'BASIC' AND plage = 'GRANDE';
-- GRANDE + STANDARD → PROFESSIONNEL
UPDATE formules_abonnement SET
plan_commercial = 'PROFESSIONNEL',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = FALSE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'STANDARD' AND plage = 'GRANDE';
-- GRANDE + PREMIUM → ENTERPRISE
UPDATE formules_abonnement SET
plan_commercial = 'ENTERPRISE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = TRUE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'PREMIUM' AND plage = 'GRANDE';
-- TRES_GRANDE + BASIC → ENTERPRISE
UPDATE formules_abonnement SET
plan_commercial = 'ENTERPRISE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = TRUE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'BASIC' AND plage = 'TRES_GRANDE';
-- TRES_GRANDE + STANDARD → ENTERPRISE
UPDATE formules_abonnement SET
plan_commercial = 'ENTERPRISE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = TRUE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'STANDARD' AND plage = 'TRES_GRANDE';
-- TRES_GRANDE + PREMIUM → ENTERPRISE
UPDATE formules_abonnement SET
plan_commercial = 'ENTERPRISE',
niveau_reporting = 'AVANCE',
api_access = TRUE,
federation_access = TRUE,
support_prioritaire = TRUE,
sla_garanti = '99.9%',
max_admins = NULL
WHERE code = 'PREMIUM' AND plage = 'TRES_GRANDE';
-- ============================================================================
-- 3. Enrichir les statuts de souscription_organisation
-- ============================================================================
-- Ajout des statuts de cycle de vie avancés
ALTER TABLE souscriptions_organisation ADD COLUMN IF NOT EXISTS date_expiration TIMESTAMP WITH TIME ZONE;
ALTER TABLE souscriptions_organisation ADD COLUMN IF NOT EXISTS jours_grace INTEGER DEFAULT 7;
ALTER TABLE souscriptions_organisation ADD COLUMN IF NOT EXISTS auto_renouvellement BOOLEAN DEFAULT FALSE;
ALTER TABLE souscriptions_organisation ADD COLUMN IF NOT EXISTS motif_suspension TEXT;
-- ============================================================================
-- 4. Index sur plan_commercial pour filtrage catalogue
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_formule_plan_commercial ON formules_abonnement (plan_commercial);

View File

@@ -0,0 +1,149 @@
-- ============================================================================
-- V20 — Enrichissement StatutMembre + Seed permissions atomiques
-- Les permissions suivent le format : MODULE > RESSOURCE > ACTION
-- ============================================================================
-- ============================================================================
-- 1. Statut INVITE sur membre_organisation
-- La colonne statut_membre est VARCHAR(30), pas d'enum SQL → simple check de longueur
-- Aucune modification DDL nécessaire, le type Java StatutMembre.INVITE est suffisant
-- ============================================================================
-- Vérifier que la colonne statut_membre est assez large pour les nouveaux statuts
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'membre_organisation'
AND column_name = 'statut_membre'
AND character_maximum_length < 30
) THEN
ALTER TABLE membre_organisation ALTER COLUMN statut_membre TYPE VARCHAR(30);
END IF;
END $$;
-- Ajouter les colonnes de cycle de vie enrichies
ALTER TABLE membre_organisation ADD COLUMN IF NOT EXISTS date_invitation TIMESTAMP WITH TIME ZONE;
ALTER TABLE membre_organisation ADD COLUMN IF NOT EXISTS date_expiration_invitation TIMESTAMP WITH TIME ZONE;
ALTER TABLE membre_organisation ADD COLUMN IF NOT EXISTS token_invitation VARCHAR(255);
ALTER TABLE membre_organisation ADD COLUMN IF NOT EXISTS invite_par UUID;
ALTER TABLE membre_organisation ADD COLUMN IF NOT EXISTS motif_archivage TEXT;
-- ============================================================================
-- 2. Seed permissions atomiques
-- Modules : ORGANISATION, MEMBRE, COTISATION, FINANCE, TONTINE, EPARGNE,
-- CREDIT, NOTIFICATION, AUDIT, RAPPORT, CONFIGURATION, AGRICULTURE,
-- ONG, CULTE, VOTE, COLLECTE, REGISTRE
-- Actions : CREATE, READ, UPDATE, DELETE, VALIDATE, EXPORT, IMPORT, SEND
-- ============================================================================
INSERT INTO permissions (id, code, module, ressource, action, libelle, description, actif, date_creation, date_modification, cree_par, modifie_par, version)
SELECT gen_random_uuid(), v.code, v.module, v.ressource, v.action, v.libelle, v.description, true, NOW(), NOW(), 'system', 'system', 0
FROM (VALUES
-- ── ORGANISATION ──────────────────────────────────────────────────────────
('ORGANISATION > ORGANISATION > CREATE', 'ORGANISATION', 'ORGANISATION', 'CREATE', 'Créer une organisation', 'Créer une nouvelle organisation sur la plateforme'),
('ORGANISATION > ORGANISATION > READ', 'ORGANISATION', 'ORGANISATION', 'READ', 'Consulter une organisation', 'Voir les détails d''une organisation'),
('ORGANISATION > ORGANISATION > UPDATE', 'ORGANISATION', 'ORGANISATION', 'UPDATE', 'Modifier une organisation', 'Modifier les informations d''une organisation'),
('ORGANISATION > ORGANISATION > DELETE', 'ORGANISATION', 'ORGANISATION', 'DELETE', 'Supprimer une organisation', 'Archiver ou supprimer une organisation'),
('ORGANISATION > ORGANISATION > EXPORT', 'ORGANISATION', 'ORGANISATION', 'EXPORT', 'Exporter les données organisation','Exporter les données de l''organisation'),
-- ── MEMBRE ────────────────────────────────────────────────────────────────
('MEMBRE > MEMBRE > CREATE', 'MEMBRE', 'MEMBRE', 'CREATE', 'Créer un membre', 'Ajouter un nouveau membre'),
('MEMBRE > MEMBRE > READ', 'MEMBRE', 'MEMBRE', 'READ', 'Consulter un membre', 'Voir la fiche d''un membre'),
('MEMBRE > MEMBRE > UPDATE', 'MEMBRE', 'MEMBRE', 'UPDATE', 'Modifier un membre', 'Modifier les informations d''un membre'),
('MEMBRE > MEMBRE > DELETE', 'MEMBRE', 'MEMBRE', 'DELETE', 'Désactiver un membre', 'Désactiver ou archiver un membre'),
('MEMBRE > MEMBRE > EXPORT', 'MEMBRE', 'MEMBRE', 'EXPORT', 'Exporter les membres', 'Exporter la liste des membres'),
('MEMBRE > MEMBRE > IMPORT', 'MEMBRE', 'MEMBRE', 'IMPORT', 'Importer des membres', 'Importer une liste de membres'),
('MEMBRE > INVITATION > SEND', 'MEMBRE', 'INVITATION', 'SEND', 'Inviter un membre', 'Envoyer une invitation à rejoindre l''organisation'),
('MEMBRE > STATUT > UPDATE', 'MEMBRE', 'STATUT', 'UPDATE', 'Changer le statut d''un membre', 'Activer, suspendre, archiver un membre'),
-- ── COTISATION ────────────────────────────────────────────────────────────
('COTISATION > COTISATION > CREATE', 'COTISATION', 'COTISATION', 'CREATE', 'Créer une cotisation', 'Enregistrer une cotisation'),
('COTISATION > COTISATION > READ', 'COTISATION', 'COTISATION', 'READ', 'Consulter les cotisations', 'Voir les cotisations'),
('COTISATION > COTISATION > UPDATE', 'COTISATION', 'COTISATION', 'UPDATE', 'Modifier une cotisation', 'Modifier une cotisation'),
('COTISATION > COTISATION > DELETE', 'COTISATION', 'COTISATION', 'DELETE', 'Supprimer une cotisation', 'Supprimer une cotisation'),
('COTISATION > COTISATION > VALIDATE', 'COTISATION', 'COTISATION', 'VALIDATE', 'Valider une cotisation', 'Valider le paiement d''une cotisation'),
('COTISATION > COTISATION > EXPORT', 'COTISATION', 'COTISATION', 'EXPORT', 'Exporter les cotisations', 'Exporter le registre des cotisations'),
-- ── FINANCE ───────────────────────────────────────────────────────────────
('FINANCE > TRANSACTION > CREATE', 'FINANCE', 'TRANSACTION', 'CREATE', 'Créer une transaction', 'Enregistrer une transaction financière'),
('FINANCE > TRANSACTION > READ', 'FINANCE', 'TRANSACTION', 'READ', 'Consulter les transactions', 'Voir les transactions financières'),
('FINANCE > TRANSACTION > VALIDATE', 'FINANCE', 'TRANSACTION', 'VALIDATE', 'Valider une transaction', 'Approuver une transaction en attente'),
('FINANCE > BUDGET > READ', 'FINANCE', 'BUDGET', 'READ', 'Consulter le budget', 'Voir le budget de l''organisation'),
('FINANCE > BUDGET > UPDATE', 'FINANCE', 'BUDGET', 'UPDATE', 'Modifier le budget', 'Modifier les lignes budgétaires'),
('FINANCE > RAPPORT > READ', 'FINANCE', 'RAPPORT', 'READ', 'Consulter les rapports financiers','Voir les rapports et bilans financiers'),
('FINANCE > RAPPORT > EXPORT', 'FINANCE', 'RAPPORT', 'EXPORT', 'Exporter les rapports financiers','Télécharger les rapports financiers'),
-- ── TONTINE ───────────────────────────────────────────────────────────────
('TONTINE > TONTINE > CREATE', 'TONTINE', 'TONTINE', 'CREATE', 'Créer une tontine', 'Créer un nouveau cycle de tontine'),
('TONTINE > TONTINE > READ', 'TONTINE', 'TONTINE', 'READ', 'Consulter une tontine', 'Voir les détails d''une tontine'),
('TONTINE > TONTINE > UPDATE', 'TONTINE', 'TONTINE', 'UPDATE', 'Modifier une tontine', 'Modifier un cycle de tontine'),
('TONTINE > ROTATION > MANAGE', 'TONTINE', 'ROTATION', 'MANAGE', 'Gérer les rotations', 'Gérer l''ordre de rotation des membres'),
('TONTINE > COTISATION > CREATE','TONTINE', 'COTISATION', 'CREATE', 'Enregistrer une cotisation tontine', 'Enregistrer le paiement d''une cotisation de tontine'),
-- ── EPARGNE ───────────────────────────────────────────────────────────────
('EPARGNE > COMPTE > CREATE', 'EPARGNE', 'COMPTE', 'CREATE', 'Ouvrir un compte épargne', 'Créer un compte épargne pour un membre'),
('EPARGNE > COMPTE > READ', 'EPARGNE', 'COMPTE', 'READ', 'Consulter un compte épargne', 'Voir un compte épargne'),
('EPARGNE > TRANSACTION > CREATE', 'EPARGNE', 'TRANSACTION', 'CREATE', 'Effectuer une transaction épargne','Dépôt ou retrait sur compte épargne'),
('EPARGNE > TRANSACTION > READ', 'EPARGNE', 'TRANSACTION', 'READ', 'Consulter les transactions épargne','Voir l''historique des transactions'),
-- ── CREDIT ────────────────────────────────────────────────────────────────
('CREDIT > DEMANDE > CREATE', 'CREDIT', 'DEMANDE', 'CREATE', 'Soumettre une demande de crédit', 'Déposer un dossier de crédit'),
('CREDIT > DEMANDE > READ', 'CREDIT', 'DEMANDE', 'READ', 'Consulter une demande de crédit', 'Voir un dossier de crédit'),
('CREDIT > DEMANDE > VALIDATE', 'CREDIT', 'DEMANDE', 'VALIDATE', 'Approuver/Rejeter un crédit', 'Instruire et décider sur un dossier de crédit'),
('CREDIT > DECAISSEMENT > CREATE','CREDIT','DECAISSEMENT','CREATE','Décaisser un crédit', 'Exécuter le décaissement d''un crédit approuvé'),
-- ── NOTIFICATION ─────────────────────────────────────────────────────────
('NOTIFICATION > NOTIFICATION > CREATE', 'NOTIFICATION', 'NOTIFICATION', 'CREATE', 'Créer une notification', 'Envoyer une notification'),
('NOTIFICATION > NOTIFICATION > READ', 'NOTIFICATION', 'NOTIFICATION', 'READ', 'Lire ses notifications', 'Voir ses propres notifications'),
('NOTIFICATION > TEMPLATE > CREATE', 'NOTIFICATION', 'TEMPLATE', 'CREATE', 'Créer un template', 'Créer un template de notification'),
('NOTIFICATION > GROUPE > SEND', 'NOTIFICATION', 'GROUPE', 'SEND', 'Envoyer en masse', 'Envoyer des notifications groupées'),
-- ── AUDIT ─────────────────────────────────────────────────────────────────
('AUDIT > LOG > READ', 'AUDIT', 'LOG', 'READ', 'Consulter les logs d''audit', 'Voir les logs et traces d''activité'),
('AUDIT > LOG > EXPORT', 'AUDIT', 'LOG', 'EXPORT', 'Exporter les logs', 'Télécharger les logs d''audit'),
('AUDIT > STATISTIQUES > READ', 'AUDIT', 'STATISTIQUES', 'READ', 'Voir les statistiques d''audit', 'Analyser les métriques d''activité'),
-- ── RAPPORT ───────────────────────────────────────────────────────────────
('RAPPORT > RAPPORT > READ', 'RAPPORT', 'RAPPORT', 'READ', 'Consulter un rapport', 'Voir les rapports de l''organisation'),
('RAPPORT > RAPPORT > EXPORT', 'RAPPORT', 'RAPPORT', 'EXPORT', 'Exporter un rapport', 'Télécharger un rapport PDF/Excel'),
('RAPPORT > RAPPORT > CREATE', 'RAPPORT', 'RAPPORT', 'CREATE', 'Générer un rapport', 'Générer un nouveau rapport'),
-- ── CONFIGURATION ─────────────────────────────────────────────────────────
('CONFIGURATION > ORGANISATION > UPDATE', 'CONFIGURATION', 'ORGANISATION', 'UPDATE', 'Configurer l''organisation', 'Modifier les paramètres de l''organisation'),
('CONFIGURATION > ROLE > MANAGE', 'CONFIGURATION', 'ROLE', 'MANAGE', 'Gérer les rôles', 'Assigner ou révoquer des rôles aux membres'),
('CONFIGURATION > MODULE > MANAGE', 'CONFIGURATION', 'MODULE', 'MANAGE', 'Gérer les modules actifs', 'Activer ou désactiver des modules'),
-- ── AGRICULTURE / COOPERATIVE ─────────────────────────────────────────────
('AGRICULTURE > CAMPAGNE > CREATE', 'AGRICULTURE', 'CAMPAGNE', 'CREATE', 'Créer une campagne agricole', 'Créer et planifier une campagne'),
('AGRICULTURE > CAMPAGNE > READ', 'AGRICULTURE', 'CAMPAGNE', 'READ', 'Consulter une campagne agricole', 'Voir les détails d''une campagne'),
('AGRICULTURE > CAMPAGNE > UPDATE', 'AGRICULTURE', 'CAMPAGNE', 'UPDATE', 'Modifier une campagne agricole', 'Modifier une campagne en cours'),
-- ── ONG / PROJETS ─────────────────────────────────────────────────────────
('ONG > PROJET > CREATE', 'ONG', 'PROJET', 'CREATE', 'Créer un projet ONG', 'Créer et planifier un projet'),
('ONG > PROJET > READ', 'ONG', 'PROJET', 'READ', 'Consulter un projet ONG', 'Voir les détails d''un projet'),
('ONG > PROJET > UPDATE', 'ONG', 'PROJET', 'UPDATE', 'Modifier un projet ONG', 'Modifier un projet en cours'),
('ONG > COLLECTE > CREATE', 'ONG', 'COLLECTE', 'CREATE', 'Lancer une collecte de fonds', 'Créer une campagne de collecte'),
-- ── CULTE / DONS ──────────────────────────────────────────────────────────
('CULTE > DON > CREATE', 'CULTE', 'DON', 'CREATE', 'Enregistrer un don religieux', 'Enregistrer un don cultuel'),
('CULTE > DON > READ', 'CULTE', 'DON', 'READ', 'Consulter les dons religieux', 'Voir les dons et contributions cultuels'),
-- ── VOTE ─────────────────────────────────────────────────────────────────
('VOTE > CAMPAGNE > CREATE', 'VOTE', 'CAMPAGNE', 'CREATE', 'Créer une campagne de vote', 'Lancer une élection ou un référendum'),
('VOTE > CAMPAGNE > READ', 'VOTE', 'CAMPAGNE', 'READ', 'Consulter une campagne vote', 'Voir une campagne de vote'),
('VOTE > BULLETIN > CREATE', 'VOTE', 'BULLETIN', 'CREATE', 'Voter', 'Déposer son bulletin de vote'),
-- ── REGISTRE / AGREMENT ───────────────────────────────────────────────────
('REGISTRE > AGREMENT > CREATE', 'REGISTRE', 'AGREMENT', 'CREATE', 'Enregistrer un agrément', 'Créer une accréditation professionnelle'),
('REGISTRE > AGREMENT > READ', 'REGISTRE', 'AGREMENT', 'READ', 'Consulter les agréments', 'Voir les agréments et accréditations')
) AS v(code, module, ressource, action, libelle, description)
WHERE NOT EXISTS (SELECT 1 FROM permissions WHERE permissions.code = v.code);
-- ============================================================================
-- 3. Index sur les colonnes d'invitation
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_mo_token_invitation ON membre_organisation (token_invitation);
CREATE INDEX IF NOT EXISTS idx_mo_date_invitation ON membre_organisation (date_invitation);

View File

@@ -0,0 +1,26 @@
-- V21 : Colonnes cycle de vie membres (MemberLifecycleService)
-- Ajout de role_org sur membre_organisation (manquant de V20)
-- + index sur token_invitation (version courte 32 chars → VARCHAR(64))
-- Ajouter role_org si absent
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'membre_organisation' AND column_name = 'role_org'
) THEN
ALTER TABLE membre_organisation ADD COLUMN role_org VARCHAR(50);
END IF;
END$$;
-- Ajuster longueur token_invitation à 64 pour UUID sans tirets (32 chars) ou extensions futures
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'membre_organisation' AND column_name = 'token_invitation'
AND character_maximum_length = 255
) THEN
ALTER TABLE membre_organisation ALTER COLUMN token_invitation TYPE VARCHAR(64);
END IF;
END$$;

View File

@@ -0,0 +1,31 @@
-- V22 : Barème de cotisation par rôle + flag génération automatique mensuelle
-- Auteur : UnionFlow Team
-- Date : 2026-04-07
-- 1. Activation génération automatique dans les paramètres de cotisation
ALTER TABLE parametres_cotisation_organisation
ADD COLUMN IF NOT EXISTS generation_automatique_activee BOOLEAN NOT NULL DEFAULT FALSE;
-- 2. Table des barèmes de cotisation par rôle fonctionnel
CREATE TABLE IF NOT EXISTS bareme_cotisation_role (
id UUID NOT NULL DEFAULT gen_random_uuid(),
organisation_id UUID NOT NULL,
role_org VARCHAR(50) NOT NULL,
montant_mensuel NUMERIC(12,2) NOT NULL DEFAULT 0.00,
montant_annuel NUMERIC(12,2) NOT NULL DEFAULT 0.00,
description VARCHAR(255),
-- BaseEntity columns
date_creation TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT TRUE,
CONSTRAINT pk_bareme_cotisation_role PRIMARY KEY (id),
CONSTRAINT fk_bareme_cot_organisation
FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT uq_bareme_cot_org_role
UNIQUE (organisation_id, role_org)
);
CREATE INDEX IF NOT EXISTS idx_bareme_cot_org ON bareme_cotisation_role (organisation_id);

View File

@@ -0,0 +1,13 @@
-- V23: Fix system_alerts legacy NOT NULL columns
--
-- Contexte: L'entité SystemAlert a été refactorisée pour utiliser les colonnes
-- modernes (alert_type, level, title) ajoutées par hibernate.ddl-auto=update.
-- Les anciennes colonnes V1 (type_alerte, severite, titre) restaient NOT NULL
-- et provoquaient un ConstraintViolationException toutes les 60 secondes dans
-- AlertMonitoringService#monitorSystemMetrics.
--
-- Fix: rendre ces colonnes obsolètes nullable pour que l'INSERT Hibernate réussisse.
ALTER TABLE system_alerts ALTER COLUMN type_alerte DROP NOT NULL;
ALTER TABLE system_alerts ALTER COLUMN severite DROP NOT NULL;
ALTER TABLE system_alerts ALTER COLUMN titre DROP NOT NULL;