chore(docker): add root Dockerfile pinning ubi8/openjdk-21:1.21 + UID 1001 for lionsctl pipeline
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 4m2s

This commit is contained in:
2026-04-24 16:19:25 +00:00
parent fb3a32817b
commit a72ab54abd
461 changed files with 51042 additions and 73486 deletions

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="annotated">
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="annotated">
</beans>

View File

@@ -4,10 +4,6 @@
# Surcharge application.properties — sans préfixes %dev.
# ============================================================================
# DevServices désactivés en dev — on utilise le PostgreSQL local (localhost:5432/unionflow)
# Les tests d'intégration avec Docker requièrent USE_DOCKER_TESTS=true
quarkus.devservices.enabled=false
# Base de données PostgreSQL locale
quarkus.datasource.username=skyfile
quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile}
@@ -16,14 +12,12 @@ quarkus.datasource.jdbc.min-size=2
quarkus.datasource.jdbc.max-size=10
# Hibernate — Mode update pour créer automatiquement les colonnes manquantes
quarkus.hibernate-orm.schema-management.strategy=update
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=true
# Flyway — activé avec réparation auto des checksums modifiés
quarkus.flyway.migrate-at-start=true
quarkus.flyway.repair-at-start=true
# Désactiver le remplacement de placeholders ${...} — les migrations utilisent $$ PL/pgSQL
quarkus.flyway.placeholder-replacement=false
# CORS — permissif en dev (autorise tous les ports localhost pour Flutter Web)
quarkus.http.cors.origins=*
@@ -56,9 +50,6 @@ quarkus.log.category."org.hibernate.SQL".level=DEBUG
quarkus.log.category."io.quarkus.oidc".level=INFO
quarkus.log.category."io.quarkus.security".level=INFO
# Kafka — utiliser le broker local, pas de DevServices
quarkus.kafka.devservices.enabled=false
# Wave — mock pour dev (pas de clé API requise)
wave.mock.enabled=true
wave.redirect.base.url=http://localhost:8085

View File

