Sync: code local unifié

Synchronisation du code source local (fait foi).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:25:40 +00:00
parent e82dc356f3
commit 75a19988b0
730 changed files with 53599 additions and 13145 deletions

View File

@@ -4,5 +4,5 @@
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="all">
bean-discovery-mode="annotated">
</beans>

View File

@@ -0,0 +1,49 @@
# ============================================================================
# UnionFlow Server — Profil DEV
# Chargé automatiquement quand le profil "dev" est actif (quarkus:dev)
# Surcharge application.properties — sans préfixes %dev.
# ============================================================================
# Base de données PostgreSQL locale
quarkus.datasource.username=skyfile
quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile}
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow
quarkus.datasource.jdbc.min-size=2
quarkus.datasource.jdbc.max-size=10
# Hibernate — Flyway gère le schéma exclusivement (none = pas de création auto)
quarkus.hibernate-orm.database.generation=none
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
# CORS — permissif en dev (autorise tous les ports localhost pour Flutter Web)
quarkus.http.cors.origins=*
# Keycloak / OIDC local
quarkus.oidc.tenant-enabled=true
quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow
quarkus.oidc.client-id=unionflow-server
quarkus.oidc.token.audience=unionflow-mobile
quarkus.oidc.credentials.secret=unionflow-secret-2025
quarkus.oidc.tls.verification=none
# OpenAPI — serveur dev
quarkus.smallrye-openapi.servers=http://localhost:8085
quarkus.smallrye-openapi.oidc-open-id-connect-url=http://localhost:8180/realms/unionflow/.well-known/openid-configuration
# Swagger UI — activé en dev
quarkus.swagger-ui.always-include=true
# Logging — verbeux en dev
quarkus.log.category."dev.lions.unionflow".level=DEBUG
quarkus.log.category."dev.lions.unionflow.server.service.RoleDebugFilter".level=INFO
quarkus.log.category."org.hibernate.SQL".level=DEBUG
quarkus.log.category."io.quarkus.oidc".level=INFO
quarkus.log.category."io.quarkus.security".level=INFO
# Wave — mock pour dev (pas de clé API requise)
wave.mock.enabled=true
wave.redirect.base.url=http://localhost:8085

View File

