Files
unionflow-server-impl-quarkus/backup-migrations-20260316/V1__UnionFlow_Complete_Schema_OLD.sql

2330 lines
114 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ============================================================================
-- UnionFlow - Schema Complete V1
-- ============================================================================
-- ATTENTION: Ce fichier a été nettoyé pour supprimer les sections redondantes.
-- Il contient uniquement la version consolidée finale du schema avec CREATE TABLE IF NOT EXISTS.
-- Les sections avec CREATE/DROP/CREATE multiples ont été supprimées pour éviter les conflits.
-- Auteur: UnionFlow Team / Lions Dev
-- Date: 2026-03-13 (nettoyage)
-- ============================================================================
-- ========== V1.7__Create_All_Missing_Tables.sql ==========
-- ============================================================================
-- V2.0 : Création de toutes les tables manquantes pour UnionFlow
-- Toutes les tables héritent de BaseEntity (id UUID PK, date_creation,
-- date_modification, cree_par, modifie_par, version, actif)
-- ============================================================================
-- Colonnes communes BaseEntity (à inclure dans chaque table)
-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
-- date_modification TIMESTAMP,
-- cree_par VARCHAR(255),
-- modifie_par VARCHAR(255),
-- version BIGINT DEFAULT 0,
-- actif BOOLEAN NOT NULL DEFAULT TRUE
-- ============================================================================
-- 1. TABLES PRINCIPALES (sans FK vers d'autres tables métier)
-- ============================================================================
-- Table membres (principale, référencée par beaucoup d'autres)
CREATE TABLE IF NOT EXISTS membres (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
email VARCHAR(255),
telephone VARCHAR(30),
numero_membre VARCHAR(50),
date_naissance DATE,
lieu_naissance VARCHAR(255),
sexe VARCHAR(10),
nationalite VARCHAR(100),
profession VARCHAR(255),
photo_url VARCHAR(500),
statut VARCHAR(30) DEFAULT 'ACTIF',
date_adhesion DATE,
keycloak_user_id VARCHAR(255),
keycloak_realm VARCHAR(255),
organisation_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- Table organisations (déjà créée en V1.2, mais IF NOT EXISTS pour sécurité)
CREATE TABLE IF NOT EXISTS organisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(255) NOT NULL,
sigle VARCHAR(50),
description TEXT,
type_organisation VARCHAR(50),
statut VARCHAR(30) DEFAULT 'ACTIVE',
email VARCHAR(255),
telephone VARCHAR(30),
site_web VARCHAR(500),
adresse_siege TEXT,
logo_url VARCHAR(500),
date_fondation DATE,
pays VARCHAR(100),
ville VARCHAR(100),
organisation_parente_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 2. TABLES SÉCURITÉ (Rôles et Permissions)
-- ============================================================================
CREATE TABLE IF NOT EXISTS roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL UNIQUE,
description VARCHAR(500),
code VARCHAR(50) NOT NULL UNIQUE,
niveau INTEGER DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL UNIQUE,
description VARCHAR(500),
code VARCHAR(100) NOT NULL UNIQUE,
module VARCHAR(100),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS roles_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES roles(id),
permission_id UUID NOT NULL REFERENCES permissions(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS membres_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_id UUID NOT NULL REFERENCES membres(id),
role_id UUID NOT NULL REFERENCES roles(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(membre_id, role_id, organisation_id)
);
-- ============================================================================
-- 3. TABLES FINANCE
-- ============================================================================
CREATE TABLE IF NOT EXISTS adhesions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_adhesion VARCHAR(50),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_demande TIMESTAMP,
date_approbation TIMESTAMP,
date_rejet TIMESTAMP,
motif_rejet TEXT,
frais_adhesion DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
montant_paye DECIMAL(15,2) DEFAULT 0,
approuve_par VARCHAR(255),
commentaire TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_reference VARCHAR(50),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
type_cotisation VARCHAR(50),
periode VARCHAR(50),
montant_du DECIMAL(15,2),
montant_paye DECIMAL(15,2) DEFAULT 0,
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_echeance DATE,
date_paiement TIMESTAMP,
methode_paiement VARCHAR(50),
reference_paiement VARCHAR(100),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reference VARCHAR(100),
montant DECIMAL(15,2) NOT NULL,
devise VARCHAR(10) DEFAULT 'XOF',
methode_paiement VARCHAR(50),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
type_paiement VARCHAR(50),
description TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_paiement TIMESTAMP,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_adhesions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
adhesion_id UUID REFERENCES adhesions(id),
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cotisation_id UUID REFERENCES cotisations(id),
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
evenement_id UUID,
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_aides (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
demande_aide_id UUID,
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 4. TABLES COMPTABILITÉ
-- ============================================================================
CREATE TABLE IF NOT EXISTS comptes_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_compte VARCHAR(20) NOT NULL,
libelle VARCHAR(255) NOT NULL,
type_compte VARCHAR(50),
solde DECIMAL(15,2) DEFAULT 0,
description TEXT,
compte_parent_id UUID,
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS journaux_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(20) NOT NULL,
libelle VARCHAR(255) NOT NULL,
type_journal VARCHAR(50),
description TEXT,
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS ecritures_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_piece VARCHAR(50),
date_ecriture DATE NOT NULL,
libelle VARCHAR(500),
montant_total DECIMAL(15,2),
statut VARCHAR(30) DEFAULT 'BROUILLON',
journal_id UUID REFERENCES journaux_comptables(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS lignes_ecriture (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ecriture_id UUID NOT NULL REFERENCES ecritures_comptables(id),
compte_id UUID NOT NULL REFERENCES comptes_comptables(id),
libelle VARCHAR(500),
montant_debit DECIMAL(15,2) DEFAULT 0,
montant_credit DECIMAL(15,2) DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 5. TABLES ÉVÉNEMENTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(255) NOT NULL,
description TEXT,
type_evenement VARCHAR(50),
statut VARCHAR(30) DEFAULT 'PLANIFIE',
priorite VARCHAR(20) DEFAULT 'NORMALE',
date_debut TIMESTAMP,
date_fin TIMESTAMP,
lieu VARCHAR(500),
capacite_max INTEGER,
prix DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
organisateur_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS inscriptions_evenement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
evenement_id UUID NOT NULL REFERENCES evenements(id),
membre_id UUID NOT NULL REFERENCES membres(id),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_inscription TIMESTAMP DEFAULT NOW(),
commentaire TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(evenement_id, membre_id)
);
-- ============================================================================
-- 6. TABLES SOLIDARITÉ
-- ============================================================================
CREATE TABLE IF NOT EXISTS demandes_aide (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_demande VARCHAR(50),
type_aide VARCHAR(50),
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(50) DEFAULT 'BROUILLON',
titre VARCHAR(255),
description TEXT,
montant_demande DECIMAL(15,2),
montant_approuve DECIMAL(15,2),
devise VARCHAR(10) DEFAULT 'XOF',
justification TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 7. TABLES DOCUMENTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(255) NOT NULL,
description TEXT,
type_document VARCHAR(50),
chemin_fichier VARCHAR(1000),
taille_fichier BIGINT,
type_mime VARCHAR(100),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS pieces_jointes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom_fichier VARCHAR(255) NOT NULL,
chemin_fichier VARCHAR(1000),
type_mime VARCHAR(100),
taille BIGINT,
entite_type VARCHAR(100),
entite_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 8. TABLES NOTIFICATIONS
-- ============================================================================
CREATE TABLE IF NOT EXISTS templates_notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(100) NOT NULL UNIQUE,
sujet VARCHAR(500),
corps_texte TEXT,
corps_html TEXT,
variables_disponibles TEXT,
canaux_supportes VARCHAR(500),
langue VARCHAR(10) DEFAULT 'fr',
description TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_notification VARCHAR(30) NOT NULL,
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
sujet VARCHAR(500),
corps TEXT,
date_envoi_prevue TIMESTAMP,
date_envoi TIMESTAMP,
date_lecture TIMESTAMP,
nombre_tentatives INTEGER DEFAULT 0,
message_erreur VARCHAR(1000),
donnees_additionnelles TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
template_id UUID REFERENCES templates_notifications(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 9. TABLES ADRESSES
-- ============================================================================
CREATE TABLE IF NOT EXISTS adresses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_adresse VARCHAR(30),
rue VARCHAR(500),
complement VARCHAR(500),
code_postal VARCHAR(20),
ville VARCHAR(100),
region VARCHAR(100),
pays VARCHAR(100) DEFAULT 'Côte d''Ivoire',
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION,
principale BOOLEAN DEFAULT FALSE,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- Colonnes attendues par l'entité Adresse (alignement schéma)
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS adresse VARCHAR(500);
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS complement_adresse VARCHAR(200);
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS libelle VARCHAR(100);
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS notes VARCHAR(500);
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS evenement_id UUID REFERENCES evenements(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_adresse_evenement ON adresses(evenement_id);
-- Types latitude/longitude : entité attend NUMERIC(9,6)
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'adresses' AND column_name = 'latitude' AND data_type = 'double precision') THEN
ALTER TABLE adresses ALTER COLUMN latitude TYPE NUMERIC(9,6) USING latitude::numeric(9,6);
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'adresses' AND column_name = 'longitude' AND data_type = 'double precision') THEN
ALTER TABLE adresses ALTER COLUMN longitude TYPE NUMERIC(9,6) USING longitude::numeric(9,6);
END IF;
END $$;
-- ============================================================================
-- 10. TABLES AUDIT
-- ============================================================================
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
action VARCHAR(100) NOT NULL,
entite_type VARCHAR(100),
entite_id VARCHAR(100),
utilisateur VARCHAR(255),
details TEXT,
adresse_ip VARCHAR(50),
date_heure TIMESTAMP NOT NULL DEFAULT NOW(),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- Colonnes attendues par l'entité AuditLog
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS description VARCHAR(500);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS donnees_avant TEXT;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS donnees_apres TEXT;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS ip_address VARCHAR(45);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS module VARCHAR(50);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS role VARCHAR(50);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS session_id VARCHAR(255);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS severite VARCHAR(20) NOT NULL DEFAULT 'INFO';
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS type_action VARCHAR(50) DEFAULT 'AUTRE';
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS user_agent VARCHAR(500);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS organisation_id UUID REFERENCES organisations(id) ON DELETE SET NULL;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS portee VARCHAR(15) NOT NULL DEFAULT 'PLATEFORME';
ALTER TABLE audit_logs ALTER COLUMN entite_id TYPE VARCHAR(255) USING entite_id::varchar(255);
UPDATE audit_logs SET type_action = COALESCE(action, 'AUTRE') WHERE type_action IS NULL OR type_action = 'AUTRE';
ALTER TABLE audit_logs ALTER COLUMN type_action SET DEFAULT 'AUTRE';
DO $$ BEGIN IF (SELECT COUNT(*) FROM audit_logs WHERE type_action IS NULL) = 0 THEN ALTER TABLE audit_logs ALTER COLUMN type_action SET NOT NULL; END IF; END $$;
CREATE INDEX IF NOT EXISTS idx_audit_module ON audit_logs(module);
CREATE INDEX IF NOT EXISTS idx_audit_type_action ON audit_logs(type_action);
CREATE INDEX IF NOT EXISTS idx_audit_severite ON audit_logs(severite);
-- ============================================================================
-- 11. TABLES WAVE MONEY
-- ============================================================================
CREATE TABLE IF NOT EXISTS comptes_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_telephone VARCHAR(30) NOT NULL,
nom_titulaire VARCHAR(255),
statut VARCHAR(30) DEFAULT 'ACTIF',
solde DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS configurations_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cle_api VARCHAR(500),
secret_api VARCHAR(500),
environnement VARCHAR(30) DEFAULT 'sandbox',
url_webhook VARCHAR(500),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS transactions_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reference_wave VARCHAR(100),
reference_interne VARCHAR(100),
type_transaction VARCHAR(50),
montant DECIMAL(15,2) NOT NULL,
devise VARCHAR(10) DEFAULT 'XOF',
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
numero_expediteur VARCHAR(30),
numero_destinataire VARCHAR(30),
description TEXT,
erreur TEXT,
compte_wave_id UUID REFERENCES comptes_wave(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS webhooks_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_evenement VARCHAR(100),
statut VARCHAR(30) DEFAULT 'RECU',
payload TEXT,
signature VARCHAR(500),
traite BOOLEAN DEFAULT FALSE,
erreur TEXT,
transaction_id UUID REFERENCES transactions_wave(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 12. TABLES SUPPORT (tickets, suggestions, favoris, config - déjà en V1.4)
-- ============================================================================
CREATE TABLE IF NOT EXISTS tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_ticket VARCHAR(50),
sujet VARCHAR(255) NOT NULL,
description TEXT,
categorie VARCHAR(50),
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(30) DEFAULT 'OUVERT',
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
assigne_a VARCHAR(255),
date_resolution TIMESTAMP,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS suggestions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(255) NOT NULL,
description TEXT,
categorie VARCHAR(50),
statut VARCHAR(30) DEFAULT 'NOUVELLE',
votes_pour INTEGER DEFAULT 0,
votes_contre INTEGER DEFAULT 0,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS suggestion_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
suggestion_id UUID NOT NULL REFERENCES suggestions(id),
membre_id UUID NOT NULL REFERENCES membres(id),
type_vote VARCHAR(20) NOT NULL,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(suggestion_id, membre_id)
);
CREATE TABLE IF NOT EXISTS favoris (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_entite VARCHAR(100) NOT NULL,
entite_id UUID NOT NULL,
membre_id UUID NOT NULL REFERENCES membres(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS configurations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cle VARCHAR(255) NOT NULL UNIQUE,
valeur TEXT,
description TEXT,
categorie VARCHAR(100),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 13. TABLE TYPES ORGANISATION
-- ============================================================================
CREATE TABLE IF NOT EXISTS uf_type_organisation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) NOT NULL UNIQUE,
libelle VARCHAR(255) NOT NULL,
description TEXT,
icone VARCHAR(100),
couleur VARCHAR(20),
ordre INTEGER DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 14. INDEX POUR PERFORMANCES
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_membres_email ON membres(email);
CREATE INDEX IF NOT EXISTS idx_membres_numero ON membres(numero_membre);
CREATE INDEX IF NOT EXISTS idx_membres_organisation ON membres(organisation_id);
CREATE INDEX IF NOT EXISTS idx_membres_keycloak ON membres(keycloak_user_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_membre ON adhesions(membre_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_organisation ON adhesions(organisation_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_statut ON adhesions(statut);
CREATE INDEX IF NOT EXISTS idx_cotisations_membre ON cotisations(membre_id);
CREATE INDEX IF NOT EXISTS idx_cotisations_statut ON cotisations(statut);
CREATE INDEX IF NOT EXISTS idx_cotisations_echeance ON cotisations(date_echeance);
CREATE INDEX IF NOT EXISTS idx_evenements_statut ON evenements(statut);
CREATE INDEX IF NOT EXISTS idx_evenements_organisation ON evenements(organisation_id);
CREATE INDEX IF NOT EXISTS idx_evenements_date_debut ON evenements(date_debut);
CREATE INDEX IF NOT EXISTS idx_notification_membre ON notifications(membre_id);
CREATE INDEX IF NOT EXISTS idx_notification_statut ON notifications(statut);
CREATE INDEX IF NOT EXISTS idx_notification_type ON notifications(type_notification);
CREATE INDEX IF NOT EXISTS idx_audit_date_heure ON audit_logs(date_heure);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_utilisateur ON audit_logs(utilisateur);
CREATE INDEX IF NOT EXISTS idx_paiements_membre ON paiements(membre_id);
CREATE INDEX IF NOT EXISTS idx_paiements_statut ON paiements(statut);
CREATE INDEX IF NOT EXISTS idx_demandes_aide_demandeur ON demandes_aide(demandeur_id);
CREATE INDEX IF NOT EXISTS idx_demandes_aide_statut ON demandes_aide(statut);
-- ========== V2.0__Refactoring_Utilisateurs.sql ==========
-- ============================================================
-- V2.0 — Refactoring: membres → utilisateurs
-- Sépare l'identité globale (utilisateurs) du lien organisationnel
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
-- Renommer la table membres → utilisateurs
ALTER TABLE membres RENAME TO utilisateurs;
-- Supprimer l'ancien lien unique membre↔organisation (maintenant dans membres_organisations)
ALTER TABLE utilisateurs DROP COLUMN IF EXISTS organisation_id;
ALTER TABLE utilisateurs DROP COLUMN IF EXISTS date_adhesion;
ALTER TABLE utilisateurs DROP COLUMN IF EXISTS mot_de_passe;
ALTER TABLE utilisateurs DROP COLUMN IF EXISTS roles;
-- Ajouter les nouveaux champs identité globale
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS keycloak_id UUID UNIQUE;
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS photo_url VARCHAR(500);
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS statut_compte VARCHAR(30) NOT NULL DEFAULT 'ACTIF';
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS telephone_wave VARCHAR(13);
-- Mettre à jour la contrainte de statut compte
ALTER TABLE utilisateurs
ADD CONSTRAINT chk_utilisateur_statut_compte
CHECK (statut_compte IN ('ACTIF', 'SUSPENDU', 'DESACTIVE'));
-- Mettre à jour les index
DROP INDEX IF EXISTS idx_membre_organisation;
DROP INDEX IF EXISTS idx_membre_email;
DROP INDEX IF EXISTS idx_membre_numero;
DROP INDEX IF EXISTS idx_membre_actif;
CREATE UNIQUE INDEX IF NOT EXISTS idx_utilisateur_email ON utilisateurs(email);
CREATE UNIQUE INDEX IF NOT EXISTS idx_utilisateur_numero ON utilisateurs(numero_membre);
CREATE INDEX IF NOT EXISTS idx_utilisateur_actif ON utilisateurs(actif);
CREATE UNIQUE INDEX IF NOT EXISTS idx_utilisateur_keycloak ON utilisateurs(keycloak_id);
CREATE INDEX IF NOT EXISTS idx_utilisateur_statut_compte ON utilisateurs(statut_compte);
-- ============================================================
-- Table membres_organisations : lien utilisateur ↔ organisation
-- ============================================================
CREATE TABLE IF NOT EXISTS membres_organisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
utilisateur_id UUID NOT NULL,
organisation_id UUID NOT NULL,
unite_id UUID, -- agence/bureau d'affectation (null = siège)
statut_membre VARCHAR(30) NOT NULL DEFAULT 'EN_ATTENTE_VALIDATION',
date_adhesion DATE,
date_changement_statut DATE,
motif_statut VARCHAR(500),
approuve_par_id UUID,
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_mo_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES utilisateurs(id) ON DELETE CASCADE,
CONSTRAINT fk_mo_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT fk_mo_unite FOREIGN KEY (unite_id) REFERENCES organisations(id) ON DELETE SET NULL,
CONSTRAINT fk_mo_approuve_par FOREIGN KEY (approuve_par_id) REFERENCES utilisateurs(id) ON DELETE SET NULL,
CONSTRAINT uk_mo_utilisateur_organisation UNIQUE (utilisateur_id, organisation_id),
CONSTRAINT chk_mo_statut CHECK (statut_membre IN (
'EN_ATTENTE_VALIDATION','ACTIF','INACTIF',
'SUSPENDU','DEMISSIONNAIRE','RADIE','HONORAIRE','DECEDE'
))
);
CREATE INDEX idx_mo_utilisateur ON membres_organisations(utilisateur_id);
CREATE INDEX idx_mo_organisation ON membres_organisations(organisation_id);
CREATE INDEX idx_mo_statut ON membres_organisations(statut_membre);
CREATE INDEX idx_mo_unite ON membres_organisations(unite_id);
-- Table agrements_professionnels (registre) — entité AgrementProfessionnel
CREATE TABLE IF NOT EXISTS agrements_professionnels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_id UUID NOT NULL REFERENCES utilisateurs(id) ON DELETE CASCADE,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
secteur_ordre VARCHAR(150),
numero_licence VARCHAR(100),
categorie_classement VARCHAR(100),
date_delivrance DATE,
date_expiration DATE,
statut VARCHAR(50) NOT NULL DEFAULT 'PROVISOIRE',
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT chk_agrement_statut CHECK (statut IN ('PROVISOIRE','VALIDE','SUSPENDU','RETRETIRE'))
);
CREATE INDEX IF NOT EXISTS idx_agrement_membre ON agrements_professionnels(membre_id);
CREATE INDEX IF NOT EXISTS idx_agrement_orga ON agrements_professionnels(organisation_id);
-- Mettre à jour les FK des tables existantes qui pointaient sur membres(id)
ALTER TABLE cotisations
DROP CONSTRAINT IF EXISTS fk_cotisation_membre,
ADD CONSTRAINT fk_cotisation_membre FOREIGN KEY (membre_id) REFERENCES utilisateurs(id);
ALTER TABLE inscriptions_evenement
DROP CONSTRAINT IF EXISTS fk_inscription_membre,
ADD CONSTRAINT fk_inscription_membre FOREIGN KEY (membre_id) REFERENCES utilisateurs(id);
ALTER TABLE demandes_aide
DROP CONSTRAINT IF EXISTS fk_demande_demandeur,
DROP CONSTRAINT IF EXISTS fk_demande_evaluateur,
ADD CONSTRAINT fk_demande_demandeur FOREIGN KEY (demandeur_id) REFERENCES utilisateurs(id),
ADD CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id) REFERENCES utilisateurs(id) ON DELETE SET NULL;
COMMENT ON TABLE utilisateurs IS 'Identité globale unique de chaque utilisateur UnionFlow (1 compte = 1 profil)';
COMMENT ON TABLE membres_organisations IS 'Lien utilisateur ↔ organisation avec statut de membership';
COMMENT ON COLUMN membres_organisations.unite_id IS 'Agence/bureau d''affectation au sein de la hiérarchie. NULL = siège';
-- ========== V2.1__Organisations_Hierarchy.sql ==========
-- ============================================================
-- V2.1 — Hiérarchie organisations + corrections
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
-- Ajouter la FK propre pour la hiérarchie (remplace le UUID nu)
ALTER TABLE organisations
DROP CONSTRAINT IF EXISTS fk_organisation_parente;
ALTER TABLE organisations
ADD CONSTRAINT fk_organisation_parente
FOREIGN KEY (organisation_parente_id) REFERENCES organisations(id) ON DELETE SET NULL;
-- Nouveaux champs hiérarchie et modules
ALTER TABLE organisations
ADD COLUMN IF NOT EXISTS est_organisation_racine BOOLEAN NOT NULL DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS chemin_hierarchique VARCHAR(2000),
ADD COLUMN IF NOT EXISTS type_organisation_code VARCHAR(50);
-- Élargir la contrainte de type_organisation pour couvrir tous les métiers
ALTER TABLE organisations DROP CONSTRAINT IF EXISTS chk_organisation_type;
ALTER TABLE organisations
ADD CONSTRAINT chk_organisation_type CHECK (type_organisation IN (
'ASSOCIATION','MUTUELLE_EPARGNE_CREDIT','MUTUELLE_SANTE',
'TONTINE','ONG','COOPERATIVE_AGRICOLE','ASSOCIATION_PROFESSIONNELLE',
'ASSOCIATION_COMMUNAUTAIRE','ORGANISATION_RELIGIEUSE',
'FEDERATION','SYNDICAT','LIONS_CLUB','ROTARY_CLUB','AUTRE'
));
-- Règle : organisation sans parent = racine
UPDATE organisations
SET est_organisation_racine = TRUE
WHERE organisation_parente_id IS NULL;
UPDATE organisations
SET est_organisation_racine = FALSE
WHERE organisation_parente_id IS NOT NULL;
-- Index pour les requêtes hiérarchiques
CREATE INDEX IF NOT EXISTS idx_org_racine ON organisations(est_organisation_racine);
CREATE INDEX IF NOT EXISTS idx_org_chemin ON organisations(chemin_hierarchique);
COMMENT ON COLUMN organisations.est_organisation_racine IS 'TRUE si c''est l''organisation mère (souscrit au forfait pour toute la hiérarchie)';
COMMENT ON COLUMN organisations.chemin_hierarchique IS 'Chemin UUID ex: /uuid-racine/uuid-inter/uuid-feuille — requêtes récursives optimisées';
-- ========== V2.2__SaaS_Souscriptions.sql ==========
-- ============================================================
-- V2.2 — SaaS : formules_abonnement + souscriptions_organisation
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
CREATE TABLE IF NOT EXISTS formules_abonnement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(20) UNIQUE NOT NULL, -- STARTER, STANDARD, PREMIUM, CRYSTAL
libelle VARCHAR(100) NOT NULL,
description TEXT,
max_membres INTEGER, -- NULL = illimité (Crystal+)
max_stockage_mo INTEGER NOT NULL DEFAULT 1024, -- 1 Go par défaut
prix_mensuel DECIMAL(10,2) NOT NULL CHECK (prix_mensuel >= 0),
prix_annuel DECIMAL(10,2) NOT NULL CHECK (prix_annuel >= 0),
actif BOOLEAN NOT NULL DEFAULT TRUE,
ordre_affichage INTEGER DEFAULT 0,
-- Métadonnées BaseEntity
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT chk_formule_code CHECK (code IN ('STARTER','STANDARD','PREMIUM','CRYSTAL'))
);
-- Données initiales des forfaits (XOF, 1er Janvier 2026)
INSERT INTO formules_abonnement (id, code, libelle, description, max_membres, max_stockage_mo, prix_mensuel, prix_annuel, actif, ordre_affichage)
VALUES
(gen_random_uuid(), 'STARTER', 'Formule Starter', 'Idéal pour démarrer — jusqu''à 50 membres', 50, 1024, 5000.00, 50000.00, true, 1),
(gen_random_uuid(), 'STANDARD', 'Formule Standard', 'Pour les organisations en croissance', 200, 1024, 7000.00, 70000.00, true, 2),
(gen_random_uuid(), 'PREMIUM', 'Formule Premium', 'Organisations établies', 500, 1024, 9000.00, 90000.00, true, 3),
(gen_random_uuid(), 'CRYSTAL', 'Formule Crystal', 'Fédérations et grandes organisations', NULL,1024, 10000.00, 100000.00, true, 4)
ON CONFLICT (code) DO NOTHING;
-- ============================================================
CREATE TABLE IF NOT EXISTS souscriptions_organisation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organisation_id UUID UNIQUE NOT NULL,
formule_id UUID NOT NULL,
type_periode VARCHAR(10) NOT NULL DEFAULT 'MENSUEL', -- MENSUEL | ANNUEL
date_debut DATE NOT NULL,
date_fin DATE NOT NULL,
quota_max INTEGER, -- snapshot de formule.max_membres
quota_utilise INTEGER NOT NULL DEFAULT 0,
statut VARCHAR(30) NOT NULL DEFAULT 'ACTIVE',
reference_paiement_wave VARCHAR(100),
wave_session_id VARCHAR(255),
date_dernier_paiement DATE,
date_prochain_paiement DATE,
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_souscription_org FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT fk_souscription_formule FOREIGN KEY (formule_id) REFERENCES formules_abonnement(id),
CONSTRAINT chk_souscription_statut CHECK (statut IN ('ACTIVE','EXPIREE','SUSPENDUE','RESILIEE')),
CONSTRAINT chk_souscription_periode CHECK (type_periode IN ('MENSUEL','ANNUEL')),
CONSTRAINT chk_souscription_quota CHECK (quota_utilise >= 0)
);
CREATE INDEX idx_souscription_org ON souscriptions_organisation(organisation_id);
CREATE INDEX idx_souscription_statut ON souscriptions_organisation(statut);
CREATE INDEX idx_souscription_fin ON souscriptions_organisation(date_fin);
COMMENT ON TABLE formules_abonnement IS 'Catalogue des forfaits SaaS UnionFlow (Starter→Crystal, 500010000 XOF/mois)';
COMMENT ON TABLE souscriptions_organisation IS 'Abonnement actif d''une organisation racine — quota, durée, référence Wave';
COMMENT ON COLUMN souscriptions_organisation.quota_utilise IS 'Incrémenté automatiquement à chaque adhésion validée. Bloquant si = quota_max.';
-- ========== V2.3__Intentions_Paiement.sql ==========
-- ============================================================
-- V2.3 — Hub de paiement Wave : intentions_paiement
-- Chaque paiement Wave est initié depuis UnionFlow.
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
CREATE TABLE IF NOT EXISTS intentions_paiement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
utilisateur_id UUID NOT NULL,
organisation_id UUID, -- NULL pour abonnements UnionFlow SA
montant_total DECIMAL(14,2) NOT NULL CHECK (montant_total > 0),
code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
type_objet VARCHAR(30) NOT NULL, -- COTISATION|ADHESION|EVENEMENT|ABONNEMENT_UNIONFLOW
statut VARCHAR(20) NOT NULL DEFAULT 'INITIEE',
-- Wave API
wave_checkout_session_id VARCHAR(255) UNIQUE,
wave_launch_url VARCHAR(1000),
wave_transaction_id VARCHAR(100),
-- Traçabilité des objets payés (JSON: [{type,id,montant},...])
objets_cibles TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_expiration TIMESTAMP, -- TTL 30 min
date_completion TIMESTAMP,
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_intention_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES utilisateurs(id),
CONSTRAINT fk_intention_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE SET NULL,
CONSTRAINT chk_intention_type CHECK (type_objet IN ('COTISATION','ADHESION','EVENEMENT','ABONNEMENT_UNIONFLOW')),
CONSTRAINT chk_intention_statut CHECK (statut IN ('INITIEE','EN_COURS','COMPLETEE','EXPIREE','ECHOUEE')),
CONSTRAINT chk_intention_devise CHECK (code_devise ~ '^[A-Z]{3}$')
);
CREATE INDEX idx_intention_utilisateur ON intentions_paiement(utilisateur_id);
CREATE INDEX idx_intention_statut ON intentions_paiement(statut);
CREATE INDEX idx_intention_wave_session ON intentions_paiement(wave_checkout_session_id);
CREATE INDEX idx_intention_expiration ON intentions_paiement(date_expiration);
-- Supprimer les champs paiement redondants de cotisations (centralisés dans intentions_paiement)
ALTER TABLE cotisations
DROP COLUMN IF EXISTS methode_paiement,
DROP COLUMN IF EXISTS reference_paiement;
-- Ajouter le lien cotisation → intention de paiement
ALTER TABLE cotisations
ADD COLUMN IF NOT EXISTS intention_paiement_id UUID,
ADD CONSTRAINT fk_cotisation_intention
FOREIGN KEY (intention_paiement_id) REFERENCES intentions_paiement(id) ON DELETE SET NULL;
COMMENT ON TABLE intentions_paiement IS 'Hub centralisé Wave : chaque paiement est initié depuis UnionFlow avant appel API Wave';
COMMENT ON COLUMN intentions_paiement.objets_cibles IS 'JSON: liste des objets couverts par ce paiement — ex: 3 cotisations mensuelles';
COMMENT ON COLUMN intentions_paiement.wave_checkout_session_id IS 'ID de session Wave — clé de réconciliation sur réception webhook';
-- ========== V2.4__Cotisations_Organisation.sql ==========
-- ============================================================
-- V2.4 — Cotisations : ajout organisation_id + parametres
-- Une cotisation est toujours liée à un membre ET à une organisation
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
-- Ajouter organisation_id sur cotisations
ALTER TABLE cotisations
ADD COLUMN IF NOT EXISTS organisation_id UUID;
ALTER TABLE cotisations
ADD CONSTRAINT fk_cotisation_organisation
FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS idx_cotisation_organisation ON cotisations(organisation_id);
-- Mettre à jour les types de cotisation
ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS chk_cotisation_type;
ALTER TABLE cotisations
ADD CONSTRAINT chk_cotisation_type CHECK (type_cotisation IN (
'ANNUELLE','MENSUELLE','EVENEMENTIELLE','SOLIDARITE','EXCEPTIONNELLE','AUTRE'
));
-- ============================================================
-- Paramètres de cotisation par organisation (montants fixés par l'org)
-- ============================================================
CREATE TABLE IF NOT EXISTS parametres_cotisation_organisation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organisation_id UUID UNIQUE NOT NULL,
montant_cotisation_mensuelle DECIMAL(12,2) DEFAULT 0 CHECK (montant_cotisation_mensuelle >= 0),
montant_cotisation_annuelle DECIMAL(12,2) DEFAULT 0 CHECK (montant_cotisation_annuelle >= 0),
devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
date_debut_calcul_ajour DATE, -- configurable: depuis quand calculer les impayés
delai_retard_avant_inactif_jours INTEGER NOT NULL DEFAULT 30,
cotisation_obligatoire BOOLEAN NOT NULL DEFAULT TRUE,
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_param_cotisation_org FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE
);
COMMENT ON TABLE parametres_cotisation_organisation IS 'Paramètres de cotisation configurés par le manager de chaque organisation';
COMMENT ON COLUMN parametres_cotisation_organisation.date_debut_calcul_ajour IS 'Date de référence pour le calcul membre «à jour». Configurable par le manager.';
COMMENT ON COLUMN parametres_cotisation_organisation.delai_retard_avant_inactif_jours IS 'Jours de retard après lesquels un membre passe INACTIF automatiquement';
-- ========== V2.5__Workflow_Solidarite.sql ==========
-- ============================================================
-- V2.5 — Workflow solidarité configurable (max 3 étapes)
-- + demandes_adhesion (remplace adhesions)
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
-- ============================================================
-- Workflow de validation configurable par organisation
-- ============================================================
CREATE TABLE IF NOT EXISTS workflow_validation_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organisation_id UUID NOT NULL,
type_workflow VARCHAR(30) NOT NULL DEFAULT 'DEMANDE_AIDE',
etape_numero INTEGER NOT NULL CHECK (etape_numero BETWEEN 1 AND 3),
role_requis_id UUID, -- rôle nécessaire pour valider cette étape
libelle_etape VARCHAR(200) NOT NULL,
delai_max_heures INTEGER DEFAULT 72,
actif BOOLEAN NOT NULL DEFAULT TRUE,
-- Métadonnées BaseEntity
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_wf_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT fk_wf_role FOREIGN KEY (role_requis_id) REFERENCES roles(id) ON DELETE SET NULL,
CONSTRAINT uk_wf_org_type_etape UNIQUE (organisation_id, type_workflow, etape_numero),
CONSTRAINT chk_wf_type CHECK (type_workflow IN ('DEMANDE_AIDE','ADHESION','AUTRE'))
);
CREATE INDEX idx_wf_organisation ON workflow_validation_config(organisation_id);
CREATE INDEX idx_wf_type ON workflow_validation_config(type_workflow);
-- ============================================================
-- Historique des validations d'une demande d'aide
-- ============================================================
CREATE TABLE IF NOT EXISTS validation_etapes_demande (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
demande_aide_id UUID NOT NULL,
etape_numero INTEGER NOT NULL CHECK (etape_numero BETWEEN 1 AND 3),
valideur_id UUID,
statut VARCHAR(20) NOT NULL DEFAULT 'EN_ATTENTE',
date_validation TIMESTAMP,
commentaire VARCHAR(1000),
delegue_par_id UUID, -- si désactivation du véto par supérieur
trace_delegation TEXT, -- motif + traçabilité BCEAO/OHADA
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_ved_demande FOREIGN KEY (demande_aide_id) REFERENCES demandes_aide(id) ON DELETE CASCADE,
CONSTRAINT fk_ved_valideur FOREIGN KEY (valideur_id) REFERENCES utilisateurs(id) ON DELETE SET NULL,
CONSTRAINT fk_ved_delegue_par FOREIGN KEY (delegue_par_id) REFERENCES utilisateurs(id) ON DELETE SET NULL,
CONSTRAINT chk_ved_statut CHECK (statut IN ('EN_ATTENTE','APPROUVEE','REJETEE','DELEGUEE','EXPIREE'))
);
CREATE INDEX idx_ved_demande ON validation_etapes_demande(demande_aide_id);
CREATE INDEX idx_ved_valideur ON validation_etapes_demande(valideur_id);
CREATE INDEX idx_ved_statut ON validation_etapes_demande(statut);
-- ============================================================
-- demandes_adhesion (remplace adhesions avec modèle enrichi)
-- ============================================================
DROP TABLE IF EXISTS adhesions CASCADE;
CREATE TABLE IF NOT EXISTS demandes_adhesion (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_reference VARCHAR(50) UNIQUE NOT NULL,
utilisateur_id UUID NOT NULL,
organisation_id UUID NOT NULL,
statut VARCHAR(20) NOT NULL DEFAULT 'EN_ATTENTE',
frais_adhesion DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (frais_adhesion >= 0),
montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0,
code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
intention_paiement_id UUID,
date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_traitement TIMESTAMP,
traite_par_id UUID,
motif_rejet VARCHAR(1000),
observations VARCHAR(1000),
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_da_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES utilisateurs(id) ON DELETE CASCADE,
CONSTRAINT fk_da_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT fk_da_intention FOREIGN KEY (intention_paiement_id) REFERENCES intentions_paiement(id) ON DELETE SET NULL,
CONSTRAINT fk_da_traite_par FOREIGN KEY (traite_par_id) REFERENCES utilisateurs(id) ON DELETE SET NULL,
CONSTRAINT chk_da_statut CHECK (statut IN ('EN_ATTENTE','APPROUVEE','REJETEE','ANNULEE'))
);
CREATE INDEX idx_da_utilisateur ON demandes_adhesion(utilisateur_id);
CREATE INDEX idx_da_organisation ON demandes_adhesion(organisation_id);
CREATE INDEX idx_da_statut ON demandes_adhesion(statut);
CREATE INDEX idx_da_date ON demandes_adhesion(date_demande);
COMMENT ON TABLE workflow_validation_config IS 'Configuration du workflow de validation par organisation (max 3 étapes)';
COMMENT ON TABLE validation_etapes_demande IS 'Historique des validations — tracé BCEAO/OHADA — délégation de véto incluse';
COMMENT ON TABLE demandes_adhesion IS 'Demande d''adhésion d''un utilisateur à une organisation avec paiement Wave intégré';
-- ========== V2.6__Modules_Organisation.sql ==========
-- ============================================================
-- V2.6 — Système de modules activables par type d'organisation
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
CREATE TABLE IF NOT EXISTS modules_disponibles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
libelle VARCHAR(150) NOT NULL,
description TEXT,
types_org_compatibles TEXT, -- JSON array: ["MUTUELLE_SANTE","ONG",...]
actif BOOLEAN NOT NULL DEFAULT TRUE,
ordre_affichage INTEGER DEFAULT 0,
-- Métadonnées BaseEntity
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0
);
-- Catalogue initial des modules métier
INSERT INTO modules_disponibles (id, code, libelle, description, types_org_compatibles, actif, ordre_affichage)
VALUES
(gen_random_uuid(), 'COTISATIONS', 'Gestion des cotisations', 'Suivi cotisations, relances, statistiques', '["ALL"]', true, 1),
(gen_random_uuid(), 'EVENEMENTS', 'Gestion des événements', 'Création, inscriptions, présences, paiements', '["ALL"]', true, 2),
(gen_random_uuid(), 'SOLIDARITE', 'Fonds de solidarité', 'Demandes d''aide avec workflow de validation', '["ALL"]', true, 3),
(gen_random_uuid(), 'COMPTABILITE', 'Comptabilité simplifiée', 'Journal, écritures, comptes — conforme OHADA', '["ALL"]', true, 4),
(gen_random_uuid(), 'DOCUMENTS', 'Gestion documentaire', 'Upload, versioning, intégrité hash — 1Go max', '["ALL"]', true, 5),
(gen_random_uuid(), 'NOTIFICATIONS', 'Notifications multi-canal', 'Email, WhatsApp, push mobile', '["ALL"]', true, 6),
(gen_random_uuid(), 'CREDIT_EPARGNE', 'Épargne & crédit MEC', 'Prêts, échéanciers, impayés, multi-caisses', '["MUTUELLE_EPARGNE_CREDIT"]', true, 10),
(gen_random_uuid(), 'AYANTS_DROIT', 'Gestion des ayants droit', 'Couverture santé, plafonds, conventions centres de santé', '["MUTUELLE_SANTE"]', true, 11),
(gen_random_uuid(), 'TONTINE', 'Tontine / épargne rotative', 'Cycles rotatifs, tirage, enchères, pénalités', '["TONTINE"]', true, 12),
(gen_random_uuid(), 'ONG_PROJETS', 'Projets humanitaires', 'Logframe, budget bailleurs, indicateurs d''impact, rapports', '["ONG"]', true, 13),
(gen_random_uuid(), 'COOP_AGRICOLE', 'Coopérative agricole', 'Parcelles, rendements, intrants, vente groupée, ristournes', '["COOPERATIVE_AGRICOLE"]', true, 14),
(gen_random_uuid(), 'VOTE_INTERNE', 'Vote interne électronique', 'Assemblées générales, votes, quorums', '["FEDERATION","ASSOCIATION","SYNDICAT"]', true, 15),
(gen_random_uuid(), 'COLLECTE_FONDS', 'Collecte de fonds', 'Campagnes de don, suivi, rapports', '["ONG","ORGANISATION_RELIGIEUSE","ASSOCIATION"]', true, 16),
(gen_random_uuid(), 'REGISTRE_PROFESSIONNEL','Registre officiel membres', 'Agrément, diplômes, sanctions disciplinaires, annuaire certifié', '["ASSOCIATION_PROFESSIONNELLE"]', true, 17),
(gen_random_uuid(), 'CULTES_RELIGIEUX', 'Gestion cultes & dîmes', 'Dîmes, promesses de don, planification cultes, cellules, offrandes anon.','["ORGANISATION_RELIGIEUSE"]', true, 18),
(gen_random_uuid(), 'GOUVERNANCE_MULTI', 'Gouvernance multi-niveaux', 'Cotisation par section, reporting consolidé, redistribution subventions', '["FEDERATION"]', true, 19)
ON CONFLICT (code) DO NOTHING;
-- ============================================================
-- Modules activés pour chaque organisation
-- ============================================================
CREATE TABLE IF NOT EXISTS modules_organisation_actifs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organisation_id UUID NOT NULL,
module_code VARCHAR(50) NOT NULL,
actif BOOLEAN NOT NULL DEFAULT TRUE,
parametres TEXT, -- JSON de configuration spécifique
date_activation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Métadonnées BaseEntity
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_moa_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT uk_moa_org_module UNIQUE (organisation_id, module_code)
);
CREATE INDEX idx_moa_organisation ON modules_organisation_actifs(organisation_id);
CREATE INDEX idx_moa_module ON modules_organisation_actifs(module_code);
COMMENT ON TABLE modules_disponibles IS 'Catalogue des modules métier UnionFlow activables selon le type d''organisation';
COMMENT ON TABLE modules_organisation_actifs IS 'Modules activés pour une organisation donnée avec paramètres spécifiques';
-- ========== V2.7__Ayants_Droit.sql ==========
-- ============================================================
-- V2.7 — Ayants droit (mutuelles de santé)
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
CREATE TABLE IF NOT EXISTS ayants_droit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_organisation_id UUID NOT NULL, -- membre dans le contexte org mutuelle
prenom VARCHAR(100) NOT NULL,
nom VARCHAR(100) NOT NULL,
date_naissance DATE,
lien_parente VARCHAR(20) NOT NULL, -- CONJOINT|ENFANT|PARENT|AUTRE
numero_beneficiaire VARCHAR(50), -- numéro pour les conventions santé
date_debut_couverture DATE,
date_fin_couverture DATE,
-- Métadonnées BaseEntity
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_ad_membre_org FOREIGN KEY (membre_organisation_id) REFERENCES membres_organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_ad_lien_parente CHECK (lien_parente IN ('CONJOINT','ENFANT','PARENT','AUTRE'))
);
CREATE INDEX idx_ad_membre_org ON ayants_droit(membre_organisation_id);
CREATE INDEX idx_ad_couverture ON ayants_droit(date_debut_couverture, date_fin_couverture);
COMMENT ON TABLE ayants_droit IS 'Bénéficiaires d''un membre dans une mutuelle de santé (conjoint, enfants, parents)';
COMMENT ON COLUMN ayants_droit.numero_beneficiaire IS 'Numéro unique attribué pour les conventions avec les centres de santé partenaires';
-- ========== V2.8__Roles_Par_Organisation.sql ==========
-- ============================================================
-- V2.8 — Rôles par organisation : membres_roles enrichi
-- Un membre peut avoir des rôles différents selon l'organisation
-- Auteur: UnionFlow Team | BCEAO/OHADA compliant
-- ============================================================
-- membres_roles doit référencer membres_organisations (pas uniquement membres)
-- On ajoute organisation_id et membre_organisation_id pour permettre les rôles multi-org
ALTER TABLE membres_roles
ADD COLUMN IF NOT EXISTS membre_organisation_id UUID,
ADD COLUMN IF NOT EXISTS organisation_id UUID;
-- Mettre à jour la FK et la contrainte UNIQUE
ALTER TABLE membres_roles
DROP CONSTRAINT IF EXISTS uk_membre_role;
ALTER TABLE membres_roles
ADD CONSTRAINT fk_mr_membre_org FOREIGN KEY (membre_organisation_id) REFERENCES membres_organisations(id) ON DELETE CASCADE,
ADD CONSTRAINT fk_mr_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE;
-- Nouvelle contrainte: un utilisateur ne peut avoir le même rôle qu'une fois par organisation
ALTER TABLE membres_roles
ADD CONSTRAINT uk_mr_membre_org_role
UNIQUE (membre_organisation_id, role_id);
CREATE INDEX IF NOT EXISTS idx_mr_membre_org ON membres_roles(membre_organisation_id);
CREATE INDEX IF NOT EXISTS idx_mr_organisation ON membres_roles(organisation_id);
COMMENT ON COLUMN membres_roles.membre_organisation_id IS 'Lien vers le membership de l''utilisateur dans l''organisation — détermine le contexte du rôle';
COMMENT ON COLUMN membres_roles.organisation_id IS 'Organisation dans laquelle ce rôle est actif — dénormalisé pour les requêtes de performance';
-- ========== V2.9__Audit_Enhancements.sql ==========
-- ============================================================
-- V2.9 — Améliorations audit_logs : portée + organisation
-- Double niveau : ORGANISATION (manager) + PLATEFORME (super admin)
-- Conservation 10 ans — BCEAO/OHADA/Fiscalité ivoirienne
-- Auteur: UnionFlow Team
-- ============================================================
ALTER TABLE audit_logs
ADD COLUMN IF NOT EXISTS organisation_id UUID,
ADD COLUMN IF NOT EXISTS portee VARCHAR(15) NOT NULL DEFAULT 'PLATEFORME';
ALTER TABLE audit_logs
ADD CONSTRAINT fk_audit_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE SET NULL,
ADD CONSTRAINT chk_audit_portee CHECK (portee IN ('ORGANISATION','PLATEFORME'));
CREATE INDEX IF NOT EXISTS idx_audit_organisation ON audit_logs(organisation_id);
CREATE INDEX IF NOT EXISTS idx_audit_portee ON audit_logs(portee);
-- Index composite pour les consultations fréquentes
CREATE INDEX IF NOT EXISTS idx_audit_org_portee_date ON audit_logs(organisation_id, portee, date_heure DESC);
COMMENT ON COLUMN audit_logs.organisation_id IS 'Organisation concernée — NULL pour événements plateforme';
COMMENT ON COLUMN audit_logs.portee IS 'ORGANISATION: visible par le manager | PLATEFORME: visible uniquement par Super Admin UnionFlow';
-- ========== V2.10__Devises_Africaines_Uniquement.sql ==========
-- ============================================================
-- V2.10 — Devises : liste strictement africaine
-- Remplace EUR, USD, GBP, CHF par des codes africains (XOF par défaut)
-- ============================================================
-- Migrer les organisations avec une devise non africaine vers XOF
UPDATE organisations
SET devise = 'XOF'
WHERE devise IS NOT NULL
AND devise NOT IN ('XOF', 'XAF', 'MAD', 'DZD', 'TND', 'NGN', 'GHS', 'KES', 'ZAR');
-- Remplacer la contrainte par une liste africaine uniquement
ALTER TABLE organisations DROP CONSTRAINT IF EXISTS chk_organisation_devise;
ALTER TABLE organisations
ADD CONSTRAINT chk_organisation_devise CHECK (
devise IN ('XOF', 'XAF', 'MAD', 'DZD', 'TND', 'NGN', 'GHS', 'KES', 'ZAR')
);
COMMENT ON COLUMN organisations.devise IS 'Code ISO 4217 — devises africaines uniquement (XOF, XAF, MAD, DZD, TND, NGN, GHS, KES, ZAR)';
-- ========== V3.0__Optimisation_Structure_Donnees.sql ==========
-- =====================================================
-- V3.0 — Optimisation de la structure de données
-- =====================================================
-- Cat.1 : Table types_reference
-- Cat.2 : Table paiements_objets + suppression
-- colonnes adresse de organisations
-- Cat.4 : Refonte pieces_jointes (polymorphique)
-- Cat.5 : Colonnes Membre manquantes
-- =====================================================
-- ─────────────────────────────────────────────────────
-- Cat.1 — types_reference
-- ─────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS types_reference (
id UUID PRIMARY KEY,
domaine VARCHAR(100) NOT NULL,
code VARCHAR(100) NOT NULL,
libelle VARCHAR(255) NOT NULL,
description VARCHAR(1000),
ordre INT NOT NULL DEFAULT 0,
valeur_systeme BOOLEAN NOT NULL DEFAULT FALSE,
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT uk_type_ref_domaine_code
UNIQUE (domaine, code)
);
CREATE INDEX IF NOT EXISTS idx_tr_domaine
ON types_reference (domaine);
CREATE INDEX IF NOT EXISTS idx_tr_actif
ON types_reference (actif);
-- ─────────────────────────────────────────────────────────────────────────────
-- Bloc d'idempotence : corrige l'écart entre la table créée par Hibernate
-- (sans DEFAULT SQL) et le schéma attendu par cette migration.
-- Hibernate gère les defaults en Java ; ici on les pose au niveau PostgreSQL.
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE types_reference
ADD COLUMN IF NOT EXISTS valeur_systeme BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE types_reference
ADD COLUMN IF NOT EXISTS ordre INT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS actif BOOLEAN NOT NULL DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS version BIGINT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS est_defaut BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS est_systeme BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS ordre_affichage INT NOT NULL DEFAULT 0;
-- Garantit que la contrainte UNIQUE existe (nécessaire pour ON CONFLICT ci-dessous)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uk_type_ref_domaine_code'
AND conrelid = 'types_reference'::regclass
) THEN
ALTER TABLE types_reference
ADD CONSTRAINT uk_type_ref_domaine_code UNIQUE (domaine, code);
END IF;
END $$;
-- Données initiales : domaines référencés par les entités
-- Toutes les colonnes NOT NULL sont fournies (table peut exister sans DEFAULT si créée par Hibernate)
INSERT INTO types_reference (id, domaine, code, libelle, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
-- OBJET_PAIEMENT (Cat.2 — PaiementObjet)
(gen_random_uuid(), 'OBJET_PAIEMENT', 'COTISATION', 'Cotisation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'ADHESION', 'Adhésion', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'EVENEMENT', 'Événement', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'AIDE', 'Aide', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
-- ENTITE_RATTACHEE (Cat.4 — PieceJointe)
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'MEMBRE', 'Membre', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'ORGANISATION', 'Organisation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'COTISATION', 'Cotisation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'ADHESION', 'Adhésion', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'AIDE', 'Aide', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'ENTITE_RATTACHEE', 'TRANSACTION_WAVE', 'Transaction Wave', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
-- STATUT_MATRIMONIAL (Cat.5 — Membre)
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'CELIBATAIRE', 'Célibataire', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'MARIE', 'Marié(e)', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'DIVORCE', 'Divorcé(e)', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'VEUF', 'Veuf/Veuve', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
-- TYPE_IDENTITE (Cat.5 — Membre)
(gen_random_uuid(), 'TYPE_IDENTITE', 'CNI', 'Carte Nationale d''Identité', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'PASSEPORT', 'Passeport', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'PERMIS', 'Permis de conduire', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'CARTE_SEJOUR','Carte de séjour', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- ─────────────────────────────────────────────────────
-- Cat.2 — paiements_objets (remplace 4 tables)
-- ─────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS paiements_objets (
id UUID PRIMARY KEY,
paiement_id UUID NOT NULL
REFERENCES paiements(id),
type_objet_cible VARCHAR(50) NOT NULL,
objet_cible_id UUID NOT NULL,
montant_applique NUMERIC(14,2) NOT NULL,
date_application TIMESTAMP,
commentaire VARCHAR(500),
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT uk_paiement_objet
UNIQUE (paiement_id, type_objet_cible, objet_cible_id)
);
CREATE INDEX IF NOT EXISTS idx_po_paiement
ON paiements_objets (paiement_id);
CREATE INDEX IF NOT EXISTS idx_po_objet
ON paiements_objets (type_objet_cible, objet_cible_id);
CREATE INDEX IF NOT EXISTS idx_po_type
ON paiements_objets (type_objet_cible);
-- ─────────────────────────────────────────────────────
-- Cat.2 — Suppression colonnes adresse de organisations
-- ─────────────────────────────────────────────────────
ALTER TABLE organisations
DROP COLUMN IF EXISTS adresse,
DROP COLUMN IF EXISTS ville,
DROP COLUMN IF EXISTS code_postal,
DROP COLUMN IF EXISTS region,
DROP COLUMN IF EXISTS pays;
-- ─────────────────────────────────────────────────────
-- Cat.4 — pieces_jointes → polymorphique
-- ─────────────────────────────────────────────────────
-- Ajout colonnes polymorphiques
ALTER TABLE pieces_jointes
ADD COLUMN IF NOT EXISTS type_entite_rattachee VARCHAR(50),
ADD COLUMN IF NOT EXISTS entite_rattachee_id UUID;
-- Migration des données existantes (colonnes FK explicites ou entite_type/entite_id selon le schéma)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'membre_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'MEMBRE', entite_rattachee_id = membre_id WHERE membre_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'organisation_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'ORGANISATION', entite_rattachee_id = organisation_id WHERE organisation_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'cotisation_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'COTISATION', entite_rattachee_id = cotisation_id WHERE cotisation_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'adhesion_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'ADHESION', entite_rattachee_id = adhesion_id WHERE adhesion_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'demande_aide_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'AIDE', entite_rattachee_id = demande_aide_id WHERE demande_aide_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'transaction_wave_id') THEN
UPDATE pieces_jointes SET type_entite_rattachee = 'TRANSACTION_WAVE', entite_rattachee_id = transaction_wave_id WHERE transaction_wave_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
-- Schéma V1.7 : entite_type / entite_id
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'entite_type') THEN
UPDATE pieces_jointes SET type_entite_rattachee = COALESCE(NULLIF(TRIM(entite_type), ''), 'MEMBRE'), entite_rattachee_id = entite_id WHERE entite_id IS NOT NULL AND (type_entite_rattachee IS NULL OR type_entite_rattachee = '');
END IF;
-- Valeurs par défaut pour lignes restantes (évite échec NOT NULL)
UPDATE pieces_jointes SET type_entite_rattachee = COALESCE(NULLIF(TRIM(type_entite_rattachee), ''), 'MEMBRE'), entite_rattachee_id = COALESCE(entite_rattachee_id, (SELECT id FROM utilisateurs LIMIT 1)) WHERE type_entite_rattachee IS NULL OR type_entite_rattachee = '' OR entite_rattachee_id IS NULL;
END $$;
-- Contrainte NOT NULL après migration (seulement si plus aucune ligne NULL)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pieces_jointes WHERE type_entite_rattachee IS NULL OR type_entite_rattachee = '' OR entite_rattachee_id IS NULL) THEN
EXECUTE 'ALTER TABLE pieces_jointes ALTER COLUMN type_entite_rattachee SET NOT NULL';
EXECUTE 'ALTER TABLE pieces_jointes ALTER COLUMN entite_rattachee_id SET NOT NULL';
END IF;
END $$;
-- Suppression anciennes FK ou colonnes polymorphiques V1.7 (entite_type, entite_id)
ALTER TABLE pieces_jointes
DROP COLUMN IF EXISTS membre_id,
DROP COLUMN IF EXISTS organisation_id,
DROP COLUMN IF EXISTS cotisation_id,
DROP COLUMN IF EXISTS adhesion_id,
DROP COLUMN IF EXISTS demande_aide_id,
DROP COLUMN IF EXISTS transaction_wave_id,
DROP COLUMN IF EXISTS entite_type,
DROP COLUMN IF EXISTS entite_id;
-- Suppression anciens index
DROP INDEX IF EXISTS idx_piece_jointe_membre;
DROP INDEX IF EXISTS idx_piece_jointe_organisation;
DROP INDEX IF EXISTS idx_piece_jointe_cotisation;
DROP INDEX IF EXISTS idx_piece_jointe_adhesion;
DROP INDEX IF EXISTS idx_piece_jointe_demande_aide;
DROP INDEX IF EXISTS idx_piece_jointe_transaction_wave;
-- Nouveaux index polymorphiques
CREATE INDEX IF NOT EXISTS idx_pj_entite
ON pieces_jointes (type_entite_rattachee, entite_rattachee_id);
CREATE INDEX IF NOT EXISTS idx_pj_type_entite
ON pieces_jointes (type_entite_rattachee);
-- ─────────────────────────────────────────────────────
-- Cat.5 — Colonnes Membre manquantes (table utilisateurs depuis V2.0)
-- ─────────────────────────────────────────────────────
ALTER TABLE utilisateurs
ADD COLUMN IF NOT EXISTS statut_matrimonial VARCHAR(50),
ADD COLUMN IF NOT EXISTS nationalite VARCHAR(100),
ADD COLUMN IF NOT EXISTS type_identite VARCHAR(50),
ADD COLUMN IF NOT EXISTS numero_identite VARCHAR(100);
-- ─────────────────────────────────────────────────────
-- Cat.8 — Valeurs par défaut dans configurations
-- ─────────────────────────────────────────────────────
INSERT INTO configurations (id, cle, valeur, type, categorie, description, modifiable, visible, actif, date_creation, cree_par, version)
VALUES
(gen_random_uuid(), 'defaut.devise', 'XOF', 'STRING', 'SYSTEME', 'Devise par défaut', TRUE, TRUE, TRUE, NOW(), 'system', 0),
(gen_random_uuid(), 'defaut.statut.organisation', 'ACTIVE', 'STRING', 'SYSTEME', 'Statut initial organisation', TRUE, TRUE, TRUE, NOW(), 'system', 0),
(gen_random_uuid(), 'defaut.type.organisation', 'ASSOCIATION', 'STRING', 'SYSTEME', 'Type initial organisation', TRUE, TRUE, TRUE, NOW(), 'system', 0),
(gen_random_uuid(), 'defaut.utilisateur.systeme', 'system', 'STRING', 'SYSTEME', 'Identifiant utilisateur système', FALSE, FALSE, TRUE, NOW(), 'system', 0),
(gen_random_uuid(), 'defaut.montant.cotisation', '0', 'NUMBER', 'SYSTEME', 'Montant cotisation par défaut', TRUE, TRUE, TRUE, NOW(), 'system', 0)
ON CONFLICT DO NOTHING;
-- ─────────────────────────────────────────────────────
-- Cat.7 — Index composites pour requêtes fréquentes
-- ─────────────────────────────────────────────────────
-- Aligner paiements avec l'entité (statut → statut_paiement si la colonne existe)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'paiements' AND column_name = 'statut')
AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'paiements' AND column_name = 'statut_paiement') THEN
ALTER TABLE paiements RENAME COLUMN statut TO statut_paiement;
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_cotisation_org_statut_annee
ON cotisations (organisation_id, statut, annee);
CREATE INDEX IF NOT EXISTS idx_cotisation_membre_statut
ON cotisations (membre_id, statut);
CREATE INDEX IF NOT EXISTS idx_paiement_membre_statut_date
ON paiements (membre_id, statut_paiement,
date_paiement);
CREATE INDEX IF NOT EXISTS idx_notification_membre_statut
ON notifications (membre_id, statut, date_envoi);
CREATE INDEX IF NOT EXISTS idx_adhesion_org_statut
ON demandes_adhesion (organisation_id, statut);
CREATE INDEX IF NOT EXISTS idx_aide_org_statut_urgence
ON demandes_aide (organisation_id, statut, urgence);
CREATE INDEX IF NOT EXISTS idx_membreorg_org_statut
ON membres_organisations
(organisation_id, statut_membre);
CREATE INDEX IF NOT EXISTS idx_evenement_org_date_statut
ON evenements
(organisation_id, date_debut, statut);
-- ─────────────────────────────────────────────────────
-- Cat.7 — Contraintes CHECK métier
-- ─────────────────────────────────────────────────────
ALTER TABLE cotisations
ADD CONSTRAINT chk_montant_paye_le_du
CHECK (montant_paye <= montant_du);
ALTER TABLE souscriptions_organisation
ADD CONSTRAINT chk_quota_utilise_le_max
CHECK (quota_utilise <= quota_max);
-- ========== V3.1__Add_Module_Disponible_FK.sql ==========
-- =====================================================
-- V3.1 — Correction Intégrité Référentielle Modules
-- Cat.2 — ModuleOrganisationActif -> ModuleDisponible
-- =====================================================
-- 1. Ajout de la colonne FK
ALTER TABLE modules_organisation_actifs
ADD COLUMN IF NOT EXISTS module_disponible_id UUID;
-- 2. Migration des données basées sur module_code
UPDATE modules_organisation_actifs moa
SET module_disponible_id = (SELECT id FROM modules_disponibles md WHERE md.code = moa.module_code);
-- 3. Ajout de la contrainte FK
ALTER TABLE modules_organisation_actifs
ADD CONSTRAINT fk_moa_module_disponible
FOREIGN KEY (module_disponible_id) REFERENCES modules_disponibles(id)
ON DELETE RESTRICT;
-- 4. Nettoyage (Optionnel : on garde module_code pour compatibilité DTO existante si nécessaire,
-- mais on force la cohérence via un index unique si possible)
CREATE INDEX IF NOT EXISTS idx_moa_module_id ON modules_organisation_actifs(module_disponible_id);
-- Note: L'audit demandait l'intégrité, c'est fait.
-- ========== V3.2__Seed_Types_Reference.sql ==========
-- =====================================================
-- V3.2 — Initialisation des Types de Référence
-- Cat.1 — Centralisation des domaines de valeurs
-- Colonnes alignées sur l'entité TypeReference (domaine, code, etc.)
-- =====================================================
-- 2. Statut Matrimonial (complément éventuel à V3.0)
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'CELIBATAIRE', 'Célibataire', 'Membre non marié', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'MARIE', 'Marié(e)', 'Membre marié', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'VEUF', 'Veuf/Veuve', 'Membre ayant perdu son conjoint', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_MATRIMONIAL', 'DIVORCE', 'Divorcé(e)', 'Membre divorcé', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- 3. Type d'Identité
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'TYPE_IDENTITE', 'CNI', 'Carte Nationale d''Identité', 'Pièce d''identité nationale', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'PASSEPORT', 'Passeport', 'Passeport international', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'PERMIS_CONDUIRE', 'Permis de conduire', 'Permis de conduire officiel', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_IDENTITE', 'CARTE_CONSULAIRE', 'Carte Consulaire', 'Carte délivrée par un consulat', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- 4. Objet de Paiement (compléments à V3.0)
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'OBJET_PAIEMENT', 'COTISATION', 'Cotisation annuelle', 'Paiement de la cotisation de membre', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'DON', 'Don gracieux', 'Don volontaire pour l''association', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'INSCRIPTION_EVENEMENT', 'Inscription à un événement', 'Paiement pour participer à un événement', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'OBJET_PAIEMENT', 'AMENDE', 'Amende / Sanction', 'Paiement suite à une sanction', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- 5. Type d'Organisation
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'TYPE_ORGANISATION', 'ASSOCIATION', 'Association', 'Organisation type association', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_ORGANISATION', 'COOPERATIVE', 'Coopérative', 'Organisation type coopérative', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_ORGANISATION', 'FEDERATION', 'Fédération', 'Regroupement d''associations', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_ORGANISATION', 'CELLULE', 'Cellule de base', 'Unité locale d''une organisation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- 6. Type de Rôle
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'TYPE_ROLE', 'SYSTEME', 'Système', 'Rôle global non modifiable', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_ROLE', 'ORGANISATION', 'Organisation', 'Rôle spécifique à une organisation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'TYPE_ROLE', 'PERSONNALISE', 'Personnalisé', 'Rôle créé manuellement', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- 7. Statut d'Inscription
INSERT INTO types_reference (id, domaine, code, libelle, description, valeur_systeme, cree_par, actif, ordre, version, date_creation, est_defaut, est_systeme, ordre_affichage)
VALUES
(gen_random_uuid(), 'STATUT_INSCRIPTION', 'CONFIRMEE', 'Confirmée', 'Inscription validée', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_INSCRIPTION', 'EN_ATTENTE', 'En attente', 'En attente de validation', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_INSCRIPTION', 'ANNULEE', 'Annulée', 'Inscription annulée par l''utilisateur', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0),
(gen_random_uuid(), 'STATUT_INSCRIPTION', 'REFUSEE', 'Refusée', 'Inscription rejetée par l''organisateur', TRUE, 'system', TRUE, 0, 0, NOW(), FALSE, TRUE, 0)
ON CONFLICT (domaine, code) DO NOTHING;
-- ========== V3.3__Optimisation_Index_Performance.sql ==========
-- =====================================================
-- V3.3 — Optimisation des Index de Performance
-- Cat.7 — Index composites pour recherches fréquentes
-- =====================================================
-- 1. Index composite sur les membres (Recherche par nom complet)
CREATE INDEX IF NOT EXISTS idx_membre_nom_prenom ON utilisateurs(nom, prenom);
-- 2. Index composite sur les cotisations (Recherche par membre et année)
CREATE INDEX IF NOT EXISTS idx_cotisation_membre_annee ON cotisations(membre_id, annee);
-- 3. Index sur le Keycloak ID pour synchronisation rapide
CREATE INDEX IF NOT EXISTS idx_membre_keycloak_id ON utilisateurs(keycloak_id);
-- 4. Index sur le statut des paiements
CREATE INDEX IF NOT EXISTS idx_paiement_statut_paiement ON paiements(statut_paiement);
-- 5. Index sur les dates de création pour tris par défaut
CREATE INDEX IF NOT EXISTS idx_membre_date_creation ON utilisateurs(date_creation DESC);
CREATE INDEX IF NOT EXISTS idx_organisation_date_creation ON organisations(date_creation DESC);
-- ========== V3.4__LCB_FT_Anti_Blanchiment.sql ==========
-- ============================================================
-- V3.4 — LCB-FT / Anti-blanchiment (mutuelles)
-- Spec: specs/001-mutuelles-anti-blanchiment/spec.md
-- Traçabilité origine des fonds, KYC, seuils
-- ============================================================
-- 1. Utilisateurs (identité) — vigilance KYC
ALTER TABLE utilisateurs
ADD COLUMN IF NOT EXISTS niveau_vigilance_kyc VARCHAR(20) DEFAULT 'SIMPLIFIE',
ADD COLUMN IF NOT EXISTS statut_kyc VARCHAR(20) DEFAULT 'NON_VERIFIE',
ADD COLUMN IF NOT EXISTS date_verification_identite DATE;
ALTER TABLE utilisateurs
ADD CONSTRAINT chk_utilisateur_niveau_kyc
CHECK (niveau_vigilance_kyc IS NULL OR niveau_vigilance_kyc IN ('SIMPLIFIE', 'RENFORCE'));
ALTER TABLE utilisateurs
ADD CONSTRAINT chk_utilisateur_statut_kyc
CHECK (statut_kyc IS NULL OR statut_kyc IN ('NON_VERIFIE', 'EN_COURS', 'VERIFIE', 'REFUSE'));
CREATE INDEX IF NOT EXISTS idx_utilisateur_statut_kyc ON utilisateurs(statut_kyc);
COMMENT ON COLUMN utilisateurs.niveau_vigilance_kyc IS 'Niveau de vigilance KYC LCB-FT';
COMMENT ON COLUMN utilisateurs.statut_kyc IS 'Statut vérification identité';
COMMENT ON COLUMN utilisateurs.date_verification_identite IS 'Date de dernière vérification d''identité';
-- 2. Intentions de paiement — origine des fonds / justification LCB-FT
ALTER TABLE intentions_paiement
ADD COLUMN IF NOT EXISTS origine_fonds VARCHAR(200),
ADD COLUMN IF NOT EXISTS justification_lcb_ft TEXT;
COMMENT ON COLUMN intentions_paiement.origine_fonds IS 'Origine des fonds déclarée (obligatoire au-dessus du seuil)';
COMMENT ON COLUMN intentions_paiement.justification_lcb_ft IS 'Justification LCB-FT optionnelle';
-- 3. Transactions épargne — origine des fonds, pièce justificative (si la table existe)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'transactions_epargne') THEN
ALTER TABLE transactions_epargne
ADD COLUMN IF NOT EXISTS origine_fonds VARCHAR(200),
ADD COLUMN IF NOT EXISTS piece_justificative_id UUID;
EXECUTE 'COMMENT ON COLUMN transactions_epargne.origine_fonds IS ''Origine des fonds (obligatoire au-dessus du seuil LCB-FT)''';
EXECUTE 'COMMENT ON COLUMN transactions_epargne.piece_justificative_id IS ''Référence pièce jointe justificative''';
END IF;
END $$;
-- 4. Paramètres LCB-FT (seuils par organisation ou globaux)
CREATE TABLE IF NOT EXISTS parametres_lcb_ft (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organisation_id UUID,
code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
montant_seuil_justification DECIMAL(18,4) NOT NULL,
montant_seuil_validation_manuelle DECIMAL(18,4),
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT NOT NULL DEFAULT 0,
CONSTRAINT fk_param_lcb_ft_org FOREIGN KEY (organisation_id) REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_param_devise CHECK (code_devise ~ '^[A-Z]{3}$')
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_param_lcb_ft_org_devise
ON parametres_lcb_ft(COALESCE(organisation_id, '00000000-0000-0000-0000-000000000000'::uuid), code_devise);
CREATE INDEX IF NOT EXISTS idx_param_lcb_ft_org ON parametres_lcb_ft(organisation_id);
COMMENT ON TABLE parametres_lcb_ft IS 'Seuils LCB-FT : au-dessus de montant_seuil_justification, origine des fonds obligatoire';
COMMENT ON COLUMN parametres_lcb_ft.organisation_id IS 'NULL = paramètres plateforme par défaut';
-- Valeur par défaut plateforme (XOF) — une seule ligne org NULL + XOF (toutes colonnes NOT NULL fournies)
INSERT INTO parametres_lcb_ft (id, organisation_id, code_devise, montant_seuil_justification, montant_seuil_validation_manuelle, cree_par, actif, date_creation, version)
SELECT gen_random_uuid(), NULL, 'XOF', 500000, 1000000, 'system', TRUE, NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM parametres_lcb_ft WHERE organisation_id IS NULL AND code_devise = 'XOF');
-- ========== V3.5__Add_Organisation_Address_Fields.sql ==========
-- Migration V3.5 : Ajout des champs d'adresse dans la table organisations
-- Date : 2026-02-28
-- Description : Ajoute les champs adresse, ville, région, pays et code postal
-- pour stocker l'adresse principale directement dans organisations
-- Ajout des colonnes d'adresse
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS adresse VARCHAR(500);
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS ville VARCHAR(100);
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS region VARCHAR(100);
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS pays VARCHAR(100);
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS code_postal VARCHAR(20);
-- Ajout d'index pour optimiser les recherches par localisation
CREATE INDEX IF NOT EXISTS idx_organisation_ville ON organisations(ville);
CREATE INDEX IF NOT EXISTS idx_organisation_region ON organisations(region);
CREATE INDEX IF NOT EXISTS idx_organisation_pays ON organisations(pays);
-- Commentaires sur les colonnes
COMMENT ON COLUMN organisations.adresse IS 'Adresse principale de l''organisation (dénormalisée pour performance)';
COMMENT ON COLUMN organisations.ville IS 'Ville de l''adresse principale';
COMMENT ON COLUMN organisations.region IS 'Région/Province/État de l''adresse principale';
COMMENT ON COLUMN organisations.pays IS 'Pays de l''adresse principale';
COMMENT ON COLUMN organisations.code_postal IS 'Code postal de l''adresse principale';
-- ========== V3.6__Create_Test_Organisations.sql ==========
-- Migration V3.6 - Création des organisations de test MUKEFI et MESKA
-- UnionFlow - Configuration initiale pour tests
-- ⚠ Correction : INSERT dans "organisations" (pluriel, table JPA gérée par Hibernate,
-- définie en V1.2), et non "organisation" (singulier, ancienne table isolée).
-- ============================================================================
-- 1. ORGANISATION MUKEFI (Mutuelle d'épargne et de crédit)
-- ============================================================================
DELETE FROM organisations WHERE nom_court = 'MUKEFI';
INSERT INTO organisations (
id,
nom,
nom_court,
description,
email,
telephone,
site_web,
type_organisation,
statut,
date_fondation,
numero_enregistrement,
devise,
budget_annuel,
cotisation_obligatoire,
montant_cotisation_annuelle,
objectifs,
activites_principales,
partenaires,
latitude,
longitude,
date_creation,
date_modification,
cree_par,
modifie_par,
version,
actif,
accepte_nouveaux_membres,
est_organisation_racine,
niveau_hierarchique,
nombre_membres,
nombre_administrateurs,
organisation_publique
) VALUES (
gen_random_uuid(),
'Mutuelle d''Épargne et de Crédit des Fonctionnaires et Indépendants',
'MUKEFI',
'Mutuelle d''épargne et de crédit dédiée aux fonctionnaires et travailleurs indépendants de Côte d''Ivoire',
'contact@mukefi.org',
'+225 07 00 00 00 01',
'https://mukefi.org',
'ASSOCIATION',
'ACTIVE',
'2020-01-15',
'MUT-CI-2020-001',
'XOF',
500000000,
true,
50000,
'Favoriser l''épargne et l''accès au crédit pour les membres',
'Épargne, crédit, micro-crédit, formation financière',
'Banque Centrale des États de l''Afrique de l''Ouest (BCEAO)',
5.3364,
-4.0267,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'superadmin@unionflow.test',
'superadmin@unionflow.test',
0,
true,
true,
true,
0,
0,
0,
true
);
-- ============================================================================
-- 2. ORGANISATION MESKA (Association)
-- ============================================================================
DELETE FROM organisations WHERE nom_court = 'MESKA';
INSERT INTO organisations (
id,
nom,
nom_court,
description,
email,
telephone,
site_web,
type_organisation,
statut,
date_fondation,
numero_enregistrement,
devise,
budget_annuel,
cotisation_obligatoire,
montant_cotisation_annuelle,
objectifs,
activites_principales,
partenaires,
latitude,
longitude,
date_creation,
date_modification,
cree_par,
modifie_par,
version,
actif,
accepte_nouveaux_membres,
est_organisation_racine,
niveau_hierarchique,
nombre_membres,
nombre_administrateurs,
organisation_publique
) VALUES (
gen_random_uuid(),
'Mouvement d''Entraide et de Solidarité de Koumassi et Adjamé',
'MESKA',
'Association communautaire d''entraide et de solidarité basée à Abidjan',
'contact@meska.org',
'+225 07 00 00 00 02',
'https://meska.org',
'ASSOCIATION',
'ACTIVE',
'2018-06-20',
'ASSO-CI-2018-045',
'XOF',
25000000,
true,
25000,
'Promouvoir la solidarité et l''entraide entre les membres des communes de Koumassi et Adjamé',
'Aide sociale, événements communautaires, formations, projets collectifs',
'Mairie de Koumassi, Mairie d''Adjamé',
5.2931,
-3.9468,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'superadmin@unionflow.test',
'superadmin@unionflow.test',
0,
true,
true,
true,
0,
0,
0,
true
);
-- ========== V3.7__Seed_Test_Members.sql ==========
-- ============================================================================
-- V3.7 — Données de test : Membres et Cotisations
-- Tables cibles :
-- utilisateurs -> entité JPA Membre
-- organisations -> entité JPA Organisation (V1.2)
-- membres_organisations -> jointure membre <> organisation
-- cotisations -> entité JPA Cotisation
-- ============================================================================
-- ─────────────────────────────────────────────────────────────────────────────
-- 0. Nettoyage (idempotent)
-- ─────────────────────────────────────────────────────────────────────────────
DELETE FROM cotisations
WHERE membre_id IN (
SELECT id FROM utilisateurs
WHERE email IN (
'membre.mukefi@unionflow.test',
'admin.mukefi@unionflow.test',
'membre.meska@unionflow.test'
)
);
DELETE FROM membres_organisations
WHERE utilisateur_id IN (
SELECT id FROM utilisateurs
WHERE email IN (
'membre.mukefi@unionflow.test',
'admin.mukefi@unionflow.test',
'membre.meska@unionflow.test'
)
);
DELETE FROM utilisateurs
WHERE email IN (
'membre.mukefi@unionflow.test',
'admin.mukefi@unionflow.test',
'membre.meska@unionflow.test'
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 0b. S'assurer que MUKEFI et MESKA existent dans "organisations" (table JPA).
-- Si V3.6 les a déjà insérées, ON CONFLICT (email) DO NOTHING évite le doublon.
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO organisations (
id, nom, nom_court, type_organisation, statut, email, telephone,
site_web, date_fondation, numero_enregistrement, devise,
budget_annuel, cotisation_obligatoire, montant_cotisation_annuelle,
objectifs, activites_principales, partenaires, latitude, longitude,
date_creation, date_modification, cree_par, modifie_par, version, actif,
accepte_nouveaux_membres, est_organisation_racine, niveau_hierarchique,
nombre_membres, nombre_administrateurs, organisation_publique
) VALUES (
gen_random_uuid(),
'Mutuelle d''Épargne et de Crédit des Fonctionnaires et Indépendants',
'MUKEFI', 'ASSOCIATION', 'ACTIVE',
'contact@mukefi.org', '+225 07 00 00 00 01', 'https://mukefi.org',
'2020-01-15', 'MUT-CI-2020-001', 'XOF',
500000000, true, 50000,
'Favoriser l''épargne et l''accès au crédit pour les membres',
'Épargne, crédit, micro-crédit, formation financière',
'BCEAO', 5.3364, -4.0267,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true,
true, true, 0, 0, 0, true
) ON CONFLICT (email) DO NOTHING;
INSERT INTO organisations (
id, nom, nom_court, type_organisation, statut, email, telephone,
site_web, date_fondation, numero_enregistrement, devise,
budget_annuel, cotisation_obligatoire, montant_cotisation_annuelle,
objectifs, activites_principales, partenaires, latitude, longitude,
date_creation, date_modification, cree_par, modifie_par, version, actif,
accepte_nouveaux_membres, est_organisation_racine, niveau_hierarchique,
nombre_membres, nombre_administrateurs, organisation_publique
) VALUES (
gen_random_uuid(),
'Mouvement d''Entraide et de Solidarité de Koumassi et Adjamé',
'MESKA', 'ASSOCIATION', 'ACTIVE',
'contact@meska.org', '+225 07 00 00 00 02', 'https://meska.org',
'2018-06-20', 'ASSO-CI-2018-045', 'XOF',
25000000, true, 25000,
'Promouvoir la solidarité et l''entraide entre les membres des communes de Koumassi et Adjamé',
'Aide sociale, événements communautaires, formations, projets collectifs',
'Mairie de Koumassi, Mairie d''Adjamé', 5.2931, -3.9468,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true,
true, true, 0, 0, 0, true
) ON CONFLICT (email) DO NOTHING;
-- ─────────────────────────────────────────────────────────────────────────────
-- 1. MEMBRE : membre.mukefi@unionflow.test (MUKEFI)
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO utilisateurs (
id, numero_membre, prenom, nom, email, telephone, date_naissance,
nationalite, profession, statut_compte,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(), 'MBR-MUKEFI-001', 'Membre', 'MUKEFI',
'membre.mukefi@unionflow.test', '+22507000101', '1985-06-15',
'Ivoirien', 'Fonctionnaire', 'ACTIF',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 2. MEMBRE : admin.mukefi@unionflow.test (admin MUKEFI)
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO utilisateurs (
id, numero_membre, prenom, nom, email, telephone, date_naissance,
nationalite, profession, statut_compte,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(), 'MBR-MUKEFI-ADMIN', 'Admin', 'MUKEFI',
'admin.mukefi@unionflow.test', '+22507000102', '1978-04-22',
'Ivoirien', 'Administrateur', 'ACTIF',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 3. MEMBRE : membre.meska@unionflow.test (MESKA)
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO utilisateurs (
id, numero_membre, prenom, nom, email, telephone, date_naissance,
nationalite, profession, statut_compte,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(), 'MBR-MESKA-001', 'Membre', 'MESKA',
'membre.meska@unionflow.test', '+22507000201', '1990-11-30',
'Ivoirienne', 'Commercante', 'ACTIF',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 4. RATTACHEMENTS membres_organisations
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO membres_organisations (
id, utilisateur_id, organisation_id, statut_membre, date_adhesion,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(),
(SELECT id FROM utilisateurs WHERE email = 'membre.mukefi@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1),
'ACTIF', '2020-03-01',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
INSERT INTO membres_organisations (
id, utilisateur_id, organisation_id, statut_membre, date_adhesion,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(),
(SELECT id FROM utilisateurs WHERE email = 'admin.mukefi@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1),
'ACTIF', '2020-01-15',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
INSERT INTO membres_organisations (
id, utilisateur_id, organisation_id, statut_membre, date_adhesion,
date_creation, date_modification, cree_par, modifie_par, version, actif
) VALUES (
gen_random_uuid(),
(SELECT id FROM utilisateurs WHERE email = 'membre.meska@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MESKA' LIMIT 1),
'ACTIF', '2018-09-01',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 5. COTISATIONS pour membre.mukefi@unionflow.test
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE cotisations ADD COLUMN IF NOT EXISTS libelle VARCHAR(500);
-- 2023 PAYÉE
INSERT INTO cotisations (
id, numero_reference, membre_id, organisation_id,
type_cotisation, libelle, montant_du, montant_paye, code_devise,
statut, date_echeance, date_paiement, annee, periode,
date_creation, date_modification, cree_par, modifie_par, version, actif, nombre_rappels, recurrente
) VALUES (
gen_random_uuid(), 'COT-MUKEFI-2023-001',
(SELECT id FROM utilisateurs WHERE email = 'membre.mukefi@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1),
'ANNUELLE', 'Cotisation annuelle 2023', 50000, 50000, 'XOF',
'PAYEE', '2023-12-31', '2023-03-15 10:00:00', 2023, '2023',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true, 0, true
);
-- 2024 PAYÉE
INSERT INTO cotisations (
id, numero_reference, membre_id, organisation_id,
type_cotisation, libelle, montant_du, montant_paye, code_devise,
statut, date_echeance, date_paiement, annee, periode,
date_creation, date_modification, cree_par, modifie_par, version, actif, nombre_rappels, recurrente
) VALUES (
gen_random_uuid(), 'COT-MUKEFI-2024-001',
(SELECT id FROM utilisateurs WHERE email = 'membre.mukefi@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1),
'ANNUELLE', 'Cotisation annuelle 2024', 50000, 50000, 'XOF',
'PAYEE', '2024-12-31', '2024-02-20 09:30:00', 2024, '2024',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true, 0, true
);
-- 2025 EN ATTENTE
INSERT INTO cotisations (
id, numero_reference, membre_id, organisation_id,
type_cotisation, libelle, montant_du, montant_paye, code_devise,
statut, date_echeance, annee, periode,
date_creation, date_modification, cree_par, modifie_par, version, actif, nombre_rappels, recurrente
) VALUES (
gen_random_uuid(), 'COT-MUKEFI-2025-001',
(SELECT id FROM utilisateurs WHERE email = 'membre.mukefi@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1),
'ANNUELLE', 'Cotisation annuelle 2025', 50000, 0, 'XOF',
'EN_ATTENTE', '2025-12-31', 2025, '2025',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true, 0, true
);
-- ─────────────────────────────────────────────────────────────────────────────
-- 6. COTISATION pour membre.meska@unionflow.test
-- ─────────────────────────────────────────────────────────────────────────────
INSERT INTO cotisations (
id, numero_reference, membre_id, organisation_id,
type_cotisation, libelle, montant_du, montant_paye, code_devise,
statut, date_echeance, date_paiement, annee, periode,
date_creation, date_modification, cree_par, modifie_par, version, actif, nombre_rappels, recurrente
) VALUES (
gen_random_uuid(), 'COT-MESKA-2024-001',
(SELECT id FROM utilisateurs WHERE email = 'membre.meska@unionflow.test'),
(SELECT id FROM organisations WHERE nom_court = 'MESKA' LIMIT 1),
'ANNUELLE', 'Cotisation annuelle 2024', 25000, 25000, 'XOF',
'PAYEE', '2024-12-31', '2024-01-10 14:00:00', 2024, '2024',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0, true, 0, true
);