@@ -15,7 +15,7 @@ quarkus.datasource.jdbc.idle-removal-interval=PT2M
quarkus.datasource.jdbc.max-lifetime=PT30M
# Hibernate — Validate uniquement (Flyway gère le schéma)
quarkus.hibernate-orm.schema-management.strategy=validate
quarkus.hibernate-orm.database.generation=validate
quarkus.hibernate-orm.statistics=false
# Flyway — ignorer les migrations appliquées en DB mais absentes localement
@@ -49,7 +49,7 @@ quarkus.smallrye-openapi.oidc-open-id-connect-url=${quarkus.oidc.auth-server-url
quarkus.swagger-ui.always-include=false
# Logging — fichier en production (le répertoire doit exister dans le container)
quarkus.log.file.enabled=false
quarkus.log.file.enable=false
quarkus.log.file.path=/var/log/unionflow/server.log
quarkus.log.file.rotation.max-file-size=10M
quarkus.log.file.rotation.max-backup-index=5

View File

@@ -8,7 +8,7 @@ quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;NON_KEYWORDS=MONTH,YEAR
# Configuration Hibernate pour tests
quarkus.hibernate-orm.schema-management.strategy=update
quarkus.hibernate-orm.database.generation=update
# Désactiver complètement l'exécution des scripts SQL au démarrage
quarkus.hibernate-orm.sql-load-script=no-file
# Empêcher Hibernate d'exécuter les scripts SQL automatiquement

View File

@@ -40,7 +40,7 @@ quarkus.datasource.password=${DB_PASSWORD:changeme}
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
# Configuration CORS
quarkus.http.cors.enabled=true
quarkus.http.cors=true
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
@@ -49,9 +49,11 @@ quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico,/auth/callba
quarkus.http.auth.permission.public.policy=permit
# Configuration Hibernate — base commune
quarkus.hibernate-orm.schema-management.strategy=update
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.metrics.enabled=false
# Configuration Flyway — base commune
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
@@ -87,16 +89,8 @@ quarkus.swagger-ui.tags-sorter=alpha
# Health
quarkus.smallrye-health.root-path=/health
# Métriques Prometheus (Micrometer) — exposées sur /q/metrics
quarkus.micrometer.enabled=true
quarkus.micrometer.export.prometheus.enabled=true
quarkus.micrometer.export.prometheus.path=/q/metrics
# Métriques Hibernate ORM
quarkus.hibernate-orm.metrics.enabled=true
# JVM + HTTP server + datasource metrics activés par défaut avec quarkus-micrometer
# Logging — base commune
quarkus.log.console.enabled=true
quarkus.log.console.enable=true
quarkus.log.console.level=INFO
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.category."dev.lions.unionflow".level=INFO
@@ -203,20 +197,3 @@ mp.messaging.incoming.chat-messages-in.topic=unionflow.chat.messages
mp.messaging.incoming.chat-messages-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.chat-messages-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.chat-messages-in.group.id=unionflow-websocket-server
# === PI-SPI BCEAO (P0.3 — deadline 30/06/2026) ===
pispi.api.base-url=${PISPI_API_URL:https://sandbox.pispi.bceao.int/business-api/v1}
pispi.institution.bic=${PISPI_BIC:BCEAOCIAB}
# Activer la priorité PI-SPI dans l'orchestrateur (obligatoire en prod après certification)
payment.pispi-priority=${PAYMENT_PISPI_PRIORITY:false}
# Secrets externes : mappage env vars actif en prod uniquement (profile-scoped).
# En dev : propriétés non définies, @ConfigProperty(defaultValue="") côté Java (mode mock).
%prod.pispi.api.client-id=${PISPI_CLIENT_ID:}
%prod.pispi.api.client-secret=${PISPI_CLIENT_SECRET:}
%prod.pispi.institution.code=${PISPI_INSTITUTION_CODE:}
%prod.pispi.webhook.secret=${PISPI_WEBHOOK_SECRET:}
%prod.pispi.webhook.allowed-ips=${PISPI_ALLOWED_IPS:}
%prod.mtnmomo.collection.subscription-key=${MTNMOMO_SUBSCRIPTION_KEY:}
%prod.orange.api.client-id=${ORANGE_API_CLIENT_ID:}
%prod.firebase.service-account-key-path=${FIREBASE_SERVICE_ACCOUNT_KEY_PATH:}

View File

@@ -1,419 +1,419 @@
-- Migration V1.3: Conversion des colonnes ID de BIGINT vers UUID
-- Auteur: UnionFlow Team
-- Date: 2025-01-16
-- Description: Convertit toutes les colonnes ID et clés étrangères de BIGINT vers UUID
-- ATTENTION: Cette migration supprime toutes les données existantes pour simplifier la conversion
-- Pour une migration avec préservation des données, voir V1.3.1__Convert_Ids_To_UUID_With_Data.sql
-- ============================================
-- ÉTAPE 1: Suppression des contraintes de clés étrangères
-- ============================================
-- Supprimer les contraintes de clés étrangères existantes
DO $$
BEGIN
-- Supprimer FK membres -> organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'fk_membre_organisation'
AND table_name = 'membres'
) THEN
ALTER TABLE membres DROP CONSTRAINT fk_membre_organisation;
END IF;
-- Supprimer FK cotisations -> membres
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_cotisation%'
AND table_name = 'cotisations'
) THEN
ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS fk_cotisation_membre CASCADE;
END IF;
-- Supprimer FK evenements -> organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_evenement%'
AND table_name = 'evenements'
) THEN
ALTER TABLE evenements DROP CONSTRAINT IF EXISTS fk_evenement_organisation CASCADE;
END IF;
-- Supprimer FK inscriptions_evenement -> membres et evenements
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_inscription%'
AND table_name = 'inscriptions_evenement'
) THEN
ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_membre CASCADE;
ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_evenement CASCADE;
END IF;
-- Supprimer FK demandes_aide -> membres et organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_demande%'
AND table_name = 'demandes_aide'
) THEN
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_demandeur CASCADE;
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_evaluateur CASCADE;
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_organisation CASCADE;
END IF;
END $$;
-- ============================================
-- ÉTAPE 2: Supprimer les séquences (BIGSERIAL)
-- ============================================
DROP SEQUENCE IF EXISTS membres_SEQ CASCADE;
DROP SEQUENCE IF EXISTS cotisations_SEQ CASCADE;
DROP SEQUENCE IF EXISTS evenements_SEQ CASCADE;
DROP SEQUENCE IF EXISTS organisations_id_seq CASCADE;
-- ============================================
-- ÉTAPE 3: Supprimer les tables existantes (pour recréation avec UUID)
-- ============================================
-- Supprimer les tables dans l'ordre inverse des dépendances
DROP TABLE IF EXISTS inscriptions_evenement CASCADE;
DROP TABLE IF EXISTS demandes_aide CASCADE;
DROP TABLE IF EXISTS cotisations CASCADE;
DROP TABLE IF EXISTS evenements CASCADE;
DROP TABLE IF EXISTS membres CASCADE;
DROP TABLE IF EXISTS organisations CASCADE;
-- ============================================
-- ÉTAPE 4: Recréer les tables avec UUID
-- ============================================
-- Table organisations avec UUID
CREATE TABLE organisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Informations de base
nom VARCHAR(200) NOT NULL,
nom_court VARCHAR(50),
type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION',
statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
description TEXT,
date_fondation DATE,
numero_enregistrement VARCHAR(100) UNIQUE,
-- Informations de contact
email VARCHAR(255) NOT NULL UNIQUE,
telephone VARCHAR(20),
telephone_secondaire VARCHAR(20),
email_secondaire VARCHAR(255),
-- Adresse
adresse VARCHAR(500),
ville VARCHAR(100),
code_postal VARCHAR(20),
region VARCHAR(100),
pays VARCHAR(100),
-- Coordonnées géographiques
latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90),
longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180),
-- Web et réseaux sociaux
site_web VARCHAR(500),
logo VARCHAR(500),
reseaux_sociaux VARCHAR(1000),
-- Hiérarchie
organisation_parente_id UUID,
niveau_hierarchique INTEGER NOT NULL DEFAULT 0,
-- Statistiques
nombre_membres INTEGER NOT NULL DEFAULT 0,
nombre_administrateurs INTEGER NOT NULL DEFAULT 0,
-- Finances
budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0),
devise VARCHAR(3) DEFAULT 'XOF',
cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE,
montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0),
-- Informations complémentaires
objectifs TEXT,
activites_principales TEXT,
certifications VARCHAR(500),
partenaires VARCHAR(1000),
notes VARCHAR(1000),
-- Paramètres
organisation_publique BOOLEAN NOT NULL DEFAULT TRUE,
accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE,
-- Métadonnées (héritées de 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,
-- Contraintes
CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')),
CONSTRAINT chk_organisation_type CHECK (type_organisation IN (
'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE',
'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE'
)),
CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')),
CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10),
CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0),
CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0),
-- Clé étrangère pour hiérarchie
CONSTRAINT fk_organisation_parente FOREIGN KEY (organisation_parente_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table membres avec UUID
CREATE TABLE membres (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_membre VARCHAR(20) UNIQUE NOT NULL,
prenom VARCHAR(100) NOT NULL,
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
mot_de_passe VARCHAR(255),
telephone VARCHAR(20),
date_naissance DATE NOT NULL,
date_adhesion DATE NOT NULL,
roles VARCHAR(500),
-- Clé étrangère vers organisations
organisation_id UUID,
-- Métadonnées (héritées de 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_membre_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table cotisations avec UUID
CREATE TABLE cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_reference VARCHAR(50) UNIQUE NOT NULL,
membre_id UUID NOT NULL,
type_cotisation VARCHAR(50) NOT NULL,
montant_du DECIMAL(12,2) NOT NULL CHECK (montant_du >= 0),
montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (montant_paye >= 0),
code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
statut VARCHAR(30) NOT NULL,
date_echeance DATE NOT NULL,
date_paiement TIMESTAMP,
description VARCHAR(500),
periode VARCHAR(20),
annee INTEGER NOT NULL CHECK (annee >= 2020 AND annee <= 2100),
mois INTEGER CHECK (mois >= 1 AND mois <= 12),
observations VARCHAR(1000),
recurrente BOOLEAN NOT NULL DEFAULT FALSE,
nombre_rappels INTEGER NOT NULL DEFAULT 0 CHECK (nombre_rappels >= 0),
date_dernier_rappel TIMESTAMP,
valide_par_id UUID,
nom_validateur VARCHAR(100),
date_validation TIMESTAMP,
methode_paiement VARCHAR(50),
reference_paiement VARCHAR(100),
-- Métadonnées (héritées de 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_cotisation_membre FOREIGN KEY (membre_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT chk_cotisation_statut CHECK (statut IN ('EN_ATTENTE', 'PAYEE', 'EN_RETARD', 'PARTIELLEMENT_PAYEE', 'ANNULEE')),
CONSTRAINT chk_cotisation_devise CHECK (code_devise ~ '^[A-Z]{3}$')
);
-- Table evenements avec UUID
CREATE TABLE evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(200) NOT NULL,
description VARCHAR(2000),
date_debut TIMESTAMP NOT NULL,
date_fin TIMESTAMP,
lieu VARCHAR(255) NOT NULL,
adresse VARCHAR(500),
ville VARCHAR(100),
pays VARCHAR(100),
code_postal VARCHAR(20),
latitude DECIMAL(9,6),
longitude DECIMAL(9,6),
type_evenement VARCHAR(50) NOT NULL,
statut VARCHAR(50) NOT NULL,
url_inscription VARCHAR(500),
url_informations VARCHAR(500),
image_url VARCHAR(500),
capacite_max INTEGER,
cout_participation DECIMAL(12,2),
devise VARCHAR(3),
est_public BOOLEAN NOT NULL DEFAULT TRUE,
tags VARCHAR(500),
notes VARCHAR(1000),
-- Clé étrangère vers organisations
organisation_id UUID,
-- Métadonnées (héritées de 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_evenement_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table inscriptions_evenement avec UUID
CREATE TABLE inscriptions_evenement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_id UUID NOT NULL,
evenement_id UUID NOT NULL,
date_inscription TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
statut VARCHAR(20) DEFAULT 'CONFIRMEE',
commentaire VARCHAR(500),
-- Métadonnées (héritées de 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_inscription_membre FOREIGN KEY (membre_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT fk_inscription_evenement FOREIGN KEY (evenement_id)
REFERENCES evenements(id) ON DELETE CASCADE,
CONSTRAINT chk_inscription_statut CHECK (statut IN ('CONFIRMEE', 'EN_ATTENTE', 'ANNULEE', 'REFUSEE')),
CONSTRAINT uk_inscription_membre_evenement UNIQUE (membre_id, evenement_id)
);
-- Table demandes_aide avec UUID
CREATE TABLE demandes_aide (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
type_aide VARCHAR(50) NOT NULL,
statut VARCHAR(50) NOT NULL,
montant_demande DECIMAL(10,2),
montant_approuve DECIMAL(10,2),
date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_evaluation TIMESTAMP,
date_versement TIMESTAMP,
justification TEXT,
commentaire_evaluation TEXT,
urgence BOOLEAN NOT NULL DEFAULT FALSE,
documents_fournis VARCHAR(500),
-- Clés étrangères
demandeur_id UUID NOT NULL,
evaluateur_id UUID,
organisation_id UUID NOT NULL,
-- Métadonnées (héritées de 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_demande_demandeur FOREIGN KEY (demandeur_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id)
REFERENCES membres(id) ON DELETE SET NULL,
CONSTRAINT fk_demande_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE CASCADE
);
-- ============================================
-- ÉTAPE 5: Recréer les index
-- ============================================
-- Index pour organisations
CREATE INDEX idx_organisation_nom ON organisations(nom);
CREATE INDEX idx_organisation_email ON organisations(email);
CREATE INDEX idx_organisation_statut ON organisations(statut);
CREATE INDEX idx_organisation_type ON organisations(type_organisation);
CREATE INDEX idx_organisation_ville ON organisations(ville);
CREATE INDEX idx_organisation_pays ON organisations(pays);
CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id);
CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement);
CREATE INDEX idx_organisation_actif ON organisations(actif);
CREATE INDEX idx_organisation_date_creation ON organisations(date_creation);
CREATE INDEX idx_organisation_publique ON organisations(organisation_publique);
CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres);
CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif);
-- Index pour membres
CREATE INDEX idx_membre_email ON membres(email);
CREATE INDEX idx_membre_numero ON membres(numero_membre);
CREATE INDEX idx_membre_actif ON membres(actif);
CREATE INDEX idx_membre_organisation ON membres(organisation_id);
-- Index pour cotisations
CREATE INDEX idx_cotisation_membre ON cotisations(membre_id);
CREATE INDEX idx_cotisation_reference ON cotisations(numero_reference);
CREATE INDEX idx_cotisation_statut ON cotisations(statut);
CREATE INDEX idx_cotisation_echeance ON cotisations(date_echeance);
CREATE INDEX idx_cotisation_type ON cotisations(type_cotisation);
CREATE INDEX idx_cotisation_annee_mois ON cotisations(annee, mois);
-- Index pour evenements
CREATE INDEX idx_evenement_date_debut ON evenements(date_debut);
CREATE INDEX idx_evenement_statut ON evenements(statut);
CREATE INDEX idx_evenement_type ON evenements(type_evenement);
CREATE INDEX idx_evenement_organisation ON evenements(organisation_id);
-- Index pour inscriptions_evenement
CREATE INDEX idx_inscription_membre ON inscriptions_evenement(membre_id);
CREATE INDEX idx_inscription_evenement ON inscriptions_evenement(evenement_id);
CREATE INDEX idx_inscription_date ON inscriptions_evenement(date_inscription);
-- Index pour demandes_aide
CREATE INDEX idx_demande_demandeur ON demandes_aide(demandeur_id);
CREATE INDEX idx_demande_evaluateur ON demandes_aide(evaluateur_id);
CREATE INDEX idx_demande_organisation ON demandes_aide(organisation_id);
CREATE INDEX idx_demande_statut ON demandes_aide(statut);
CREATE INDEX idx_demande_type ON demandes_aide(type_aide);
CREATE INDEX idx_demande_date_demande ON demandes_aide(date_demande);
-- ============================================
-- ÉTAPE 6: Commentaires sur les tables
-- ============================================
COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.) avec UUID';
COMMENT ON TABLE membres IS 'Table des membres avec UUID';
COMMENT ON TABLE cotisations IS 'Table des cotisations avec UUID';
COMMENT ON TABLE evenements IS 'Table des événements avec UUID';
COMMENT ON TABLE inscriptions_evenement IS 'Table des inscriptions aux événements avec UUID';
COMMENT ON TABLE demandes_aide IS 'Table des demandes d''aide avec UUID';
COMMENT ON COLUMN organisations.id IS 'UUID unique de l''organisation';
COMMENT ON COLUMN membres.id IS 'UUID unique du membre';
COMMENT ON COLUMN cotisations.id IS 'UUID unique de la cotisation';
COMMENT ON COLUMN evenements.id IS 'UUID unique de l''événement';
COMMENT ON COLUMN inscriptions_evenement.id IS 'UUID unique de l''inscription';
COMMENT ON COLUMN demandes_aide.id IS 'UUID unique de la demande d''aide';
-- Migration V1.3: Conversion des colonnes ID de BIGINT vers UUID
-- Auteur: UnionFlow Team
-- Date: 2025-01-16
-- Description: Convertit toutes les colonnes ID et clés étrangères de BIGINT vers UUID
-- ATTENTION: Cette migration supprime toutes les données existantes pour simplifier la conversion
-- Pour une migration avec préservation des données, voir V1.3.1__Convert_Ids_To_UUID_With_Data.sql
-- ============================================
-- ÉTAPE 1: Suppression des contraintes de clés étrangères
-- ============================================
-- Supprimer les contraintes de clés étrangères existantes
DO $$
BEGIN
-- Supprimer FK membres -> organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'fk_membre_organisation'
AND table_name = 'membres'
) THEN
ALTER TABLE membres DROP CONSTRAINT fk_membre_organisation;
END IF;
-- Supprimer FK cotisations -> membres
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_cotisation%'
AND table_name = 'cotisations'
) THEN
ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS fk_cotisation_membre CASCADE;
END IF;
-- Supprimer FK evenements -> organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_evenement%'
AND table_name = 'evenements'
) THEN
ALTER TABLE evenements DROP CONSTRAINT IF EXISTS fk_evenement_organisation CASCADE;
END IF;
-- Supprimer FK inscriptions_evenement -> membres et evenements
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_inscription%'
AND table_name = 'inscriptions_evenement'
) THEN
ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_membre CASCADE;
ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_evenement CASCADE;
END IF;
-- Supprimer FK demandes_aide -> membres et organisations
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name LIKE 'fk_demande%'
AND table_name = 'demandes_aide'
) THEN
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_demandeur CASCADE;
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_evaluateur CASCADE;
ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_organisation CASCADE;
END IF;
END $$;
-- ============================================
-- ÉTAPE 2: Supprimer les séquences (BIGSERIAL)
-- ============================================
DROP SEQUENCE IF EXISTS membres_SEQ CASCADE;
DROP SEQUENCE IF EXISTS cotisations_SEQ CASCADE;
DROP SEQUENCE IF EXISTS evenements_SEQ CASCADE;
DROP SEQUENCE IF EXISTS organisations_id_seq CASCADE;
-- ============================================
-- ÉTAPE 3: Supprimer les tables existantes (pour recréation avec UUID)
-- ============================================
-- Supprimer les tables dans l'ordre inverse des dépendances
DROP TABLE IF EXISTS inscriptions_evenement CASCADE;
DROP TABLE IF EXISTS demandes_aide CASCADE;
DROP TABLE IF EXISTS cotisations CASCADE;
DROP TABLE IF EXISTS evenements CASCADE;
DROP TABLE IF EXISTS membres CASCADE;
DROP TABLE IF EXISTS organisations CASCADE;
-- ============================================
-- ÉTAPE 4: Recréer les tables avec UUID
-- ============================================
-- Table organisations avec UUID
CREATE TABLE organisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Informations de base
nom VARCHAR(200) NOT NULL,
nom_court VARCHAR(50),
type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION',
statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
description TEXT,
date_fondation DATE,
numero_enregistrement VARCHAR(100) UNIQUE,
-- Informations de contact
email VARCHAR(255) NOT NULL UNIQUE,
telephone VARCHAR(20),
telephone_secondaire VARCHAR(20),
email_secondaire VARCHAR(255),
-- Adresse
adresse VARCHAR(500),
ville VARCHAR(100),
code_postal VARCHAR(20),
region VARCHAR(100),
pays VARCHAR(100),
-- Coordonnées géographiques
latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90),
longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180),
-- Web et réseaux sociaux
site_web VARCHAR(500),
logo VARCHAR(500),
reseaux_sociaux VARCHAR(1000),
-- Hiérarchie
organisation_parente_id UUID,
niveau_hierarchique INTEGER NOT NULL DEFAULT 0,
-- Statistiques
nombre_membres INTEGER NOT NULL DEFAULT 0,
nombre_administrateurs INTEGER NOT NULL DEFAULT 0,
-- Finances
budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0),
devise VARCHAR(3) DEFAULT 'XOF',
cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE,
montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0),
-- Informations complémentaires
objectifs TEXT,
activites_principales TEXT,
certifications VARCHAR(500),
partenaires VARCHAR(1000),
notes VARCHAR(1000),
-- Paramètres
organisation_publique BOOLEAN NOT NULL DEFAULT TRUE,
accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE,
-- Métadonnées (héritées de 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,
-- Contraintes
CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')),
CONSTRAINT chk_organisation_type CHECK (type_organisation IN (
'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE',
'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE'
)),
CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')),
CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10),
CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0),
CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0),
-- Clé étrangère pour hiérarchie
CONSTRAINT fk_organisation_parente FOREIGN KEY (organisation_parente_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table membres avec UUID
CREATE TABLE membres (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_membre VARCHAR(20) UNIQUE NOT NULL,
prenom VARCHAR(100) NOT NULL,
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
mot_de_passe VARCHAR(255),
telephone VARCHAR(20),
date_naissance DATE NOT NULL,
date_adhesion DATE NOT NULL,
roles VARCHAR(500),
-- Clé étrangère vers organisations
organisation_id UUID,
-- Métadonnées (héritées de 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_membre_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table cotisations avec UUID
CREATE TABLE cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_reference VARCHAR(50) UNIQUE NOT NULL,
membre_id UUID NOT NULL,
type_cotisation VARCHAR(50) NOT NULL,
montant_du DECIMAL(12,2) NOT NULL CHECK (montant_du >= 0),
montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (montant_paye >= 0),
code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF',
statut VARCHAR(30) NOT NULL,
date_echeance DATE NOT NULL,
date_paiement TIMESTAMP,
description VARCHAR(500),
periode VARCHAR(20),
annee INTEGER NOT NULL CHECK (annee >= 2020 AND annee <= 2100),
mois INTEGER CHECK (mois >= 1 AND mois <= 12),
observations VARCHAR(1000),
recurrente BOOLEAN NOT NULL DEFAULT FALSE,
nombre_rappels INTEGER NOT NULL DEFAULT 0 CHECK (nombre_rappels >= 0),
date_dernier_rappel TIMESTAMP,
valide_par_id UUID,
nom_validateur VARCHAR(100),
date_validation TIMESTAMP,
methode_paiement VARCHAR(50),
reference_paiement VARCHAR(100),
-- Métadonnées (héritées de 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_cotisation_membre FOREIGN KEY (membre_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT chk_cotisation_statut CHECK (statut IN ('EN_ATTENTE', 'PAYEE', 'EN_RETARD', 'PARTIELLEMENT_PAYEE', 'ANNULEE')),
CONSTRAINT chk_cotisation_devise CHECK (code_devise ~ '^[A-Z]{3}$')
);
-- Table evenements avec UUID
CREATE TABLE evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(200) NOT NULL,
description VARCHAR(2000),
date_debut TIMESTAMP NOT NULL,
date_fin TIMESTAMP,
lieu VARCHAR(255) NOT NULL,
adresse VARCHAR(500),
ville VARCHAR(100),
pays VARCHAR(100),
code_postal VARCHAR(20),
latitude DECIMAL(9,6),
longitude DECIMAL(9,6),
type_evenement VARCHAR(50) NOT NULL,
statut VARCHAR(50) NOT NULL,
url_inscription VARCHAR(500),
url_informations VARCHAR(500),
image_url VARCHAR(500),
capacite_max INTEGER,
cout_participation DECIMAL(12,2),
devise VARCHAR(3),
est_public BOOLEAN NOT NULL DEFAULT TRUE,
tags VARCHAR(500),
notes VARCHAR(1000),
-- Clé étrangère vers organisations
organisation_id UUID,
-- Métadonnées (héritées de 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_evenement_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE SET NULL
);
-- Table inscriptions_evenement avec UUID
CREATE TABLE inscriptions_evenement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_id UUID NOT NULL,
evenement_id UUID NOT NULL,
date_inscription TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
statut VARCHAR(20) DEFAULT 'CONFIRMEE',
commentaire VARCHAR(500),
-- Métadonnées (héritées de 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_inscription_membre FOREIGN KEY (membre_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT fk_inscription_evenement FOREIGN KEY (evenement_id)
REFERENCES evenements(id) ON DELETE CASCADE,
CONSTRAINT chk_inscription_statut CHECK (statut IN ('CONFIRMEE', 'EN_ATTENTE', 'ANNULEE', 'REFUSEE')),
CONSTRAINT uk_inscription_membre_evenement UNIQUE (membre_id, evenement_id)
);
-- Table demandes_aide avec UUID
CREATE TABLE demandes_aide (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
type_aide VARCHAR(50) NOT NULL,
statut VARCHAR(50) NOT NULL,
montant_demande DECIMAL(10,2),
montant_approuve DECIMAL(10,2),
date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_evaluation TIMESTAMP,
date_versement TIMESTAMP,
justification TEXT,
commentaire_evaluation TEXT,
urgence BOOLEAN NOT NULL DEFAULT FALSE,
documents_fournis VARCHAR(500),
-- Clés étrangères
demandeur_id UUID NOT NULL,
evaluateur_id UUID,
organisation_id UUID NOT NULL,
-- Métadonnées (héritées de 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_demande_demandeur FOREIGN KEY (demandeur_id)
REFERENCES membres(id) ON DELETE CASCADE,
CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id)
REFERENCES membres(id) ON DELETE SET NULL,
CONSTRAINT fk_demande_organisation FOREIGN KEY (organisation_id)
REFERENCES organisations(id) ON DELETE CASCADE
);
-- ============================================
-- ÉTAPE 5: Recréer les index
-- ============================================
-- Index pour organisations
CREATE INDEX idx_organisation_nom ON organisations(nom);
CREATE INDEX idx_organisation_email ON organisations(email);
CREATE INDEX idx_organisation_statut ON organisations(statut);
CREATE INDEX idx_organisation_type ON organisations(type_organisation);
CREATE INDEX idx_organisation_ville ON organisations(ville);
CREATE INDEX idx_organisation_pays ON organisations(pays);
CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id);
CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement);
CREATE INDEX idx_organisation_actif ON organisations(actif);
CREATE INDEX idx_organisation_date_creation ON organisations(date_creation);
CREATE INDEX idx_organisation_publique ON organisations(organisation_publique);
CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres);
CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif);
-- Index pour membres
CREATE INDEX idx_membre_email ON membres(email);
CREATE INDEX idx_membre_numero ON membres(numero_membre);
CREATE INDEX idx_membre_actif ON membres(actif);
CREATE INDEX idx_membre_organisation ON membres(organisation_id);
-- Index pour cotisations
CREATE INDEX idx_cotisation_membre ON cotisations(membre_id);
CREATE INDEX idx_cotisation_reference ON cotisations(numero_reference);
CREATE INDEX idx_cotisation_statut ON cotisations(statut);
CREATE INDEX idx_cotisation_echeance ON cotisations(date_echeance);
CREATE INDEX idx_cotisation_type ON cotisations(type_cotisation);
CREATE INDEX idx_cotisation_annee_mois ON cotisations(annee, mois);
-- Index pour evenements
CREATE INDEX idx_evenement_date_debut ON evenements(date_debut);
CREATE INDEX idx_evenement_statut ON evenements(statut);
CREATE INDEX idx_evenement_type ON evenements(type_evenement);
CREATE INDEX idx_evenement_organisation ON evenements(organisation_id);
-- Index pour inscriptions_evenement
CREATE INDEX idx_inscription_membre ON inscriptions_evenement(membre_id);
CREATE INDEX idx_inscription_evenement ON inscriptions_evenement(evenement_id);
CREATE INDEX idx_inscription_date ON inscriptions_evenement(date_inscription);
-- Index pour demandes_aide
CREATE INDEX idx_demande_demandeur ON demandes_aide(demandeur_id);
CREATE INDEX idx_demande_evaluateur ON demandes_aide(evaluateur_id);
CREATE INDEX idx_demande_organisation ON demandes_aide(organisation_id);
CREATE INDEX idx_demande_statut ON demandes_aide(statut);
CREATE INDEX idx_demande_type ON demandes_aide(type_aide);
CREATE INDEX idx_demande_date_demande ON demandes_aide(date_demande);
-- ============================================
-- ÉTAPE 6: Commentaires sur les tables
-- ============================================
COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.) avec UUID';
COMMENT ON TABLE membres IS 'Table des membres avec UUID';
COMMENT ON TABLE cotisations IS 'Table des cotisations avec UUID';
COMMENT ON TABLE evenements IS 'Table des événements avec UUID';
COMMENT ON TABLE inscriptions_evenement IS 'Table des inscriptions aux événements avec UUID';
COMMENT ON TABLE demandes_aide IS 'Table des demandes d''aide avec UUID';
COMMENT ON COLUMN organisations.id IS 'UUID unique de l''organisation';
COMMENT ON COLUMN membres.id IS 'UUID unique du membre';
COMMENT ON COLUMN cotisations.id IS 'UUID unique de la cotisation';
COMMENT ON COLUMN evenements.id IS 'UUID unique de l''événement';
COMMENT ON COLUMN inscriptions_evenement.id IS 'UUID unique de l''inscription';
COMMENT ON COLUMN demandes_aide.id IS 'UUID unique de la demande d''aide';