@@ -1,56 +0,0 @@
# Configuration UnionFlow Server - Mode Minimal
quarkus.application.name=unionflow-server-minimal
quarkus.application.version=1.0.0
# Configuration HTTP
quarkus.http.port=8080
quarkus.http.host=0.0.0.0
# Configuration CORS
quarkus.http.cors=true
quarkus.http.cors.origins=*
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
# Configuration Base de données H2 (en mémoire)
quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=
quarkus.datasource.jdbc.url=jdbc:h2:mem:unionflow_minimal;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
# Configuration Hibernate
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
# Désactiver Flyway
quarkus.flyway.migrate-at-start=false
# Désactiver Keycloak temporairement
quarkus.oidc.tenant-enabled=false
# Chemins publics (tous publics en mode minimal)
quarkus.http.auth.permission.public.paths=/*
quarkus.http.auth.permission.public.policy=permit
# Configuration OpenAPI
quarkus.smallrye-openapi.info-title=UnionFlow Server API - Minimal
quarkus.smallrye-openapi.info-version=1.0.0
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union (mode minimal)
quarkus.smallrye-openapi.servers=http://localhost:8080
# Configuration Swagger UI
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
# Configuration santé
quarkus.smallrye-health.root-path=/health
# Configuration logging
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=DEBUG
quarkus.log.category."org.hibernate".level=WARN
quarkus.log.category."io.quarkus".level=INFO

View File

@@ -1,77 +1,63 @@
# Configuration UnionFlow Server - PRODUCTION
# Ce fichier est utilisé avec le profil Quarkus "prod"
# ============================================================================
# UnionFlow Server — Profil PROD
# Chargé automatiquement quand le profil "prod" est actif
# Surcharge application.properties — sans préfixes %prod.
# ============================================================================
# Configuration HTTP
quarkus.http.port=8085
quarkus.http.host=0.0.0.0
# Configuration CORS - Production (strict)
quarkus.http.cors=true
quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev}
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
quarkus.http.cors.allow-credentials=true
# Configuration Base de données PostgreSQL - Production
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:unionflow}
# Base de données PostgreSQL — Production (variables d'environnement obligatoires)
quarkus.datasource.username=${DB_USERNAME}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
quarkus.datasource.jdbc.url=${DB_URL}
quarkus.datasource.jdbc.min-size=5
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.acquisition-timeout=5
quarkus.datasource.jdbc.idle-removal-interval=PT2M
quarkus.datasource.jdbc.max-lifetime=PT30M
# Configuration Hibernate - Production (IMPORTANT: update, pas drop-and-create)
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
quarkus.hibernate-orm.metrics.enabled=false
# Hibernate — Validate uniquement (Flyway gère le schéma)
quarkus.hibernate-orm.database.generation=validate
quarkus.hibernate-orm.statistics=false
# Configuration Flyway - Production (ACTIVÉ)
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
quarkus.flyway.baseline-version=1.0.0
# CORS — strict en production
quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev}
quarkus.http.cors.access-control-allow-credentials=true
# Configuration Keycloak OIDC - Production
# WebSocket — public (auth gérée dans le handshake)
quarkus.http.auth.permission.websocket.paths=/ws/*
quarkus.http.auth.permission.websocket.policy=permit
# Keycloak / OIDC — Production
quarkus.oidc.tenant-enabled=true
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow}
quarkus.oidc.client-id=unionflow-server
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.tls.verification=required
quarkus.oidc.application-type=service
# Configuration Keycloak Policy Enforcer
quarkus.keycloak.policy-enforcer.enable=false
quarkus.keycloak.policy-enforcer.lazy-load-paths=true
quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
# Chemins publics (non protégés)
quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico
quarkus.http.auth.permission.public.policy=permit
# Configuration OpenAPI - Production (Swagger désactivé ou protégé)
quarkus.smallrye-openapi.info-title=UnionFlow Server API
quarkus.smallrye-openapi.info-version=1.0.0
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak
# OpenAPI — serveur prod
quarkus.smallrye-openapi.servers=https://api.lions.dev/unionflow
quarkus.smallrye-openapi.oidc-open-id-connect-url=${quarkus.oidc.auth-server-url}/.well-known/openid-configuration
# Configuration Swagger UI - Production (DÉSACTIVÉ pour sécurité)
# Swagger UI — désactivé en production
quarkus.swagger-ui.always-include=false
# Configuration santé
quarkus.smallrye-health.root-path=/health
# Configuration logging - Production
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
quarkus.log.category."org.hibernate".level=WARN
quarkus.log.category."io.quarkus".level=INFO
# Logging — fichier en production
quarkus.log.file.enable=true
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
quarkus.log.category."org.jboss.resteasy".level=WARN
# Configuration Wave Money - Production
wave.api.key=${WAVE_API_KEY:}
wave.api.secret=${WAVE_API_SECRET:}
wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1}
wave.environment=${WAVE_ENVIRONMENT:production}
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}
# REST Client lions-user-manager
quarkus.rest-client.lions-user-manager-api.url=${LIONS_USER_MANAGER_URL:http://lions-user-manager:8081}
# Wave Money — Production
wave.environment=production
# Email — Production
quarkus.mailer.from=${MAIL_FROM:noreply@unionflow.lions.dev}
quarkus.mailer.host=${MAIL_HOST:smtp.lions.dev}
quarkus.mailer.port=${MAIL_PORT:587}
quarkus.mailer.username=${MAIL_USERNAME:}
quarkus.mailer.password=${MAIL_PASSWORD:}
quarkus.mailer.start-tls=REQUIRED
quarkus.mailer.ssl=false

View File

@@ -8,9 +8,9 @@ quarkus.datasource.password=
quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
# Configuration Hibernate pour tests
quarkus.hibernate-orm.database.generation=drop-and-create
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-source=none
quarkus.hibernate-orm.sql-load-script=no-file
# Empêcher Hibernate d'exécuter les scripts SQL automatiquement
# Note: Ne pas définir quarkus.hibernate-orm.sql-load-script car une chaîne vide peut causer des problèmes
@@ -28,4 +28,10 @@ quarkus.keycloak.policy-enforcer.enable=false
quarkus.http.port=0
quarkus.http.test-port=0
# Wave — mock pour tests
wave.mock.enabled=true
wave.api.key=
wave.api.secret=
wave.redirect.base.url=http://localhost:8080

View File

@@ -1,85 +1,74 @@
# Configuration UnionFlow Server
# ============================================================================
# UnionFlow Server — Configuration commune (tous profils)
# Chargée en premier, les fichiers application-{profil}.properties surchargent
# ============================================================================
quarkus.application.name=unionflow-server
quarkus.application.version=1.0.0
# Configuration HTTP
quarkus.http.port=8085
quarkus.http.host=0.0.0.0
quarkus.http.limits.max-body-size=10M
quarkus.http.limits.max-header-size=16K
# Configuration Datasource — db-kind est une propriété build-time (commune à tous profils)
# Les valeurs réelles sont surchargées par application-dev.properties et application-prod.properties
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:unionflow}
quarkus.datasource.password=${DB_PASSWORD:changeme}
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
# Configuration CORS
quarkus.http.cors=true
quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:8086,https://unionflow.lions.dev,https://security.lions.dev}
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
# Configuration Base de données PostgreSQL (par défaut)
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:unionflow}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
quarkus.datasource.jdbc.min-size=2
quarkus.datasource.jdbc.max-size=10
# Chemins publics
quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico,/auth/callback,/auth/*
quarkus.http.auth.permission.public.policy=permit
# Configuration Base de données PostgreSQL pour développement
%dev.quarkus.datasource.username=skyfile
%dev.quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile}
%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow
# Configuration Hibernate
quarkus.hibernate-orm.database.generation=update
# Configuration Hibernate — base commune
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
# Désactiver l'avertissement PanacheEntity (nous utilisons BaseEntity personnalisé)
quarkus.hibernate-orm.metrics.enabled=false
# Configuration Hibernate pour développement
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev.quarkus.hibernate-orm.sql-load-script=import.sql
%dev.quarkus.hibernate-orm.log.sql=true
# Configuration Flyway pour migrations
# Configuration Flyway — base commune
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
quarkus.flyway.baseline-version=1.0.0
quarkus.flyway.baseline-version=0
# Configuration Flyway pour développement (désactivé)
%dev.quarkus.flyway.migrate-at-start=false
# Configuration Keycloak OIDC (par défaut)
quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow
quarkus.oidc.client-id=unionflow-server
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.tls.verification=none
# Configuration Keycloak OIDC — base commune
quarkus.oidc.application-type=service
quarkus.oidc.roles.role-claim-path=realm_access/roles
# Configuration Keycloak pour développement
%dev.quarkus.oidc.tenant-enabled=false
%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow
# Configuration Keycloak Policy Enforcer (temporairement désactivé)
# Keycloak Policy Enforcer (PERMISSIVE — sécurité gérée par @RolesAllowed)
quarkus.keycloak.policy-enforcer.enable=false
quarkus.keycloak.policy-enforcer.lazy-load-paths=true
quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
# Chemins publics (non protégés)
quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico,/auth/callback,/auth/*
quarkus.http.auth.permission.public.policy=permit
# Configuration OpenAPI
quarkus.smallrye-openapi.info-title=UnionFlow Server API
quarkus.smallrye-openapi.info-version=1.0.0
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak
quarkus.smallrye-openapi.servers=http://localhost:8085
quarkus.smallrye-openapi.security-scheme=oidc
quarkus.smallrye-openapi.security-scheme-name=Keycloak
quarkus.smallrye-openapi.security-scheme-description=Authentification Bearer JWT via Keycloak
# Configuration Swagger UI
# Swagger UI
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
quarkus.swagger-ui.doc-expansion=list
quarkus.swagger-ui.filter=true
quarkus.swagger-ui.deep-linking=true
quarkus.swagger-ui.operations-sorter=alpha
quarkus.swagger-ui.tags-sorter=alpha
# Configuration santé
# Health
quarkus.smallrye-health.root-path=/health
# Configuration logging
# Logging — base commune
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
@@ -87,17 +76,91 @@ quarkus.log.category."dev.lions.unionflow".level=INFO
quarkus.log.category."org.hibernate".level=WARN
quarkus.log.category."io.quarkus".level=INFO
# Configuration logging pour développement
%dev.quarkus.log.category."dev.lions.unionflow".level=DEBUG
%dev.quarkus.log.category."org.hibernate.SQL".level=DEBUG
# Arc / MapStruct
quarkus.arc.remove-unused-beans=false
quarkus.arc.unremovable-types=dev.lions.unionflow.server.mapper.**
# Configuration Jandex pour résoudre les warnings de réflexion
# Jandex
quarkus.index-dependency.unionflow-server-api.group-id=dev.lions.unionflow
quarkus.index-dependency.unionflow-server-api.artifact-id=unionflow-server-api
# Configuration Wave Money
wave.api.key=${WAVE_API_KEY:}
wave.api.secret=${WAVE_API_SECRET:}
# REST Client lions-user-manager
quarkus.rest-client.lions-user-manager-api.url=${LIONS_USER_MANAGER_URL:http://localhost:8081}
# Wave Money — Checkout API (https://docs.wave.com/checkout)
# Test : WAVE_API_KEY vide ou absent + wave.mock.enabled=true pour mocker Wave
wave.api.key=${WAVE_API_KEY: }
wave.api.secret=${WAVE_API_SECRET: }
wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1}
wave.environment=${WAVE_ENVIRONMENT:sandbox}
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}
wave.webhook.secret=${WAVE_WEBHOOK_SECRET: }
# URLs de redirection (https en prod). Défaut dev: http://localhost:8080
wave.redirect.base.url=${WAVE_REDIRECT_BASE_URL:http://localhost:8080}
# Mock Wave (tests) : true = pas d'appel API, validation simulée. Si api.key vide, mock auto.
wave.mock.enabled=${WAVE_MOCK_ENABLED:false}
# Schéma deep link pour le retour vers l'app mobile (ex: unionflow)
wave.deep.link.scheme=${WAVE_DEEP_LINK_SCHEME:unionflow}
# ============================================================================
# Kafka Event Streaming Configuration
# ============================================================================
# Kafka Bootstrap Servers
kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
# Producer Channels (Outgoing)
mp.messaging.outgoing.finance-approvals-out.connector=smallrye-kafka
mp.messaging.outgoing.finance-approvals-out.topic=unionflow.finance.approvals
mp.messaging.outgoing.finance-approvals-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.finance-approvals-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.dashboard-stats-out.connector=smallrye-kafka
mp.messaging.outgoing.dashboard-stats-out.topic=unionflow.dashboard.stats
mp.messaging.outgoing.dashboard-stats-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.dashboard-stats-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.notifications-out.connector=smallrye-kafka
mp.messaging.outgoing.notifications-out.topic=unionflow.notifications.user
mp.messaging.outgoing.notifications-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.notifications-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.members-events-out.connector=smallrye-kafka
mp.messaging.outgoing.members-events-out.topic=unionflow.members.events
mp.messaging.outgoing.members-events-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.members-events-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.contributions-events-out.connector=smallrye-kafka
mp.messaging.outgoing.contributions-events-out.topic=unionflow.contributions.events
mp.messaging.outgoing.contributions-events-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.contributions-events-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
# Consumer Channels (Incoming)
mp.messaging.incoming.finance-approvals-in.connector=smallrye-kafka
mp.messaging.incoming.finance-approvals-in.topic=unionflow.finance.approvals
mp.messaging.incoming.finance-approvals-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.finance-approvals-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.finance-approvals-in.group.id=unionflow-websocket-server
mp.messaging.incoming.dashboard-stats-in.connector=smallrye-kafka
mp.messaging.incoming.dashboard-stats-in.topic=unionflow.dashboard.stats
mp.messaging.incoming.dashboard-stats-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.dashboard-stats-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.dashboard-stats-in.group.id=unionflow-websocket-server
mp.messaging.incoming.notifications-in.connector=smallrye-kafka
mp.messaging.incoming.notifications-in.topic=unionflow.notifications.user
mp.messaging.incoming.notifications-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.notifications-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.notifications-in.group.id=unionflow-websocket-server
mp.messaging.incoming.members-events-in.connector=smallrye-kafka
mp.messaging.incoming.members-events-in.topic=unionflow.members.events
mp.messaging.incoming.members-events-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.members-events-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.members-events-in.group.id=unionflow-websocket-server
mp.messaging.incoming.contributions-events-in.connector=smallrye-kafka
mp.messaging.incoming.contributions-events-in.topic=unionflow.contributions.events
mp.messaging.incoming.contributions-events-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.contributions-events-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.contributions-events-in.group.id=unionflow-websocket-server

View File

@@ -0,0 +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)';

View File

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

View File

@@ -0,0 +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

View File

@@ -0,0 +1,725 @@
-- ============================================================================
-- V2.0 : Création de toutes les tables manquantes pour UnionFlow
-- Toutes les tables héritent de BaseEntity (id UUID PK, date_creation,
-- date_modification, cree_par, modifie_par, version, actif)
-- ============================================================================
-- Colonnes communes BaseEntity (à inclure dans chaque table)
-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
-- date_modification TIMESTAMP,
-- cree_par VARCHAR(255),
-- modifie_par VARCHAR(255),
-- version BIGINT DEFAULT 0,
-- actif BOOLEAN NOT NULL DEFAULT TRUE
-- ============================================================================
-- 1. TABLES PRINCIPALES (sans FK vers d'autres tables métier)
-- ============================================================================
-- Table membres (principale, référencée par beaucoup d'autres)
CREATE TABLE IF NOT EXISTS membres (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
email VARCHAR(255),
telephone VARCHAR(30),
numero_membre VARCHAR(50),
date_naissance DATE,
lieu_naissance VARCHAR(255),
sexe VARCHAR(10),
nationalite VARCHAR(100),
profession VARCHAR(255),
photo_url VARCHAR(500),
statut VARCHAR(30) DEFAULT 'ACTIF',
date_adhesion DATE,
keycloak_user_id VARCHAR(255),
keycloak_realm VARCHAR(255),
organisation_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- Table organisations (déjà créée en V1.2, mais IF NOT EXISTS pour sécurité)
CREATE TABLE IF NOT EXISTS organisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(255) NOT NULL,
sigle VARCHAR(50),
description TEXT,
type_organisation VARCHAR(50),
statut VARCHAR(30) DEFAULT 'ACTIVE',
email VARCHAR(255),
telephone VARCHAR(30),
site_web VARCHAR(500),
adresse_siege TEXT,
logo_url VARCHAR(500),
date_fondation DATE,
pays VARCHAR(100),
ville VARCHAR(100),
organisation_parente_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 2. TABLES SÉCURITÉ (Rôles et Permissions)
-- ============================================================================
CREATE TABLE IF NOT EXISTS roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL UNIQUE,
description VARCHAR(500),
code VARCHAR(50) NOT NULL UNIQUE,
niveau INTEGER DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(100) NOT NULL UNIQUE,
description VARCHAR(500),
code VARCHAR(100) NOT NULL UNIQUE,
module VARCHAR(100),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS roles_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES roles(id),
permission_id UUID NOT NULL REFERENCES permissions(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS membres_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
membre_id UUID NOT NULL REFERENCES membres(id),
role_id UUID NOT NULL REFERENCES roles(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(membre_id, role_id, organisation_id)
);
-- ============================================================================
-- 3. TABLES FINANCE
-- ============================================================================
CREATE TABLE IF NOT EXISTS adhesions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_adhesion VARCHAR(50),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_demande TIMESTAMP,
date_approbation TIMESTAMP,
date_rejet TIMESTAMP,
motif_rejet TEXT,
frais_adhesion DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
montant_paye DECIMAL(15,2) DEFAULT 0,
approuve_par VARCHAR(255),
commentaire TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_reference VARCHAR(50),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
type_cotisation VARCHAR(50),
periode VARCHAR(50),
montant_du DECIMAL(15,2),
montant_paye DECIMAL(15,2) DEFAULT 0,
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_echeance DATE,
date_paiement TIMESTAMP,
methode_paiement VARCHAR(50),
reference_paiement VARCHAR(100),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reference VARCHAR(100),
montant DECIMAL(15,2) NOT NULL,
devise VARCHAR(10) DEFAULT 'XOF',
methode_paiement VARCHAR(50),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
type_paiement VARCHAR(50),
description TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_paiement TIMESTAMP,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_adhesions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
adhesion_id UUID REFERENCES adhesions(id),
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_cotisations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cotisation_id UUID REFERENCES cotisations(id),
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
evenement_id UUID,
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS paiements_aides (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
demande_aide_id UUID,
paiement_id UUID REFERENCES paiements(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 4. TABLES COMPTABILITÉ
-- ============================================================================
CREATE TABLE IF NOT EXISTS comptes_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_compte VARCHAR(20) NOT NULL,
libelle VARCHAR(255) NOT NULL,
type_compte VARCHAR(50),
solde DECIMAL(15,2) DEFAULT 0,
description TEXT,
compte_parent_id UUID,
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS journaux_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(20) NOT NULL,
libelle VARCHAR(255) NOT NULL,
type_journal VARCHAR(50),
description TEXT,
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS ecritures_comptables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_piece VARCHAR(50),
date_ecriture DATE NOT NULL,
libelle VARCHAR(500),
montant_total DECIMAL(15,2),
statut VARCHAR(30) DEFAULT 'BROUILLON',
journal_id UUID REFERENCES journaux_comptables(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS lignes_ecriture (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ecriture_id UUID NOT NULL REFERENCES ecritures_comptables(id),
compte_id UUID NOT NULL REFERENCES comptes_comptables(id),
libelle VARCHAR(500),
montant_debit DECIMAL(15,2) DEFAULT 0,
montant_credit DECIMAL(15,2) DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 5. TABLES ÉVÉNEMENTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS evenements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(255) NOT NULL,
description TEXT,
type_evenement VARCHAR(50),
statut VARCHAR(30) DEFAULT 'PLANIFIE',
priorite VARCHAR(20) DEFAULT 'NORMALE',
date_debut TIMESTAMP,
date_fin TIMESTAMP,
lieu VARCHAR(500),
capacite_max INTEGER,
prix DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
organisateur_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS inscriptions_evenement (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
evenement_id UUID NOT NULL REFERENCES evenements(id),
membre_id UUID NOT NULL REFERENCES membres(id),
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
date_inscription TIMESTAMP DEFAULT NOW(),
commentaire TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(evenement_id, membre_id)
);
-- ============================================================================
-- 6. TABLES SOLIDARITÉ
-- ============================================================================
CREATE TABLE IF NOT EXISTS demandes_aide (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_demande VARCHAR(50),
type_aide VARCHAR(50),
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(50) DEFAULT 'BROUILLON',
titre VARCHAR(255),
description TEXT,
montant_demande DECIMAL(15,2),
montant_approuve DECIMAL(15,2),
devise VARCHAR(10) DEFAULT 'XOF',
justification TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 7. TABLES DOCUMENTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom VARCHAR(255) NOT NULL,
description TEXT,
type_document VARCHAR(50),
chemin_fichier VARCHAR(1000),
taille_fichier BIGINT,
type_mime VARCHAR(100),
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS pieces_jointes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nom_fichier VARCHAR(255) NOT NULL,
chemin_fichier VARCHAR(1000),
type_mime VARCHAR(100),
taille BIGINT,
entite_type VARCHAR(100),
entite_id UUID,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 8. TABLES NOTIFICATIONS
-- ============================================================================
CREATE TABLE IF NOT EXISTS templates_notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(100) NOT NULL UNIQUE,
sujet VARCHAR(500),
corps_texte TEXT,
corps_html TEXT,
variables_disponibles TEXT,
canaux_supportes VARCHAR(500),
langue VARCHAR(10) DEFAULT 'fr',
description TEXT,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_notification VARCHAR(30) NOT NULL,
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
sujet VARCHAR(500),
corps TEXT,
date_envoi_prevue TIMESTAMP,
date_envoi TIMESTAMP,
date_lecture TIMESTAMP,
nombre_tentatives INTEGER DEFAULT 0,
message_erreur VARCHAR(1000),
donnees_additionnelles TEXT,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
template_id UUID REFERENCES templates_notifications(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 9. TABLES ADRESSES
-- ============================================================================
CREATE TABLE IF NOT EXISTS adresses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_adresse VARCHAR(30),
rue VARCHAR(500),
complement VARCHAR(500),
code_postal VARCHAR(20),
ville VARCHAR(100),
region VARCHAR(100),
pays VARCHAR(100) DEFAULT 'Côte d''Ivoire',
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION,
principale BOOLEAN DEFAULT FALSE,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 10. TABLES AUDIT
-- ============================================================================
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
action VARCHAR(100) NOT NULL,
entite_type VARCHAR(100),
entite_id VARCHAR(100),
utilisateur VARCHAR(255),
details TEXT,
adresse_ip VARCHAR(50),
date_heure TIMESTAMP NOT NULL DEFAULT NOW(),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 11. TABLES WAVE MONEY
-- ============================================================================
CREATE TABLE IF NOT EXISTS comptes_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_telephone VARCHAR(30) NOT NULL,
nom_titulaire VARCHAR(255),
statut VARCHAR(30) DEFAULT 'ACTIF',
solde DECIMAL(15,2) DEFAULT 0,
devise VARCHAR(10) DEFAULT 'XOF',
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS configurations_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cle_api VARCHAR(500),
secret_api VARCHAR(500),
environnement VARCHAR(30) DEFAULT 'sandbox',
url_webhook VARCHAR(500),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS transactions_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reference_wave VARCHAR(100),
reference_interne VARCHAR(100),
type_transaction VARCHAR(50),
montant DECIMAL(15,2) NOT NULL,
devise VARCHAR(10) DEFAULT 'XOF',
statut VARCHAR(30) DEFAULT 'EN_ATTENTE',
numero_expediteur VARCHAR(30),
numero_destinataire VARCHAR(30),
description TEXT,
erreur TEXT,
compte_wave_id UUID REFERENCES comptes_wave(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS webhooks_wave (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_evenement VARCHAR(100),
statut VARCHAR(30) DEFAULT 'RECU',
payload TEXT,
signature VARCHAR(500),
traite BOOLEAN DEFAULT FALSE,
erreur TEXT,
transaction_id UUID REFERENCES transactions_wave(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 12. TABLES SUPPORT (tickets, suggestions, favoris, config - déjà en V1.4)
-- ============================================================================
CREATE TABLE IF NOT EXISTS tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
numero_ticket VARCHAR(50),
sujet VARCHAR(255) NOT NULL,
description TEXT,
categorie VARCHAR(50),
priorite VARCHAR(20) DEFAULT 'NORMALE',
statut VARCHAR(30) DEFAULT 'OUVERT',
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
assigne_a VARCHAR(255),
date_resolution TIMESTAMP,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS suggestions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titre VARCHAR(255) NOT NULL,
description TEXT,
categorie VARCHAR(50),
statut VARCHAR(30) DEFAULT 'NOUVELLE',
votes_pour INTEGER DEFAULT 0,
votes_contre INTEGER DEFAULT 0,
membre_id UUID REFERENCES membres(id),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS suggestion_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
suggestion_id UUID NOT NULL REFERENCES suggestions(id),
membre_id UUID NOT NULL REFERENCES membres(id),
type_vote VARCHAR(20) NOT NULL,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE(suggestion_id, membre_id)
);
CREATE TABLE IF NOT EXISTS favoris (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_entite VARCHAR(100) NOT NULL,
entite_id UUID NOT NULL,
membre_id UUID NOT NULL REFERENCES membres(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS configurations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cle VARCHAR(255) NOT NULL UNIQUE,
valeur TEXT,
description TEXT,
categorie VARCHAR(100),
organisation_id UUID REFERENCES organisations(id),
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 13. TABLE TYPES ORGANISATION
-- ============================================================================
CREATE TABLE IF NOT EXISTS uf_type_organisation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) NOT NULL UNIQUE,
libelle VARCHAR(255) NOT NULL,
description TEXT,
icone VARCHAR(100),
couleur VARCHAR(20),
ordre INTEGER DEFAULT 0,
date_creation TIMESTAMP NOT NULL DEFAULT NOW(),
date_modification TIMESTAMP,
cree_par VARCHAR(255),
modifie_par VARCHAR(255),
version BIGINT DEFAULT 0,
actif BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================================================
-- 14. INDEX POUR PERFORMANCES
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_membres_email ON membres(email);
CREATE INDEX IF NOT EXISTS idx_membres_numero ON membres(numero_membre);
CREATE INDEX IF NOT EXISTS idx_membres_organisation ON membres(organisation_id);
CREATE INDEX IF NOT EXISTS idx_membres_keycloak ON membres(keycloak_user_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_membre ON adhesions(membre_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_organisation ON adhesions(organisation_id);
CREATE INDEX IF NOT EXISTS idx_adhesions_statut ON adhesions(statut);
CREATE INDEX IF NOT EXISTS idx_cotisations_membre ON cotisations(membre_id);
CREATE INDEX IF NOT EXISTS idx_cotisations_statut ON cotisations(statut);
CREATE INDEX IF NOT EXISTS idx_cotisations_echeance ON cotisations(date_echeance);
CREATE INDEX IF NOT EXISTS idx_evenements_statut ON evenements(statut);
CREATE INDEX IF NOT EXISTS idx_evenements_organisation ON evenements(organisation_id);
CREATE INDEX IF NOT EXISTS idx_evenements_date_debut ON evenements(date_debut);
CREATE INDEX IF NOT EXISTS idx_notification_membre ON notifications(membre_id);
CREATE INDEX IF NOT EXISTS idx_notification_statut ON notifications(statut);
CREATE INDEX IF NOT EXISTS idx_notification_type ON notifications(type_notification);
CREATE INDEX IF NOT EXISTS idx_audit_date_heure ON audit_logs(date_heure);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_utilisateur ON audit_logs(utilisateur);
CREATE INDEX IF NOT EXISTS idx_paiements_membre ON paiements(membre_id);
CREATE INDEX IF NOT EXISTS idx_paiements_statut ON paiements(statut);
CREATE INDEX IF NOT EXISTS idx_demandes_aide_demandeur ON demandes_aide(demandeur_id);
CREATE INDEX IF NOT EXISTS idx_demandes_aide_statut ON demandes_aide(statut);

View File

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

View File

@@ -0,0 +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)';

View File

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

View File

@@ -0,0 +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.';

View File

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

View File

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

View File

@@ -0,0 +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é';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +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.

View File

@@ -0,0 +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;

View File

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

View File

@@ -0,0 +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');

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +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/`.)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,690 @@
-- =============================================================================
-- V2 — Alignement schéma / entités JPA
-- =============================================================================
-- Ce script aligne les tables existantes (créées par V1) avec les entités
-- JPA du projet. Toutes les instructions sont idempotentes (IF NOT EXISTS,
-- ADD COLUMN IF NOT EXISTS). À exécuter après V1 sur toute base.
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. ADRESSES
-- -----------------------------------------------------------------------------
ALTER TABLE adresses ADD COLUMN IF NOT EXISTS type_adresse VARCHAR(50);
ALTER TABLE adresses ALTER COLUMN type_adresse TYPE VARCHAR(50) USING type_adresse::varchar(50);
-- -----------------------------------------------------------------------------
-- 2. AUDIT_LOGS (complément si pas déjà fait dans V1)
-- -----------------------------------------------------------------------------
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS description VARCHAR(500);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS donnees_avant TEXT;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS donnees_apres TEXT;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS ip_address VARCHAR(45);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS module VARCHAR(50);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS role VARCHAR(50);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS session_id VARCHAR(255);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS severite VARCHAR(20) DEFAULT 'INFO';
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS type_action VARCHAR(50) DEFAULT 'AUTRE';
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS user_agent VARCHAR(500);
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS organisation_id UUID REFERENCES organisations(id) ON DELETE SET NULL;
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS portee VARCHAR(15) NOT NULL DEFAULT 'PLATEFORME';
DO $$ BEGIN ALTER TABLE audit_logs ALTER COLUMN entite_id TYPE VARCHAR(255) USING entite_id::varchar(255); EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_audit_module ON audit_logs(module);
CREATE INDEX IF NOT EXISTS idx_audit_type_action ON audit_logs(type_action);
CREATE INDEX IF NOT EXISTS idx_audit_severite ON audit_logs(severite);
-- -----------------------------------------------------------------------------
-- 3. AYANTS_DROIT
-- -----------------------------------------------------------------------------
ALTER TABLE ayants_droit ADD COLUMN IF NOT EXISTS piece_identite VARCHAR(100);
ALTER TABLE ayants_droit ADD COLUMN IF NOT EXISTS pourcentage_couverture NUMERIC(5,2);
ALTER TABLE ayants_droit ADD COLUMN IF NOT EXISTS sexe VARCHAR(20);
ALTER TABLE ayants_droit ADD COLUMN IF NOT EXISTS statut VARCHAR(50) DEFAULT 'EN_ATTENTE';
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_ayant_droit_statut' AND conrelid = 'ayants_droit'::regclass) THEN
ALTER TABLE ayants_droit ADD CONSTRAINT chk_ayant_droit_statut CHECK (statut IN ('EN_ATTENTE','ACTIF','INACTIF','REJETE','DECEDE','MAJORITE_ATTEINTE'));
END IF;
EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 4. COMPTES_COMPTABLES
-- -----------------------------------------------------------------------------
ALTER TABLE comptes_comptables ADD COLUMN IF NOT EXISTS classe_comptable INTEGER DEFAULT 0;
ALTER TABLE comptes_comptables ADD COLUMN IF NOT EXISTS compte_analytique BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE comptes_comptables ADD COLUMN IF NOT EXISTS compte_collectif BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE comptes_comptables ADD COLUMN IF NOT EXISTS solde_actuel NUMERIC(14,2);
ALTER TABLE comptes_comptables ADD COLUMN IF NOT EXISTS solde_initial NUMERIC(14,2);
DO $$ BEGIN ALTER TABLE comptes_comptables ALTER COLUMN description TYPE VARCHAR(500) USING description::varchar(500); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE comptes_comptables ALTER COLUMN libelle TYPE VARCHAR(200) USING libelle::varchar(200); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE comptes_comptables ALTER COLUMN numero_compte TYPE VARCHAR(10) USING numero_compte::varchar(10); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE comptes_comptables ALTER COLUMN type_compte TYPE VARCHAR(30) USING type_compte::varchar(30); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 5. COMPTES_WAVE
-- -----------------------------------------------------------------------------
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS commentaire VARCHAR(500);
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS date_derniere_verification TIMESTAMP;
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS environnement VARCHAR(20);
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS statut_compte VARCHAR(30) NOT NULL DEFAULT 'NON_VERIFIE';
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS wave_account_id VARCHAR(255);
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS wave_api_key VARCHAR(500);
ALTER TABLE comptes_wave ADD COLUMN IF NOT EXISTS membre_id UUID REFERENCES utilisateurs(id) ON DELETE SET NULL;
DO $$ BEGIN ALTER TABLE comptes_wave ALTER COLUMN numero_telephone TYPE VARCHAR(13) USING numero_telephone::varchar(13); EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_compte_wave_statut ON comptes_wave(statut_compte);
CREATE INDEX IF NOT EXISTS idx_compte_wave_membre ON comptes_wave(membre_id);
-- -----------------------------------------------------------------------------
-- 6. CONFIGURATIONS_WAVE
-- -----------------------------------------------------------------------------
ALTER TABLE configurations_wave ADD COLUMN IF NOT EXISTS cle VARCHAR(100);
ALTER TABLE configurations_wave ADD COLUMN IF NOT EXISTS description VARCHAR(500);
ALTER TABLE configurations_wave ADD COLUMN IF NOT EXISTS type_valeur VARCHAR(20);
ALTER TABLE configurations_wave ADD COLUMN IF NOT EXISTS valeur TEXT;
DO $$ BEGIN ALTER TABLE configurations_wave ALTER COLUMN environnement TYPE VARCHAR(20) USING environnement::varchar(20); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 7. COTISATIONS
-- -----------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE cotisations ALTER COLUMN libelle TYPE VARCHAR(100) USING libelle::varchar(100); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 8. DEMANDES_AIDE
-- -----------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE demandes_aide ALTER COLUMN documents_fournis TYPE VARCHAR(255) USING documents_fournis::varchar(255); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE demandes_aide ALTER COLUMN statut TYPE VARCHAR(255) USING statut::varchar(255); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE demandes_aide ALTER COLUMN type_aide TYPE VARCHAR(255) USING type_aide::varchar(255); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 9. DOCUMENTS
-- -----------------------------------------------------------------------------
ALTER TABLE documents ADD COLUMN IF NOT EXISTS chemin_stockage VARCHAR(1000);
ALTER TABLE documents ADD COLUMN IF NOT EXISTS date_dernier_telechargement TIMESTAMP;
ALTER TABLE documents ADD COLUMN IF NOT EXISTS hash_md5 VARCHAR(32);
ALTER TABLE documents ADD COLUMN IF NOT EXISTS hash_sha256 VARCHAR(64);
ALTER TABLE documents ADD COLUMN IF NOT EXISTS nom_fichier VARCHAR(255);
ALTER TABLE documents ADD COLUMN IF NOT EXISTS nom_original VARCHAR(255);
ALTER TABLE documents ADD COLUMN IF NOT EXISTS nombre_telechargements INTEGER NOT NULL DEFAULT 0;
ALTER TABLE documents ADD COLUMN IF NOT EXISTS taille_octets BIGINT DEFAULT 0;
DO $$ BEGIN ALTER TABLE documents ALTER COLUMN description TYPE VARCHAR(1000) USING description::varchar(1000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- Rétrocompat V1 : nom -> nom_fichier, chemin_fichier -> chemin_stockage, taille_fichier -> taille_octets
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'documents' AND column_name = 'nom') THEN
UPDATE documents SET nom_fichier = COALESCE(nom_fichier, nom) WHERE id IS NOT NULL;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'documents' AND column_name = 'chemin_fichier') THEN
UPDATE documents SET chemin_stockage = COALESCE(chemin_stockage, chemin_fichier) WHERE id IS NOT NULL;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'documents' AND column_name = 'taille_fichier') THEN
UPDATE documents SET taille_octets = COALESCE(taille_octets, taille_fichier) WHERE id IS NOT NULL;
END IF;
EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE documents SET chemin_stockage = COALESCE(chemin_stockage, 'legacy/' || id::text) WHERE chemin_stockage IS NULL AND id IS NOT NULL;
UPDATE documents SET nom_fichier = COALESCE(nom_fichier, 'document') WHERE id IS NOT NULL;
UPDATE documents SET taille_octets = COALESCE(taille_octets, 0) WHERE id IS NOT NULL;
UPDATE documents SET nombre_telechargements = COALESCE(nombre_telechargements, 0) WHERE id IS NOT NULL;
DO $$ BEGIN IF (SELECT COUNT(*) FROM documents WHERE chemin_stockage IS NULL) = 0 THEN ALTER TABLE documents ALTER COLUMN chemin_stockage SET NOT NULL; END IF; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN IF (SELECT COUNT(*) FROM documents WHERE nom_fichier IS NULL) = 0 THEN ALTER TABLE documents ALTER COLUMN nom_fichier SET NOT NULL; END IF; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN IF (SELECT COUNT(*) FROM documents WHERE taille_octets IS NULL) = 0 THEN ALTER TABLE documents ALTER COLUMN taille_octets SET NOT NULL; END IF; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN IF (SELECT COUNT(*) FROM documents WHERE nombre_telechargements IS NULL) = 0 THEN ALTER TABLE documents ALTER COLUMN nombre_telechargements SET NOT NULL; END IF; EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_document_nom_fichier ON documents(nom_fichier);
CREATE INDEX IF NOT EXISTS idx_document_hash_md5 ON documents(hash_md5);
CREATE INDEX IF NOT EXISTS idx_document_hash_sha256 ON documents(hash_sha256);
-- -----------------------------------------------------------------------------
-- 10. ECRITURES_COMPTABLES
-- -----------------------------------------------------------------------------
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS commentaire VARCHAR(1000);
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS lettrage VARCHAR(20);
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS montant_credit NUMERIC(14,2);
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS montant_debit NUMERIC(14,2);
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS pointe BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS reference VARCHAR(100);
ALTER TABLE ecritures_comptables ADD COLUMN IF NOT EXISTS paiement_id UUID REFERENCES paiements(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_ecriture_paiement ON ecritures_comptables(paiement_id);
-- -----------------------------------------------------------------------------
-- 11. EVENEMENTS
-- -----------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE evenements ALTER COLUMN adresse TYPE VARCHAR(1000) USING adresse::varchar(1000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS contact_organisateur VARCHAR(500);
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS date_limite_inscription TIMESTAMP;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS inscription_requise BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS instructions_particulieres VARCHAR(1000);
DO $$ BEGIN ALTER TABLE evenements ALTER COLUMN lieu TYPE VARCHAR(500) USING lieu::varchar(500); EXCEPTION WHEN OTHERS THEN NULL; END $$;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS materiel_requis VARCHAR(2000);
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS prix NUMERIC(10,2);
DO $$ BEGIN ALTER TABLE evenements ALTER COLUMN statut TYPE VARCHAR(30) USING statut::varchar(30); EXCEPTION WHEN OTHERS THEN NULL; END $$;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS visible_public BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS organisateur_id UUID REFERENCES utilisateurs(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_evenement_organisateur ON evenements(organisateur_id);
-- -----------------------------------------------------------------------------
-- 12. JOURNAUX_COMPTABLES
-- -----------------------------------------------------------------------------
ALTER TABLE journaux_comptables ADD COLUMN IF NOT EXISTS date_debut DATE;
ALTER TABLE journaux_comptables ADD COLUMN IF NOT EXISTS date_fin DATE;
ALTER TABLE journaux_comptables ADD COLUMN IF NOT EXISTS statut VARCHAR(20);
DO $$ BEGIN ALTER TABLE journaux_comptables ALTER COLUMN code TYPE VARCHAR(10) USING code::varchar(10); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE journaux_comptables ALTER COLUMN description TYPE VARCHAR(500) USING description::varchar(500); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE journaux_comptables ALTER COLUMN libelle TYPE VARCHAR(100) USING libelle::varchar(100); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE journaux_comptables ALTER COLUMN type_journal TYPE VARCHAR(30) USING type_journal::varchar(30); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 13. LIGNES_ECRITURE
-- -----------------------------------------------------------------------------
ALTER TABLE lignes_ecriture ADD COLUMN IF NOT EXISTS numero_ligne INTEGER DEFAULT 1;
ALTER TABLE lignes_ecriture ADD COLUMN IF NOT EXISTS reference VARCHAR(100);
ALTER TABLE lignes_ecriture ADD COLUMN IF NOT EXISTS compte_comptable_id UUID;
DO $$ BEGIN ALTER TABLE lignes_ecriture ALTER COLUMN montant_credit TYPE NUMERIC(14,2) USING montant_credit::numeric(14,2); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE lignes_ecriture ALTER COLUMN montant_debit TYPE NUMERIC(14,2) USING montant_debit::numeric(14,2); EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE lignes_ecriture SET numero_ligne = 1 WHERE numero_ligne IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE lignes_ecriture ALTER COLUMN numero_ligne SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'comptes_comptables') THEN
UPDATE lignes_ecriture l SET compte_comptable_id = (SELECT id FROM comptes_comptables LIMIT 1) WHERE l.compte_comptable_id IS NULL AND l.id IS NOT NULL;
ALTER TABLE lignes_ecriture ADD CONSTRAINT fk_ligne_compte FOREIGN KEY (compte_comptable_id) REFERENCES comptes_comptables(id) ON DELETE RESTRICT;
END IF;
EXCEPTION WHEN duplicate_object OR OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_ligne_ecriture_compte ON lignes_ecriture(compte_comptable_id);
-- -----------------------------------------------------------------------------
-- 14. MEMBRES_ROLES
-- -----------------------------------------------------------------------------
ALTER TABLE membres_roles ADD COLUMN IF NOT EXISTS commentaire VARCHAR(500);
ALTER TABLE membres_roles ADD COLUMN IF NOT EXISTS date_debut DATE;
ALTER TABLE membres_roles ADD COLUMN IF NOT EXISTS date_fin DATE;
-- -----------------------------------------------------------------------------
-- 15. ORGANISATIONS
-- -----------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE organisations ALTER COLUMN activites_principales TYPE VARCHAR(2000) USING activites_principales::varchar(2000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE organisations ALTER COLUMN description TYPE VARCHAR(2000) USING description::varchar(2000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE organisations ALTER COLUMN objectifs TYPE VARCHAR(2000) USING objectifs::varchar(2000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 16. PAIEMENTS
-- -----------------------------------------------------------------------------
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS code_devise VARCHAR(3) DEFAULT 'XOF';
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS commentaire VARCHAR(1000);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS date_validation TIMESTAMP;
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS ip_address VARCHAR(45);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS numero_reference VARCHAR(50);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS reference_externe VARCHAR(500);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS url_preuve VARCHAR(1000);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS user_agent VARCHAR(500);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS validateur VARCHAR(255);
ALTER TABLE paiements ADD COLUMN IF NOT EXISTS transaction_wave_id UUID REFERENCES transactions_wave(id) ON DELETE SET NULL;
DO $$ BEGIN ALTER TABLE paiements ALTER COLUMN montant TYPE NUMERIC(14,2) USING montant::numeric(14,2); EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE paiements SET numero_reference = 'REF-' || id WHERE numero_reference IS NULL AND id IS NOT NULL;
UPDATE paiements SET code_devise = 'XOF' WHERE code_devise IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE paiements ALTER COLUMN numero_reference SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paiements ALTER COLUMN code_devise SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_paiement_transaction_wave ON paiements(transaction_wave_id);
-- -----------------------------------------------------------------------------
-- 17. PERMISSIONS
-- -----------------------------------------------------------------------------
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS action VARCHAR(50) DEFAULT 'READ';
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS libelle VARCHAR(200);
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS ressource VARCHAR(50) DEFAULT '*';
DO $$ BEGIN ALTER TABLE permissions ALTER COLUMN module TYPE VARCHAR(50) USING module::varchar(50); EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE permissions SET action = 'READ' WHERE action IS NULL AND id IS NOT NULL;
UPDATE permissions SET ressource = '*' WHERE ressource IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE permissions ALTER COLUMN action SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE permissions ALTER COLUMN ressource SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 18. PIECES_JOINTES
-- -----------------------------------------------------------------------------
ALTER TABLE pieces_jointes ADD COLUMN IF NOT EXISTS commentaire VARCHAR(500);
ALTER TABLE pieces_jointes ADD COLUMN IF NOT EXISTS libelle VARCHAR(200);
ALTER TABLE pieces_jointes ADD COLUMN IF NOT EXISTS ordre INTEGER DEFAULT 1;
ALTER TABLE pieces_jointes ADD COLUMN IF NOT EXISTS document_id UUID REFERENCES documents(id) ON DELETE CASCADE;
UPDATE pieces_jointes SET ordre = 1 WHERE ordre IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE pieces_jointes ALTER COLUMN ordre SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pieces_jointes' AND column_name = 'document_id') THEN
UPDATE pieces_jointes SET document_id = (SELECT id FROM documents LIMIT 1) WHERE document_id IS NULL AND id IS NOT NULL;
END IF;
EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_pj_document ON pieces_jointes(document_id);
-- -----------------------------------------------------------------------------
-- 19. ROLES
-- -----------------------------------------------------------------------------
ALTER TABLE roles ADD COLUMN IF NOT EXISTS libelle VARCHAR(100) DEFAULT 'Role';
ALTER TABLE roles ADD COLUMN IF NOT EXISTS niveau_hierarchique INTEGER NOT NULL DEFAULT 0;
ALTER TABLE roles ADD COLUMN IF NOT EXISTS type_role VARCHAR(50) DEFAULT 'FONCTION';
ALTER TABLE roles ADD COLUMN IF NOT EXISTS organisation_id UUID REFERENCES organisations(id) ON DELETE CASCADE;
UPDATE roles SET libelle = COALESCE(code, 'Role') WHERE libelle IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE roles ALTER COLUMN libelle SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE roles ALTER COLUMN type_role SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_role_organisation ON roles(organisation_id);
-- -----------------------------------------------------------------------------
-- 20. ROLES_PERMISSIONS
-- -----------------------------------------------------------------------------
ALTER TABLE roles_permissions ADD COLUMN IF NOT EXISTS commentaire VARCHAR(500);
-- -----------------------------------------------------------------------------
-- 21. SUGGESTION_VOTES
-- -----------------------------------------------------------------------------
ALTER TABLE suggestion_votes ADD COLUMN IF NOT EXISTS cree_par VARCHAR(255);
ALTER TABLE suggestion_votes ADD COLUMN IF NOT EXISTS date_modification TIMESTAMP;
ALTER TABLE suggestion_votes ADD COLUMN IF NOT EXISTS modifie_par VARCHAR(255);
ALTER TABLE suggestion_votes ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 0;
-- -----------------------------------------------------------------------------
-- 22. TEMPLATES_NOTIFICATIONS
-- -----------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE templates_notifications ALTER COLUMN description TYPE VARCHAR(1000) USING description::varchar(1000); EXCEPTION WHEN OTHERS THEN NULL; END $$;
-- -----------------------------------------------------------------------------
-- 23. TRANSACTIONS_WAVE
-- -----------------------------------------------------------------------------
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF';
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS date_derniere_tentative TIMESTAMP;
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS frais NUMERIC(12,2);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS message_erreur VARCHAR(1000);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS metadonnees TEXT;
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS montant_net NUMERIC(14,2);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS nombre_tentatives INTEGER NOT NULL DEFAULT 0;
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS reponse_wave_api TEXT;
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS statut_transaction VARCHAR(30) NOT NULL DEFAULT 'INITIALISE';
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS telephone_beneficiaire VARCHAR(13);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS telephone_payeur VARCHAR(13);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS wave_reference VARCHAR(100);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS wave_request_id VARCHAR(100);
ALTER TABLE transactions_wave ADD COLUMN IF NOT EXISTS wave_transaction_id VARCHAR(100);
DO $$ BEGIN ALTER TABLE transactions_wave ALTER COLUMN montant TYPE NUMERIC(14,2) USING montant::numeric(14,2); EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE transactions_wave SET wave_transaction_id = 'legacy-' || id WHERE wave_transaction_id IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE transactions_wave ALTER COLUMN wave_transaction_id SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE UNIQUE INDEX IF NOT EXISTS idx_transaction_wave_id ON transactions_wave(wave_transaction_id);
CREATE INDEX IF NOT EXISTS idx_transaction_wave_statut ON transactions_wave(statut_transaction);
CREATE INDEX IF NOT EXISTS idx_transaction_wave_request_id ON transactions_wave(wave_request_id);
CREATE INDEX IF NOT EXISTS idx_transaction_wave_reference ON transactions_wave(wave_reference);
-- -----------------------------------------------------------------------------
-- 24. TYPES_REFERENCE
-- -----------------------------------------------------------------------------
ALTER TABLE types_reference ADD COLUMN IF NOT EXISTS couleur VARCHAR(50);
ALTER TABLE types_reference ADD COLUMN IF NOT EXISTS icone VARCHAR(100);
ALTER TABLE types_reference ADD COLUMN IF NOT EXISTS severity VARCHAR(20);
ALTER TABLE types_reference ADD COLUMN IF NOT EXISTS organisation_id UUID REFERENCES organisations(id) ON DELETE CASCADE;
DO $$ BEGIN ALTER TABLE types_reference ALTER COLUMN code TYPE VARCHAR(50) USING code::varchar(50); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE types_reference ALTER COLUMN domaine TYPE VARCHAR(50) USING domaine::varchar(50); EXCEPTION WHEN OTHERS THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE types_reference ALTER COLUMN libelle TYPE VARCHAR(200) USING libelle::varchar(200); EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_typeref_org ON types_reference(organisation_id);
-- -----------------------------------------------------------------------------
-- 25. WEBHOOKS_WAVE
-- -----------------------------------------------------------------------------
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS commentaire VARCHAR(500);
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS date_reception TIMESTAMP;
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS date_traitement TIMESTAMP;
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS message_erreur VARCHAR(1000);
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS nombre_tentatives INTEGER NOT NULL DEFAULT 0;
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS statut_traitement VARCHAR(30) NOT NULL DEFAULT 'PENDING';
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS wave_event_id VARCHAR(100);
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS paiement_id UUID REFERENCES paiements(id) ON DELETE SET NULL;
ALTER TABLE webhooks_wave ADD COLUMN IF NOT EXISTS transaction_wave_id UUID REFERENCES transactions_wave(id) ON DELETE SET NULL;
DO $$ BEGIN ALTER TABLE webhooks_wave ALTER COLUMN type_evenement TYPE VARCHAR(50) USING type_evenement::varchar(50); EXCEPTION WHEN OTHERS THEN NULL; END $$;
UPDATE webhooks_wave SET wave_event_id = 'evt-' || id WHERE wave_event_id IS NULL AND id IS NOT NULL;
DO $$ BEGIN ALTER TABLE webhooks_wave ALTER COLUMN wave_event_id SET NOT NULL; EXCEPTION WHEN OTHERS THEN NULL; END $$;
CREATE INDEX IF NOT EXISTS idx_webhook_paiement ON webhooks_wave(paiement_id);
CREATE INDEX IF NOT EXISTS idx_webhook_transaction ON webhooks_wave(transaction_wave_id);
CREATE INDEX IF NOT EXISTS idx_webhook_wave_statut ON webhooks_wave(statut_traitement);
CREATE INDEX IF NOT EXISTS idx_webhook_wave_type ON webhooks_wave(type_evenement);
-- =============================================================================
-- 26. TABLES MANQUANTES (création si non présentes dans V1)
-- =============================================================================
-- Campagnes agricoles
CREATE TABLE IF NOT EXISTS campagnes_agricoles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
designation VARCHAR(200) NOT NULL,
statut VARCHAR(50) NOT NULL DEFAULT 'PREPARATION',
surface_estimee_ha NUMERIC(19,4),
type_culture VARCHAR(100),
volume_prev_tonnes NUMERIC(19,4),
volume_reel_tonnes NUMERIC(19,4),
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_campagne_agricole_statut CHECK (statut IN ('PREPARATION','LABOUR_SEMIS','ENTRETIEN','RECOLTE','COMMERCIALISATION','CLOTUREE'))
);
CREATE INDEX IF NOT EXISTS idx_agricole_organisation ON campagnes_agricoles(organisation_id);
-- Campagnes collecte
CREATE TABLE IF NOT EXISTS campagnes_collecte (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
courte_description VARCHAR(500),
date_cloture_prevue TIMESTAMP,
date_ouverture TIMESTAMP NOT NULL,
est_publique BOOLEAN NOT NULL DEFAULT TRUE,
html_description_complete TEXT,
image_banniere_url VARCHAR(500),
montant_collecte_actuel NUMERIC(19,4) DEFAULT 0,
nombre_donateurs INTEGER DEFAULT 0,
objectif_financier NUMERIC(19,4),
statut VARCHAR(50) NOT NULL DEFAULT 'BROUILLON',
titre VARCHAR(200) NOT NULL,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_campagne_collecte_statut CHECK (statut IN ('BROUILLON','EN_COURS','ATTEINTE','EXPIREE','SUSPENDUE'))
);
CREATE INDEX IF NOT EXISTS idx_collecte_organisation ON campagnes_collecte(organisation_id);
-- Campagnes vote
CREATE TABLE IF NOT EXISTS campagnes_vote (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
autoriser_vote_blanc BOOLEAN NOT NULL DEFAULT TRUE,
date_fermeture TIMESTAMP NOT NULL,
date_ouverture TIMESTAMP NOT NULL,
description TEXT,
mode_scrutin VARCHAR(50) NOT NULL DEFAULT 'MAJORITAIRE_UN_TOUR',
restreindre_membres_ajour BOOLEAN NOT NULL DEFAULT FALSE,
statut VARCHAR(50) NOT NULL DEFAULT 'BROUILLON',
titre VARCHAR(200) NOT NULL,
total_electeurs INTEGER,
total_votants INTEGER,
total_blancs_nuls INTEGER,
type_vote VARCHAR(50) NOT NULL,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_campagne_vote_statut CHECK (statut IN ('BROUILLON','PLANIFIE','OUVERT','SUSPENDU','CLOTURE','RESULTATS_PUBLIES')),
CONSTRAINT chk_campagne_vote_mode CHECK (mode_scrutin IN ('MAJORITAIRE_UN_TOUR','MAJORITAIRE_DEUX_TOURS','PROPORTIONNEL','BUREAU_CONSENSUEL')),
CONSTRAINT chk_campagne_vote_type CHECK (type_vote IN ('ELECTION_BUREAU','ADOPTION_RESOLUTION','MODIFICATION_STATUTS','EXCLUSION_MEMBRE','REFERENDUM'))
);
CREATE INDEX IF NOT EXISTS idx_vote_orga ON campagnes_vote(organisation_id);
-- Candidats
CREATE TABLE IF NOT EXISTS candidats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
membre_associe_id VARCHAR(36),
nom_candidature VARCHAR(150) NOT NULL,
nombre_voix INTEGER DEFAULT 0,
photo_url VARCHAR(500),
pourcentage NUMERIC(5,2),
profession_foi TEXT,
campagne_vote_id UUID NOT NULL REFERENCES campagnes_vote(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_candidat_campagne ON candidats(campagne_vote_id);
-- Comptes épargne
CREATE TABLE IF NOT EXISTS comptes_epargne (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
date_derniere_transaction DATE,
date_ouverture DATE NOT NULL,
description VARCHAR(500),
numero_compte VARCHAR(50) NOT NULL UNIQUE,
solde_actuel NUMERIC(19,4) NOT NULL DEFAULT 0,
solde_bloque NUMERIC(19,4) NOT NULL DEFAULT 0,
statut VARCHAR(30) NOT NULL DEFAULT 'ACTIF',
type_compte VARCHAR(50) NOT NULL DEFAULT 'COURANT',
membre_id UUID NOT NULL REFERENCES utilisateurs(id) ON DELETE CASCADE,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_compte_epargne_statut CHECK (statut IN ('ACTIF','INACTIF','BLOQUE','EN_CLOTURE','CLOTURE')),
CONSTRAINT chk_compte_epargne_type CHECK (type_compte IN ('COURANT','EPARGNE_LIBRE','EPARGNE_BLOQUEE','DEPOT_A_TERME','EPARGNE_PROJET'))
);
CREATE INDEX IF NOT EXISTS idx_compte_epargne_membre ON comptes_epargne(membre_id);
CREATE INDEX IF NOT EXISTS idx_compte_epargne_orga ON comptes_epargne(organisation_id);
-- Contributions collecte
CREATE TABLE IF NOT EXISTS contributions_collecte (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
alias_donateur VARCHAR(150),
date_contribution TIMESTAMP NOT NULL,
est_anonyme BOOLEAN NOT NULL DEFAULT FALSE,
message_soutien VARCHAR(500),
montant_soutien NUMERIC(19,4) NOT NULL,
statut_paiement VARCHAR(50) DEFAULT 'INITIALISE',
transaction_paiement_id VARCHAR(100),
campagne_id UUID NOT NULL REFERENCES campagnes_collecte(id) ON DELETE CASCADE,
membre_donateur_id UUID REFERENCES utilisateurs(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_contribution_campagne ON contributions_collecte(campagne_id);
CREATE INDEX IF NOT EXISTS idx_contribution_membre ON contributions_collecte(membre_donateur_id);
-- Demandes crédit
CREATE TABLE IF NOT EXISTS demandes_credit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
cout_total_credit NUMERIC(19,4),
date_premier_echeance DATE,
date_soumission DATE NOT NULL,
date_validation DATE,
duree_mois_approuvee INTEGER,
duree_mois_demande INTEGER NOT NULL,
justification_detaillee TEXT,
montant_approuve NUMERIC(19,4),
montant_demande NUMERIC(19,4) NOT NULL,
notes_comite TEXT,
numero_dossier VARCHAR(50) NOT NULL UNIQUE,
statut VARCHAR(50) NOT NULL DEFAULT 'BROUILLON',
taux_interet_annuel NUMERIC(5,2),
type_credit VARCHAR(50) NOT NULL,
compte_lie_id UUID REFERENCES comptes_epargne(id) ON DELETE SET NULL,
membre_id UUID NOT NULL REFERENCES utilisateurs(id) ON DELETE CASCADE,
CONSTRAINT chk_demande_credit_statut CHECK (statut IN ('BROUILLON','SOUMISE','EN_EVALUATION','INFORMATIONS_REQUISES','APPROUVEE','REJETEE','DECAISSEE','SOLDEE','EN_CONTENTIEUX')),
CONSTRAINT chk_demande_credit_type CHECK (type_credit IN ('CONSOMMATION','IMMOBILIER','PROFESSIONNEL','AGRICOLE','SCOLAIRE','URGENCE','DECOUVERT'))
);
CREATE INDEX IF NOT EXISTS idx_credit_membre ON demandes_credit(membre_id);
CREATE INDEX IF NOT EXISTS idx_credit_compte ON demandes_credit(compte_lie_id);
-- Dons religieux
CREATE TABLE IF NOT EXISTS dons_religieux (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
date_encaissement TIMESTAMP NOT NULL,
montant NUMERIC(19,4) NOT NULL,
periode_nature VARCHAR(150),
type_don VARCHAR(50) NOT NULL,
fidele_id UUID REFERENCES utilisateurs(id) ON DELETE SET NULL,
institution_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_don_type CHECK (type_don IN ('QUETE_ORDINAIRE','DIME','ZAKAT','OFFRANDE_SPECIALE','INTENTION_PRIERE'))
);
CREATE INDEX IF NOT EXISTS idx_don_fidele ON dons_religieux(fidele_id);
CREATE INDEX IF NOT EXISTS idx_don_institution ON dons_religieux(institution_id);
-- Échéances crédit
CREATE TABLE IF NOT EXISTS echeances_credit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
capital_amorti NUMERIC(19,4) NOT NULL,
capital_restant_du NUMERIC(19,4) NOT NULL,
date_echeance_prevue DATE NOT NULL,
date_paiement_effectif DATE,
interets_periode NUMERIC(19,4) NOT NULL,
montant_regle NUMERIC(19,4),
montant_total_exigible NUMERIC(19,4) NOT NULL,
ordre INTEGER NOT NULL,
penalites_retard NUMERIC(19,4),
statut VARCHAR(50) NOT NULL DEFAULT 'A_VENIR',
demande_credit_id UUID NOT NULL REFERENCES demandes_credit(id) ON DELETE CASCADE,
CONSTRAINT chk_echeance_statut CHECK (statut IN ('A_VENIR','EXIGIBLE','PAYEE','PAYEE_PARTIELLEMENT','EN_RETARD','IMPAYEE','RESTRUCTUREE'))
);
CREATE INDEX IF NOT EXISTS idx_echeance_demande ON echeances_credit(demande_credit_id);
-- Échelons organigramme
CREATE TABLE IF NOT EXISTS echelons_organigramme (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
designation VARCHAR(200) NOT NULL,
niveau_echelon VARCHAR(50) NOT NULL,
zone_delegation VARCHAR(200),
echelon_parent_id UUID REFERENCES organisations(id) ON DELETE SET NULL,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_echelon_niveau CHECK (niveau_echelon IN ('SIEGE_MONDIAL','NATIONAL','REGIONAL','LOCAL'))
);
CREATE INDEX IF NOT EXISTS idx_echelon_org ON echelons_organigramme(organisation_id);
CREATE INDEX IF NOT EXISTS idx_echelon_parent ON echelons_organigramme(echelon_parent_id);
-- Garanties demande
CREATE TABLE IF NOT EXISTS garanties_demande (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
document_preuve_id VARCHAR(36),
reference_description VARCHAR(500),
type_garantie VARCHAR(50) NOT NULL,
valeur_estimee NUMERIC(19,4),
demande_credit_id UUID NOT NULL REFERENCES demandes_credit(id) ON DELETE CASCADE,
CONSTRAINT chk_garantie_type CHECK (type_garantie IN ('EPARGNE_BLOQUEE','CAUTION_SOLIDAIRE','MATERIELLE','IMMOBILIERE','FOND_GARANTIE'))
);
CREATE INDEX IF NOT EXISTS idx_garantie_demande ON garanties_demande(demande_credit_id);
-- Projets ONG
CREATE TABLE IF NOT EXISTS projets_ong (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
budget_previsionnel NUMERIC(19,4),
date_fin_estimee DATE,
date_lancement DATE,
depenses_reelles NUMERIC(19,4),
description TEXT,
nom_projet VARCHAR(200) NOT NULL,
statut VARCHAR(50) NOT NULL DEFAULT 'EN_ETUDE',
zone_geographique VARCHAR(200),
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_projet_ong_statut CHECK (statut IN ('EN_ETUDE','FINANCEMENT','EN_COURS','EVALUE','CLOTURE'))
);
CREATE INDEX IF NOT EXISTS idx_projet_ong_organisation ON projets_ong(organisation_id);
-- Tontines
CREATE TABLE IF NOT EXISTS tontines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
date_debut_effective DATE,
date_fin_prevue DATE,
description TEXT,
frequence VARCHAR(50) NOT NULL,
limite_participants INTEGER,
montant_mise_tour NUMERIC(19,4),
nom VARCHAR(150) NOT NULL,
statut VARCHAR(50) NOT NULL DEFAULT 'PLANIFIEE',
type_tontine VARCHAR(50) NOT NULL,
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
CONSTRAINT chk_tontine_statut CHECK (statut IN ('PLANIFIEE','EN_COURS','EN_PAUSE','CLOTUREE','ANNULEE')),
CONSTRAINT chk_tontine_frequence CHECK (frequence IN ('JOURNALIERE','HEBDOMADAIRE','DECADE','QUINZAINE','MENSUELLE','TRIMESTRIELLE')),
CONSTRAINT chk_tontine_type CHECK (type_tontine IN ('ROTATIVE_CLASSIQUE','VARIABLE','ACCUMULATIVE'))
);
CREATE INDEX IF NOT EXISTS idx_tontine_organisation ON tontines(organisation_id);
-- Tours tontine
CREATE TABLE IF NOT EXISTS tours_tontine (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
cagnotte_collectee NUMERIC(19,4) NOT NULL DEFAULT 0,
date_ouverture_cotisations DATE NOT NULL,
date_tirage_remise DATE,
montant_cible NUMERIC(19,4) NOT NULL,
ordre_tour INTEGER NOT NULL,
statut_interne VARCHAR(30),
membre_beneficiaire_id UUID REFERENCES utilisateurs(id) ON DELETE SET NULL,
tontine_id UUID NOT NULL REFERENCES tontines(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_tour_tontine ON tours_tontine(tontine_id);
CREATE INDEX IF NOT EXISTS idx_tour_beneficiaire ON tours_tontine(membre_beneficiaire_id);
-- Transactions épargne
CREATE TABLE IF NOT EXISTS transactions_epargne (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
date_transaction TIMESTAMP NOT NULL,
montant NUMERIC(19,4) NOT NULL,
motif VARCHAR(500),
operateur_id VARCHAR(36),
origine_fonds VARCHAR(200),
piece_justificative_id UUID,
reference_externe VARCHAR(100),
solde_apres NUMERIC(19,4),
solde_avant NUMERIC(19,4),
statut_execution VARCHAR(50) DEFAULT 'REUSSIE',
type_transaction VARCHAR(50) NOT NULL,
compte_id UUID NOT NULL REFERENCES comptes_epargne(id) ON DELETE CASCADE,
CONSTRAINT chk_tx_epargne_type CHECK (type_transaction IN ('DEPOT','RETRAIT','TRANSFERT_ENTRANT','TRANSFERT_SORTANT','PAIEMENT_INTERETS','PRELEVEMENT_FRAIS','RETENUE_GARANTIE','LIBERATION_GARANTIE','REMBOURSEMENT_CREDIT')),
CONSTRAINT chk_tx_epargne_statut CHECK (statut_execution IN ('INITIALISE','EN_ATTENTE','EN_COURS','REUSSIE','ECHOUE','ANNULEE','EXPIRED'))
);
CREATE INDEX IF NOT EXISTS idx_tx_epargne_compte ON transactions_epargne(compte_id);
CREATE INDEX IF NOT EXISTS idx_tx_epargne_reference ON transactions_epargne(reference_externe);
-- =============================================================================
-- Fin V2 — Entity Schema Alignment
-- =============================================================================

View File

@@ -0,0 +1,46 @@
-- Un compte épargne pour le membre de test (membre.mukefi@unionflow.test / MUKEFI).
-- N'insère rien si l'utilisateur ou l'organisation n'existent pas, ou si un compte actif existe déjà.
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

@@ -0,0 +1,4 @@
-- Autoriser type_objet = 'DEPOT_EPARGNE' dans intentions_paiement (dépôt épargne via Wave).
ALTER TABLE intentions_paiement DROP CONSTRAINT IF EXISTS chk_intention_type;
ALTER TABLE intentions_paiement ADD CONSTRAINT chk_intention_type
CHECK (type_objet IN ('COTISATION','ADHESION','EVENEMENT','ABONNEMENT_UNIONFLOW','DEPOT_EPARGNE'));

View File

@@ -0,0 +1,15 @@
-- Table de suivi entre membres (réseau) : qui suit qui
CREATE TABLE IF NOT EXISTS membre_suivi (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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 DEFAULT 0,
follower_utilisateur_id UUID NOT NULL REFERENCES utilisateurs(id) ON DELETE CASCADE,
suivi_utilisateur_id UUID NOT NULL REFERENCES utilisateurs(id) ON DELETE CASCADE,
CONSTRAINT uq_membre_suivi_follower_suivi UNIQUE (follower_utilisateur_id, suivi_utilisateur_id)
);
CREATE INDEX IF NOT EXISTS idx_membre_suivi_follower ON membre_suivi(follower_utilisateur_id);
CREATE INDEX IF NOT EXISTS idx_membre_suivi_suivi ON membre_suivi(suivi_utilisateur_id);

View File

@@ -0,0 +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

View File

@@ -0,0 +1,156 @@
-- Migration V6: Création des tables pour le module Finance Workflow
-- Author: UnionFlow Team
-- Date: 2026-03-13
-- Description: Approbations de transactions multi-niveaux et gestion budgétaire
-- =====================================================
-- Table: transaction_approvals
-- =====================================================
CREATE TABLE transaction_approvals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
transaction_id UUID NOT NULL,
transaction_type VARCHAR(20) NOT NULL CHECK (transaction_type IN ('CONTRIBUTION', 'DEPOSIT', 'WITHDRAWAL', 'TRANSFER', 'SOLIDARITY', 'EVENT', 'OTHER')),
amount NUMERIC(14, 2) NOT NULL CHECK (amount >= 0),
currency VARCHAR(3) NOT NULL DEFAULT 'XOF' CHECK (currency ~ '^[A-Z]{3}$'),
requester_id UUID NOT NULL,
requester_name VARCHAR(200) NOT NULL,
organisation_id UUID REFERENCES organisations(id) ON DELETE SET NULL,
required_level VARCHAR(10) NOT NULL CHECK (required_level IN ('NONE', 'LEVEL1', 'LEVEL2', 'LEVEL3')),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'APPROVED', 'VALIDATED', 'REJECTED', 'EXPIRED', 'CANCELLED')),
rejection_reason VARCHAR(1000),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
completed_at TIMESTAMP,
metadata TEXT,
-- Colonnes d'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 NOT NULL DEFAULT TRUE
);
-- Index pour transaction_approvals
CREATE INDEX idx_approval_transaction ON transaction_approvals(transaction_id);
CREATE INDEX idx_approval_status ON transaction_approvals(status);
CREATE INDEX idx_approval_requester ON transaction_approvals(requester_id);
CREATE INDEX idx_approval_organisation ON transaction_approvals(organisation_id);
CREATE INDEX idx_approval_created ON transaction_approvals(created_at);
CREATE INDEX idx_approval_level ON transaction_approvals(required_level);
-- =====================================================
-- Table: approver_actions
-- =====================================================
CREATE TABLE approver_actions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
approval_id UUID NOT NULL REFERENCES transaction_approvals(id) ON DELETE CASCADE,
approver_id UUID NOT NULL,
approver_name VARCHAR(200) NOT NULL,
approver_role VARCHAR(50) NOT NULL,
decision VARCHAR(10) NOT NULL DEFAULT 'PENDING' CHECK (decision IN ('PENDING', 'APPROVED', 'REJECTED')),
comment VARCHAR(1000),
decided_at TIMESTAMP,
-- Colonnes d'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 NOT NULL DEFAULT TRUE
);
-- Index pour approver_actions
CREATE INDEX idx_approver_action_approval ON approver_actions(approval_id);
CREATE INDEX idx_approver_action_approver ON approver_actions(approver_id);
CREATE INDEX idx_approver_action_decision ON approver_actions(decision);
CREATE INDEX idx_approver_action_decided_at ON approver_actions(decided_at);
-- =====================================================
-- Table: budgets
-- =====================================================
CREATE TABLE budgets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
description VARCHAR(1000),
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
period VARCHAR(20) NOT NULL CHECK (period IN ('MONTHLY', 'QUARTERLY', 'SEMIANNUAL', 'ANNUAL')),
year INTEGER NOT NULL CHECK (year >= 2020 AND year <= 2100),
month INTEGER CHECK (month >= 1 AND month <= 12),
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' CHECK (status IN ('DRAFT', 'ACTIVE', 'CLOSED', 'CANCELLED')),
total_planned NUMERIC(16, 2) NOT NULL DEFAULT 0 CHECK (total_planned >= 0),
total_realized NUMERIC(16, 2) NOT NULL DEFAULT 0 CHECK (total_realized >= 0),
currency VARCHAR(3) NOT NULL DEFAULT 'XOF' CHECK (currency ~ '^[A-Z]{3}$'),
created_by_id UUID NOT NULL,
created_at_budget TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP,
approved_by_id UUID,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
metadata TEXT,
-- Colonnes d'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 NOT NULL DEFAULT TRUE,
-- Contraintes
CONSTRAINT chk_budget_dates CHECK (end_date >= start_date)
);
-- Index pour budgets
CREATE INDEX idx_budget_organisation ON budgets(organisation_id);
CREATE INDEX idx_budget_status ON budgets(status);
CREATE INDEX idx_budget_period ON budgets(period);
CREATE INDEX idx_budget_year_month ON budgets(year, month);
CREATE INDEX idx_budget_created_by ON budgets(created_by_id);
-- =====================================================
-- Table: budget_lines
-- =====================================================
CREATE TABLE budget_lines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
budget_id UUID NOT NULL REFERENCES budgets(id) ON DELETE CASCADE,
category VARCHAR(20) NOT NULL CHECK (category IN ('CONTRIBUTIONS', 'SAVINGS', 'SOLIDARITY', 'EVENTS', 'OPERATIONAL', 'INVESTMENTS', 'OTHER')),
name VARCHAR(200) NOT NULL,
description VARCHAR(500),
amount_planned NUMERIC(16, 2) NOT NULL CHECK (amount_planned >= 0),
amount_realized NUMERIC(16, 2) NOT NULL DEFAULT 0 CHECK (amount_realized >= 0),
notes VARCHAR(1000),
-- Colonnes d'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 NOT NULL DEFAULT TRUE
);
-- Index pour budget_lines
CREATE INDEX idx_budget_line_budget ON budget_lines(budget_id);
CREATE INDEX idx_budget_line_category ON budget_lines(category);
-- =====================================================
-- Commentaires sur les tables
-- =====================================================
COMMENT ON TABLE transaction_approvals IS 'Approbations de transactions financières avec workflow multi-niveaux';
COMMENT ON TABLE approver_actions IS 'Actions des approbateurs (approve/reject) sur les demandes d''approbation';
COMMENT ON TABLE budgets IS 'Budgets prévisionnels (mensuel/trimestriel/annuel) avec suivi de réalisation';
COMMENT ON TABLE budget_lines IS 'Lignes budgétaires détaillées par catégorie';
-- =====================================================
-- Commentaires sur les colonnes clés
-- =====================================================
COMMENT ON COLUMN transaction_approvals.required_level IS 'Niveau d''approbation requis selon le montant (LEVEL1=1 approbateur, LEVEL2=2, LEVEL3=3)';
COMMENT ON COLUMN transaction_approvals.status IS 'Statut: PENDING → APPROVED → VALIDATED ou REJECTED';
COMMENT ON COLUMN transaction_approvals.expires_at IS 'Date d''expiration de la demande (timeout, défaut 7 jours)';
COMMENT ON COLUMN budgets.period IS 'Période du budget: MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL';
COMMENT ON COLUMN budgets.total_planned IS 'Somme des montants prévus de toutes les lignes';
COMMENT ON COLUMN budgets.total_realized IS 'Somme des montants réalisés de toutes les lignes';
COMMENT ON COLUMN budget_lines.category IS 'Catégorie budgétaire: CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER';

View File

@@ -0,0 +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