View File

@@ -1,7 +1,7 @@
-- Migration V1.4: Ajout de la colonne profession à la table membres
-- Auteur: UnionFlow Team
-- Date: 2026-02-19
-- Description: Permet l'autocomplétion et le filtrage par profession (MembreDTO, MembreSearchCriteria)
ALTER TABLE membres ADD COLUMN IF NOT EXISTS profession VARCHAR(100);
COMMENT ON COLUMN membres.profession IS 'Profession du membre (ex. Ingénieur, Médecin)';
-- Migration V1.4: Ajout de la colonne profession à la table membres
-- Auteur: UnionFlow Team
-- Date: 2026-02-19
-- Description: Permet l'autocomplétion et le filtrage par profession (MembreDTO, MembreSearchCriteria)
ALTER TABLE membres ADD COLUMN IF NOT EXISTS profession VARCHAR(100);
COMMENT ON COLUMN membres.profession IS 'Profession du membre (ex. Ingénieur, Médecin)';

View File

@@ -1,217 +1,217 @@
-- Migration V1.4: Création des tables Tickets, Suggestions, Favoris et Configuration
-- Auteur: UnionFlow Team
-- Date: 2025-12-18
-- Description: Création des tables pour la gestion des tickets support, suggestions utilisateur, favoris et configuration système
-- ============================================
-- TABLE: tickets
-- ============================================
CREATE TABLE IF NOT EXISTS tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Champs de base
numero_ticket VARCHAR(50) NOT NULL UNIQUE,
utilisateur_id UUID NOT NULL,
sujet VARCHAR(255) NOT NULL,
description TEXT,
-- Classification
categorie VARCHAR(50), -- TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE
priorite VARCHAR(50), -- BASSE, NORMALE, HAUTE, URGENTE
statut VARCHAR(50) DEFAULT 'OUVERT', -- OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME
-- Gestion
agent_id UUID,
agent_nom VARCHAR(255),
-- Dates
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_derniere_reponse TIMESTAMP,
date_resolution TIMESTAMP,
date_fermeture TIMESTAMP,
-- Statistiques
nb_messages INTEGER DEFAULT 0,
nb_fichiers INTEGER DEFAULT 0,
note_satisfaction INTEGER CHECK (note_satisfaction >= 1 AND note_satisfaction <= 5),
-- Résolution
resolution TEXT,
-- Audit (BaseEntity)
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL,
-- Indexes
CONSTRAINT fk_ticket_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES membres(id) ON DELETE CASCADE
);
CREATE INDEX idx_ticket_utilisateur ON tickets(utilisateur_id);
CREATE INDEX idx_ticket_statut ON tickets(statut);
CREATE INDEX idx_ticket_categorie ON tickets(categorie);
CREATE INDEX idx_ticket_numero ON tickets(numero_ticket);
CREATE INDEX idx_ticket_date_creation ON tickets(date_creation DESC);
-- ============================================
-- TABLE: suggestions
-- ============================================
CREATE TABLE IF NOT EXISTS suggestions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Utilisateur
utilisateur_id UUID NOT NULL,
utilisateur_nom VARCHAR(255),
-- Contenu
titre VARCHAR(255) NOT NULL,
description TEXT,
justification TEXT,
-- Classification
categorie VARCHAR(50), -- UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING
priorite_estimee VARCHAR(50), -- BASSE, MOYENNE, HAUTE, CRITIQUE
statut VARCHAR(50) DEFAULT 'NOUVELLE', -- NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE
-- Statistiques
nb_votes INTEGER DEFAULT 0,
nb_commentaires INTEGER DEFAULT 0,
nb_vues INTEGER DEFAULT 0,
-- Dates
date_soumission TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_evaluation TIMESTAMP,
date_implementation TIMESTAMP,
-- Version
version_ciblee VARCHAR(50),
mise_a_jour TEXT,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL
);
CREATE INDEX idx_suggestion_utilisateur ON suggestions(utilisateur_id);
CREATE INDEX idx_suggestion_statut ON suggestions(statut);
CREATE INDEX idx_suggestion_categorie ON suggestions(categorie);
CREATE INDEX idx_suggestion_date_soumission ON suggestions(date_soumission DESC);
CREATE INDEX idx_suggestion_nb_votes ON suggestions(nb_votes DESC);
-- ============================================
-- TABLE: suggestion_votes
-- ============================================
CREATE TABLE IF NOT EXISTS suggestion_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
suggestion_id UUID NOT NULL,
utilisateur_id UUID NOT NULL,
date_vote TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Audit
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
actif BOOLEAN DEFAULT true NOT NULL,
-- Contrainte d'unicité : un utilisateur ne peut voter qu'une fois par suggestion
CONSTRAINT uk_suggestion_vote UNIQUE (suggestion_id, utilisateur_id),
CONSTRAINT fk_vote_suggestion FOREIGN KEY (suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
);
CREATE INDEX idx_vote_suggestion ON suggestion_votes(suggestion_id);
CREATE INDEX idx_vote_utilisateur ON suggestion_votes(utilisateur_id);
-- ============================================
-- TABLE: favoris
-- ============================================
CREATE TABLE IF NOT EXISTS favoris (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Utilisateur
utilisateur_id UUID NOT NULL,
-- Type et contenu
type_favori VARCHAR(50) NOT NULL, -- PAGE, DOCUMENT, CONTACT, RACCOURCI
titre VARCHAR(255) NOT NULL,
description VARCHAR(1000),
url VARCHAR(1000),
-- Présentation
icon VARCHAR(100),
couleur VARCHAR(50),
categorie VARCHAR(100),
-- Organisation
ordre INTEGER DEFAULT 0,
-- Statistiques
nb_visites INTEGER DEFAULT 0,
derniere_visite TIMESTAMP,
est_plus_utilise BOOLEAN DEFAULT false,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL,
CONSTRAINT fk_favori_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES membres(id) ON DELETE CASCADE
);
CREATE INDEX idx_favori_utilisateur ON favoris(utilisateur_id);
CREATE INDEX idx_favori_type ON favoris(type_favori);
CREATE INDEX idx_favori_categorie ON favoris(categorie);
CREATE INDEX idx_favori_ordre ON favoris(utilisateur_id, ordre);
-- ============================================
-- TABLE: configurations
-- ============================================
CREATE TABLE IF NOT EXISTS configurations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Clé unique
cle VARCHAR(255) NOT NULL UNIQUE,
-- Valeur
valeur TEXT,
type VARCHAR(50), -- STRING, NUMBER, BOOLEAN, JSON, DATE
-- Classification
categorie VARCHAR(50), -- SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE
description VARCHAR(1000),
-- Contrôles
modifiable BOOLEAN DEFAULT true,
visible BOOLEAN DEFAULT true,
-- Métadonnées (JSON stocké en TEXT)
metadonnees TEXT,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL
);
CREATE INDEX idx_config_cle ON configurations(cle);
CREATE INDEX idx_config_categorie ON configurations(categorie);
CREATE INDEX idx_config_visible ON configurations(visible) WHERE visible = true;
-- ============================================
-- COMMENTAIRES
-- ============================================
COMMENT ON TABLE tickets IS 'Table pour la gestion des tickets support';
COMMENT ON TABLE suggestions IS 'Table pour la gestion des suggestions utilisateur';
COMMENT ON TABLE suggestion_votes IS 'Table pour gérer les votes sur les suggestions (évite les votes multiples)';
COMMENT ON TABLE favoris IS 'Table pour la gestion des favoris utilisateur';
COMMENT ON TABLE configurations IS 'Table pour la gestion de la configuration système';
-- Migration V1.4: Création des tables Tickets, Suggestions, Favoris et Configuration
-- Auteur: UnionFlow Team
-- Date: 2025-12-18
-- Description: Création des tables pour la gestion des tickets support, suggestions utilisateur, favoris et configuration système
-- ============================================
-- TABLE: tickets
-- ============================================
CREATE TABLE IF NOT EXISTS tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Champs de base
numero_ticket VARCHAR(50) NOT NULL UNIQUE,
utilisateur_id UUID NOT NULL,
sujet VARCHAR(255) NOT NULL,
description TEXT,
-- Classification
categorie VARCHAR(50), -- TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE
priorite VARCHAR(50), -- BASSE, NORMALE, HAUTE, URGENTE
statut VARCHAR(50) DEFAULT 'OUVERT', -- OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME
-- Gestion
agent_id UUID,
agent_nom VARCHAR(255),
-- Dates
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_derniere_reponse TIMESTAMP,
date_resolution TIMESTAMP,
date_fermeture TIMESTAMP,
-- Statistiques
nb_messages INTEGER DEFAULT 0,
nb_fichiers INTEGER DEFAULT 0,
note_satisfaction INTEGER CHECK (note_satisfaction >= 1 AND note_satisfaction <= 5),
-- Résolution
resolution TEXT,
-- Audit (BaseEntity)
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL,
-- Indexes
CONSTRAINT fk_ticket_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES membres(id) ON DELETE CASCADE
);
CREATE INDEX idx_ticket_utilisateur ON tickets(utilisateur_id);
CREATE INDEX idx_ticket_statut ON tickets(statut);
CREATE INDEX idx_ticket_categorie ON tickets(categorie);
CREATE INDEX idx_ticket_numero ON tickets(numero_ticket);
CREATE INDEX idx_ticket_date_creation ON tickets(date_creation DESC);
-- ============================================
-- TABLE: suggestions
-- ============================================
CREATE TABLE IF NOT EXISTS suggestions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Utilisateur
utilisateur_id UUID NOT NULL,
utilisateur_nom VARCHAR(255),
-- Contenu
titre VARCHAR(255) NOT NULL,
description TEXT,
justification TEXT,
-- Classification
categorie VARCHAR(50), -- UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING
priorite_estimee VARCHAR(50), -- BASSE, MOYENNE, HAUTE, CRITIQUE
statut VARCHAR(50) DEFAULT 'NOUVELLE', -- NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE
-- Statistiques
nb_votes INTEGER DEFAULT 0,
nb_commentaires INTEGER DEFAULT 0,
nb_vues INTEGER DEFAULT 0,
-- Dates
date_soumission TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_evaluation TIMESTAMP,
date_implementation TIMESTAMP,
-- Version
version_ciblee VARCHAR(50),
mise_a_jour TEXT,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL
);
CREATE INDEX idx_suggestion_utilisateur ON suggestions(utilisateur_id);
CREATE INDEX idx_suggestion_statut ON suggestions(statut);
CREATE INDEX idx_suggestion_categorie ON suggestions(categorie);
CREATE INDEX idx_suggestion_date_soumission ON suggestions(date_soumission DESC);
CREATE INDEX idx_suggestion_nb_votes ON suggestions(nb_votes DESC);
-- ============================================
-- TABLE: suggestion_votes
-- ============================================
CREATE TABLE IF NOT EXISTS suggestion_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
suggestion_id UUID NOT NULL,
utilisateur_id UUID NOT NULL,
date_vote TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Audit
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
actif BOOLEAN DEFAULT true NOT NULL,
-- Contrainte d'unicité : un utilisateur ne peut voter qu'une fois par suggestion
CONSTRAINT uk_suggestion_vote UNIQUE (suggestion_id, utilisateur_id),
CONSTRAINT fk_vote_suggestion FOREIGN KEY (suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
);
CREATE INDEX idx_vote_suggestion ON suggestion_votes(suggestion_id);
CREATE INDEX idx_vote_utilisateur ON suggestion_votes(utilisateur_id);
-- ============================================
-- TABLE: favoris
-- ============================================
CREATE TABLE IF NOT EXISTS favoris (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Utilisateur
utilisateur_id UUID NOT NULL,
-- Type et contenu
type_favori VARCHAR(50) NOT NULL, -- PAGE, DOCUMENT, CONTACT, RACCOURCI
titre VARCHAR(255) NOT NULL,
description VARCHAR(1000),
url VARCHAR(1000),
-- Présentation
icon VARCHAR(100),
couleur VARCHAR(50),
categorie VARCHAR(100),
-- Organisation
ordre INTEGER DEFAULT 0,
-- Statistiques
nb_visites INTEGER DEFAULT 0,
derniere_visite TIMESTAMP,
est_plus_utilise BOOLEAN DEFAULT false,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL,
CONSTRAINT fk_favori_utilisateur FOREIGN KEY (utilisateur_id) REFERENCES membres(id) ON DELETE CASCADE
);
CREATE INDEX idx_favori_utilisateur ON favoris(utilisateur_id);
CREATE INDEX idx_favori_type ON favoris(type_favori);
CREATE INDEX idx_favori_categorie ON favoris(categorie);
CREATE INDEX idx_favori_ordre ON favoris(utilisateur_id, ordre);
-- ============================================
-- TABLE: configurations
-- ============================================
CREATE TABLE IF NOT EXISTS configurations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Clé unique
cle VARCHAR(255) NOT NULL UNIQUE,
-- Valeur
valeur TEXT,
type VARCHAR(50), -- STRING, NUMBER, BOOLEAN, JSON, DATE
-- Classification
categorie VARCHAR(50), -- SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE
description VARCHAR(1000),
-- Contrôles
modifiable BOOLEAN DEFAULT true,
visible BOOLEAN DEFAULT true,
-- Métadonnées (JSON stocké en TEXT)
metadonnees TEXT,
-- Audit (BaseEntity)
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN DEFAULT true NOT NULL
);
CREATE INDEX idx_config_cle ON configurations(cle);
CREATE INDEX idx_config_categorie ON configurations(categorie);
CREATE INDEX idx_config_visible ON configurations(visible) WHERE visible = true;
-- ============================================
-- COMMENTAIRES
-- ============================================
COMMENT ON TABLE tickets IS 'Table pour la gestion des tickets support';
COMMENT ON TABLE suggestions IS 'Table pour la gestion des suggestions utilisateur';
COMMENT ON TABLE suggestion_votes IS 'Table pour gérer les votes sur les suggestions (évite les votes multiples)';
COMMENT ON TABLE favoris IS 'Table pour la gestion des favoris utilisateur';
COMMENT ON TABLE configurations IS 'Table pour la gestion de la configuration système';

View File

@@ -1,24 +1,24 @@
-- Migration V1.5: Ajout des champs de liaison avec Keycloak dans la table membres
-- Date: 2025-12-24
-- Description: Permet de lier un Membre (business) à un User Keycloak (authentification)
-- Ajouter la colonne keycloak_user_id pour stocker l'UUID du user Keycloak
ALTER TABLE membres
ADD COLUMN IF NOT EXISTS keycloak_user_id VARCHAR(36);
-- Ajouter la colonne keycloak_realm pour stocker le nom du realm (généralement "unionflow")
ALTER TABLE membres
ADD COLUMN IF NOT EXISTS keycloak_realm VARCHAR(50);
-- Créer un index unique sur keycloak_user_id pour garantir l'unicité et optimiser les recherches
-- Un user Keycloak ne peut être lié qu'à un seul Membre
CREATE UNIQUE INDEX IF NOT EXISTS idx_membre_keycloak_user
ON membres(keycloak_user_id)
WHERE keycloak_user_id IS NOT NULL;
-- Ajouter un commentaire pour la documentation
COMMENT ON COLUMN membres.keycloak_user_id IS 'UUID du user Keycloak lié à ce membre. NULL si le membre n''a pas de compte de connexion.';
COMMENT ON COLUMN membres.keycloak_realm IS 'Nom du realm Keycloak où le user est enregistré (ex: "unionflow", "btpxpress"). NULL si pas de compte Keycloak.';
-- Note: Le champ mot_de_passe existant devrait être déprécié car Keycloak est la source de vérité pour l'authentification
-- Cependant, on le conserve pour compatibilité avec les données existantes et migration progressive
-- Migration V1.5: Ajout des champs de liaison avec Keycloak dans la table membres
-- Date: 2025-12-24
-- Description: Permet de lier un Membre (business) à un User Keycloak (authentification)
-- Ajouter la colonne keycloak_user_id pour stocker l'UUID du user Keycloak
ALTER TABLE membres
ADD COLUMN IF NOT EXISTS keycloak_user_id VARCHAR(36);
-- Ajouter la colonne keycloak_realm pour stocker le nom du realm (généralement "unionflow")
ALTER TABLE membres
ADD COLUMN IF NOT EXISTS keycloak_realm VARCHAR(50);
-- Créer un index unique sur keycloak_user_id pour garantir l'unicité et optimiser les recherches
-- Un user Keycloak ne peut être lié qu'à un seul Membre
CREATE UNIQUE INDEX IF NOT EXISTS idx_membre_keycloak_user
ON membres(keycloak_user_id)
WHERE keycloak_user_id IS NOT NULL;
-- Ajouter un commentaire pour la documentation
COMMENT ON COLUMN membres.keycloak_user_id IS 'UUID du user Keycloak lié à ce membre. NULL si le membre n''a pas de compte de connexion.';
COMMENT ON COLUMN membres.keycloak_realm IS 'Nom du realm Keycloak où le user est enregistré (ex: "unionflow", "btpxpress"). NULL si pas de compte Keycloak.';
-- Note: Le champ mot_de_passe existant devrait être déprécié car Keycloak est la source de vérité pour l'authentification
-- Cependant, on le conserve pour compatibilité avec les données existantes et migration progressive

View File

@@ -1,96 +1,96 @@
-- ============================================================
-- 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);
-- 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.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);
-- 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';

View File

@@ -1,20 +1,20 @@
-- ============================================================
-- 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)';
-- ============================================================
-- 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)';

View File

@@ -1,44 +1,44 @@
-- ============================================================
-- 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.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';

View File

@@ -1,76 +1,76 @@
-- ============================================================
-- 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.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.';

View File

@@ -1,61 +1,61 @@
-- ============================================================
-- 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.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';

View File

@@ -1,51 +1,51 @@
-- ============================================================
-- 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.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';

View File

@@ -1,114 +1,114 @@
-- ============================================================
-- 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.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é';

View File

@@ -1,72 +1,72 @@
-- ============================================================
-- 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.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';

View File

@@ -1,34 +1,34 @@
-- ============================================================
-- 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.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';

View File

@@ -1,31 +1,31 @@
-- ============================================================
-- 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.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';

View File

@@ -1,23 +1,23 @@
-- ============================================================
-- 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.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';

View File

@@ -1,266 +1,266 @@
-- =====================================================
-- 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.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);

View File

@@ -1,24 +1,24 @@
-- =====================================================
-- 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.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.

View File

@@ -1,58 +1,58 @@
-- =====================================================
-- 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.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;

View File

@@ -1,20 +1,20 @@
-- =====================================================
-- 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.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);

View File

@@ -1,73 +1,73 @@
-- ============================================================
-- 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.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');

View File

@@ -1,23 +1,23 @@
-- 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';
-- 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';

View File

@@ -1,152 +1,152 @@
-- 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
);
-- 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
);

View File

@@ -1,237 +1,237 @@
-- ============================================================================
-- 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
-- ─────────────────────────────────────────────────────────────────────────────
-- 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
);
-- ============================================================================
-- 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
-- ─────────────────────────────────────────────────────────────────────────────
-- 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
);

View File

@@ -1,50 +1,50 @@
-- ============================================================================
-- V3.8 — Données de test : un compte épargne pour le membre MUKEFI
-- Permet d'afficher au moins un compte sur l'écran "Comptes épargne".
-- ============================================================================
-- Un compte épargne pour membre.mukefi@unionflow.test (organisation MUKEFI)
INSERT INTO comptes_epargne (
id,
actif,
date_creation,
date_modification,
cree_par,
modifie_par,
version,
date_ouverture,
date_derniere_transaction,
description,
numero_compte,
solde_actuel,
solde_bloque,
statut,
type_compte,
membre_id,
organisation_id
)
SELECT
gen_random_uuid(),
true,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'system',
'system',
0,
CURRENT_DATE,
NULL,
'Compte épargne principal test',
'MUK-' || UPPER(SUBSTRING(REPLACE(gen_random_uuid()::text, '-', '') FROM 1 FOR 8)),
0,
0,
'ACTIF',
'EPARGNE_LIBRE',
u.id,
o.id
FROM utilisateurs u,
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1) o
WHERE u.email = 'membre.mukefi@unionflow.test'
AND NOT EXISTS (
SELECT 1 FROM comptes_epargne ce
WHERE ce.membre_id = u.id AND ce.actif = true
);
-- ============================================================================
-- V3.8 — Données de test : un compte épargne pour le membre MUKEFI
-- Permet d'afficher au moins un compte sur l'écran "Comptes épargne".
-- ============================================================================
-- Un compte épargne pour membre.mukefi@unionflow.test (organisation MUKEFI)
INSERT INTO comptes_epargne (
id,
actif,
date_creation,
date_modification,
cree_par,
modifie_par,
version,
date_ouverture,
date_derniere_transaction,
description,
numero_compte,
solde_actuel,
solde_bloque,
statut,
type_compte,
membre_id,
organisation_id
)
SELECT
gen_random_uuid(),
true,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'system',
'system',
0,
CURRENT_DATE,
NULL,
'Compte épargne principal test',
'MUK-' || UPPER(SUBSTRING(REPLACE(gen_random_uuid()::text, '-', '') FROM 1 FOR 8)),
0,
0,
'ACTIF',
'EPARGNE_LIBRE',
u.id,
o.id
FROM utilisateurs u,
(SELECT id FROM organisations WHERE nom_court = 'MUKEFI' LIMIT 1) o
WHERE u.email = 'membre.mukefi@unionflow.test'
AND NOT EXISTS (
SELECT 1 FROM comptes_epargne ce
WHERE ce.membre_id = u.id AND ce.actif = true
);

View File

@@ -1,57 +1,57 @@
# Stratégie des migrations Flyway
## Vue densemble
| Version | Fichier | Rôle |
|--------|---------|------|
| **V1** | `V1__UnionFlow_Complete_Schema.sql` | Schéma historique consolidé (anciennes V1.2 à V3.7) + données de référence et de test |
| **V2** | `V2__Entity_Schema_Alignment.sql` | Alignement du schéma avec les entités JPA (colonnes/tables manquantes, types, index). Idempotent. |
Les 25 fichiers dorigine sont conservés dans **`db/legacy-migrations/`** (référence uniquement, Flyway ne les exécute pas).
## Ordre dexécution
1. **V1** : crée les tables, contraintes et données de base.
2. **V2** : ajoute ou modifie colonnes/tables pour correspondre aux entités JPA (ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS). Peut être exécuté plusieurs fois sans effet de bord.
## Nouvelle base de données
Avec une base vide, Flyway exécute **V1** puis **V2**. Aucune autre action.
## Base déjà migrée avec les anciennes versions (V1.2 à V3.7)
Si la base a déjà été migrée avec les 25 anciens scripts, il faut **une seule fois** mettre à jour lhistorique Flyway pour refléter la consolidation :
1. Sauvegarder la base.
2. Se connecter en base (psql, DBeaver, etc.) et exécuter :
```sql
-- Marquer la consolidation comme appliquée (une seule fois)
DELETE FROM flyway_schema_history WHERE version IN (
'1.2','1.3','1.4','1.5','1.6','1.7','2.0','2.1','2.2','2.3','2.4','2.5','2.6','2.7','2.8','2.9','2.10',
'3.0','3.1','3.2','3.3','3.4','3.5','3.6','3.7'
);
INSERT INTO flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success)
VALUES (
(SELECT COALESCE(MAX(installed_rank),0) + 1 FROM flyway_schema_history f2),
'1', 'UnionFlow Complete Schema', 'SQL', 'V1__UnionFlow_Complete_Schema.sql', NULL, current_user, 0, true
);
```
Après cela, Flyway considère que la version **1** est appliquée et nexécutera plus les anciens scripts.
## Évolutions futures
- **Changement de schéma métier** (nouvelles tables, nouvelles colonnes métier) : ajouter une migration **V3**, **V4**, etc. (une par release ou lot cohérent).
- **Alignement entités JPA** : si de nouvelles entités ou champs sont ajoutés au code, compléter **`V2__Entity_Schema_Alignment.sql`** avec des `ADD COLUMN IF NOT EXISTS` / `CREATE TABLE IF NOT EXISTS` pour garder un seul fichier dalignement.
## Régénérer le script consolidé
Si les fichiers dans `legacy/` sont modifiés et que vous voulez régénérer `V1__UnionFlow_Complete_Schema.sql` :
```powershell
cd unionflow-server-impl-quarkus
./scripts/merge-migrations.ps1
```
(Remettre temporairement les 25 fichiers dans `db/migration/` avant de lancer le script, puis les redéplacer dans `legacy/`.)
# Stratégie des migrations Flyway
## Vue densemble
| Version | Fichier | Rôle |
|--------|---------|------|
| **V1** | `V1__UnionFlow_Complete_Schema.sql` | Schéma historique consolidé (anciennes V1.2 à V3.7) + données de référence et de test |
| **V2** | `V2__Entity_Schema_Alignment.sql` | Alignement du schéma avec les entités JPA (colonnes/tables manquantes, types, index). Idempotent. |
Les 25 fichiers dorigine sont conservés dans **`db/legacy-migrations/`** (référence uniquement, Flyway ne les exécute pas).
## Ordre dexécution
1. **V1** : crée les tables, contraintes et données de base.
2. **V2** : ajoute ou modifie colonnes/tables pour correspondre aux entités JPA (ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS). Peut être exécuté plusieurs fois sans effet de bord.
## Nouvelle base de données
Avec une base vide, Flyway exécute **V1** puis **V2**. Aucune autre action.
## Base déjà migrée avec les anciennes versions (V1.2 à V3.7)
Si la base a déjà été migrée avec les 25 anciens scripts, il faut **une seule fois** mettre à jour lhistorique Flyway pour refléter la consolidation :
1. Sauvegarder la base.
2. Se connecter en base (psql, DBeaver, etc.) et exécuter :
```sql
-- Marquer la consolidation comme appliquée (une seule fois)
DELETE FROM flyway_schema_history WHERE version IN (
'1.2','1.3','1.4','1.5','1.6','1.7','2.0','2.1','2.2','2.3','2.4','2.5','2.6','2.7','2.8','2.9','2.10',
'3.0','3.1','3.2','3.3','3.4','3.5','3.6','3.7'
);
INSERT INTO flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success)
VALUES (
(SELECT COALESCE(MAX(installed_rank),0) + 1 FROM flyway_schema_history f2),
'1', 'UnionFlow Complete Schema', 'SQL', 'V1__UnionFlow_Complete_Schema.sql', NULL, current_user, 0, true
);
```
Après cela, Flyway considère que la version **1** est appliquée et nexécutera plus les anciens scripts.
## Évolutions futures
- **Changement de schéma métier** (nouvelles tables, nouvelles colonnes métier) : ajouter une migration **V3**, **V4**, etc. (une par release ou lot cohérent).
- **Alignement entités JPA** : si de nouvelles entités ou champs sont ajoutés au code, compléter **`V2__Entity_Schema_Alignment.sql`** avec des `ADD COLUMN IF NOT EXISTS` / `CREATE TABLE IF NOT EXISTS` pour garder un seul fichier dalignement.
## Régénérer le script consolidé
Si les fichiers dans `legacy/` sont modifiés et que vous voulez régénérer `V1__UnionFlow_Complete_Schema.sql` :
```powershell
cd unionflow-server-impl-quarkus
./scripts/merge-migrations.ps1
```
(Remettre temporairement les 25 fichiers dans `db/migration/` avant de lancer le script, puis les redéplacer dans `legacy/`.)

View File

@@ -1,87 +0,0 @@
-- ============================================================================
-- V32 — Mutuelle : Parts Sociales + Paramètres Financiers + Intérêts
--
-- Ajoute les tables nécessaires pour les fonctionnalités manquantes identifiées
-- dans l'analyse du fichier FUSION 2013-2021.xlsx de la Mutuelle GBANE :
-- 1. comptes_parts_sociales — capital social des membres
-- 2. transactions_parts_sociales — historique des mouvements de parts
-- 3. parametres_financiers_mutuelle — taux, périodicités, valeur nominale
-- ============================================================================
-- ── 1. Paramètres financiers de la mutuelle ────────────────────────────────
CREATE TABLE IF NOT EXISTS parametres_financiers_mutuelle (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
organisation_id UUID NOT NULL UNIQUE,
valeur_nominale_par_defaut NUMERIC(19,4) NOT NULL DEFAULT 5000,
taux_interet_annuel_epargne NUMERIC(6,4) NOT NULL DEFAULT 0.0300,
taux_dividende_parts_annuel NUMERIC(6,4) NOT NULL DEFAULT 0.0500,
periodicite_calcul VARCHAR(20) NOT NULL DEFAULT 'MENSUEL',
seuil_min_epargne_interets NUMERIC(19,4) DEFAULT 0,
prochaine_calcul_interets DATE,
dernier_calcul_interets DATE,
dernier_nb_comptes_traites INTEGER DEFAULT 0,
-- BaseEntity cols
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,
CONSTRAINT fk_pfm_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id)
);
CREATE INDEX IF NOT EXISTS idx_pfm_org ON parametres_financiers_mutuelle(organisation_id);
-- ── 2. Comptes de parts sociales ───────────────────────────────────────────
CREATE TABLE IF NOT EXISTS comptes_parts_sociales (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
membre_id UUID NOT NULL,
organisation_id UUID NOT NULL,
numero_compte VARCHAR(50) NOT NULL UNIQUE,
nombre_parts INTEGER NOT NULL DEFAULT 0,
valeur_nominale NUMERIC(19,4) NOT NULL,
montant_total NUMERIC(19,4) NOT NULL DEFAULT 0,
total_dividendes_recus NUMERIC(19,4) NOT NULL DEFAULT 0,
statut VARCHAR(30) NOT NULL DEFAULT 'ACTIF',
date_ouverture DATE NOT NULL DEFAULT CURRENT_DATE,
date_derniere_operation DATE,
notes VARCHAR(500),
-- BaseEntity cols
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,
CONSTRAINT fk_cps_membre FOREIGN KEY (membre_id) REFERENCES utilisateurs(id),
CONSTRAINT fk_cps_organisation FOREIGN KEY (organisation_id) REFERENCES organisations(id)
);
CREATE INDEX IF NOT EXISTS idx_cps_numero ON comptes_parts_sociales(numero_compte);
CREATE INDEX IF NOT EXISTS idx_cps_membre ON comptes_parts_sociales(membre_id);
CREATE INDEX IF NOT EXISTS idx_cps_org ON comptes_parts_sociales(organisation_id);
-- ── 3. Transactions sur parts sociales ────────────────────────────────────
CREATE TABLE IF NOT EXISTS transactions_parts_sociales (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
compte_id UUID NOT NULL,
type_transaction VARCHAR(50) NOT NULL,
nombre_parts INTEGER NOT NULL,
montant NUMERIC(19,4) NOT NULL,
solde_parts_avant INTEGER NOT NULL DEFAULT 0,
solde_parts_apres INTEGER NOT NULL DEFAULT 0,
motif VARCHAR(500),
reference_externe VARCHAR(100),
date_transaction TIMESTAMP NOT NULL DEFAULT NOW(),
-- BaseEntity cols
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,
CONSTRAINT fk_tps_compte FOREIGN KEY (compte_id) REFERENCES comptes_parts_sociales(id)
);
CREATE INDEX IF NOT EXISTS idx_tps_compte ON transactions_parts_sociales(compte_id);
CREATE INDEX IF NOT EXISTS idx_tps_date ON transactions_parts_sociales(date_transaction);

View File

@@ -1,15 +0,0 @@
-- ============================================================================
-- V33 — Correction colonnes legacy de audit_logs
--
-- La V1 crée audit_logs avec action VARCHAR(50) NOT NULL (ancien schéma).
-- L'entité AuditLog utilise type_action à la place.
-- Hibernate ne remplit pas action → violation NOT NULL sur chaque insert.
-- Fix : rendre action nullable + nettoyer les autres colonnes orphelines.
-- ============================================================================
-- Rendre la colonne legacy nullable (elle est supersédée par type_action)
ALTER TABLE audit_logs ALTER COLUMN action DROP NOT NULL;
-- Aligner entite_id : la V1 déclare UUID mais l'entité stocke une String (UUID textuel)
-- → changer en VARCHAR pour éviter des cast errors sur certains IDs non-UUID
ALTER TABLE audit_logs ALTER COLUMN entite_id TYPE VARCHAR(255) USING entite_id::VARCHAR;

View File

@@ -1,39 +0,0 @@
-- ============================================================================
-- V34 — Rendre membre_id nullable dans les tables où l'entité Hibernate
-- utilise désormais une autre colonne (utilisateur_id, membre_organisation_id).
--
-- Contexte : V1 crée ces tables avec membre_id UUID NOT NULL. Les entités ont
-- évolué pour utiliser utilisateur_id (MembreOrganisation, DemandeAdhesion,
-- IntentionPaiement) ou membre_organisation_id (MembreRole). Hibernate update
-- a ajouté les nouvelles colonnes mais n'a pas supprimé membre_id.
-- Résultat : chaque insert lève une violation NOT NULL sur membre_id.
-- Fix : rendre membre_id nullable (colonne legacy, plus utilisée par le code).
-- ============================================================================
-- membres_organisations : entité utilise utilisateur_id
ALTER TABLE membres_organisations ALTER COLUMN membre_id DROP NOT NULL;
-- membres_roles : entité utilise membre_organisation_id
ALTER TABLE membres_roles ALTER COLUMN membre_id DROP NOT NULL;
-- demandes_adhesion : entité utilise utilisateur_id
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'demandes_adhesion' AND column_name = 'membre_id'
) THEN
ALTER TABLE demandes_adhesion ALTER COLUMN membre_id DROP NOT NULL;
END IF;
END $$;
-- intentions_paiement : entité utilise utilisateur_id
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'intentions_paiement' AND column_name = 'membre_id'
) THEN
ALTER TABLE intentions_paiement ALTER COLUMN membre_id DROP NOT NULL;
END IF;
END $$;

View File

@@ -1,77 +0,0 @@
-- ============================================================================
-- V35 — Recalibrage nombre_membres + trigger auto-maintien
--
-- DATA-01 : Le compteur organisations.nombre_membres est désynchronisé quand
-- des membres sont importés directement en DB (hors service Java).
-- Fix :
-- 1. Recalibrage immédiat depuis membres_organisations réels (actifs)
-- 2. Trigger PostgreSQL pour maintenir le compteur à jour automatiquement
-- ============================================================================
-- 1. Recalibrage ponctuel : recalculer depuis la table membres_organisations
UPDATE organisations o
SET nombre_membres = (
SELECT COUNT(*)
FROM membres_organisations mo
WHERE mo.organisation_id = o.id
AND mo.actif = true
AND mo.statut IN ('ACTIF', 'ACTIF_PREMIUM')
);
-- 2. Fonction trigger : incrémente/décrémente selon INSERT/UPDATE/DELETE
CREATE OR REPLACE FUNCTION update_organisation_nombre_membres()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
-- Nouveau membre actif → incrémenter
IF NEW.actif = true AND NEW.statut IN ('ACTIF', 'ACTIF_PREMIUM') THEN
UPDATE organisations
SET nombre_membres = GREATEST(0, nombre_membres + 1)
WHERE id = NEW.organisation_id;
END IF;
ELSIF TG_OP = 'UPDATE' THEN
-- Transition actif/inactif ou statut
DECLARE
was_counted BOOLEAN := OLD.actif = true AND OLD.statut IN ('ACTIF', 'ACTIF_PREMIUM');
is_counted BOOLEAN := NEW.actif = true AND NEW.statut IN ('ACTIF', 'ACTIF_PREMIUM');
BEGIN
IF NOT was_counted AND is_counted THEN
UPDATE organisations
SET nombre_membres = GREATEST(0, nombre_membres + 1)
WHERE id = NEW.organisation_id;
ELSIF was_counted AND NOT is_counted THEN
UPDATE organisations
SET nombre_membres = GREATEST(0, nombre_membres - 1)
WHERE id = OLD.organisation_id;
END IF;
END;
ELSIF TG_OP = 'DELETE' THEN
-- Suppression physique (rare)
IF OLD.actif = true AND OLD.statut IN ('ACTIF', 'ACTIF_PREMIUM') THEN
UPDATE organisations
SET nombre_membres = GREATEST(0, nombre_membres - 1)
WHERE id = OLD.organisation_id;
END IF;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;
-- 3. Attacher le trigger à membres_organisations
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger
WHERE tgname = 'trg_update_nombre_membres'
AND tgrelid = 'membres_organisations'::regclass
) THEN
CREATE TRIGGER trg_update_nombre_membres
AFTER INSERT OR UPDATE OF actif, statut OR DELETE
ON membres_organisations
FOR EACH ROW
EXECUTE FUNCTION update_organisation_nombre_membres();
END IF;
END $$;

View File

@@ -1,393 +0,0 @@
-- ============================================================================
-- V36 — SYSCOHADA : Alignement schéma + Seeds plan comptable standard + Trigger
--
-- P0.4 ROADMAP_2026.md — Obligation OHADA SYSCOHADA révisé (applicable depuis 2018)
-- Corrige l'écart entre V1 (schéma minimal) et les entités Java (colonnes Hibernate).
-- Ajoute le plan comptable standard SYSCOHADA pour mutuelles/coopératives UEMOA.
-- ============================================================================
-- ============================================================================
-- 1. COMPTES_COMPTABLES — Alignement colonnes V1 → entité Java
-- ============================================================================
-- La V1 crée la table avec numero/libelle/type_compte/organisation_id seulement.
-- L'entité Java attend : numero_compte, classe_comptable, solde_initial, solde_actuel,
-- compte_collectif, compte_analytique, cree_par, modifie_par.
-- Renommer la colonne numero → numero_compte si elle n'a pas déjà été renommée par Hibernate
-- Sinon : si les deux colonnes coexistent (Hibernate a créé numero_compte, V1 a laissé numero),
-- on supprime l'ancienne colonne obsolète numero (NOT NULL sans défaut, bloque les INSERTs).
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'comptes_comptables' AND column_name = 'numero'
) THEN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'comptes_comptables' AND column_name = 'numero_compte'
) THEN
ALTER TABLE comptes_comptables RENAME COLUMN numero TO numero_compte;
ELSE
-- Les deux colonnes coexistent : recopier les valeurs vers numero_compte si besoin,
-- puis supprimer la colonne obsolète numero.
UPDATE comptes_comptables SET numero_compte = numero
WHERE numero_compte IS NULL AND numero IS NOT NULL;
ALTER TABLE comptes_comptables DROP COLUMN numero;
END IF;
END IF;
END $$;
-- Ajouter colonnes manquantes si pas encore créées par Hibernate update
ALTER TABLE comptes_comptables
ADD COLUMN IF NOT EXISTS classe_comptable INTEGER,
ADD COLUMN IF NOT EXISTS solde_initial DECIMAL(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS solde_actuel DECIMAL(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS compte_collectif BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS compte_analytique BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS description VARCHAR(500),
ADD COLUMN IF NOT EXISTS cree_par VARCHAR(255),
ADD COLUMN IF NOT EXISTS modifie_par VARCHAR(255);
-- Déduire classe_comptable depuis numero_compte si null (première chiffre du numéro)
UPDATE comptes_comptables
SET classe_comptable = CAST(LEFT(numero_compte, 1) AS INTEGER)
WHERE classe_comptable IS NULL AND numero_compte IS NOT NULL AND LENGTH(numero_compte) > 0;
-- Rendre classe_comptable NOT NULL après backfill
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'comptes_comptables' AND column_name = 'classe_comptable'
AND is_nullable = 'NO'
) THEN
ALTER TABLE comptes_comptables ALTER COLUMN classe_comptable SET NOT NULL;
END IF;
END $$;
-- Contrainte classe 1-9 (SYSCOHADA a 9 classes, pas 7)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_compte_classe_syscohada') THEN
ALTER TABLE comptes_comptables
ADD CONSTRAINT chk_compte_classe_syscohada
CHECK (classe_comptable >= 1 AND classe_comptable <= 9);
END IF;
END $$;
-- ============================================================================
-- 2. JOURNAUX_COMPTABLES — Alignement colonnes
-- ============================================================================
ALTER TABLE journaux_comptables
ADD COLUMN IF NOT EXISTS date_debut DATE,
ADD COLUMN IF NOT EXISTS date_fin DATE,
ADD COLUMN IF NOT EXISTS statut VARCHAR(20) DEFAULT 'OUVERT',
ADD COLUMN IF NOT EXISTS description VARCHAR(500),
ADD COLUMN IF NOT EXISTS cree_par VARCHAR(255),
ADD COLUMN IF NOT EXISTS modifie_par VARCHAR(255);
-- ============================================================================
-- 3. ECRITURES_COMPTABLES — Alignement colonnes
-- ============================================================================
ALTER TABLE ecritures_comptables
ADD COLUMN IF NOT EXISTS organisation_id UUID REFERENCES organisations(id),
ADD COLUMN IF NOT EXISTS paiement_id UUID REFERENCES paiements(id),
ADD COLUMN IF NOT EXISTS reference VARCHAR(100),
ADD COLUMN IF NOT EXISTS lettrage VARCHAR(20),
ADD COLUMN IF NOT EXISTS pointe BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS montant_debit DECIMAL(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS montant_credit DECIMAL(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS commentaire VARCHAR(1000),
ADD COLUMN IF NOT EXISTS cree_par VARCHAR(255),
ADD COLUMN IF NOT EXISTS modifie_par VARCHAR(255);
-- ============================================================================
-- 4. LIGNES_ECRITURE — Alignement colonnes (debit/credit → montant_debit/credit)
-- ============================================================================
-- Renommer compte_id → compte_comptable_id si besoin
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'compte_id'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'compte_comptable_id'
) THEN
ALTER TABLE lignes_ecriture RENAME COLUMN compte_id TO compte_comptable_id;
END IF;
END $$;
-- Renommer debit/credit → montant_debit/montant_credit si besoin
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'debit'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'montant_debit'
) THEN
ALTER TABLE lignes_ecriture RENAME COLUMN debit TO montant_debit;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'credit'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'lignes_ecriture' AND column_name = 'montant_credit'
) THEN
ALTER TABLE lignes_ecriture RENAME COLUMN credit TO montant_credit;
END IF;
END $$;
ALTER TABLE lignes_ecriture
ADD COLUMN IF NOT EXISTS numero_ligne INTEGER,
ADD COLUMN IF NOT EXISTS reference VARCHAR(100),
ADD COLUMN IF NOT EXISTS cree_par VARCHAR(255),
ADD COLUMN IF NOT EXISTS modifie_par VARCHAR(255);
-- ============================================================================
-- 5. TABLE MODELE_PLAN_COMPTABLE — Template SYSCOHADA (comptes standards réutilisables)
-- ============================================================================
CREATE TABLE IF NOT EXISTS modele_plan_comptable (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_compte VARCHAR(10) NOT NULL UNIQUE,
libelle VARCHAR(200) NOT NULL,
classe_comptable INTEGER NOT NULL CHECK (classe_comptable >= 1 AND classe_comptable <= 9),
type_compte VARCHAR(30) NOT NULL,
description VARCHAR(500),
actif BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT chk_modele_classe CHECK (classe_comptable >= 1 AND classe_comptable <= 9)
);
-- ============================================================================
-- 6. SEEDS — Plan comptable SYSCOHADA standard pour mutuelles/coopératives UEMOA
-- ============================================================================
INSERT INTO modele_plan_comptable (numero_compte, libelle, classe_comptable, type_compte) VALUES
-- CLASSE 1 — Ressources durables
('101000', 'Fonds propres', 1, 'PASSIF'),
('104000', 'Réserve légale', 1, 'PASSIF'),
('106000', 'Réserves statutaires', 1, 'PASSIF'),
('120000', 'Résultat de l''exercice', 1, 'PASSIF'),
('160000', 'Emprunts à long terme', 1, 'PASSIF'),
('165000', 'Dépôts et cautionnements reçus', 1, 'PASSIF'),
-- CLASSE 2 — Actif immobilisé
('222000', 'Matériel de transport', 2, 'ACTIF'),
('232000', 'Matériel informatique', 2, 'ACTIF'),
('244000', 'Logiciels informatiques', 2, 'ACTIF'),
('281000', 'Amortissements immobilisations', 2, 'ACTIF'),
-- CLASSE 4 — Tiers
('411000', 'Membres débiteurs — cotisations dues', 4, 'ACTIF'),
('412000', 'Membres débiteurs — parts sociales dues', 4, 'ACTIF'),
('413000', 'Membres débiteurs — avances sur prestations', 4, 'ACTIF'),
('421000', 'Personnel — rémunérations dues', 4, 'PASSIF'),
('431000', 'Sécurité sociale — cotisations patronales', 4, 'PASSIF'),
('441000', 'État — TVA collectée', 4, 'PASSIF'),
('447000', 'État — autres impôts et taxes', 4, 'PASSIF'),
('467000', 'Tiers divers débiteurs', 4, 'ACTIF'),
('468000', 'Tiers divers créditeurs', 4, 'PASSIF'),
-- CLASSE 5 — Trésorerie
('512100', 'Compte Wave Senegal', 5, 'TRESORERIE'),
('512200', 'Compte Orange Money', 5, 'TRESORERIE'),
('512300', 'Compte MTN MoMo', 5, 'TRESORERIE'),
('512400', 'Compte Moov Money', 5, 'TRESORERIE'),
('512500', 'Compte bancaire principal', 5, 'TRESORERIE'),
('531000', 'Caisse principale', 5, 'TRESORERIE'),
('581000', 'Virements internes de trésorerie', 5, 'TRESORERIE'),
-- CLASSE 6 — Charges
('601000', 'Achats de marchandises', 6, 'CHARGES'),
('611000', 'Transports', 6, 'CHARGES'),
('612000', 'Frais de télécommunications', 6, 'CHARGES'),
('613000', 'Frais d''assurance', 6, 'CHARGES'),
('614000', 'Location matériel', 6, 'CHARGES'),
('616000', 'Frais d''entretien et réparations', 6, 'CHARGES'),
('621000', 'Personnel externe (prestataires)', 6, 'CHARGES'),
('622000', 'Rémunérations du personnel', 6, 'CHARGES'),
('631000', 'Frais financiers — intérêts d''emprunts', 6, 'CHARGES'),
('641000', 'Charges sur prestations mutuelles', 6, 'CHARGES'),
('651000', 'Pertes sur créances irrécouvrables', 6, 'CHARGES'),
-- CLASSE 7 — Produits
('706100', 'Cotisations ordinaires membres', 7, 'PRODUITS'),
('706200', 'Cotisations spéciales / majorées', 7, 'PRODUITS'),
('706300', 'Parts sociales', 7, 'PRODUITS'),
('706400', 'Droits d''adhésion', 7, 'PRODUITS'),
('762000', 'Produits financiers — intérêts épargne', 7, 'PRODUITS'),
('771000', 'Subventions d''exploitation reçues', 7, 'PRODUITS'),
('775000', 'Prestations de services', 7, 'PRODUITS'),
-- CLASSE 8 — Charges et produits exceptionnels / hors activité
('870000', 'Dons reçus', 8, 'PRODUITS'),
('871000', 'Legs et donations', 8, 'PRODUITS'),
('875000', 'Produits exceptionnels d''événements', 8, 'PRODUITS'),
('878000', 'Autres produits hors activité ordinaire', 8, 'PRODUITS'),
('880000', 'Charges exceptionnelles', 8, 'CHARGES'),
-- CLASSE 9 — Engagements / comptabilité analytique
('990000', 'Engagements hors bilan donnés', 9, 'AUTRE'),
('991000', 'Engagements hors bilan reçus', 9, 'AUTRE')
ON CONFLICT (numero_compte) DO NOTHING;
-- ============================================================================
-- 7. TRIGGER — Initialisation automatique du plan comptable à la création d'org
-- ============================================================================
CREATE OR REPLACE FUNCTION init_plan_comptable_organisation()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO comptes_comptables (
id, numero_compte, libelle, classe_comptable, type_compte,
description, organisation_id, solde_initial, solde_actuel,
compte_collectif, compte_analytique, actif,
date_creation, version
)
SELECT
gen_random_uuid(),
m.numero_compte,
m.libelle,
m.classe_comptable,
m.type_compte,
m.description,
NEW.id,
0, 0,
false, false, true,
NOW(), 0
FROM modele_plan_comptable m
WHERE m.actif = true;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger
WHERE tgname = 'trg_init_plan_comptable_org'
AND tgrelid = 'organisations'::regclass
) THEN
CREATE TRIGGER trg_init_plan_comptable_org
AFTER INSERT ON organisations
FOR EACH ROW
EXECUTE FUNCTION init_plan_comptable_organisation();
END IF;
END $$;
-- ============================================================================
-- 8. BACKFILL — Initialiser le plan comptable pour les organisations existantes
-- (qui ont été créées avant ce trigger)
-- ============================================================================
INSERT INTO comptes_comptables (
id, numero_compte, libelle, classe_comptable, type_compte,
description, organisation_id, solde_initial, solde_actuel,
compte_collectif, compte_analytique, actif,
date_creation, version
)
SELECT
gen_random_uuid(),
m.numero_compte,
m.libelle,
m.classe_comptable,
m.type_compte,
m.description,
o.id,
0, 0,
false, false, true,
NOW(), 0
FROM organisations o
CROSS JOIN modele_plan_comptable m
WHERE m.actif = true
AND NOT EXISTS (
SELECT 1 FROM comptes_comptables cc
WHERE cc.organisation_id = o.id
AND cc.numero_compte = m.numero_compte
);
-- ============================================================================
-- 9. JOURNAUX STANDARD par organisation
-- ============================================================================
-- Remplacer la contrainte UNIQUE globale sur `code` par une contrainte composite
-- (organisation_id, code) — plusieurs orgs peuvent avoir un journal ACH/VTE/etc.
DO $$
DECLARE
constraint_name text;
BEGIN
SELECT tc.constraint_name INTO constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu
ON tc.constraint_name = ccu.constraint_name
WHERE tc.table_name = 'journaux_comptables'
AND tc.constraint_type = 'UNIQUE'
AND ccu.column_name = 'code'
AND NOT EXISTS (
SELECT 1 FROM information_schema.constraint_column_usage ccu2
WHERE ccu2.constraint_name = tc.constraint_name
AND ccu2.column_name = 'organisation_id'
);
IF constraint_name IS NOT NULL THEN
EXECUTE 'ALTER TABLE journaux_comptables DROP CONSTRAINT ' || quote_ident(constraint_name);
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uk_journaux_org_code'
) THEN
ALTER TABLE journaux_comptables
ADD CONSTRAINT uk_journaux_org_code UNIQUE (organisation_id, code);
END IF;
END $$;
INSERT INTO journaux_comptables (
id, code, libelle, type_journal, organisation_id,
statut, actif, date_creation, version
)
SELECT
gen_random_uuid(),
jtype.code,
jtype.libelle,
jtype.type_journal,
o.id,
'OUVERT', true, NOW(), 0
FROM organisations o
CROSS JOIN (VALUES
('ACH', 'Journal des achats', 'ACHATS'),
('VTE', 'Journal des ventes / cotisations', 'VENTES'),
('BQ', 'Journal bancaire', 'BANQUE'),
('CAI', 'Journal de caisse', 'CAISSE'),
('OD', 'Journal des opérations diverses', 'OD')
) AS jtype(code, libelle, type_journal)
WHERE NOT EXISTS (
SELECT 1 FROM journaux_comptables jc
WHERE jc.organisation_id = o.id
AND jc.type_journal = jtype.type_journal
);
-- ============================================================================
-- 10. INDEX utiles
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_comptes_org_numero
ON comptes_comptables (organisation_id, numero_compte);
CREATE INDEX IF NOT EXISTS idx_comptes_org_classe
ON comptes_comptables (organisation_id, classe_comptable);
CREATE INDEX IF NOT EXISTS idx_ecritures_org_date
ON ecritures_comptables (organisation_id, date_ecriture);
CREATE INDEX IF NOT EXISTS idx_lignes_compte
ON lignes_ecriture (compte_comptable_id);

View File

@@ -1,14 +0,0 @@
-- ============================================================================
-- V37 — Keycloak 26 Organizations : ajout keycloak_org_id sur organisations
--
-- P0.2 ROADMAP_2026.md — Migration Keycloak 23 → 26 + Organizations natives
-- Stocke l'ID Keycloak Organization correspondant à chaque organisation UnionFlow.
-- Null = organisation pas encore migrée vers Keycloak 26 Organizations.
-- ============================================================================
ALTER TABLE organisations
ADD COLUMN IF NOT EXISTS keycloak_org_id UUID;
CREATE INDEX IF NOT EXISTS idx_organisations_keycloak_org_id
ON organisations (keycloak_org_id)
WHERE keycloak_org_id IS NOT NULL;

View File

@@ -1,64 +0,0 @@
-- ============================================================================
-- V38 — Module KYC/AML : table kyc_dossier
--
-- P1.5 ROADMAP_2026.md — KYC/AML — conformité GIABA/BCEAO LCB-FT
-- Rétention 10 ans (GIABA) gérée par colonne annee_reference + archivage planifié.
-- ============================================================================
CREATE TABLE IF NOT EXISTS kyc_dossier (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identité du membre
membre_id UUID NOT NULL REFERENCES utilisateurs(id),
-- Pièce d'identité
type_piece VARCHAR(30) NOT NULL,
numero_piece VARCHAR(50) NOT NULL,
date_expiration_piece DATE,
-- Fichiers stockés (MinIO/S3 — identifiants opaques)
piece_identite_recto_file_id VARCHAR(500),
piece_identite_verso_file_id VARCHAR(500),
justif_domicile_file_id VARCHAR(500),
-- Évaluation risque LCB-FT
statut VARCHAR(20) NOT NULL DEFAULT 'NON_VERIFIE',
niveau_risque VARCHAR(20) NOT NULL DEFAULT 'FAIBLE',
score_risque INTEGER NOT NULL DEFAULT 0
CHECK (score_risque >= 0 AND score_risque <= 100),
-- PEP (Personne Exposée Politiquement)
est_pep BOOLEAN NOT NULL DEFAULT FALSE,
nationalite VARCHAR(5),
-- Validation
date_verification TIMESTAMP,
validateur_id UUID REFERENCES utilisateurs(id),
notes_validateur VARCHAR(1000),
-- Rétention 10 ans GIABA — partitionnement logique par année
annee_reference INTEGER NOT NULL DEFAULT EXTRACT(YEAR FROM NOW()),
-- BaseEntity
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
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_kyc_annee_reference CHECK (annee_reference >= 2020 AND annee_reference <= 2100)
);
-- Un seul dossier actif par membre (le plus récent est actif, les anciens archivés)
CREATE UNIQUE INDEX IF NOT EXISTS idx_kyc_membre_actif
ON kyc_dossier (membre_id)
WHERE actif = TRUE;
CREATE INDEX IF NOT EXISTS idx_kyc_membre_id ON kyc_dossier (membre_id);
CREATE INDEX IF NOT EXISTS idx_kyc_statut ON kyc_dossier (statut);
CREATE INDEX IF NOT EXISTS idx_kyc_niveau_risque ON kyc_dossier (niveau_risque);
CREATE INDEX IF NOT EXISTS idx_kyc_est_pep ON kyc_dossier (est_pep) WHERE est_pep = TRUE;
CREATE INDEX IF NOT EXISTS idx_kyc_annee ON kyc_dossier (annee_reference);
CREATE INDEX IF NOT EXISTS idx_kyc_date_expiration ON kyc_dossier (date_expiration_piece)
WHERE date_expiration_piece IS NOT NULL;

View File

@@ -1,174 +0,0 @@
-- ============================================================================
-- V39 — PostgreSQL Row-Level Security : isolation multi-tenant
--
-- P1.2 ROADMAP_2026.md — Multi-tenancy RLS sur tables tenant-scoped
--
-- Variables de session :
-- app.current_org_id : UUID de l'organisation active (set par RlsConnectionInitializer)
-- app.is_super_admin : 'true' si SUPER_ADMIN (bypass RLS pour dashboards globaux)
--
-- Notes sécurité :
-- - Ne pas activer FORCE ROW LEVEL SECURITY ici — le user Flyway (owner) bypasse naturellement.
-- - En prod : créer user `unionflow_app` sans BYPASSRLS pour le pool Quarkus.
-- - Le user Flyway (`unionflow_admin` ou `postgres`) doit avoir BYPASSRLS ou être owner.
-- ============================================================================
-- ============================================================================
-- Helper : policy template pour tables avec organisation_id direct
-- ============================================================================
-- TABLE cotisations
ALTER TABLE cotisations ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_cotisations') THEN
CREATE POLICY rls_tenant_cotisations ON cotisations
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE souscriptions_organisation
ALTER TABLE souscriptions_organisation ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_souscriptions') THEN
CREATE POLICY rls_tenant_souscriptions ON souscriptions_organisation
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE evenements
ALTER TABLE evenements ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_evenements') THEN
CREATE POLICY rls_tenant_evenements ON evenements
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE documents
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_documents') THEN
CREATE POLICY rls_tenant_documents ON documents
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE comptes_comptables
ALTER TABLE comptes_comptables ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_comptes_comptables') THEN
CREATE POLICY rls_tenant_comptes_comptables ON comptes_comptables
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE journaux_comptables
ALTER TABLE journaux_comptables ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_journaux_comptables') THEN
CREATE POLICY rls_tenant_journaux_comptables ON journaux_comptables
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE ecritures_comptables
ALTER TABLE ecritures_comptables ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_ecritures_comptables') THEN
CREATE POLICY rls_tenant_ecritures_comptables ON ecritures_comptables
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE kyc_dossier (scoped via membres_organisations JOIN)
-- Note : kyc_dossier n'a pas d'organisation_id direct — scope via membre_id + membres_organisations
ALTER TABLE kyc_dossier ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_kyc_dossier') THEN
CREATE POLICY rls_tenant_kyc_dossier ON kyc_dossier
USING (
EXISTS (
SELECT 1 FROM membres_organisations mo
WHERE mo.utilisateur_id = kyc_dossier.membre_id
AND mo.organisation_id = current_setting('app.current_org_id', true)::uuid
AND mo.actif = true
)
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE membres_organisations (scope par organisation)
ALTER TABLE membres_organisations ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_membres_organisations') THEN
CREATE POLICY rls_tenant_membres_organisations ON membres_organisations
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE budgets
ALTER TABLE budgets ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_budgets') THEN
CREATE POLICY rls_tenant_budgets ON budgets
USING (
organisation_id = current_setting('app.current_org_id', true)::uuid
OR current_setting('app.is_super_admin', true) = 'true'
);
END IF;
END $$;
-- TABLE tontines (si applicable)
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tontines')
AND NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'rls_tenant_tontines') THEN
EXECUTE 'ALTER TABLE tontines ENABLE ROW LEVEL SECURITY';
EXECUTE '
CREATE POLICY rls_tenant_tontines ON tontines
USING (
organisation_id = current_setting(''app.current_org_id'', true)::uuid
OR current_setting(''app.is_super_admin'', true) = ''true''
)';
END IF;
END $$;
-- ============================================================================
-- Rôle PostgreSQL applicatif (prod only — commenté pour ne pas casser dev)
-- À exécuter manuellement en prod avec le bon mot de passe.
-- ============================================================================
-- CREATE ROLE unionflow_app LOGIN PASSWORD '<UNIONFLOW_APP_DB_PASSWORD>';
-- GRANT CONNECT ON DATABASE unionflow TO unionflow_app;
-- GRANT USAGE ON SCHEMA public TO unionflow_app;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO unionflow_app;
-- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO unionflow_app;
-- -- unionflow_app N'A PAS BYPASSRLS — RLS s'applique toujours
--
-- CREATE ROLE unionflow_admin LOGIN PASSWORD '<UNIONFLOW_ADMIN_DB_PASSWORD>' BYPASSRLS;
-- GRANT ALL ON ALL TABLES IN SCHEMA public TO unionflow_admin;
-- GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO unionflow_admin;
-- -- unionflow_admin utilisé par Flyway et SuperAdminCrossTenantService

View File

@@ -1,9 +0,0 @@
-- V40: Ajout du provider de paiement par défaut sur FormuleAbonnement
-- Permet de configurer le provider (WAVE, ORANGE_MONEY, MTN_MOMO, PISPI) par formule
-- NULL = utiliser le provider global configuré dans application.properties
ALTER TABLE formules_abonnement
ADD COLUMN IF NOT EXISTS provider_defaut VARCHAR(20);
COMMENT ON COLUMN formules_abonnement.provider_defaut IS
'Code du provider de paiement par défaut pour cette formule (WAVE, ORANGE_MONEY, MTN_MOMO, PISPI). NULL = provider global.';

View File

@@ -1,12 +0,0 @@
-- V41: Token FCM (Firebase Cloud Messaging) pour les notifications push mobile
-- Nullable : vide si le membre n'a pas installé l'app mobile ou refusé les notifications
-- Table : utilisateurs (entité Membre.java → @Table(name = "utilisateurs"))
ALTER TABLE utilisateurs
ADD COLUMN IF NOT EXISTS fcm_token VARCHAR(500);
COMMENT ON COLUMN utilisateurs.fcm_token IS
'Token FCM pour les notifications push Firebase. NULL si non enregistré.';
CREATE INDEX IF NOT EXISTS idx_utilisateurs_fcm_token
ON utilisateurs (fcm_token) WHERE fcm_token IS NOT NULL;

View File

@@ -1,41 +0,0 @@
-- V42: Créer les rôles PostgreSQL pour l'isolation RLS
-- unionflow_app : rôle applicatif (sans BYPASSRLS) — utilisé en prod par le backend
-- unionflow_admin: rôle administrateur (BYPASSRLS) — utilisé pour les migrations Flyway et les ops DBA
DO $$
BEGIN
-- Rôle applicatif (sans bypass RLS — soumis aux policies)
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'unionflow_app') THEN
CREATE ROLE unionflow_app LOGIN PASSWORD 'CHANGE_ME_APP_PASSWORD';
END IF;
-- Rôle administrateur (bypass RLS — pour Flyway, exports, audits DBA)
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'unionflow_admin') THEN
CREATE ROLE unionflow_admin LOGIN PASSWORD 'CHANGE_ME_ADMIN_PASSWORD' BYPASSRLS;
END IF;
END
$$;
-- Accorder les privilèges sur le schéma public
GRANT USAGE ON SCHEMA public TO unionflow_app, unionflow_admin;
-- unionflow_app : DML uniquement (pas DDL)
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO unionflow_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO unionflow_app;
-- unionflow_admin : tous les droits (DDL inclus pour Flyway)
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO unionflow_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO unionflow_admin;
-- Garantir les droits sur les objets créés ultérieurement (nouvelles tables Flyway)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO unionflow_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO unionflow_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL PRIVILEGES ON TABLES TO unionflow_admin;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL PRIVILEGES ON SEQUENCES TO unionflow_admin;
COMMENT ON ROLE unionflow_app IS 'Rôle applicatif UnionFlow — soumis aux policies RLS tenant isolation';
COMMENT ON ROLE unionflow_admin IS 'Rôle DBA UnionFlow — BYPASSRLS pour Flyway et exports';

View File

@@ -1,74 +1,74 @@
# Migration V6 - Notes techniques
## Colonnes de timestamp en double
La migration V6 contient volontairement des colonnes de timestamp en double pour certaines tables. Ceci n'est PAS une erreur mais un choix de design.
### transaction_approvals
**Colonnes:**
- `created_at` : Timestamp métier utilisé pour la logique d'approbation (calcul d'expiration)
- `date_creation` : Timestamp d'audit BaseEntity (créé automatiquement par JPA)
**Raison:**
L'entité TransactionApproval a besoin d'un timestamp métier (`createdAt`) pour calculer l'expiration (`expiresAt = createdAt + 7 jours`). Ce timestamp ne doit pas être confondu avec `dateCreation` qui est purement pour l'audit.
**Code Java correspondant:**
```java
@Entity
public class TransactionApproval extends BaseEntity {
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
// Logique métier utilisant createdAt
@PrePersist
protected void onCreate() {
super.onCreate();
if (createdAt == null) {
createdAt = LocalDateTime.now();
}
if (expiresAt == null && createdAt != null) {
expiresAt = createdAt.plusDays(7);
}
}
}
```
### budgets
**Colonnes:**
- `created_at_budget` : Timestamp métier de création du budget (distinct de la modification de l'enregistrement)
- `date_creation` : Timestamp d'audit BaseEntity
**Raison:**
Un budget peut être créé à une date, puis modifié plusieurs fois. `created_at_budget` représente la date de création du budget lui-même (logique métier), tandis que `date_creation` représente la première insertion en base (audit).
**Code Java correspondant:**
```java
@Entity
public class Budget extends BaseEntity {
@Column(name = "created_by_id", nullable = false)
private UUID createdById;
@Column(name = "created_at_budget", nullable = false)
private LocalDateTime createdAtBudget;
}
```
## Recommandation future
Pour éviter cette confusion, on pourrait :
1. Renommer `created_at` en `requested_at` (transaction_approvals)
2. Renommer `created_at_budget` en `budget_creation_date` (budgets)
Mais cela nécessiterait une modification des entités Java et une nouvelle migration.
## Colonnes BaseEntity standard
Toutes les tables incluent les colonnes BaseEntity :
- `date_creation` : Date de création de l'enregistrement (auto)
- `date_modification` : Date de dernière modification (auto)
- `cree_par` : Utilisateur créateur
- `modifie_par` : Dernier utilisateur modificateur
- `version` : Numéro de version (optimistic locking)
- `actif` : Flag soft delete
# Migration V6 - Notes techniques
## Colonnes de timestamp en double
La migration V6 contient volontairement des colonnes de timestamp en double pour certaines tables. Ceci n'est PAS une erreur mais un choix de design.
### transaction_approvals
**Colonnes:**
- `created_at` : Timestamp métier utilisé pour la logique d'approbation (calcul d'expiration)
- `date_creation` : Timestamp d'audit BaseEntity (créé automatiquement par JPA)
**Raison:**
L'entité TransactionApproval a besoin d'un timestamp métier (`createdAt`) pour calculer l'expiration (`expiresAt = createdAt + 7 jours`). Ce timestamp ne doit pas être confondu avec `dateCreation` qui est purement pour l'audit.
**Code Java correspondant:**
```java
@Entity
public class TransactionApproval extends BaseEntity {
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
// Logique métier utilisant createdAt
@PrePersist
protected void onCreate() {
super.onCreate();
if (createdAt == null) {
createdAt = LocalDateTime.now();
}
if (expiresAt == null && createdAt != null) {
expiresAt = createdAt.plusDays(7);
}
}
}
```
### budgets
**Colonnes:**
- `created_at_budget` : Timestamp métier de création du budget (distinct de la modification de l'enregistrement)
- `date_creation` : Timestamp d'audit BaseEntity
**Raison:**
Un budget peut être créé à une date, puis modifié plusieurs fois. `created_at_budget` représente la date de création du budget lui-même (logique métier), tandis que `date_creation` représente la première insertion en base (audit).
**Code Java correspondant:**
```java
@Entity
public class Budget extends BaseEntity {
@Column(name = "created_by_id", nullable = false)
private UUID createdById;
@Column(name = "created_at_budget", nullable = false)
private LocalDateTime createdAtBudget;
}
```
## Recommandation future
Pour éviter cette confusion, on pourrait :
1. Renommer `created_at` en `requested_at` (transaction_approvals)
2. Renommer `created_at_budget` en `budget_creation_date` (budgets)
Mais cela nécessiterait une modification des entités Java et une nouvelle migration.
## Colonnes BaseEntity standard
Toutes les tables incluent les colonnes BaseEntity :
- `date_creation` : Date de création de l'enregistrement (auto)
- `date_modification` : Date de dernière modification (auto)
- `cree_par` : Utilisateur créateur
- `modifie_par` : Dernier utilisateur modificateur
- `version` : Numéro de version (optimistic locking)
- `actif` : Flag soft delete

View File

@@ -1,10 +1,10 @@
-- Script d'insertion de données initiales pour UnionFlow
-- Ce fichier est exécuté automatiquement par Hibernate au démarrage
-- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create)
--
-- IMPORTANT: Ce fichier ne doit PAS contenir de données fictives pour la production.
-- Les données doivent être insérées manuellement via l'interface d'administration
-- ou via des scripts de migration Flyway si nécessaire.
--
-- Ce fichier est laissé vide intentionnellement pour éviter l'insertion automatique
-- de données fictives lors du démarrage du serveur.
-- Script d'insertion de données initiales pour UnionFlow
-- Ce fichier est exécuté automatiquement par Hibernate au démarrage
-- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create)
--
-- IMPORTANT: Ce fichier ne doit PAS contenir de données fictives pour la production.
-- Les données doivent être insérées manuellement via l'interface d'administration
-- ou via des scripts de migration Flyway si nécessaire.
--
-- Ce fichier est laissé vide intentionnellement pour éviter l'insertion automatique
-- de données fictives lors du démarrage du serveur.

View File

@@ -1,376 +1,376 @@
{
"realm": "unionflow",
"displayName": "UnionFlow",
"displayNameHtml": "<div class=\"kc-logo-text\"><span>UnionFlow</span></div>",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"registrationEmailAsUsername": true,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true,
"permanentLockout": false,
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 30,
"defaultRoles": [
"offline_access",
"uma_authorization",
"default-roles-unionflow"
],
"requiredCredentials": [
"password"
],
"otpPolicyType": "totp",
"otpPolicyAlgorithm": "HmacSHA1",
"otpPolicyInitialCounter": 0,
"otpPolicyDigits": 6,
"otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30,
"supportedLocales": [
"fr",
"en"
],
"defaultLocale": "fr",
"internationalizationEnabled": true,
"clients": [
{
"clientId": "unionflow-server",
"name": "UnionFlow Server API",
"description": "Client pour l'API serveur UnionFlow",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "unionflow-secret-2025",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080",
"http://localhost:3000"
],
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.force.post.binding": "false",
"saml.multivalued.roles": "false",
"saml.encrypt": "false",
"saml.server.signature": "false",
"saml.server.signature.keyinfo.ext": "false",
"exclude.session.state.from.auth.response": "false",
"saml_force_name_id_format": "false",
"saml.client.signature": "false",
"tls.client.certificate.bound.access.tokens": "false",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"saml.onetimeuse.condition": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "email",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "email",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email",
"jsonType.label": "String"
}
},
{
"name": "given_name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "firstName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "given_name",
"jsonType.label": "String"
}
},
{
"name": "family_name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "lastName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "family_name",
"jsonType.label": "String"
}
},
{
"name": "roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
],
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"serviceAccountsEnabled": true,
"directAccessGrantsEnabled": true
},
{
"clientId": "unionflow-mobile",
"name": "UnionFlow Mobile App",
"description": "Client pour l'application mobile UnionFlow",
"enabled": true,
"publicClient": true,
"redirectUris": [
"unionflow://callback",
"http://localhost:3000/callback"
],
"webOrigins": [
"*"
],
"protocol": "openid-connect",
"attributes": {
"pkce.code.challenge.method": "S256"
},
"fullScopeAllowed": true,
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}
],
"roles": {
"realm": [
{
"name": "ADMIN",
"description": "Administrateur système avec tous les droits",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "PRESIDENT",
"description": "Président de l'union avec droits de gestion complète",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "SECRETAIRE",
"description": "Secrétaire avec droits de gestion des membres et événements",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "TRESORIER",
"description": "Trésorier avec droits de gestion financière",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "GESTIONNAIRE_MEMBRE",
"description": "Gestionnaire des membres avec droits de CRUD sur les membres",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "ORGANISATEUR_EVENEMENT",
"description": "Organisateur d'événements avec droits de gestion des événements",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "MEMBRE",
"description": "Membre standard avec droits de consultation",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
}
]
},
"users": [
{
"username": "admin",
"enabled": true,
"emailVerified": true,
"firstName": "Administrateur",
"lastName": "Système",
"email": "admin@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "admin123",
"temporary": false
}
],
"realmRoles": [
"ADMIN",
"PRESIDENT"
],
"clientRoles": {}
},
{
"username": "president",
"enabled": true,
"emailVerified": true,
"firstName": "Jean",
"lastName": "Dupont",
"email": "president@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "president123",
"temporary": false
}
],
"realmRoles": [
"PRESIDENT",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "secretaire",
"enabled": true,
"emailVerified": true,
"firstName": "Marie",
"lastName": "Martin",
"email": "secretaire@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "secretaire123",
"temporary": false
}
],
"realmRoles": [
"SECRETAIRE",
"GESTIONNAIRE_MEMBRE",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "tresorier",
"enabled": true,
"emailVerified": true,
"firstName": "Pierre",
"lastName": "Durand",
"email": "tresorier@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "tresorier123",
"temporary": false
}
],
"realmRoles": [
"TRESORIER",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "membre1",
"enabled": true,
"emailVerified": true,
"firstName": "Sophie",
"lastName": "Bernard",
"email": "membre1@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "membre123",
"temporary": false
}
],
"realmRoles": [
"MEMBRE"
],
"clientRoles": {}
}
],
"groups": [
{
"name": "Administration",
"path": "/Administration",
"realmRoles": [
"ADMIN"
],
"subGroups": []
},
{
"name": "Bureau",
"path": "/Bureau",
"realmRoles": [
"PRESIDENT",
"SECRETAIRE",
"TRESORIER"
],
"subGroups": []
},
{
"name": "Gestionnaires",
"path": "/Gestionnaires",
"realmRoles": [
"GESTIONNAIRE_MEMBRE",
"ORGANISATEUR_EVENEMENT"
],
"subGroups": []
},
{
"name": "Membres",
"path": "/Membres",
"realmRoles": [
"MEMBRE"
],
"subGroups": []
}
]
{
"realm": "unionflow",
"displayName": "UnionFlow",
"displayNameHtml": "<div class=\"kc-logo-text\"><span>UnionFlow</span></div>",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"registrationEmailAsUsername": true,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true,
"permanentLockout": false,
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 30,
"defaultRoles": [
"offline_access",
"uma_authorization",
"default-roles-unionflow"
],
"requiredCredentials": [
"password"
],
"otpPolicyType": "totp",
"otpPolicyAlgorithm": "HmacSHA1",
"otpPolicyInitialCounter": 0,
"otpPolicyDigits": 6,
"otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30,
"supportedLocales": [
"fr",
"en"
],
"defaultLocale": "fr",
"internationalizationEnabled": true,
"clients": [
{
"clientId": "unionflow-server",
"name": "UnionFlow Server API",
"description": "Client pour l'API serveur UnionFlow",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "unionflow-secret-2025",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080",
"http://localhost:3000"
],
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.force.post.binding": "false",
"saml.multivalued.roles": "false",
"saml.encrypt": "false",
"saml.server.signature": "false",
"saml.server.signature.keyinfo.ext": "false",
"exclude.session.state.from.auth.response": "false",
"saml_force_name_id_format": "false",
"saml.client.signature": "false",
"tls.client.certificate.bound.access.tokens": "false",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"saml.onetimeuse.condition": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "email",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "email",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email",
"jsonType.label": "String"
}
},
{
"name": "given_name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "firstName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "given_name",
"jsonType.label": "String"
}
},
{
"name": "family_name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "lastName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "family_name",
"jsonType.label": "String"
}
},
{
"name": "roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
],
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"serviceAccountsEnabled": true,
"directAccessGrantsEnabled": true
},
{
"clientId": "unionflow-mobile",
"name": "UnionFlow Mobile App",
"description": "Client pour l'application mobile UnionFlow",
"enabled": true,
"publicClient": true,
"redirectUris": [
"unionflow://callback",
"http://localhost:3000/callback"
],
"webOrigins": [
"*"
],
"protocol": "openid-connect",
"attributes": {
"pkce.code.challenge.method": "S256"
},
"fullScopeAllowed": true,
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}
],
"roles": {
"realm": [
{
"name": "ADMIN",
"description": "Administrateur système avec tous les droits",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "PRESIDENT",
"description": "Président de l'union avec droits de gestion complète",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "SECRETAIRE",
"description": "Secrétaire avec droits de gestion des membres et événements",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "TRESORIER",
"description": "Trésorier avec droits de gestion financière",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "GESTIONNAIRE_MEMBRE",
"description": "Gestionnaire des membres avec droits de CRUD sur les membres",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "ORGANISATEUR_EVENEMENT",
"description": "Organisateur d'événements avec droits de gestion des événements",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
},
{
"name": "MEMBRE",
"description": "Membre standard avec droits de consultation",
"composite": false,
"clientRole": false,
"containerId": "unionflow"
}
]
},
"users": [
{
"username": "admin",
"enabled": true,
"emailVerified": true,
"firstName": "Administrateur",
"lastName": "Système",
"email": "admin@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "admin123",
"temporary": false
}
],
"realmRoles": [
"ADMIN",
"PRESIDENT"
],
"clientRoles": {}
},
{
"username": "president",
"enabled": true,
"emailVerified": true,
"firstName": "Jean",
"lastName": "Dupont",
"email": "president@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "president123",
"temporary": false
}
],
"realmRoles": [
"PRESIDENT",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "secretaire",
"enabled": true,
"emailVerified": true,
"firstName": "Marie",
"lastName": "Martin",
"email": "secretaire@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "secretaire123",
"temporary": false
}
],
"realmRoles": [
"SECRETAIRE",
"GESTIONNAIRE_MEMBRE",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "tresorier",
"enabled": true,
"emailVerified": true,
"firstName": "Pierre",
"lastName": "Durand",
"email": "tresorier@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "tresorier123",
"temporary": false
}
],
"realmRoles": [
"TRESORIER",
"MEMBRE"
],
"clientRoles": {}
},
{
"username": "membre1",
"enabled": true,
"emailVerified": true,
"firstName": "Sophie",
"lastName": "Bernard",
"email": "membre1@unionflow.dev",
"credentials": [
{
"type": "password",
"value": "membre123",
"temporary": false
}
],
"realmRoles": [
"MEMBRE"
],
"clientRoles": {}
}
],
"groups": [
{
"name": "Administration",
"path": "/Administration",
"realmRoles": [
"ADMIN"
],
"subGroups": []
},
{
"name": "Bureau",
"path": "/Bureau",
"realmRoles": [
"PRESIDENT",
"SECRETAIRE",
"TRESORIER"
],
"subGroups": []
},
{
"name": "Gestionnaires",
"path": "/Gestionnaires",
"realmRoles": [
"GESTIONNAIRE_MEMBRE",
"ORGANISATEUR_EVENEMENT"
],
"subGroups": []
},
{
"name": "Membres",
"path": "/Membres",
"realmRoles": [
"MEMBRE"
],
"subGroups": []
}
]
}

View File

@@ -1,71 +1,71 @@
# =============================================================
# UnionFlow — Messages externalisés (i18n)
# =============================================================
# Fichier principal (FR). Pour d'autres locales, créer
# messages_en.properties, messages_pt.properties, etc.
# =============================================================
# ── Validation ────────────────────────────────────────────────
validation.champ.obligatoire=Ce champ est obligatoire
validation.email.invalide=Adresse email invalide
validation.telephone.invalide=Numéro de téléphone invalide
validation.montant.positif=Le montant doit être positif
validation.ordre.positif=L'ordre doit être positif (>= 1)
validation.date.future=La date ne peut pas être dans le futur
validation.doublon.email=Une entité avec cet email existe déjà
validation.doublon.nom=Une entité avec ce nom existe déjà
validation.doublon.code=Un élément avec ce code existe déjà
# ── Organisation ──────────────────────────────────────────────
organisation.creation.succes=Organisation créée avec succès
organisation.modification.succes=Organisation mise à jour
organisation.suppression.succes=Organisation supprimée
organisation.suppression.membres.actifs=Impossible de supprimer une organisation avec des membres actifs
organisation.introuvable=Organisation non trouvée avec l''ID: {0}
organisation.email.doublon=Une organisation avec cet email existe déjà
organisation.nom.doublon=Une organisation avec ce nom existe déjà
organisation.numero.doublon=Une organisation avec ce numéro d''enregistrement existe déjà
# ── Membre ────────────────────────────────────────────────────
membre.creation.succes=Membre créé avec succès
membre.modification.succes=Membre mis à jour
membre.introuvable=Membre non trouvé avec l''ID: {0}
membre.email.doublon=Un membre avec cet email existe déjà
membre.numero.doublon=Un membre avec ce numéro existe déjà
# ── Cotisation ────────────────────────────────────────────────
cotisation.creation.succes=Cotisation créée avec succès
cotisation.introuvable=Cotisation non trouvée avec l''ID: {0}
cotisation.paiement.depasse=Le montant payé dépasse le montant dû
# ── Paiement ──────────────────────────────────────────────────
paiement.creation.succes=Paiement enregistré avec succès
paiement.introuvable=Paiement non trouvé avec l''ID: {0}
paiement.rattachement.obligatoire=type_entite_rattachee et entite_rattachee_id sont obligatoires
# ── Document ──────────────────────────────────────────────────
document.creation.succes=Document créé avec succès
document.introuvable=Document non trouvé avec l''ID: {0}
# ── Pièce jointe ─────────────────────────────────────────────
piecejointe.creation.succes=Pièce jointe créée
piecejointe.validation.rattachement=Le type d'entité et l'ID rattaché sont obligatoires
# ── Type référence ────────────────────────────────────────────
typeref.creation.succes=Type de référence créé
typeref.modification.succes=Type de référence mis à jour
typeref.suppression.succes=Type de référence supprimé
typeref.introuvable=Type de référence non trouvé: {0}
typeref.doublon=Un type de référence avec ce domaine/code existe déjà
typeref.systeme.protege=Les valeurs système ne peuvent pas être modifiées
# ── Sécurité ──────────────────────────────────────────────────
securite.non.authentifie=Utilisateur non authentifié
securite.acces.refuse=Accès refusé
securite.token.invalide=Token d''accès invalide
# ── Événement ─────────────────────────────────────────────────
evenement.creation.succes=Événement créé avec succès
evenement.introuvable=Événement non trouvé avec l''ID: {0}
evenement.inscription.fermee=Les inscriptions sont fermées
evenement.capacite.atteinte=Capacité maximale atteinte
# =============================================================
# UnionFlow — Messages externalisés (i18n)
# =============================================================
# Fichier principal (FR). Pour d'autres locales, créer
# messages_en.properties, messages_pt.properties, etc.
# =============================================================
# ── Validation ────────────────────────────────────────────────
validation.champ.obligatoire=Ce champ est obligatoire
validation.email.invalide=Adresse email invalide
validation.telephone.invalide=Numéro de téléphone invalide
validation.montant.positif=Le montant doit être positif
validation.ordre.positif=L'ordre doit être positif (>= 1)
validation.date.future=La date ne peut pas être dans le futur
validation.doublon.email=Une entité avec cet email existe déjà
validation.doublon.nom=Une entité avec ce nom existe déjà
validation.doublon.code=Un élément avec ce code existe déjà
# ── Organisation ──────────────────────────────────────────────
organisation.creation.succes=Organisation créée avec succès
organisation.modification.succes=Organisation mise à jour
organisation.suppression.succes=Organisation supprimée
organisation.suppression.membres.actifs=Impossible de supprimer une organisation avec des membres actifs
organisation.introuvable=Organisation non trouvée avec l''ID: {0}
organisation.email.doublon=Une organisation avec cet email existe déjà
organisation.nom.doublon=Une organisation avec ce nom existe déjà
organisation.numero.doublon=Une organisation avec ce numéro d''enregistrement existe déjà
# ── Membre ────────────────────────────────────────────────────
membre.creation.succes=Membre créé avec succès
membre.modification.succes=Membre mis à jour
membre.introuvable=Membre non trouvé avec l''ID: {0}
membre.email.doublon=Un membre avec cet email existe déjà
membre.numero.doublon=Un membre avec ce numéro existe déjà
# ── Cotisation ────────────────────────────────────────────────
cotisation.creation.succes=Cotisation créée avec succès
cotisation.introuvable=Cotisation non trouvée avec l''ID: {0}
cotisation.paiement.depasse=Le montant payé dépasse le montant dû
# ── Paiement ──────────────────────────────────────────────────
paiement.creation.succes=Paiement enregistré avec succès
paiement.introuvable=Paiement non trouvé avec l''ID: {0}
paiement.rattachement.obligatoire=type_entite_rattachee et entite_rattachee_id sont obligatoires
# ── Document ──────────────────────────────────────────────────
document.creation.succes=Document créé avec succès
document.introuvable=Document non trouvé avec l''ID: {0}
# ── Pièce jointe ─────────────────────────────────────────────
piecejointe.creation.succes=Pièce jointe créée
piecejointe.validation.rattachement=Le type d'entité et l'ID rattaché sont obligatoires
# ── Type référence ────────────────────────────────────────────
typeref.creation.succes=Type de référence créé
typeref.modification.succes=Type de référence mis à jour
typeref.suppression.succes=Type de référence supprimé
typeref.introuvable=Type de référence non trouvé: {0}
typeref.doublon=Un type de référence avec ce domaine/code existe déjà
typeref.systeme.protege=Les valeurs système ne peuvent pas être modifiées
# ── Sécurité ──────────────────────────────────────────────────
securite.non.authentifie=Utilisateur non authentifié
securite.acces.refuse=Accès refusé
securite.token.invalide=Token d''accès invalide
# ── Événement ─────────────────────────────────────────────────
evenement.creation.succes=Événement créé avec succès
evenement.introuvable=Événement non trouvé avec l''ID: {0}
evenement.inscription.fermee=Les inscriptions sont fermées
evenement.capacite.atteinte=Capacité maximale atteinte

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Bienvenue sur UnionFlow</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f6f9; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 30px auto; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.1); }
.header { background: #1A568C; color: #fff; padding: 28px 32px; }
.header h1 { margin: 0; font-size: 22px; }
.body { padding: 28px 32px; color: #333; line-height: 1.6; }
.btn { display: inline-block; margin-top: 20px; padding: 12px 28px; background: #1A568C; color: #fff; text-decoration: none; border-radius: 5px; font-weight: bold; }
.footer { background: #f4f6f9; text-align: center; padding: 16px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎉 Bienvenue sur UnionFlow !</h1>
</div>
<div class="body">
<p>Bonjour <strong>{prenom} {nom}</strong>,</p>
<p>Votre compte a été créé avec succès sur <strong>UnionFlow</strong>, la plateforme de gestion des mutuelles, coopératives et syndicats de Côte d'Ivoire.</p>
<p>Vous faites maintenant partie de l'organisation : <strong>{nomOrganisation}</strong></p>
<p>Votre identifiant de connexion est votre adresse email : <strong>{email}</strong></p>
{#if lienConnexion}
<p>
<a href="{lienConnexion}" class="btn">Accéder à mon espace</a>
</p>
{/if}
<p>En cas de question, contactez votre administrateur ou notre support : <a href="mailto:support@lions.dev">support@lions.dev</a></p>
<p>Cordialement,<br>L'équipe UnionFlow</p>
</div>
<div class="footer">UnionFlow © 2026 — Lions Tech SARL — Abidjan, Côte d'Ivoire</div>
</div>
</body>
</html>

View File

@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Confirmation de cotisation</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f6f9; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 30px auto; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.1); }
.header { background: #1A568C; color: #fff; padding: 28px 32px; }
.header h1 { margin: 0; font-size: 22px; }
.body { padding: 28px 32px; color: #333; line-height: 1.6; }
.receipt { background: #f8faff; border: 1px solid #dce8f8; border-radius: 6px; padding: 18px; margin: 18px 0; }
.receipt table { width: 100%; border-collapse: collapse; }
.receipt td { padding: 7px 0; }
.receipt td:last-child { text-align: right; font-weight: bold; }
.amount { font-size: 24px; font-weight: bold; color: #1A568C; }
.badge-success { display: inline-block; background: #e6f4ea; color: #2e7d32; padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: bold; }
.footer { background: #f4f6f9; text-align: center; padding: 16px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>✅ Cotisation confirmée</h1>
</div>
<div class="body">
<p>Bonjour <strong>{prenom} {nom}</strong>,</p>
<p>Nous avons bien reçu votre cotisation. <span class="badge-success">CONFIRMÉ</span></p>
<div class="receipt">
<table>
<tr><td>Organisation</td><td>{nomOrganisation}</td></tr>
<tr><td>Période</td><td>{periode}</td></tr>
<tr><td>Référence</td><td>{numeroReference}</td></tr>
<tr><td>Mode de paiement</td><td>{methodePaiement}</td></tr>
<tr><td>Date de paiement</td><td>{datePaiement}</td></tr>
<tr><td>Montant</td><td><span class="amount">{montant} XOF</span></td></tr>
</table>
</div>
<p>Conservez cet email comme justificatif de paiement.</p>
<p>Cordialement,<br>L'équipe UnionFlow</p>
</div>
<div class="footer">UnionFlow © 2026 — Lions Tech SARL — Abidjan, Côte d'Ivoire</div>
</div>
</body>
</html>

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Rappel de cotisation</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f6f9; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 30px auto; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.1); }
.header { background: #e65100; color: #fff; padding: 28px 32px; }
.header h1 { margin: 0; font-size: 22px; }
.body { padding: 28px 32px; color: #333; line-height: 1.6; }
.alert { background: #fff3e0; border-left: 4px solid #e65100; padding: 14px 18px; border-radius: 4px; margin: 18px 0; }
.btn { display: inline-block; margin-top: 16px; padding: 12px 28px; background: #e65100; color: #fff; text-decoration: none; border-radius: 5px; font-weight: bold; }
.footer { background: #f4f6f9; text-align: center; padding: 16px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>⚠️ Rappel de cotisation</h1>
</div>
<div class="body">
<p>Bonjour <strong>{prenom} {nom}</strong>,</p>
<div class="alert">
<strong>Votre cotisation pour la période {periode} est en attente de paiement.</strong>
</div>
<p>Organisation : <strong>{nomOrganisation}</strong></p>
<p>Montant dû : <strong>{montant} XOF</strong></p>
<p>Date limite : <strong>{dateLimite}</strong></p>
{#if lienPaiement}
<p>
<a href="{lienPaiement}" class="btn">Payer ma cotisation</a>
</p>
{/if}
<p>Si vous avez déjà effectué ce paiement, veuillez ignorer ce message ou contacter votre trésorier.</p>
<p>Cordialement,<br>L'équipe UnionFlow</p>
</div>
<div class="footer">UnionFlow © 2026 — Lions Tech SARL — Abidjan, Côte d'Ivoire</div>
</div>
</body>
</html>

View File

@@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Souscription confirmée</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f6f9; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 30px auto; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.1); }
.header { background: #1A568C; color: #fff; padding: 28px 32px; }
.header h1 { margin: 0; font-size: 22px; }
.body { padding: 28px 32px; color: #333; line-height: 1.6; }
.plan-card { background: #e8f0fe; border-radius: 8px; padding: 20px; margin: 18px 0; text-align: center; }
.plan-name { font-size: 20px; font-weight: bold; color: #1A568C; }
.plan-price { font-size: 28px; font-weight: bold; color: #1A568C; margin: 8px 0; }
.features { margin: 16px 0; }
.features li { padding: 4px 0; }
.badge { display: inline-block; background: #e6f4ea; color: #2e7d32; padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: bold; }
.footer { background: #f4f6f9; text-align: center; padding: 16px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>✅ Souscription activée</h1>
</div>
<div class="body">
<p>Bonjour <strong>{nomAdministrateur}</strong>,</p>
<p>La souscription de votre organisation <strong>{nomOrganisation}</strong> a été activée avec succès. <span class="badge">ACTIF</span></p>
<div class="plan-card">
<div class="plan-name">Plan {nomFormule}</div>
<div class="plan-price">{montant} XOF / {periodicite}</div>
</div>
<p><strong>Détails de la souscription :</strong></p>
<ul class="features">
<li>Date d'activation : {dateActivation}</li>
<li>Date d'expiration : {dateExpiration}</li>
<li>Membres maximum : {maxMembres}</li>
<li>Stockage : {maxStockageMo} Mo</li>
{#if apiAccess}<li>✓ Accès API REST</li>{/if}
{#if supportPrioritaire}<li>✓ Support prioritaire</li>{/if}
</ul>
<p>Cordialement,<br>L'équipe UnionFlow</p>
</div>
<div class="footer">UnionFlow © 2026 — Lions Tech SARL — Abidjan, Côte d'Ivoire</div>
</div>
</body>
</html>