diff --git a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
index 9f7bdd7..1e7924f 100644
--- a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
+++ b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
@@ -1,138 +1,138 @@
-package de.lions.unionflow.server.auth;
-
-import jakarta.annotation.security.PermitAll;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Response;
-import org.jboss.logging.Logger;
-
-/**
- * Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application
- * mobile.
- */
-@Path("/auth")
-@PermitAll
-public class AuthCallbackResource {
-
- private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
-
- /**
- * Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile
- * avec les paramètres reçus.
- */
- @GET
- @Path("/callback")
- public Response handleCallback(
- @QueryParam("code") String code,
- @QueryParam("state") String state,
- @QueryParam("session_state") String sessionState,
- @QueryParam("error") String error,
- @QueryParam("error_description") String errorDescription) {
-
- try {
- // Log des paramètres reçus pour debug
- log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s",
- code, state, sessionState, error, errorDescription);
-
- // URL de redirection simple vers l'application mobile
- String redirectUrl = "dev.lions.unionflow-mobile://callback";
-
- // Si nous avons un code d'autorisation, c'est un succès
- if (code != null && !code.isEmpty()) {
- redirectUrl += "?code=" + code;
- if (state != null && !state.isEmpty()) {
- redirectUrl += "&state=" + state;
- }
- } else if (error != null) {
- redirectUrl += "?error=" + error;
- if (errorDescription != null) {
- redirectUrl += "&error_description=" + errorDescription;
- }
- }
-
- // Page HTML simple qui redirige automatiquement vers l'app mobile
- String html =
- """
-
-
-
- Redirection vers UnionFlow
-
-
-
-
-
-
-
🔐 Authentification réussie
-
-
Redirection vers l'application UnionFlow...
-
Si la redirection ne fonctionne pas automatiquement,
- cliquez ici
-
-
-
-
-"""
- .formatted(redirectUrl, redirectUrl, redirectUrl);
-
- return Response.ok(html).type("text/html").build();
-
- } catch (Exception e) {
- // En cas d'erreur, retourner une page d'erreur simple
- String errorHtml =
- """
-
-
- Erreur d'authentification
-
- ❌ Erreur d'authentification
- Une erreur s'est produite lors de la redirection.
- Veuillez fermer cette page et réessayer.
-
-
- """;
- return Response.status(500).entity(errorHtml).type("text/html").build();
- }
- }
-}
+package de.lions.unionflow.server.auth;
+
+import jakarta.annotation.security.PermitAll;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Response;
+import org.jboss.logging.Logger;
+
+/**
+ * Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application
+ * mobile.
+ */
+@Path("/auth")
+@PermitAll
+public class AuthCallbackResource {
+
+ private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
+
+ /**
+ * Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile
+ * avec les paramètres reçus.
+ */
+ @GET
+ @Path("/callback")
+ public Response handleCallback(
+ @QueryParam("code") String code,
+ @QueryParam("state") String state,
+ @QueryParam("session_state") String sessionState,
+ @QueryParam("error") String error,
+ @QueryParam("error_description") String errorDescription) {
+
+ try {
+ // Log des paramètres reçus pour debug
+ log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s",
+ code, state, sessionState, error, errorDescription);
+
+ // URL de redirection simple vers l'application mobile
+ String redirectUrl = "dev.lions.unionflow-mobile://callback";
+
+ // Si nous avons un code d'autorisation, c'est un succès
+ if (code != null && !code.isEmpty()) {
+ redirectUrl += "?code=" + code;
+ if (state != null && !state.isEmpty()) {
+ redirectUrl += "&state=" + state;
+ }
+ } else if (error != null) {
+ redirectUrl += "?error=" + error;
+ if (errorDescription != null) {
+ redirectUrl += "&error_description=" + errorDescription;
+ }
+ }
+
+ // Page HTML simple qui redirige automatiquement vers l'app mobile
+ String html =
+ """
+
+
+
+ Redirection vers UnionFlow
+
+
+
+
+
+
+
🔐 Authentification réussie
+
+
Redirection vers l'application UnionFlow...
+
Si la redirection ne fonctionne pas automatiquement,
+ cliquez ici
+
+
+
+
+"""
+ .formatted(redirectUrl, redirectUrl, redirectUrl);
+
+ return Response.ok(html).type("text/html").build();
+
+ } catch (Exception e) {
+ // En cas d'erreur, retourner une page d'erreur simple
+ String errorHtml =
+ """
+
+
+ Erreur d'authentification
+
+ ❌ Erreur d'authentification
+ Une erreur s'est produite lors de la redirection.
+ Veuillez fermer cette page et réessayer.
+
+
+ """;
+ return Response.status(500).entity(errorHtml).type("text/html").build();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java b/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java
index 6439f61..21b65ab 100644
--- a/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java
+++ b/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java
@@ -1,250 +1,250 @@
-package dev.lions.unionflow.server;
-
-import io.quarkus.runtime.Quarkus;
-import io.quarkus.runtime.QuarkusApplication;
-import io.quarkus.runtime.annotations.QuarkusMain;
-import jakarta.enterprise.context.ApplicationScoped;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.jboss.logging.Logger;
-
-/**
- * Point d'entrée principal du serveur UnionFlow.
- *
- * UnionFlow est une plateforme de gestion associative multi-tenant
- * destinée aux organisations de solidarité (associations, mutuelles, coopératives,
- * tontines, ONG) en Afrique de l'Ouest.
- *
- *
Architecture
- *
- * - Backend : Quarkus 3.15.1, Java 17, Hibernate Panache
- * - Base de données : PostgreSQL 15 avec Flyway
- * - Authentification : Keycloak 23 (OIDC/OAuth2)
- * - API : REST (JAX-RS) + WebSocket (temps réel)
- * - Paiements : Wave Money CI (Mobile Money)
- *
- *
- * Modules fonctionnels
- *
- * - Organisations — Hiérarchie multi-niveau, types paramétrables,
- * modules activables par organisation
- * - Membres — Adhésion, profils, rôles/permissions RBAC,
- * synchronisation bidirectionnelle avec Keycloak
- * - Cotisations & Paiements — Campagnes récurrentes,
- * ventilation polymorphique, intégration Wave Money
- * - Événements — Création, inscriptions, gestion des présences,
- * géolocalisation
- * - Solidarité — Demandes d'aide, propositions, matching intelligent,
- * workflow de validation multi-étapes
- * - Mutuelles — Épargne, crédit, tontines, suivi des tours
- * - Comptabilité — Plan comptable SYSCOHADA, journaux,
- * écritures automatiques, balance, grand livre
- * - Documents — Gestion polymorphique de pièces jointes
- * (stockage local + métadonnées)
- * - Notifications — Templates multicanaux (email, SMS, push),
- * préférences utilisateur, historique persistant
- * - Analytics & Dashboard — KPIs temps réel via WebSocket,
- * métriques d'activité, tendances, rapports PDF
- * - Administration — Audit trail complet, tickets support,
- * suggestions utilisateurs, favoris
- * - SaaS Multi-tenant — Formules d'abonnement flexibles,
- * souscriptions par organisation, facturation
- * - Configuration dynamique — Table {@code configurations},
- * pas de hardcoding, paramétrage par organisation
- * - Données de référence — Table {@code types_reference}
- * entièrement CRUD-able (évite les enums Java)
- *
- *
- * Inventaire technique
- *
- * - 60 entités JPA — {@code BaseEntity} + {@code AuditEntityListener}
- * pour audit automatique
- * - 46 services CDI — Logique métier transactionnelle
- * - 37 endpoints REST — API JAX-RS avec validation Bean Validation
- * - 49 repositories — Hibernate Panache pour accès données
- * - Migrations Flyway — V1.0 --> V3.0 (schéma complet 60 tables)
- * - Tests — 1127 tests unitaires et d'intégration Quarkus
- * - Couverture — JaCoCo 40% minimum (cible 60%)
- *
- *
- * Patterns et Best Practices
- *
- * - Clean Architecture — Séparation API/Impl/Entity
- * - DTO Pattern — Request/Response distincts (142 DTOs dans server-api)
- * - Repository Pattern — Abstraction accès données
- * - Service Layer — Transactionnel, validation métier
- * - Audit automatique — EntityListener JPA pour traçabilité complète
- * - Soft Delete — Champ {@code actif} sur toutes les entités
- * - Optimistic Locking — Champ {@code version} pour concurrence
- * - Configuration externalisée — MicroProfile Config, pas de hardcoding
- *
- *
- * Sécurité
- *
- * - OIDC avec Keycloak (realm: unionflow)
- * - JWT signature côté backend (HMAC-SHA256)
- * - RBAC avec rôles: SUPER_ADMIN, ADMIN_ORGANISATION, MEMBRE
- * - Permissions granulaires par module
- * - CORS configuré pour client web
- * - HTTPS obligatoire en production
- *
- *
- * @author UnionFlow Team
- * @version 3.0.0
- * @since 2025-01-29
- */
-@QuarkusMain
-@ApplicationScoped
-public class UnionFlowServerApplication implements QuarkusApplication {
-
- private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class);
-
- /** Port HTTP configuré (défaut: 8080). */
- @ConfigProperty(name = "quarkus.http.port", defaultValue = "8080")
- int httpPort;
-
- /** Host HTTP configuré (défaut: 0.0.0.0). */
- @ConfigProperty(name = "quarkus.http.host", defaultValue = "0.0.0.0")
- String httpHost;
-
- /** Nom de l'application. */
- @ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server")
- String applicationName;
-
- /** Version de l'application. */
- @ConfigProperty(name = "quarkus.application.version", defaultValue = "3.0.0")
- String applicationVersion;
-
- /** Profil actif (dev, test, prod). */
- @ConfigProperty(name = "quarkus.profile")
- String activeProfile;
-
- /** Version de Quarkus. */
- @ConfigProperty(name = "quarkus.platform.version", defaultValue = "3.15.1")
- String quarkusVersion;
-
- /**
- * Point d'entrée JVM.
- *
- * Lance l'application Quarkus en mode bloquant.
- * En mode natif, cette méthode démarre instantanément (< 50ms).
- *
- * @param args Arguments de ligne de commande (non utilisés)
- */
- public static void main(String... args) {
- Quarkus.run(UnionFlowServerApplication.class, args);
- }
-
- /**
- * Méthode de démarrage de l'application.
- *
- *
Affiche les informations de démarrage (URLs, configuration)
- * puis attend le signal d'arrêt (SIGTERM, SIGINT).
- *
- * @param args Arguments passés depuis main()
- * @return Code de sortie (0 = succès)
- * @throws Exception Si erreur fatale au démarrage
- */
- @Override
- public int run(String... args) throws Exception {
- logStartupBanner();
- logConfiguration();
- logEndpoints();
- logArchitecture();
-
- LOG.info("UnionFlow Server prêt à recevoir des requêtes");
- LOG.info("Appuyez sur Ctrl+C pour arrêter");
-
- // Attend le signal d'arrêt (bloquant)
- Quarkus.waitForExit();
-
- LOG.info("UnionFlow Server arrêté proprement");
- return 0;
- }
-
- /**
- * Affiche la bannière ASCII de démarrage.
- */
- private void logStartupBanner() {
- LOG.info("----------------------------------------------------------");
- LOG.info("- -");
- LOG.info("- UNIONFLOW SERVER v" + applicationVersion + " ");
- LOG.info("- Plateforme de Gestion Associative Multi-Tenant -");
- LOG.info("- -");
- LOG.info("----------------------------------------------------------");
- }
-
- /**
- * Affiche la configuration active.
- */
- private void logConfiguration() {
- LOG.infof("Profil : %s", activeProfile);
- LOG.infof("Application : %s v%s", applicationName, applicationVersion);
- LOG.infof("Java : %s", System.getProperty("java.version"));
- LOG.infof("Quarkus : %s", quarkusVersion);
- }
-
- /**
- * Affiche les URLs des endpoints principaux.
- */
- private void logEndpoints() {
- String baseUrl = buildBaseUrl();
-
- LOG.info("--------------------------------------------------------------");
- LOG.info("📡 Endpoints disponibles:");
- LOG.infof(" - API REST --> %s/api", baseUrl);
- LOG.infof(" - Swagger UI --> %s/q/swagger-ui", baseUrl);
- LOG.infof(" - Health Check --> %s/q/health", baseUrl);
- LOG.infof(" - Metrics --> %s/q/metrics", baseUrl);
- LOG.infof(" - OpenAPI --> %s/q/openapi", baseUrl);
-
- if ("dev".equals(activeProfile)) {
- LOG.infof(" - Dev UI --> %s/q/dev", baseUrl);
- LOG.infof(" - H2 Console --> %s/q/dev/io.quarkus.quarkus-datasource/datasources", baseUrl);
- }
-
- LOG.info("--------------------------------------------------------------");
- }
-
- /**
- * Affiche l'inventaire de l'architecture.
- */
- private void logArchitecture() {
- LOG.info(" Architecture:");
- LOG.info(" - 60 Entités JPA");
- LOG.info(" - 46 Services CDI");
- LOG.info(" - 37 Endpoints REST");
- LOG.info(" - 49 Repositories Panache");
- LOG.info(" - 142 DTOs (Request/Response)");
- LOG.info(" - 1127 Tests automatisés");
- LOG.info("--------------------------------------------------------------");
- }
-
- /**
- * Retourne la valeur de la variable d'environnement UNIONFLOW_DOMAIN.
- * Méthode protégée pour permettre la substitution en tests.
- *
- * @return valeur de UNIONFLOW_DOMAIN, ou null si non définie
- */
- protected String getUnionflowDomain() {
- return System.getenv("UNIONFLOW_DOMAIN");
- }
-
- /**
- * Construit l'URL de base de l'application.
- *
- * @return URL complète (ex: http://localhost:8080)
- */
- String buildBaseUrl() {
- // En production, utiliser le nom de domaine configuré
- if ("prod".equals(activeProfile)) {
- String domain = getUnionflowDomain();
- if (domain != null && !domain.isEmpty()) {
- return "https://" + domain;
- }
- }
-
- // En dev/test, utiliser localhost
- String host = "0.0.0.0".equals(httpHost) ? "localhost" : httpHost;
- return String.format("http://%s:%d", host, httpPort);
- }
-}
+package dev.lions.unionflow.server;
+
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.QuarkusApplication;
+import io.quarkus.runtime.annotations.QuarkusMain;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+/**
+ * Point d'entrée principal du serveur UnionFlow.
+ *
+ *
UnionFlow est une plateforme de gestion associative multi-tenant
+ * destinée aux organisations de solidarité (associations, mutuelles, coopératives,
+ * tontines, ONG) en Afrique de l'Ouest.
+ *
+ *
Architecture
+ *
+ * - Backend : Quarkus 3.15.1, Java 17, Hibernate Panache
+ * - Base de données : PostgreSQL 15 avec Flyway
+ * - Authentification : Keycloak 23 (OIDC/OAuth2)
+ * - API : REST (JAX-RS) + WebSocket (temps réel)
+ * - Paiements : Wave Money CI (Mobile Money)
+ *
+ *
+ * Modules fonctionnels
+ *
+ * - Organisations — Hiérarchie multi-niveau, types paramétrables,
+ * modules activables par organisation
+ * - Membres — Adhésion, profils, rôles/permissions RBAC,
+ * synchronisation bidirectionnelle avec Keycloak
+ * - Cotisations & Paiements — Campagnes récurrentes,
+ * ventilation polymorphique, intégration Wave Money
+ * - Événements — Création, inscriptions, gestion des présences,
+ * géolocalisation
+ * - Solidarité — Demandes d'aide, propositions, matching intelligent,
+ * workflow de validation multi-étapes
+ * - Mutuelles — Épargne, crédit, tontines, suivi des tours
+ * - Comptabilité — Plan comptable SYSCOHADA, journaux,
+ * écritures automatiques, balance, grand livre
+ * - Documents — Gestion polymorphique de pièces jointes
+ * (stockage local + métadonnées)
+ * - Notifications — Templates multicanaux (email, SMS, push),
+ * préférences utilisateur, historique persistant
+ * - Analytics & Dashboard — KPIs temps réel via WebSocket,
+ * métriques d'activité, tendances, rapports PDF
+ * - Administration — Audit trail complet, tickets support,
+ * suggestions utilisateurs, favoris
+ * - SaaS Multi-tenant — Formules d'abonnement flexibles,
+ * souscriptions par organisation, facturation
+ * - Configuration dynamique — Table {@code configurations},
+ * pas de hardcoding, paramétrage par organisation
+ * - Données de référence — Table {@code types_reference}
+ * entièrement CRUD-able (évite les enums Java)
+ *
+ *
+ * Inventaire technique
+ *
+ * - 60 entités JPA — {@code BaseEntity} + {@code AuditEntityListener}
+ * pour audit automatique
+ * - 46 services CDI — Logique métier transactionnelle
+ * - 37 endpoints REST — API JAX-RS avec validation Bean Validation
+ * - 49 repositories — Hibernate Panache pour accès données
+ * - Migrations Flyway — V1.0 --> V3.0 (schéma complet 60 tables)
+ * - Tests — 1127 tests unitaires et d'intégration Quarkus
+ * - Couverture — JaCoCo 40% minimum (cible 60%)
+ *
+ *
+ * Patterns et Best Practices
+ *
+ * - Clean Architecture — Séparation API/Impl/Entity
+ * - DTO Pattern — Request/Response distincts (142 DTOs dans server-api)
+ * - Repository Pattern — Abstraction accès données
+ * - Service Layer — Transactionnel, validation métier
+ * - Audit automatique — EntityListener JPA pour traçabilité complète
+ * - Soft Delete — Champ {@code actif} sur toutes les entités
+ * - Optimistic Locking — Champ {@code version} pour concurrence
+ * - Configuration externalisée — MicroProfile Config, pas de hardcoding
+ *
+ *
+ * Sécurité
+ *
+ * - OIDC avec Keycloak (realm: unionflow)
+ * - JWT signature côté backend (HMAC-SHA256)
+ * - RBAC avec rôles: SUPER_ADMIN, ADMIN_ORGANISATION, MEMBRE
+ * - Permissions granulaires par module
+ * - CORS configuré pour client web
+ * - HTTPS obligatoire en production
+ *
+ *
+ * @author UnionFlow Team
+ * @version 3.0.0
+ * @since 2025-01-29
+ */
+@QuarkusMain
+@ApplicationScoped
+public class UnionFlowServerApplication implements QuarkusApplication {
+
+ private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class);
+
+ /** Port HTTP configuré (défaut: 8080). */
+ @ConfigProperty(name = "quarkus.http.port", defaultValue = "8080")
+ int httpPort;
+
+ /** Host HTTP configuré (défaut: 0.0.0.0). */
+ @ConfigProperty(name = "quarkus.http.host", defaultValue = "0.0.0.0")
+ String httpHost;
+
+ /** Nom de l'application. */
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server")
+ String applicationName;
+
+ /** Version de l'application. */
+ @ConfigProperty(name = "quarkus.application.version", defaultValue = "3.0.0")
+ String applicationVersion;
+
+ /** Profil actif (dev, test, prod). */
+ @ConfigProperty(name = "quarkus.profile")
+ String activeProfile;
+
+ /** Version de Quarkus. */
+ @ConfigProperty(name = "quarkus.platform.version", defaultValue = "3.15.1")
+ String quarkusVersion;
+
+ /**
+ * Point d'entrée JVM.
+ *
+ * Lance l'application Quarkus en mode bloquant.
+ * En mode natif, cette méthode démarre instantanément (< 50ms).
+ *
+ * @param args Arguments de ligne de commande (non utilisés)
+ */
+ public static void main(String... args) {
+ Quarkus.run(UnionFlowServerApplication.class, args);
+ }
+
+ /**
+ * Méthode de démarrage de l'application.
+ *
+ *
Affiche les informations de démarrage (URLs, configuration)
+ * puis attend le signal d'arrêt (SIGTERM, SIGINT).
+ *
+ * @param args Arguments passés depuis main()
+ * @return Code de sortie (0 = succès)
+ * @throws Exception Si erreur fatale au démarrage
+ */
+ @Override
+ public int run(String... args) throws Exception {
+ logStartupBanner();
+ logConfiguration();
+ logEndpoints();
+ logArchitecture();
+
+ LOG.info("UnionFlow Server prêt à recevoir des requêtes");
+ LOG.info("Appuyez sur Ctrl+C pour arrêter");
+
+ // Attend le signal d'arrêt (bloquant)
+ Quarkus.waitForExit();
+
+ LOG.info("UnionFlow Server arrêté proprement");
+ return 0;
+ }
+
+ /**
+ * Affiche la bannière ASCII de démarrage.
+ */
+ private void logStartupBanner() {
+ LOG.info("----------------------------------------------------------");
+ LOG.info("- -");
+ LOG.info("- UNIONFLOW SERVER v" + applicationVersion + " ");
+ LOG.info("- Plateforme de Gestion Associative Multi-Tenant -");
+ LOG.info("- -");
+ LOG.info("----------------------------------------------------------");
+ }
+
+ /**
+ * Affiche la configuration active.
+ */
+ private void logConfiguration() {
+ LOG.infof("Profil : %s", activeProfile);
+ LOG.infof("Application : %s v%s", applicationName, applicationVersion);
+ LOG.infof("Java : %s", System.getProperty("java.version"));
+ LOG.infof("Quarkus : %s", quarkusVersion);
+ }
+
+ /**
+ * Affiche les URLs des endpoints principaux.
+ */
+ private void logEndpoints() {
+ String baseUrl = buildBaseUrl();
+
+ LOG.info("--------------------------------------------------------------");
+ LOG.info("📡 Endpoints disponibles:");
+ LOG.infof(" - API REST --> %s/api", baseUrl);
+ LOG.infof(" - Swagger UI --> %s/q/swagger-ui", baseUrl);
+ LOG.infof(" - Health Check --> %s/q/health", baseUrl);
+ LOG.infof(" - Metrics --> %s/q/metrics", baseUrl);
+ LOG.infof(" - OpenAPI --> %s/q/openapi", baseUrl);
+
+ if ("dev".equals(activeProfile)) {
+ LOG.infof(" - Dev UI --> %s/q/dev", baseUrl);
+ LOG.infof(" - H2 Console --> %s/q/dev/io.quarkus.quarkus-datasource/datasources", baseUrl);
+ }
+
+ LOG.info("--------------------------------------------------------------");
+ }
+
+ /**
+ * Affiche l'inventaire de l'architecture.
+ */
+ private void logArchitecture() {
+ LOG.info(" Architecture:");
+ LOG.info(" - 60 Entités JPA");
+ LOG.info(" - 46 Services CDI");
+ LOG.info(" - 37 Endpoints REST");
+ LOG.info(" - 49 Repositories Panache");
+ LOG.info(" - 142 DTOs (Request/Response)");
+ LOG.info(" - 1127 Tests automatisés");
+ LOG.info("--------------------------------------------------------------");
+ }
+
+ /**
+ * Retourne la valeur de la variable d'environnement UNIONFLOW_DOMAIN.
+ * Méthode protégée pour permettre la substitution en tests.
+ *
+ * @return valeur de UNIONFLOW_DOMAIN, ou null si non définie
+ */
+ protected String getUnionflowDomain() {
+ return System.getenv("UNIONFLOW_DOMAIN");
+ }
+
+ /**
+ * Construit l'URL de base de l'application.
+ *
+ * @return URL complète (ex: http://localhost:8080)
+ */
+ String buildBaseUrl() {
+ // En production, utiliser le nom de domaine configuré
+ if ("prod".equals(activeProfile)) {
+ String domain = getUnionflowDomain();
+ if (domain != null && !domain.isEmpty()) {
+ return "https://" + domain;
+ }
+ }
+
+ // En dev/test, utiliser localhost
+ String host = "0.0.0.0".equals(httpHost) ? "localhost" : httpHost;
+ return String.format("http://%s:%d", host, httpPort);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java b/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java
index 9531108..c4261ba 100644
--- a/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java
+++ b/src/main/java/dev/lions/unionflow/server/client/AdminServiceTokenHeadersFactory.java
@@ -1,48 +1,48 @@
-package dev.lions.unionflow.server.client;
-
-import io.quarkus.oidc.client.NamedOidcClient;
-import io.quarkus.oidc.client.OidcClient;
-import io.quarkus.oidc.client.Tokens;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.core.MultivaluedHashMap;
-import jakarta.ws.rs.core.MultivaluedMap;
-import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
-import org.jboss.logging.Logger;
-
-/**
- * Injecte le token du service account "admin-service" (client credentials grant)
- * dans tous les appels faits via {@link AdminUserServiceClient} et {@link AdminRoleServiceClient}.
- *
- *
Utilise directement l'API {@link OidcClient} pour récupérer/rafraîchir le token.
- * Cette approche explicite évite toute ambiguïté avec {@code @OidcClientFilter} quand
- * plusieurs interfaces REST partagent le même configKey.
- */
-@ApplicationScoped
-public class AdminServiceTokenHeadersFactory implements ClientHeadersFactory {
-
- private static final Logger LOG = Logger.getLogger(AdminServiceTokenHeadersFactory.class);
-
- @Inject
- @NamedOidcClient("admin-service")
- OidcClient adminOidcClient;
-
- @Override
- public MultivaluedMap update(
- MultivaluedMap incomingHeaders,
- MultivaluedMap clientOutgoingHeaders) {
-
- MultivaluedMap result = new MultivaluedHashMap<>();
- try {
- Tokens tokens = adminOidcClient.getTokens().await().indefinitely();
- result.add("Authorization", "Bearer " + tokens.getAccessToken());
- LOG.debugf("Token service account injecté pour admin-service (longueur: %d)",
- tokens.getAccessToken().length());
- } catch (Exception e) {
- LOG.errorf("Impossible d'obtenir le token service account 'admin-service': %s", e.getMessage());
- throw new jakarta.ws.rs.ServiceUnavailableException(
- "Service d'authentification interne indisponible: " + e.getMessage());
- }
- return result;
- }
-}
+package dev.lions.unionflow.server.client;
+
+import io.quarkus.oidc.client.NamedOidcClient;
+import io.quarkus.oidc.client.OidcClient;
+import io.quarkus.oidc.client.Tokens;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
+import org.jboss.logging.Logger;
+
+/**
+ * Injecte le token du service account "admin-service" (client credentials grant)
+ * dans tous les appels faits via {@link AdminUserServiceClient} et {@link AdminRoleServiceClient}.
+ *
+ * Utilise directement l'API {@link OidcClient} pour récupérer/rafraîchir le token.
+ * Cette approche explicite évite toute ambiguïté avec {@code @OidcClientFilter} quand
+ * plusieurs interfaces REST partagent le même configKey.
+ */
+@ApplicationScoped
+public class AdminServiceTokenHeadersFactory implements ClientHeadersFactory {
+
+ private static final Logger LOG = Logger.getLogger(AdminServiceTokenHeadersFactory.class);
+
+ @Inject
+ @NamedOidcClient("admin-service")
+ OidcClient adminOidcClient;
+
+ @Override
+ public MultivaluedMap update(
+ MultivaluedMap incomingHeaders,
+ MultivaluedMap clientOutgoingHeaders) {
+
+ MultivaluedMap result = new MultivaluedHashMap<>();
+ try {
+ Tokens tokens = adminOidcClient.getTokens().await().indefinitely();
+ result.add("Authorization", "Bearer " + tokens.getAccessToken());
+ LOG.debugf("Token service account injecté pour admin-service (longueur: %d)",
+ tokens.getAccessToken().length());
+ } catch (Exception e) {
+ LOG.errorf("Impossible d'obtenir le token service account 'admin-service': %s", e.getMessage());
+ throw new jakarta.ws.rs.ServiceUnavailableException(
+ "Service d'authentification interne indisponible: " + e.getMessage());
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/client/JwtPropagationFilter.java b/src/main/java/dev/lions/unionflow/server/client/JwtPropagationFilter.java
index aad6e2a..d9c44a4 100644
--- a/src/main/java/dev/lions/unionflow/server/client/JwtPropagationFilter.java
+++ b/src/main/java/dev/lions/unionflow/server/client/JwtPropagationFilter.java
@@ -1,71 +1,71 @@
-package dev.lions.unionflow.server.client;
-
-import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
-import io.quarkus.security.identity.SecurityIdentity;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.client.ClientRequestContext;
-import jakarta.ws.rs.client.ClientRequestFilter;
-import jakarta.ws.rs.core.HttpHeaders;
-import jakarta.ws.rs.ext.Provider;
-import org.eclipse.microprofile.jwt.JsonWebToken;
-import org.jboss.logging.Logger;
-
-import java.io.IOException;
-
-/**
- * Filtre REST Client qui propage le token JWT de la requête entrante.
- *
- * NE PAS annoter avec {@code @Provider} — cela l'enregistrerait GLOBALEMENT
- * sur tous les REST clients, y compris AdminUserServiceClient/AdminRoleServiceClient
- * qui utilisent AdminServiceTokenHeadersFactory (service account). Le filtre global
- * écraserait le token de service account avec le JWT utilisateur → 401 sur LUM.
- *
- *
La propagation JWT est assurée par {@link OidcTokenPropagationHeadersFactory}
- * sur les clients qui en ont besoin ({@code @RegisterClientHeaders}).
- */
-public class JwtPropagationFilter implements ClientRequestFilter {
-
- private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class);
-
- @Inject
- SecurityIdentity securityIdentity;
-
- @Override
- public void filter(ClientRequestContext requestContext) throws IOException {
- if (securityIdentity != null && !securityIdentity.isAnonymous()) {
- // Récupérer le token JWT depuis le principal
- if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
- OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal();
- String token = principal.getRawToken();
-
- if (token != null && !token.isBlank()) {
- requestContext.getHeaders().putSingle(
- HttpHeaders.AUTHORIZATION,
- "Bearer " + token
- );
- LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
- } else {
- LOG.warnf("Token JWT vide pour %s", requestContext.getUri());
- }
- } else if (securityIdentity.getPrincipal() instanceof JsonWebToken) {
- JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal();
- String token = jwt.getRawToken();
-
- if (token != null && !token.isBlank()) {
- requestContext.getHeaders().putSingle(
- HttpHeaders.AUTHORIZATION,
- "Bearer " + token
- );
- LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
- }
- } else {
- LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)",
- requestContext.getUri(),
- securityIdentity.getPrincipal().getClass().getName());
- }
- } else {
- LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s",
- requestContext.getUri());
- }
- }
-}
+package dev.lions.unionflow.server.client;
+
+import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
+import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.ext.Provider;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+import org.jboss.logging.Logger;
+
+import java.io.IOException;
+
+/**
+ * Filtre REST Client qui propage le token JWT de la requête entrante.
+ *
+ *
NE PAS annoter avec {@code @Provider} — cela l'enregistrerait GLOBALEMENT
+ * sur tous les REST clients, y compris AdminUserServiceClient/AdminRoleServiceClient
+ * qui utilisent AdminServiceTokenHeadersFactory (service account). Le filtre global
+ * écraserait le token de service account avec le JWT utilisateur → 401 sur LUM.
+ *
+ *
La propagation JWT est assurée par {@link OidcTokenPropagationHeadersFactory}
+ * sur les clients qui en ont besoin ({@code @RegisterClientHeaders}).
+ */
+public class JwtPropagationFilter implements ClientRequestFilter {
+
+ private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class);
+
+ @Inject
+ SecurityIdentity securityIdentity;
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ if (securityIdentity != null && !securityIdentity.isAnonymous()) {
+ // Récupérer le token JWT depuis le principal
+ if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
+ OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal();
+ String token = principal.getRawToken();
+
+ if (token != null && !token.isBlank()) {
+ requestContext.getHeaders().putSingle(
+ HttpHeaders.AUTHORIZATION,
+ "Bearer " + token
+ );
+ LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
+ } else {
+ LOG.warnf("Token JWT vide pour %s", requestContext.getUri());
+ }
+ } else if (securityIdentity.getPrincipal() instanceof JsonWebToken) {
+ JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal();
+ String token = jwt.getRawToken();
+
+ if (token != null && !token.isBlank()) {
+ requestContext.getHeaders().putSingle(
+ HttpHeaders.AUTHORIZATION,
+ "Bearer " + token
+ );
+ LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
+ }
+ } else {
+ LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)",
+ requestContext.getUri(),
+ securityIdentity.getPrincipal().getClass().getName());
+ }
+ } else {
+ LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s",
+ requestContext.getUri());
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/client/OidcTokenPropagationHeadersFactory.java b/src/main/java/dev/lions/unionflow/server/client/OidcTokenPropagationHeadersFactory.java
index 7152f05..0c0d288 100644
--- a/src/main/java/dev/lions/unionflow/server/client/OidcTokenPropagationHeadersFactory.java
+++ b/src/main/java/dev/lions/unionflow/server/client/OidcTokenPropagationHeadersFactory.java
@@ -1,72 +1,72 @@
-package dev.lions.unionflow.server.client;
-
-import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
-import io.quarkus.security.identity.SecurityIdentity;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Instance;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.core.MultivaluedHashMap;
-import jakarta.ws.rs.core.MultivaluedMap;
-import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
-import org.jboss.logging.Logger;
-
-/**
- * Factory pour propager automatiquement le token JWT OIDC
- * vers les appels REST Client (compatible Quarkus REST).
- *
- * Stratégie : copier le header Authorization de la requête entrante
- * ou récupérer le token depuis SecurityIdentity si disponible.
- */
-@ApplicationScoped
-public class OidcTokenPropagationHeadersFactory implements ClientHeadersFactory {
-
- private static final Logger LOG = Logger.getLogger(OidcTokenPropagationHeadersFactory.class);
-
- @Inject
- Instance securityIdentity;
-
- @Override
- public MultivaluedMap update(
- MultivaluedMap incomingHeaders,
- MultivaluedMap clientOutgoingHeaders) {
-
- MultivaluedMap result = new MultivaluedHashMap<>();
-
- // STRATÉGIE 1 : Copier directement le header Authorization de la requête entrante
- if (incomingHeaders != null && incomingHeaders.containsKey("Authorization")) {
- String authHeader = incomingHeaders.getFirst("Authorization");
- if (authHeader != null && !authHeader.isBlank()) {
- result.add("Authorization", authHeader);
- LOG.infof("✅ Token JWT propagé depuis incomingHeaders (longueur: %d)", authHeader.length());
- return result;
- }
- }
-
- // STRATÉGIE 2 : Récupérer depuis SecurityIdentity
- // En contexte CDI, securityIdentity.isResolvable() est toujours true.
- SecurityIdentity identity = securityIdentity.get();
-
- if (identity != null && !identity.isAnonymous()) {
- if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
- OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
- String token = principal.getRawToken();
-
- if (token != null && !token.isBlank()) {
- result.add("Authorization", "Bearer " + token);
- LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length());
- return result;
- } else {
- LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity");
- }
- } else {
- LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)",
- identity.getPrincipal() != null ? identity.getPrincipal().getClass().getName() : "null");
- }
- } else {
- LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme");
- }
-
- LOG.errorf("❌ Impossible de propager le token JWT - aucune stratégie n'a fonctionné");
- return result;
- }
-}
+package dev.lions.unionflow.server.client;
+
+import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
+import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Instance;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
+import org.jboss.logging.Logger;
+
+/**
+ * Factory pour propager automatiquement le token JWT OIDC
+ * vers les appels REST Client (compatible Quarkus REST).
+ *
+ * Stratégie : copier le header Authorization de la requête entrante
+ * ou récupérer le token depuis SecurityIdentity si disponible.
+ */
+@ApplicationScoped
+public class OidcTokenPropagationHeadersFactory implements ClientHeadersFactory {
+
+ private static final Logger LOG = Logger.getLogger(OidcTokenPropagationHeadersFactory.class);
+
+ @Inject
+ Instance securityIdentity;
+
+ @Override
+ public MultivaluedMap update(
+ MultivaluedMap incomingHeaders,
+ MultivaluedMap clientOutgoingHeaders) {
+
+ MultivaluedMap result = new MultivaluedHashMap<>();
+
+ // STRATÉGIE 1 : Copier directement le header Authorization de la requête entrante
+ if (incomingHeaders != null && incomingHeaders.containsKey("Authorization")) {
+ String authHeader = incomingHeaders.getFirst("Authorization");
+ if (authHeader != null && !authHeader.isBlank()) {
+ result.add("Authorization", authHeader);
+ LOG.infof("✅ Token JWT propagé depuis incomingHeaders (longueur: %d)", authHeader.length());
+ return result;
+ }
+ }
+
+ // STRATÉGIE 2 : Récupérer depuis SecurityIdentity
+ // En contexte CDI, securityIdentity.isResolvable() est toujours true.
+ SecurityIdentity identity = securityIdentity.get();
+
+ if (identity != null && !identity.isAnonymous()) {
+ if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
+ OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
+ String token = principal.getRawToken();
+
+ if (token != null && !token.isBlank()) {
+ result.add("Authorization", "Bearer " + token);
+ LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length());
+ return result;
+ } else {
+ LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity");
+ }
+ } else {
+ LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)",
+ identity.getPrincipal() != null ? identity.getPrincipal().getClass().getName() : "null");
+ }
+ } else {
+ LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme");
+ }
+
+ LOG.errorf("❌ Impossible de propager le token JWT - aucune stratégie n'a fonctionné");
+ return result;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/client/RoleServiceClient.java b/src/main/java/dev/lions/unionflow/server/client/RoleServiceClient.java
index a1202ec..77247e2 100644
--- a/src/main/java/dev/lions/unionflow/server/client/RoleServiceClient.java
+++ b/src/main/java/dev/lions/unionflow/server/client/RoleServiceClient.java
@@ -1,57 +1,57 @@
-package dev.lions.unionflow.server.client;
-
-import dev.lions.user.manager.dto.role.RoleDTO;
-import jakarta.ws.rs.*;
-import jakarta.ws.rs.core.MediaType;
-import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
-import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
-
-import java.util.List;
-
-/**
- * REST Client pour l'API rôles de lions-user-manager (Keycloak).
- * Même base URL que UserServiceClient (configKey = lions-user-manager-api).
- */
-@Path("/api/roles")
-@RegisterRestClient(configKey = "lions-user-manager-api")
-@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
-@Produces(MediaType.APPLICATION_JSON)
-@Consumes(MediaType.APPLICATION_JSON)
-public interface RoleServiceClient {
-
- @GET
- @Path("/realm")
- List getRealmRoles(@QueryParam("realm") String realmName);
-
- @GET
- @Path("/user/realm/{userId}")
- List getUserRealmRoles(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName
- );
-
- @POST
- @Path("/assign/realm/{userId}")
- void assignRealmRoles(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName,
- RoleNamesRequest request
- );
-
- @POST
- @Path("/revoke/realm/{userId}")
- void revokeRealmRoles(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName,
- RoleNamesRequest request
- );
-
- /** Corps de requête pour assign/revoke (compatible lions-user-manager). */
- class RoleNamesRequest {
- public List roleNames;
- public RoleNamesRequest() {}
- public RoleNamesRequest(List roleNames) { this.roleNames = roleNames; }
- public List getRoleNames() { return roleNames; }
- public void setRoleNames(List roleNames) { this.roleNames = roleNames; }
- }
-}
+package dev.lions.unionflow.server.client;
+
+import dev.lions.user.manager.dto.role.RoleDTO;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import java.util.List;
+
+/**
+ * REST Client pour l'API rôles de lions-user-manager (Keycloak).
+ * Même base URL que UserServiceClient (configKey = lions-user-manager-api).
+ */
+@Path("/api/roles")
+@RegisterRestClient(configKey = "lions-user-manager-api")
+@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface RoleServiceClient {
+
+ @GET
+ @Path("/realm")
+ List getRealmRoles(@QueryParam("realm") String realmName);
+
+ @GET
+ @Path("/user/realm/{userId}")
+ List getUserRealmRoles(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName
+ );
+
+ @POST
+ @Path("/assign/realm/{userId}")
+ void assignRealmRoles(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName,
+ RoleNamesRequest request
+ );
+
+ @POST
+ @Path("/revoke/realm/{userId}")
+ void revokeRealmRoles(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName,
+ RoleNamesRequest request
+ );
+
+ /** Corps de requête pour assign/revoke (compatible lions-user-manager). */
+ class RoleNamesRequest {
+ public List roleNames;
+ public RoleNamesRequest() {}
+ public RoleNamesRequest(List roleNames) { this.roleNames = roleNames; }
+ public List getRoleNames() { return roleNames; }
+ public void setRoleNames(List roleNames) { this.roleNames = roleNames; }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/client/UserServiceClient.java b/src/main/java/dev/lions/unionflow/server/client/UserServiceClient.java
index fb1b5f2..c59767c 100644
--- a/src/main/java/dev/lions/unionflow/server/client/UserServiceClient.java
+++ b/src/main/java/dev/lions/unionflow/server/client/UserServiceClient.java
@@ -1,77 +1,77 @@
-package dev.lions.unionflow.server.client;
-
-import dev.lions.user.manager.dto.user.UserDTO;
-import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
-import dev.lions.user.manager.dto.user.UserSearchResultDTO;
-import jakarta.ws.rs.*;
-import jakarta.ws.rs.core.MediaType;
-import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
-import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
-
-/**
- * REST Client pour le service de gestion des utilisateurs Keycloak
- * via lions-user-manager API
- *
- * Configuration dans application.properties:
- * quarkus.rest-client.lions-user-manager-api.url=http://localhost:8081
- */
-@Path("/api/users")
-@RegisterRestClient(configKey = "lions-user-manager-api")
-@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
-@Produces(MediaType.APPLICATION_JSON)
-@Consumes(MediaType.APPLICATION_JSON)
-public interface UserServiceClient {
-
- /**
- * Rechercher des utilisateurs selon des critères
- */
- @POST
- @Path("/search")
- UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria);
-
- /**
- * Récupérer un utilisateur par ID
- */
- @GET
- @Path("/{userId}")
- UserDTO getUserById(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName);
-
- /**
- * Créer un nouvel utilisateur
- */
- @POST
- UserDTO createUser(
- UserDTO user,
- @QueryParam("realm") String realmName);
-
- /**
- * Mettre à jour un utilisateur
- */
- @PUT
- @Path("/{userId}")
- UserDTO updateUser(
- @PathParam("userId") String userId,
- UserDTO user,
- @QueryParam("realm") String realmName);
-
- /**
- * Supprimer un utilisateur
- */
- @DELETE
- @Path("/{userId}")
- void deleteUser(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName);
-
- /**
- * Envoyer un email de vérification
- */
- @POST
- @Path("/{userId}/send-verification-email")
- void sendVerificationEmail(
- @PathParam("userId") String userId,
- @QueryParam("realm") String realmName);
-
-}
+package dev.lions.unionflow.server.client;
+
+import dev.lions.user.manager.dto.user.UserDTO;
+import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
+import dev.lions.user.manager.dto.user.UserSearchResultDTO;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+/**
+ * REST Client pour le service de gestion des utilisateurs Keycloak
+ * via lions-user-manager API
+ *
+ * Configuration dans application.properties:
+ * quarkus.rest-client.lions-user-manager-api.url=http://localhost:8081
+ */
+@Path("/api/users")
+@RegisterRestClient(configKey = "lions-user-manager-api")
+@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface UserServiceClient {
+
+ /**
+ * Rechercher des utilisateurs selon des critères
+ */
+ @POST
+ @Path("/search")
+ UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria);
+
+ /**
+ * Récupérer un utilisateur par ID
+ */
+ @GET
+ @Path("/{userId}")
+ UserDTO getUserById(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName);
+
+ /**
+ * Créer un nouvel utilisateur
+ */
+ @POST
+ UserDTO createUser(
+ UserDTO user,
+ @QueryParam("realm") String realmName);
+
+ /**
+ * Mettre à jour un utilisateur
+ */
+ @PUT
+ @Path("/{userId}")
+ UserDTO updateUser(
+ @PathParam("userId") String userId,
+ UserDTO user,
+ @QueryParam("realm") String realmName);
+
+ /**
+ * Supprimer un utilisateur
+ */
+ @DELETE
+ @Path("/{userId}")
+ void deleteUser(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName);
+
+ /**
+ * Envoyer un email de vérification
+ */
+ @POST
+ @Path("/{userId}/send-verification-email")
+ void sendVerificationEmail(
+ @PathParam("userId") String userId,
+ @QueryParam("realm") String realmName);
+
+}
diff --git a/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java b/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java
index b41aff2..3378ae4 100644
--- a/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java
+++ b/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java
@@ -1,143 +1,143 @@
-package dev.lions.unionflow.server.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import dev.lions.unionflow.server.entity.Evenement;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le
- * format attendu par
- * l'application mobile Flutter
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-01-16
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class EvenementMobileDTO {
-
- private UUID id;
- private String titre;
- private String description;
- private LocalDateTime dateDebut;
- private LocalDateTime dateFin;
- private String lieu;
- private String adresse;
- private String ville;
- private String codePostal;
-
- // Mapping: typeEvenement -> type
- private String type;
-
- // Mapping: statut -> statut (OK)
- private String statut;
-
- // Mapping: capaciteMax -> maxParticipants
- private Integer maxParticipants;
-
- // Nombre de participants actuels (calculé depuis les inscriptions)
- private Integer participantsActuels;
-
- // IDs et noms pour les relations
- private UUID organisateurId;
- private String organisateurNom;
- private UUID organisationId;
- private String organisationNom;
-
- // Priorité (à ajouter dans l'entité si nécessaire)
- private String priorite;
-
- // Mapping: visiblePublic -> estPublic
- private Boolean estPublic;
-
- // Mapping: inscriptionRequise -> inscriptionRequise (OK)
- private Boolean inscriptionRequise;
-
- // Mapping: prix -> cout
- private BigDecimal cout;
-
- // Devise
- private String devise;
-
- // Tags (à implémenter si nécessaire)
- private String[] tags;
-
- // URLs
- private String imageUrl;
- private String documentUrl;
-
- // Notes
- private String notes;
-
- // Dates de création/modification
- private LocalDateTime dateCreation;
- private LocalDateTime dateModification;
-
- // Actif
- private Boolean actif;
-
- /**
- * Convertit une entité Evenement en DTO mobile
- *
- * @param evenement L'entité à convertir
- * @return Le DTO mobile
- */
- public static EvenementMobileDTO fromEntity(Evenement evenement) {
- if (evenement == null) {
- return null;
- }
-
- return EvenementMobileDTO.builder()
- .id(evenement.getId()) // Utilise getId() depuis BaseEntity
- .titre(evenement.getTitre())
- .description(evenement.getDescription())
- .dateDebut(evenement.getDateDebut())
- .dateFin(evenement.getDateFin())
- .lieu(evenement.getLieu())
- .adresse(evenement.getAdresse())
- .ville(null) // Pas de champ ville dans l'entité
- .codePostal(null) // Pas de champ codePostal dans l'entité
- // Mapping des enums
- .type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement() : null)
- .statut(evenement.getStatut() != null ? evenement.getStatut() : "PLANIFIE")
- // Mapping des champs renommés
- .maxParticipants(evenement.getCapaciteMax())
- .participantsActuels(evenement.getNombreInscrits())
- // Relations (gestion sécurisée des lazy loading)
- .organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null)
- .organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null)
- .organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null)
- .organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null)
- // Priorité (valeur par défaut)
- .priorite("MOYENNE")
- // Mapping booléens
- .estPublic(evenement.getVisiblePublic())
- .inscriptionRequise(evenement.getInscriptionRequise())
- // Mapping prix -> cout
- .cout(evenement.getPrix())
- .devise("XOF")
- // Tags vides pour l'instant
- .tags(new String[] {})
- // URLs (à implémenter si nécessaire)
- .imageUrl(null)
- .documentUrl(null)
- // Notes
- .notes(evenement.getInstructionsParticulieres())
- // Dates
- .dateCreation(evenement.getDateCreation())
- .dateModification(evenement.getDateModification())
- // Actif
- .actif(evenement.getActif())
- .build();
- }
-}
+package dev.lions.unionflow.server.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import dev.lions.unionflow.server.entity.Evenement;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le
+ * format attendu par
+ * l'application mobile Flutter
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-01-16
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EvenementMobileDTO {
+
+ private UUID id;
+ private String titre;
+ private String description;
+ private LocalDateTime dateDebut;
+ private LocalDateTime dateFin;
+ private String lieu;
+ private String adresse;
+ private String ville;
+ private String codePostal;
+
+ // Mapping: typeEvenement -> type
+ private String type;
+
+ // Mapping: statut -> statut (OK)
+ private String statut;
+
+ // Mapping: capaciteMax -> maxParticipants
+ private Integer maxParticipants;
+
+ // Nombre de participants actuels (calculé depuis les inscriptions)
+ private Integer participantsActuels;
+
+ // IDs et noms pour les relations
+ private UUID organisateurId;
+ private String organisateurNom;
+ private UUID organisationId;
+ private String organisationNom;
+
+ // Priorité (à ajouter dans l'entité si nécessaire)
+ private String priorite;
+
+ // Mapping: visiblePublic -> estPublic
+ private Boolean estPublic;
+
+ // Mapping: inscriptionRequise -> inscriptionRequise (OK)
+ private Boolean inscriptionRequise;
+
+ // Mapping: prix -> cout
+ private BigDecimal cout;
+
+ // Devise
+ private String devise;
+
+ // Tags (à implémenter si nécessaire)
+ private String[] tags;
+
+ // URLs
+ private String imageUrl;
+ private String documentUrl;
+
+ // Notes
+ private String notes;
+
+ // Dates de création/modification
+ private LocalDateTime dateCreation;
+ private LocalDateTime dateModification;
+
+ // Actif
+ private Boolean actif;
+
+ /**
+ * Convertit une entité Evenement en DTO mobile
+ *
+ * @param evenement L'entité à convertir
+ * @return Le DTO mobile
+ */
+ public static EvenementMobileDTO fromEntity(Evenement evenement) {
+ if (evenement == null) {
+ return null;
+ }
+
+ return EvenementMobileDTO.builder()
+ .id(evenement.getId()) // Utilise getId() depuis BaseEntity
+ .titre(evenement.getTitre())
+ .description(evenement.getDescription())
+ .dateDebut(evenement.getDateDebut())
+ .dateFin(evenement.getDateFin())
+ .lieu(evenement.getLieu())
+ .adresse(evenement.getAdresse())
+ .ville(null) // Pas de champ ville dans l'entité
+ .codePostal(null) // Pas de champ codePostal dans l'entité
+ // Mapping des enums
+ .type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement() : null)
+ .statut(evenement.getStatut() != null ? evenement.getStatut() : "PLANIFIE")
+ // Mapping des champs renommés
+ .maxParticipants(evenement.getCapaciteMax())
+ .participantsActuels(evenement.getNombreInscrits())
+ // Relations (gestion sécurisée des lazy loading)
+ .organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null)
+ .organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null)
+ .organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null)
+ .organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null)
+ // Priorité (valeur par défaut)
+ .priorite("MOYENNE")
+ // Mapping booléens
+ .estPublic(evenement.getVisiblePublic())
+ .inscriptionRequise(evenement.getInscriptionRequise())
+ // Mapping prix -> cout
+ .cout(evenement.getPrix())
+ .devise("XOF")
+ // Tags vides pour l'instant
+ .tags(new String[] {})
+ // URLs (à implémenter si nécessaire)
+ .imageUrl(null)
+ .documentUrl(null)
+ // Notes
+ .notes(evenement.getInstructionsParticulieres())
+ // Dates
+ .dateCreation(evenement.getDateCreation())
+ .dateModification(evenement.getDateModification())
+ // Actif
+ .actif(evenement.getActif())
+ .build();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Adresse.java b/src/main/java/dev/lions/unionflow/server/entity/Adresse.java
index e6e65b8..c8a0dbd 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Adresse.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Adresse.java
@@ -1,150 +1,150 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Adresse pour la gestion des adresses des organisations, membres et
- * événements
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(name = "adresses", indexes = {
- @Index(name = "idx_adresse_ville", columnList = "ville"),
- @Index(name = "idx_adresse_pays", columnList = "pays"),
- @Index(name = "idx_adresse_type", columnList = "type_adresse"),
- @Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
- @Index(name = "idx_adresse_membre", columnList = "membre_id"),
- @Index(name = "idx_adresse_evenement", columnList = "evenement_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Adresse extends BaseEntity {
-
- /** Type d'adresse (code depuis types_reference) */
- @Column(name = "type_adresse", nullable = false, length = 50)
- private String typeAdresse;
-
- /** Adresse complète */
- @Column(name = "adresse", length = 500)
- private String adresse;
-
- /** Complément d'adresse */
- @Column(name = "complement_adresse", length = 200)
- private String complementAdresse;
-
- /** Code postal */
- @Column(name = "code_postal", length = 20)
- private String codePostal;
-
- /** Ville */
- @Column(name = "ville", length = 100)
- private String ville;
-
- /** Région */
- @Column(name = "region", length = 100)
- private String region;
-
- /** Pays */
- @Column(name = "pays", length = 100)
- private String pays;
-
- /** Coordonnées géographiques - Latitude */
- @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
- @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
- @Digits(integer = 3, fraction = 6)
- @Column(name = "latitude", precision = 9, scale = 6)
- private BigDecimal latitude;
-
- /** Coordonnées géographiques - Longitude */
- @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
- @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
- @Digits(integer = 3, fraction = 6)
- @Column(name = "longitude", precision = 9, scale = 6)
- private BigDecimal longitude;
-
- /** Adresse principale (une seule par entité) */
- @Builder.Default
- @Column(name = "principale", nullable = false)
- private Boolean principale = false;
-
- /** Libellé personnalisé */
- @Column(name = "libelle", length = 100)
- private String libelle;
-
- /** Notes et commentaires */
- @Column(name = "notes", length = 500)
- private String notes;
-
- // Relations
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id")
- private Membre membre;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "evenement_id")
- private Evenement evenement;
-
- /** Méthode métier pour obtenir l'adresse complète formatée */
- public String getAdresseComplete() {
- StringBuilder sb = new StringBuilder();
- if (adresse != null && !adresse.isEmpty()) {
- sb.append(adresse);
- }
- if (complementAdresse != null && !complementAdresse.isEmpty()) {
- if (sb.length() > 0)
- sb.append(", ");
- sb.append(complementAdresse);
- }
- if (codePostal != null && !codePostal.isEmpty()) {
- if (sb.length() > 0)
- sb.append(", ");
- sb.append(codePostal);
- }
- if (ville != null && !ville.isEmpty()) {
- if (sb.length() > 0)
- sb.append(" ");
- sb.append(ville);
- }
- if (region != null && !region.isEmpty()) {
- if (sb.length() > 0)
- sb.append(", ");
- sb.append(region);
- }
- if (pays != null && !pays.isEmpty()) {
- if (sb.length() > 0)
- sb.append(", ");
- sb.append(pays);
- }
- return sb.toString();
- }
-
- /** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
- public boolean hasCoordinates() {
- return latitude != null && longitude != null;
- }
-
- /** Callback JPA avant la persistance */
- protected void onCreate() {
- super.onCreate(); // Appelle le onCreate de BaseEntity
- if (principale == null) {
- principale = false;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Adresse pour la gestion des adresses des organisations, membres et
+ * événements
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(name = "adresses", indexes = {
+ @Index(name = "idx_adresse_ville", columnList = "ville"),
+ @Index(name = "idx_adresse_pays", columnList = "pays"),
+ @Index(name = "idx_adresse_type", columnList = "type_adresse"),
+ @Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_adresse_membre", columnList = "membre_id"),
+ @Index(name = "idx_adresse_evenement", columnList = "evenement_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Adresse extends BaseEntity {
+
+ /** Type d'adresse (code depuis types_reference) */
+ @Column(name = "type_adresse", nullable = false, length = 50)
+ private String typeAdresse;
+
+ /** Adresse complète */
+ @Column(name = "adresse", length = 500)
+ private String adresse;
+
+ /** Complément d'adresse */
+ @Column(name = "complement_adresse", length = 200)
+ private String complementAdresse;
+
+ /** Code postal */
+ @Column(name = "code_postal", length = 20)
+ private String codePostal;
+
+ /** Ville */
+ @Column(name = "ville", length = 100)
+ private String ville;
+
+ /** Région */
+ @Column(name = "region", length = 100)
+ private String region;
+
+ /** Pays */
+ @Column(name = "pays", length = 100)
+ private String pays;
+
+ /** Coordonnées géographiques - Latitude */
+ @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
+ @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
+ @Digits(integer = 3, fraction = 6)
+ @Column(name = "latitude", precision = 9, scale = 6)
+ private BigDecimal latitude;
+
+ /** Coordonnées géographiques - Longitude */
+ @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
+ @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
+ @Digits(integer = 3, fraction = 6)
+ @Column(name = "longitude", precision = 9, scale = 6)
+ private BigDecimal longitude;
+
+ /** Adresse principale (une seule par entité) */
+ @Builder.Default
+ @Column(name = "principale", nullable = false)
+ private Boolean principale = false;
+
+ /** Libellé personnalisé */
+ @Column(name = "libelle", length = 100)
+ private String libelle;
+
+ /** Notes et commentaires */
+ @Column(name = "notes", length = 500)
+ private String notes;
+
+ // Relations
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id")
+ private Membre membre;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "evenement_id")
+ private Evenement evenement;
+
+ /** Méthode métier pour obtenir l'adresse complète formatée */
+ public String getAdresseComplete() {
+ StringBuilder sb = new StringBuilder();
+ if (adresse != null && !adresse.isEmpty()) {
+ sb.append(adresse);
+ }
+ if (complementAdresse != null && !complementAdresse.isEmpty()) {
+ if (sb.length() > 0)
+ sb.append(", ");
+ sb.append(complementAdresse);
+ }
+ if (codePostal != null && !codePostal.isEmpty()) {
+ if (sb.length() > 0)
+ sb.append(", ");
+ sb.append(codePostal);
+ }
+ if (ville != null && !ville.isEmpty()) {
+ if (sb.length() > 0)
+ sb.append(" ");
+ sb.append(ville);
+ }
+ if (region != null && !region.isEmpty()) {
+ if (sb.length() > 0)
+ sb.append(", ");
+ sb.append(region);
+ }
+ if (pays != null && !pays.isEmpty()) {
+ if (sb.length() > 0)
+ sb.append(", ");
+ sb.append(pays);
+ }
+ return sb.toString();
+ }
+
+ /** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
+ public boolean hasCoordinates() {
+ return latitude != null && longitude != null;
+ }
+
+ /** Callback JPA avant la persistance */
+ protected void onCreate() {
+ super.onCreate(); // Appelle le onCreate de BaseEntity
+ if (principale == null) {
+ principale = false;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/AlertConfiguration.java b/src/main/java/dev/lions/unionflow/server/entity/AlertConfiguration.java
index 4edd6f7..424612a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/AlertConfiguration.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/AlertConfiguration.java
@@ -1,113 +1,113 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * Entité singleton pour la configuration des alertes système.
- * Une seule ligne en base de données.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@Entity
-@Table(name = "alert_configuration")
-@Getter
-@Setter
-public class AlertConfiguration extends BaseEntity {
-
- /**
- * Alerte CPU activée
- */
- @Column(name = "cpu_high_alert_enabled", nullable = false)
- private Boolean cpuHighAlertEnabled = true;
-
- /**
- * Seuil CPU en pourcentage (0-100)
- */
- @Column(name = "cpu_threshold_percent", nullable = false)
- private Integer cpuThresholdPercent = 80;
-
- /**
- * Durée en minutes avant déclenchement alerte CPU
- */
- @Column(name = "cpu_duration_minutes", nullable = false)
- private Integer cpuDurationMinutes = 5;
-
- /**
- * Alerte mémoire faible activée
- */
- @Column(name = "memory_low_alert_enabled", nullable = false)
- private Boolean memoryLowAlertEnabled = true;
-
- /**
- * Seuil mémoire en pourcentage (0-100)
- */
- @Column(name = "memory_threshold_percent", nullable = false)
- private Integer memoryThresholdPercent = 85;
-
- /**
- * Alerte erreur critique activée
- */
- @Column(name = "critical_error_alert_enabled", nullable = false)
- private Boolean criticalErrorAlertEnabled = true;
-
- /**
- * Alerte erreur activée
- */
- @Column(name = "error_alert_enabled", nullable = false)
- private Boolean errorAlertEnabled = true;
-
- /**
- * Alerte échec de connexion activée
- */
- @Column(name = "connection_failure_alert_enabled", nullable = false)
- private Boolean connectionFailureAlertEnabled = true;
-
- /**
- * Seuil d'échecs de connexion
- */
- @Column(name = "connection_failure_threshold", nullable = false)
- private Integer connectionFailureThreshold = 100;
-
- /**
- * Fenêtre temporelle en minutes pour les échecs de connexion
- */
- @Column(name = "connection_failure_window_minutes", nullable = false)
- private Integer connectionFailureWindowMinutes = 5;
-
- /**
- * Notifications par email activées
- */
- @Column(name = "email_notifications_enabled", nullable = false)
- private Boolean emailNotificationsEnabled = true;
-
- /**
- * Notifications push activées
- */
- @Column(name = "push_notifications_enabled", nullable = false)
- private Boolean pushNotificationsEnabled = false;
-
- /**
- * Notifications SMS activées
- */
- @Column(name = "sms_notifications_enabled", nullable = false)
- private Boolean smsNotificationsEnabled = false;
-
- /**
- * Liste des emails destinataires des alertes (séparés par virgule)
- */
- @Column(name = "alert_email_recipients", length = 1000)
- private String alertEmailRecipients = "admin@unionflow.test";
-
- /**
- * S'assurer qu'il n'y a qu'une seule configuration
- */
- @PrePersist
- @PreUpdate
- protected void ensureSingleton() {
- // La logique singleton sera gérée par le repository
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Entité singleton pour la configuration des alertes système.
+ * Une seule ligne en base de données.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@Entity
+@Table(name = "alert_configuration")
+@Getter
+@Setter
+public class AlertConfiguration extends BaseEntity {
+
+ /**
+ * Alerte CPU activée
+ */
+ @Column(name = "cpu_high_alert_enabled", nullable = false)
+ private Boolean cpuHighAlertEnabled = true;
+
+ /**
+ * Seuil CPU en pourcentage (0-100)
+ */
+ @Column(name = "cpu_threshold_percent", nullable = false)
+ private Integer cpuThresholdPercent = 80;
+
+ /**
+ * Durée en minutes avant déclenchement alerte CPU
+ */
+ @Column(name = "cpu_duration_minutes", nullable = false)
+ private Integer cpuDurationMinutes = 5;
+
+ /**
+ * Alerte mémoire faible activée
+ */
+ @Column(name = "memory_low_alert_enabled", nullable = false)
+ private Boolean memoryLowAlertEnabled = true;
+
+ /**
+ * Seuil mémoire en pourcentage (0-100)
+ */
+ @Column(name = "memory_threshold_percent", nullable = false)
+ private Integer memoryThresholdPercent = 85;
+
+ /**
+ * Alerte erreur critique activée
+ */
+ @Column(name = "critical_error_alert_enabled", nullable = false)
+ private Boolean criticalErrorAlertEnabled = true;
+
+ /**
+ * Alerte erreur activée
+ */
+ @Column(name = "error_alert_enabled", nullable = false)
+ private Boolean errorAlertEnabled = true;
+
+ /**
+ * Alerte échec de connexion activée
+ */
+ @Column(name = "connection_failure_alert_enabled", nullable = false)
+ private Boolean connectionFailureAlertEnabled = true;
+
+ /**
+ * Seuil d'échecs de connexion
+ */
+ @Column(name = "connection_failure_threshold", nullable = false)
+ private Integer connectionFailureThreshold = 100;
+
+ /**
+ * Fenêtre temporelle en minutes pour les échecs de connexion
+ */
+ @Column(name = "connection_failure_window_minutes", nullable = false)
+ private Integer connectionFailureWindowMinutes = 5;
+
+ /**
+ * Notifications par email activées
+ */
+ @Column(name = "email_notifications_enabled", nullable = false)
+ private Boolean emailNotificationsEnabled = true;
+
+ /**
+ * Notifications push activées
+ */
+ @Column(name = "push_notifications_enabled", nullable = false)
+ private Boolean pushNotificationsEnabled = false;
+
+ /**
+ * Notifications SMS activées
+ */
+ @Column(name = "sms_notifications_enabled", nullable = false)
+ private Boolean smsNotificationsEnabled = false;
+
+ /**
+ * Liste des emails destinataires des alertes (séparés par virgule)
+ */
+ @Column(name = "alert_email_recipients", length = 1000)
+ private String alertEmailRecipients = "admin@unionflow.test";
+
+ /**
+ * S'assurer qu'il n'y a qu'une seule configuration
+ */
+ @PrePersist
+ @PreUpdate
+ protected void ensureSingleton() {
+ // La logique singleton sera gérée par le repository
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/AlerteLcbFt.java b/src/main/java/dev/lions/unionflow/server/entity/AlerteLcbFt.java
index 2d53275..64ec375 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/AlerteLcbFt.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/AlerteLcbFt.java
@@ -1,124 +1,124 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-/**
- * Entité représentant une alerte LCB-FT (Lutte Contre le Blanchiment et Financement du Terrorisme).
- * Les alertes sont générées automatiquement lors de transactions dépassant les seuils configurés.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@Entity
-@Table(name = "alertes_lcb_ft", indexes = {
- @Index(name = "idx_alerte_lcb_ft_organisation", columnList = "organisation_id"),
- @Index(name = "idx_alerte_lcb_ft_type", columnList = "type_alerte"),
- @Index(name = "idx_alerte_lcb_ft_date", columnList = "date_alerte"),
- @Index(name = "idx_alerte_lcb_ft_traitee", columnList = "traitee")
-})
-@Getter
-@Setter
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-public class AlerteLcbFt extends BaseEntity {
-
- /**
- * Organisation concernée par l'alerte
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- /**
- * Membre concerné par l'alerte
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id")
- private Membre membre;
-
- /**
- * Type d'alerte : SEUIL_DEPASSE, JUSTIFICATION_MANQUANTE, etc.
- */
- @Column(name = "type_alerte", nullable = false, length = 50)
- private String typeAlerte;
-
- /**
- * Date et heure de génération de l'alerte
- */
- @Column(name = "date_alerte", nullable = false)
- private LocalDateTime dateAlerte;
-
- /**
- * Description de l'alerte
- */
- @Column(name = "description", length = 500)
- private String description;
-
- /**
- * Détails supplémentaires (JSON ou texte)
- */
- @Column(name = "details", columnDefinition = "TEXT")
- private String details;
-
- /**
- * Montant de la transaction ayant généré l'alerte
- */
- @Column(name = "montant", precision = 15, scale = 2)
- private BigDecimal montant;
-
- /**
- * Seuil qui a été dépassé
- */
- @Column(name = "seuil", precision = 15, scale = 2)
- private BigDecimal seuil;
-
- /**
- * Type d'opération : DEPOT, RETRAIT, TRANSFERT, etc.
- */
- @Column(name = "type_operation", length = 50)
- private String typeOperation;
-
- /**
- * Référence de la transaction concernée (UUID)
- */
- @Column(name = "transaction_ref", length = 100)
- private String transactionRef;
-
- /**
- * Niveau de gravité : INFO, WARNING, CRITICAL
- */
- @Column(name = "severite", nullable = false, length = 20)
- private String severite;
-
- /**
- * Indique si l'alerte a été traitée
- */
- @Builder.Default
- @Column(name = "traitee", nullable = false)
- private Boolean traitee = false;
-
- /**
- * Date de traitement de l'alerte
- */
- @Column(name = "date_traitement")
- private LocalDateTime dateTraitement;
-
- /**
- * Utilisateur ayant traité l'alerte
- */
- @Column(name = "traite_par")
- private UUID traitePar;
-
- /**
- * Commentaire sur le traitement
- */
- @Column(name = "commentaire_traitement", columnDefinition = "TEXT")
- private String commentaireTraitement;
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Entité représentant une alerte LCB-FT (Lutte Contre le Blanchiment et Financement du Terrorisme).
+ * Les alertes sont générées automatiquement lors de transactions dépassant les seuils configurés.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@Entity
+@Table(name = "alertes_lcb_ft", indexes = {
+ @Index(name = "idx_alerte_lcb_ft_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_alerte_lcb_ft_type", columnList = "type_alerte"),
+ @Index(name = "idx_alerte_lcb_ft_date", columnList = "date_alerte"),
+ @Index(name = "idx_alerte_lcb_ft_traitee", columnList = "traitee")
+})
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AlerteLcbFt extends BaseEntity {
+
+ /**
+ * Organisation concernée par l'alerte
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /**
+ * Membre concerné par l'alerte
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id")
+ private Membre membre;
+
+ /**
+ * Type d'alerte : SEUIL_DEPASSE, JUSTIFICATION_MANQUANTE, etc.
+ */
+ @Column(name = "type_alerte", nullable = false, length = 50)
+ private String typeAlerte;
+
+ /**
+ * Date et heure de génération de l'alerte
+ */
+ @Column(name = "date_alerte", nullable = false)
+ private LocalDateTime dateAlerte;
+
+ /**
+ * Description de l'alerte
+ */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /**
+ * Détails supplémentaires (JSON ou texte)
+ */
+ @Column(name = "details", columnDefinition = "TEXT")
+ private String details;
+
+ /**
+ * Montant de la transaction ayant généré l'alerte
+ */
+ @Column(name = "montant", precision = 15, scale = 2)
+ private BigDecimal montant;
+
+ /**
+ * Seuil qui a été dépassé
+ */
+ @Column(name = "seuil", precision = 15, scale = 2)
+ private BigDecimal seuil;
+
+ /**
+ * Type d'opération : DEPOT, RETRAIT, TRANSFERT, etc.
+ */
+ @Column(name = "type_operation", length = 50)
+ private String typeOperation;
+
+ /**
+ * Référence de la transaction concernée (UUID)
+ */
+ @Column(name = "transaction_ref", length = 100)
+ private String transactionRef;
+
+ /**
+ * Niveau de gravité : INFO, WARNING, CRITICAL
+ */
+ @Column(name = "severite", nullable = false, length = 20)
+ private String severite;
+
+ /**
+ * Indique si l'alerte a été traitée
+ */
+ @Builder.Default
+ @Column(name = "traitee", nullable = false)
+ private Boolean traitee = false;
+
+ /**
+ * Date de traitement de l'alerte
+ */
+ @Column(name = "date_traitement")
+ private LocalDateTime dateTraitement;
+
+ /**
+ * Utilisateur ayant traité l'alerte
+ */
+ @Column(name = "traite_par")
+ private UUID traitePar;
+
+ /**
+ * Commentaire sur le traitement
+ */
+ @Column(name = "commentaire_traitement", columnDefinition = "TEXT")
+ private String commentaireTraitement;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ApproverAction.java b/src/main/java/dev/lions/unionflow/server/entity/ApproverAction.java
index 9699386..67fad47 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ApproverAction.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ApproverAction.java
@@ -1,94 +1,94 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.time.LocalDateTime;
-import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Action d'Approbateur
- *
- * Représente l'action (approve/reject) d'un approbateur sur une demande d'approbation.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-13
- */
-@Entity
-@Table(name = "approver_actions", indexes = {
- @Index(name = "idx_approver_action_approval", columnList = "approval_id"),
- @Index(name = "idx_approver_action_approver", columnList = "approver_id"),
- @Index(name = "idx_approver_action_decision", columnList = "decision"),
- @Index(name = "idx_approver_action_decided_at", columnList = "decided_at")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ApproverAction extends BaseEntity {
-
- /** Approbation parente */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "approval_id", nullable = false)
- private TransactionApproval approval;
-
- /** ID de l'approbateur (membre) */
- @NotNull
- @Column(name = "approver_id", nullable = false)
- private UUID approverId;
-
- /** Nom complet de l'approbateur (cache) */
- @NotBlank
- @Column(name = "approver_name", nullable = false, length = 200)
- private String approverName;
-
- /** Rôle de l'approbateur au moment de l'action */
- @NotBlank
- @Column(name = "approver_role", nullable = false, length = 50)
- private String approverRole;
-
- /** Décision (PENDING, APPROVED, REJECTED) */
- @NotBlank
- @Pattern(regexp = "^(PENDING|APPROVED|REJECTED)$")
- @Builder.Default
- @Column(name = "decision", nullable = false, length = 10)
- private String decision = "PENDING";
-
- /** Commentaire optionnel */
- @Size(max = 1000)
- @Column(name = "comment", length = 1000)
- private String comment;
-
- /** Date de la décision */
- @Column(name = "decided_at")
- private LocalDateTime decidedAt;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (decision == null) {
- decision = "PENDING";
- }
- }
-
- /** Méthode métier pour approuver avec commentaire */
- public void approve(String comment) {
- this.decision = "APPROVED";
- this.comment = comment;
- this.decidedAt = LocalDateTime.now();
- }
-
- /** Méthode métier pour rejeter avec raison */
- public void reject(String reason) {
- this.decision = "REJECTED";
- this.comment = reason;
- this.decidedAt = LocalDateTime.now();
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Action d'Approbateur
+ *
+ * Représente l'action (approve/reject) d'un approbateur sur une demande d'approbation.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-13
+ */
+@Entity
+@Table(name = "approver_actions", indexes = {
+ @Index(name = "idx_approver_action_approval", columnList = "approval_id"),
+ @Index(name = "idx_approver_action_approver", columnList = "approver_id"),
+ @Index(name = "idx_approver_action_decision", columnList = "decision"),
+ @Index(name = "idx_approver_action_decided_at", columnList = "decided_at")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ApproverAction extends BaseEntity {
+
+ /** Approbation parente */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "approval_id", nullable = false)
+ private TransactionApproval approval;
+
+ /** ID de l'approbateur (membre) */
+ @NotNull
+ @Column(name = "approver_id", nullable = false)
+ private UUID approverId;
+
+ /** Nom complet de l'approbateur (cache) */
+ @NotBlank
+ @Column(name = "approver_name", nullable = false, length = 200)
+ private String approverName;
+
+ /** Rôle de l'approbateur au moment de l'action */
+ @NotBlank
+ @Column(name = "approver_role", nullable = false, length = 50)
+ private String approverRole;
+
+ /** Décision (PENDING, APPROVED, REJECTED) */
+ @NotBlank
+ @Pattern(regexp = "^(PENDING|APPROVED|REJECTED)$")
+ @Builder.Default
+ @Column(name = "decision", nullable = false, length = 10)
+ private String decision = "PENDING";
+
+ /** Commentaire optionnel */
+ @Size(max = 1000)
+ @Column(name = "comment", length = 1000)
+ private String comment;
+
+ /** Date de la décision */
+ @Column(name = "decided_at")
+ private LocalDateTime decidedAt;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (decision == null) {
+ decision = "PENDING";
+ }
+ }
+
+ /** Méthode métier pour approuver avec commentaire */
+ public void approve(String comment) {
+ this.decision = "APPROVED";
+ this.comment = comment;
+ this.decidedAt = LocalDateTime.now();
+ }
+
+ /** Méthode métier pour rejeter avec raison */
+ public void reject(String reason) {
+ this.decision = "REJECTED";
+ this.comment = reason;
+ this.decidedAt = LocalDateTime.now();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java b/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java
index 40243a7..d17cb2c 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java
@@ -1,99 +1,99 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.audit.PorteeAudit;
-import jakarta.persistence.*;
-import java.time.LocalDateTime;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * Entité pour les logs d'audit
- * Enregistre toutes les actions importantes du système
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2025-01-17
- */
-@Entity
-@Table(name = "audit_logs", indexes = {
- @Index(name = "idx_audit_date_heure", columnList = "date_heure"),
- @Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
- @Index(name = "idx_audit_module", columnList = "module"),
- @Index(name = "idx_audit_type_action", columnList = "type_action"),
- @Index(name = "idx_audit_severite", columnList = "severite")
-})
-@Getter
-@Setter
-public class AuditLog extends BaseEntity {
-
- @Column(name = "type_action", nullable = false, length = 50)
- private String typeAction;
-
- @Column(name = "severite", nullable = false, length = 20)
- private String severite;
-
- @Column(name = "utilisateur", length = 255)
- private String utilisateur;
-
- @Column(name = "role", length = 50)
- private String role;
-
- @Column(name = "module", length = 50)
- private String module;
-
- @Column(name = "description", length = 500)
- private String description;
-
- @Column(name = "details", columnDefinition = "TEXT")
- private String details;
-
- @Column(name = "ip_address", length = 45)
- private String ipAddress;
-
- @Column(name = "user_agent", length = 500)
- private String userAgent;
-
- @Column(name = "session_id", length = 255)
- private String sessionId;
-
- @Column(name = "date_heure", nullable = false)
- private LocalDateTime dateHeure;
-
- @Column(name = "donnees_avant", columnDefinition = "TEXT")
- private String donneesAvant;
-
- @Column(name = "donnees_apres", columnDefinition = "TEXT")
- private String donneesApres;
-
- @Column(name = "entite_id", length = 255)
- private String entiteId;
-
- @Column(name = "entite_type", length = 100)
- private String entiteType;
-
- /**
- * Organisation concernée par cet événement d'audit.
- * NULL pour les événements de portée PLATEFORME.
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- /**
- * Portée de visibilité :
- * ORGANISATION = visible par le manager de l'organisation
- * PLATEFORME = visible uniquement par le Super Admin UnionFlow
- */
- @Enumerated(EnumType.STRING)
- @Column(name = "portee", nullable = false, length = 15)
- private PorteeAudit portee = PorteeAudit.PLATEFORME;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (dateHeure == null) {
- dateHeure = LocalDateTime.now();
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.audit.PorteeAudit;
+import jakarta.persistence.*;
+import java.time.LocalDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Entité pour les logs d'audit
+ * Enregistre toutes les actions importantes du système
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2025-01-17
+ */
+@Entity
+@Table(name = "audit_logs", indexes = {
+ @Index(name = "idx_audit_date_heure", columnList = "date_heure"),
+ @Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
+ @Index(name = "idx_audit_module", columnList = "module"),
+ @Index(name = "idx_audit_type_action", columnList = "type_action"),
+ @Index(name = "idx_audit_severite", columnList = "severite")
+})
+@Getter
+@Setter
+public class AuditLog extends BaseEntity {
+
+ @Column(name = "type_action", nullable = false, length = 50)
+ private String typeAction;
+
+ @Column(name = "severite", nullable = false, length = 20)
+ private String severite;
+
+ @Column(name = "utilisateur", length = 255)
+ private String utilisateur;
+
+ @Column(name = "role", length = 50)
+ private String role;
+
+ @Column(name = "module", length = 50)
+ private String module;
+
+ @Column(name = "description", length = 500)
+ private String description;
+
+ @Column(name = "details", columnDefinition = "TEXT")
+ private String details;
+
+ @Column(name = "ip_address", length = 45)
+ private String ipAddress;
+
+ @Column(name = "user_agent", length = 500)
+ private String userAgent;
+
+ @Column(name = "session_id", length = 255)
+ private String sessionId;
+
+ @Column(name = "date_heure", nullable = false)
+ private LocalDateTime dateHeure;
+
+ @Column(name = "donnees_avant", columnDefinition = "TEXT")
+ private String donneesAvant;
+
+ @Column(name = "donnees_apres", columnDefinition = "TEXT")
+ private String donneesApres;
+
+ @Column(name = "entite_id", length = 255)
+ private String entiteId;
+
+ @Column(name = "entite_type", length = 100)
+ private String entiteType;
+
+ /**
+ * Organisation concernée par cet événement d'audit.
+ * NULL pour les événements de portée PLATEFORME.
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /**
+ * Portée de visibilité :
+ * ORGANISATION = visible par le manager de l'organisation
+ * PLATEFORME = visible uniquement par le Super Admin UnionFlow
+ */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "portee", nullable = false, length = 15)
+ private PorteeAudit portee = PorteeAudit.PLATEFORME;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (dateHeure == null) {
+ dateHeure = LocalDateTime.now();
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/AyantDroit.java b/src/main/java/dev/lions/unionflow/server/entity/AyantDroit.java
index 6c39439..43bf864 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/AyantDroit.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/AyantDroit.java
@@ -1,95 +1,95 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente;
-import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.time.LocalDate;
-import java.math.BigDecimal;
-import lombok.*;
-
-/**
- * Ayant droit d'un membre dans une mutuelle de santé.
- *
- *
- * Permet la gestion des bénéficiaires (conjoint, enfants, parents) pour
- * les conventions avec les centres de santé partenaires et les plafonds
- * annuels.
- *
- *
- * Table : {@code ayants_droit}
- */
-@Entity
-@Table(name = "ayants_droit", indexes = {
- @Index(name = "idx_ad_membre_org", columnList = "membre_organisation_id"),
- @Index(name = "idx_ad_couverture", columnList = "date_debut_couverture, date_fin_couverture")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class AyantDroit extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_organisation_id", nullable = false)
- private MembreOrganisation membreOrganisation;
-
- @NotBlank
- @Column(name = "prenom", nullable = false, length = 100)
- private String prenom;
-
- @NotBlank
- @Column(name = "nom", nullable = false, length = 100)
- private String nom;
-
- @Column(name = "date_naissance")
- private LocalDate dateNaissance;
-
- @Enumerated(EnumType.STRING)
- @NotNull
- @Column(name = "lien_parente", nullable = false, length = 20)
- private LienParente lienParente;
-
- /** Numéro attribué pour les conventions santé avec les centres partenaires */
- @Column(name = "numero_beneficiaire", length = 50)
- private String numeroBeneficiaire;
-
- @Column(name = "date_debut_couverture")
- private LocalDate dateDebutCouverture;
-
- /** NULL = couverture ouverte */
- @Column(name = "date_fin_couverture")
- private LocalDate dateFinCouverture;
-
- @Column(name = "sexe", length = 20)
- private String sexe;
-
- @Column(name = "piece_identite", length = 100)
- private String pieceIdentite;
-
- @Column(name = "pourcentage_couverture", precision = 5, scale = 2)
- private BigDecimal pourcentageCouvertureSante;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutAyantDroit statut = StatutAyantDroit.EN_ATTENTE;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean isCouvertAujourdhui() {
- LocalDate today = LocalDate.now();
- if (dateDebutCouverture != null && today.isBefore(dateDebutCouverture))
- return false;
- if (dateFinCouverture != null && today.isAfter(dateFinCouverture))
- return false;
- return Boolean.TRUE.equals(getActif());
- }
-
- public String getNomComplet() {
- return prenom + " " + nom;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente;
+import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.time.LocalDate;
+import java.math.BigDecimal;
+import lombok.*;
+
+/**
+ * Ayant droit d'un membre dans une mutuelle de santé.
+ *
+ *
+ * Permet la gestion des bénéficiaires (conjoint, enfants, parents) pour
+ * les conventions avec les centres de santé partenaires et les plafonds
+ * annuels.
+ *
+ *
+ * Table : {@code ayants_droit}
+ */
+@Entity
+@Table(name = "ayants_droit", indexes = {
+ @Index(name = "idx_ad_membre_org", columnList = "membre_organisation_id"),
+ @Index(name = "idx_ad_couverture", columnList = "date_debut_couverture, date_fin_couverture")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class AyantDroit extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_organisation_id", nullable = false)
+ private MembreOrganisation membreOrganisation;
+
+ @NotBlank
+ @Column(name = "prenom", nullable = false, length = 100)
+ private String prenom;
+
+ @NotBlank
+ @Column(name = "nom", nullable = false, length = 100)
+ private String nom;
+
+ @Column(name = "date_naissance")
+ private LocalDate dateNaissance;
+
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ @Column(name = "lien_parente", nullable = false, length = 20)
+ private LienParente lienParente;
+
+ /** Numéro attribué pour les conventions santé avec les centres partenaires */
+ @Column(name = "numero_beneficiaire", length = 50)
+ private String numeroBeneficiaire;
+
+ @Column(name = "date_debut_couverture")
+ private LocalDate dateDebutCouverture;
+
+ /** NULL = couverture ouverte */
+ @Column(name = "date_fin_couverture")
+ private LocalDate dateFinCouverture;
+
+ @Column(name = "sexe", length = 20)
+ private String sexe;
+
+ @Column(name = "piece_identite", length = 100)
+ private String pieceIdentite;
+
+ @Column(name = "pourcentage_couverture", precision = 5, scale = 2)
+ private BigDecimal pourcentageCouvertureSante;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutAyantDroit statut = StatutAyantDroit.EN_ATTENTE;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean isCouvertAujourdhui() {
+ LocalDate today = LocalDate.now();
+ if (dateDebutCouverture != null && today.isBefore(dateDebutCouverture))
+ return false;
+ if (dateFinCouverture != null && today.isAfter(dateFinCouverture))
+ return false;
+ return Boolean.TRUE.equals(getActif());
+ }
+
+ public String getNomComplet() {
+ return prenom + " " + nom;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java b/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java
index a7d20d4..45c46fe 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java
@@ -1,101 +1,101 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.entity.listener.AuditEntityListener;
-import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
-import jakarta.persistence.Column;
-import jakarta.persistence.EntityListeners;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.MappedSuperclass;
-import jakarta.persistence.PrePersist;
-import jakarta.persistence.PreUpdate;
-import jakarta.persistence.Version;
-import java.time.LocalDateTime;
-import java.util.UUID;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * Classe de base pour toutes les entités UnionFlow.
- *
- *
- * Étend PanacheEntityBase pour bénéficier du pattern Active Record et résoudre
- * les warnings Hibernate.
- * Fournit les champs communs d'audit et le versioning optimistic.
- *
- * @author UnionFlow Team
- * @version 4.0
- */
-@MappedSuperclass
-@EntityListeners(AuditEntityListener.class)
-@Data
-@EqualsAndHashCode(callSuper = false)
-public abstract class BaseEntity extends PanacheEntityBase {
-
- /** Identifiant unique auto-généré. */
- @Id
- @GeneratedValue(strategy = GenerationType.UUID)
- @Column(name = "id", updatable = false, nullable = false)
- private UUID id;
-
- /**
- * Date de création.
- */
- @Column(name = "date_creation", nullable = false, updatable = false)
- private LocalDateTime dateCreation;
-
- /**
- * Date de dernière modification.
- */
- @Column(name = "date_modification")
- private LocalDateTime dateModification;
-
- /**
- * Email de l'utilisateur ayant créé l'entité.
- */
- @Column(name = "cree_par", length = 255)
- private String creePar;
-
- /**
- * Email du dernier utilisateur ayant modifié l'entité.
- */
- @Column(name = "modifie_par", length = 255)
- private String modifiePar;
-
- /** Version pour l'optimistic locking JPA. */
- @Version
- @Column(name = "version")
- private Long version;
-
- /**
- * État actif/inactif pour le soft-delete.
- */
- @Column(name = "actif", nullable = false)
- private Boolean actif;
-
- @PrePersist
- protected void onCreate() {
- if (this.dateCreation == null) {
- this.dateCreation = LocalDateTime.now();
- }
- if (this.actif == null) {
- this.actif = true;
- }
- }
-
- @PreUpdate
- protected void onUpdate() {
- this.dateModification = LocalDateTime.now();
- }
-
- /**
- * Marque l'entité comme modifiée par un utilisateur donné.
- *
- * @param utilisateur email de l'utilisateur
- */
- public void marquerCommeModifie(String utilisateur) {
- this.dateModification = LocalDateTime.now();
- this.modifiePar = utilisateur;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.entity.listener.AuditEntityListener;
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.Column;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
+import jakarta.persistence.Version;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * Classe de base pour toutes les entités UnionFlow.
+ *
+ *
+ * Étend PanacheEntityBase pour bénéficier du pattern Active Record et résoudre
+ * les warnings Hibernate.
+ * Fournit les champs communs d'audit et le versioning optimistic.
+ *
+ * @author UnionFlow Team
+ * @version 4.0
+ */
+@MappedSuperclass
+@EntityListeners(AuditEntityListener.class)
+@Data
+@EqualsAndHashCode(callSuper = false)
+public abstract class BaseEntity extends PanacheEntityBase {
+
+ /** Identifiant unique auto-généré. */
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ @Column(name = "id", updatable = false, nullable = false)
+ private UUID id;
+
+ /**
+ * Date de création.
+ */
+ @Column(name = "date_creation", nullable = false, updatable = false)
+ private LocalDateTime dateCreation;
+
+ /**
+ * Date de dernière modification.
+ */
+ @Column(name = "date_modification")
+ private LocalDateTime dateModification;
+
+ /**
+ * Email de l'utilisateur ayant créé l'entité.
+ */
+ @Column(name = "cree_par", length = 255)
+ private String creePar;
+
+ /**
+ * Email du dernier utilisateur ayant modifié l'entité.
+ */
+ @Column(name = "modifie_par", length = 255)
+ private String modifiePar;
+
+ /** Version pour l'optimistic locking JPA. */
+ @Version
+ @Column(name = "version")
+ private Long version;
+
+ /**
+ * État actif/inactif pour le soft-delete.
+ */
+ @Column(name = "actif", nullable = false)
+ private Boolean actif;
+
+ @PrePersist
+ protected void onCreate() {
+ if (this.dateCreation == null) {
+ this.dateCreation = LocalDateTime.now();
+ }
+ if (this.actif == null) {
+ this.actif = true;
+ }
+ }
+
+ @PreUpdate
+ protected void onUpdate() {
+ this.dateModification = LocalDateTime.now();
+ }
+
+ /**
+ * Marque l'entité comme modifiée par un utilisateur donné.
+ *
+ * @param utilisateur email de l'utilisateur
+ */
+ public void marquerCommeModifie(String utilisateur) {
+ this.dateModification = LocalDateTime.now();
+ this.modifiePar = utilisateur;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Budget.java b/src/main/java/dev/lions/unionflow/server/entity/Budget.java
index 7b36fcd..8221411 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Budget.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Budget.java
@@ -1,218 +1,218 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Budget
- *
- * Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-13
- */
-@Entity
-@Table(name = "budgets", indexes = {
- @Index(name = "idx_budget_organisation", columnList = "organisation_id"),
- @Index(name = "idx_budget_status", columnList = "status"),
- @Index(name = "idx_budget_period", columnList = "period"),
- @Index(name = "idx_budget_year_month", columnList = "year, month"),
- @Index(name = "idx_budget_created_by", columnList = "created_by_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Budget extends BaseEntity {
-
- /** Nom du budget */
- @NotBlank
- @Size(max = 200)
- @Column(name = "name", nullable = false, length = 200)
- private String name;
-
- /** Description optionnelle */
- @Size(max = 1000)
- @Column(name = "description", length = 1000)
- private String description;
-
- /** Organisation concernée */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- /** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */
- @NotBlank
- @Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$")
- @Column(name = "period", nullable = false, length = 20)
- private String period;
-
- /** Année du budget */
- @NotNull
- @Min(value = 2020, message = "L'année doit être >= 2020")
- @Max(value = 2100, message = "L'année doit être <= 2100")
- @Column(name = "year", nullable = false)
- private Integer year;
-
- /** Mois (1-12) pour budget mensuel, null sinon */
- @Min(value = 1)
- @Max(value = 12)
- @Column(name = "month")
- private Integer month;
-
- /** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */
- @NotBlank
- @Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$")
- @Builder.Default
- @Column(name = "status", nullable = false, length = 20)
- private String status = "DRAFT";
-
- /** Lignes budgétaires */
- @OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
- @Builder.Default
- private List lines = new ArrayList<>();
-
- /** Total prévu (somme des montants prévus des lignes) */
- @NotNull
- @DecimalMin(value = "0.0")
- @Digits(integer = 14, fraction = 2)
- @Builder.Default
- @Column(name = "total_planned", nullable = false, precision = 16, scale = 2)
- private BigDecimal totalPlanned = BigDecimal.ZERO;
-
- /** Total réalisé (somme des montants réalisés des lignes) */
- @DecimalMin(value = "0.0")
- @Digits(integer = 14, fraction = 2)
- @Builder.Default
- @Column(name = "total_realized", nullable = false, precision = 16, scale = 2)
- private BigDecimal totalRealized = BigDecimal.ZERO;
-
- /** Code devise ISO 3 lettres */
- @NotBlank
- @Pattern(regexp = "^[A-Z]{3}$")
- @Builder.Default
- @Column(name = "currency", nullable = false, length = 3)
- private String currency = "XOF";
-
- /** ID du créateur du budget */
- @NotNull
- @Column(name = "created_by_id", nullable = false)
- private UUID createdById;
-
- /** Date de création */
- @NotNull
- @Column(name = "created_at_budget", nullable = false)
- private LocalDateTime createdAtBudget;
-
- /** Date d'approbation */
- @Column(name = "approved_at")
- private LocalDateTime approvedAt;
-
- /** ID de l'approbateur */
- @Column(name = "approved_by_id")
- private UUID approvedById;
-
- /** Date de début de la période budgétaire */
- @NotNull
- @Column(name = "start_date", nullable = false)
- private LocalDate startDate;
-
- /** Date de fin de la période budgétaire */
- @NotNull
- @Column(name = "end_date", nullable = false)
- private LocalDate endDate;
-
- /** Métadonnées additionnelles (JSON) */
- @Column(name = "metadata", columnDefinition = "TEXT")
- private String metadata;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (createdAtBudget == null) {
- createdAtBudget = LocalDateTime.now();
- }
- if (currency == null) {
- currency = "XOF";
- }
- if (status == null) {
- status = "DRAFT";
- }
- if (totalPlanned == null) {
- totalPlanned = BigDecimal.ZERO;
- }
- if (totalRealized == null) {
- totalRealized = BigDecimal.ZERO;
- }
- }
-
- /** Méthode métier pour ajouter une ligne budgétaire */
- public void addLine(BudgetLine line) {
- lines.add(line);
- line.setBudget(this);
- recalculateTotals();
- }
-
- /** Méthode métier pour supprimer une ligne budgétaire */
- public void removeLine(BudgetLine line) {
- lines.remove(line);
- line.setBudget(null);
- recalculateTotals();
- }
-
- /** Méthode métier pour recalculer les totaux */
- public void recalculateTotals() {
- this.totalPlanned = lines.stream()
- .map(BudgetLine::getAmountPlanned)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- this.totalRealized = lines.stream()
- .map(BudgetLine::getAmountRealized)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- /** Méthode métier pour calculer le taux de réalisation (%) */
- public double getRealizationRate() {
- if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) {
- return 0.0;
- }
- return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP)
- .multiply(new BigDecimal("100"))
- .doubleValue();
- }
-
- /** Méthode métier pour calculer l'écart (réalisé - prévu) */
- public BigDecimal getVariance() {
- return totalRealized.subtract(totalPlanned);
- }
-
- /** Méthode métier pour vérifier si le budget est dépassé */
- public boolean isOverBudget() {
- return totalRealized.compareTo(totalPlanned) > 0;
- }
-
- /** Méthode métier pour vérifier si le budget est actif */
- public boolean isActive() {
- return "ACTIVE".equals(status);
- }
-
- /** Méthode métier pour vérifier si la période est en cours */
- public boolean isCurrentPeriod() {
- LocalDate now = LocalDate.now();
- return !now.isBefore(startDate) && !now.isAfter(endDate);
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Budget
+ *
+ * Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-13
+ */
+@Entity
+@Table(name = "budgets", indexes = {
+ @Index(name = "idx_budget_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_budget_status", columnList = "status"),
+ @Index(name = "idx_budget_period", columnList = "period"),
+ @Index(name = "idx_budget_year_month", columnList = "year, month"),
+ @Index(name = "idx_budget_created_by", columnList = "created_by_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Budget extends BaseEntity {
+
+ /** Nom du budget */
+ @NotBlank
+ @Size(max = 200)
+ @Column(name = "name", nullable = false, length = 200)
+ private String name;
+
+ /** Description optionnelle */
+ @Size(max = 1000)
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ /** Organisation concernée */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */
+ @NotBlank
+ @Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$")
+ @Column(name = "period", nullable = false, length = 20)
+ private String period;
+
+ /** Année du budget */
+ @NotNull
+ @Min(value = 2020, message = "L'année doit être >= 2020")
+ @Max(value = 2100, message = "L'année doit être <= 2100")
+ @Column(name = "year", nullable = false)
+ private Integer year;
+
+ /** Mois (1-12) pour budget mensuel, null sinon */
+ @Min(value = 1)
+ @Max(value = 12)
+ @Column(name = "month")
+ private Integer month;
+
+ /** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */
+ @NotBlank
+ @Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$")
+ @Builder.Default
+ @Column(name = "status", nullable = false, length = 20)
+ private String status = "DRAFT";
+
+ /** Lignes budgétaires */
+ @OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List lines = new ArrayList<>();
+
+ /** Total prévu (somme des montants prévus des lignes) */
+ @NotNull
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 14, fraction = 2)
+ @Builder.Default
+ @Column(name = "total_planned", nullable = false, precision = 16, scale = 2)
+ private BigDecimal totalPlanned = BigDecimal.ZERO;
+
+ /** Total réalisé (somme des montants réalisés des lignes) */
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 14, fraction = 2)
+ @Builder.Default
+ @Column(name = "total_realized", nullable = false, precision = 16, scale = 2)
+ private BigDecimal totalRealized = BigDecimal.ZERO;
+
+ /** Code devise ISO 3 lettres */
+ @NotBlank
+ @Pattern(regexp = "^[A-Z]{3}$")
+ @Builder.Default
+ @Column(name = "currency", nullable = false, length = 3)
+ private String currency = "XOF";
+
+ /** ID du créateur du budget */
+ @NotNull
+ @Column(name = "created_by_id", nullable = false)
+ private UUID createdById;
+
+ /** Date de création */
+ @NotNull
+ @Column(name = "created_at_budget", nullable = false)
+ private LocalDateTime createdAtBudget;
+
+ /** Date d'approbation */
+ @Column(name = "approved_at")
+ private LocalDateTime approvedAt;
+
+ /** ID de l'approbateur */
+ @Column(name = "approved_by_id")
+ private UUID approvedById;
+
+ /** Date de début de la période budgétaire */
+ @NotNull
+ @Column(name = "start_date", nullable = false)
+ private LocalDate startDate;
+
+ /** Date de fin de la période budgétaire */
+ @NotNull
+ @Column(name = "end_date", nullable = false)
+ private LocalDate endDate;
+
+ /** Métadonnées additionnelles (JSON) */
+ @Column(name = "metadata", columnDefinition = "TEXT")
+ private String metadata;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (createdAtBudget == null) {
+ createdAtBudget = LocalDateTime.now();
+ }
+ if (currency == null) {
+ currency = "XOF";
+ }
+ if (status == null) {
+ status = "DRAFT";
+ }
+ if (totalPlanned == null) {
+ totalPlanned = BigDecimal.ZERO;
+ }
+ if (totalRealized == null) {
+ totalRealized = BigDecimal.ZERO;
+ }
+ }
+
+ /** Méthode métier pour ajouter une ligne budgétaire */
+ public void addLine(BudgetLine line) {
+ lines.add(line);
+ line.setBudget(this);
+ recalculateTotals();
+ }
+
+ /** Méthode métier pour supprimer une ligne budgétaire */
+ public void removeLine(BudgetLine line) {
+ lines.remove(line);
+ line.setBudget(null);
+ recalculateTotals();
+ }
+
+ /** Méthode métier pour recalculer les totaux */
+ public void recalculateTotals() {
+ this.totalPlanned = lines.stream()
+ .map(BudgetLine::getAmountPlanned)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ this.totalRealized = lines.stream()
+ .map(BudgetLine::getAmountRealized)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ /** Méthode métier pour calculer le taux de réalisation (%) */
+ public double getRealizationRate() {
+ if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) {
+ return 0.0;
+ }
+ return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP)
+ .multiply(new BigDecimal("100"))
+ .doubleValue();
+ }
+
+ /** Méthode métier pour calculer l'écart (réalisé - prévu) */
+ public BigDecimal getVariance() {
+ return totalRealized.subtract(totalPlanned);
+ }
+
+ /** Méthode métier pour vérifier si le budget est dépassé */
+ public boolean isOverBudget() {
+ return totalRealized.compareTo(totalPlanned) > 0;
+ }
+
+ /** Méthode métier pour vérifier si le budget est actif */
+ public boolean isActive() {
+ return "ACTIVE".equals(status);
+ }
+
+ /** Méthode métier pour vérifier si la période est en cours */
+ public boolean isCurrentPeriod() {
+ LocalDate now = LocalDate.now();
+ return !now.isBefore(startDate) && !now.isAfter(endDate);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/BudgetLine.java b/src/main/java/dev/lions/unionflow/server/entity/BudgetLine.java
index dfd4949..02f0c07 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/BudgetLine.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/BudgetLine.java
@@ -1,102 +1,102 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Ligne Budgétaire
- *
- * Représente une ligne dans un budget (catégorie de dépense/recette).
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-13
- */
-@Entity
-@Table(name = "budget_lines", indexes = {
- @Index(name = "idx_budget_line_budget", columnList = "budget_id"),
- @Index(name = "idx_budget_line_category", columnList = "category")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class BudgetLine extends BaseEntity {
-
- /** Budget parent */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "budget_id", nullable = false)
- private Budget budget;
-
- /** Catégorie (CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER) */
- @NotBlank
- @Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$")
- @Column(name = "category", nullable = false, length = 20)
- private String category;
-
- /** Nom de la ligne */
- @NotBlank
- @Size(max = 200)
- @Column(name = "name", nullable = false, length = 200)
- private String name;
-
- /** Description optionnelle */
- @Size(max = 500)
- @Column(name = "description", length = 500)
- private String description;
-
- /** Montant prévu */
- @NotNull
- @DecimalMin(value = "0.0")
- @Digits(integer = 14, fraction = 2)
- @Column(name = "amount_planned", nullable = false, precision = 16, scale = 2)
- private BigDecimal amountPlanned;
-
- /** Montant réalisé */
- @DecimalMin(value = "0.0")
- @Digits(integer = 14, fraction = 2)
- @Builder.Default
- @Column(name = "amount_realized", nullable = false, precision = 16, scale = 2)
- private BigDecimal amountRealized = BigDecimal.ZERO;
-
- /** Notes additionnelles */
- @Size(max = 1000)
- @Column(name = "notes", length = 1000)
- private String notes;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (amountRealized == null) {
- amountRealized = BigDecimal.ZERO;
- }
- }
-
- /** Méthode métier pour calculer le taux de réalisation (%) */
- public double getRealizationRate() {
- if (amountPlanned.compareTo(BigDecimal.ZERO) == 0) {
- return 0.0;
- }
- return amountRealized.divide(amountPlanned, 4, java.math.RoundingMode.HALF_UP)
- .multiply(new BigDecimal("100"))
- .doubleValue();
- }
-
- /** Méthode métier pour calculer l'écart */
- public BigDecimal getVariance() {
- return amountRealized.subtract(amountPlanned);
- }
-
- /** Méthode métier pour vérifier si la ligne est dépassée */
- public boolean isOverBudget() {
- return amountRealized.compareTo(amountPlanned) > 0;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Ligne Budgétaire
+ *
+ * Représente une ligne dans un budget (catégorie de dépense/recette).
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-13
+ */
+@Entity
+@Table(name = "budget_lines", indexes = {
+ @Index(name = "idx_budget_line_budget", columnList = "budget_id"),
+ @Index(name = "idx_budget_line_category", columnList = "category")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class BudgetLine extends BaseEntity {
+
+ /** Budget parent */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "budget_id", nullable = false)
+ private Budget budget;
+
+ /** Catégorie (CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER) */
+ @NotBlank
+ @Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$")
+ @Column(name = "category", nullable = false, length = 20)
+ private String category;
+
+ /** Nom de la ligne */
+ @NotBlank
+ @Size(max = 200)
+ @Column(name = "name", nullable = false, length = 200)
+ private String name;
+
+ /** Description optionnelle */
+ @Size(max = 500)
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Montant prévu */
+ @NotNull
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 14, fraction = 2)
+ @Column(name = "amount_planned", nullable = false, precision = 16, scale = 2)
+ private BigDecimal amountPlanned;
+
+ /** Montant réalisé */
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 14, fraction = 2)
+ @Builder.Default
+ @Column(name = "amount_realized", nullable = false, precision = 16, scale = 2)
+ private BigDecimal amountRealized = BigDecimal.ZERO;
+
+ /** Notes additionnelles */
+ @Size(max = 1000)
+ @Column(name = "notes", length = 1000)
+ private String notes;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (amountRealized == null) {
+ amountRealized = BigDecimal.ZERO;
+ }
+ }
+
+ /** Méthode métier pour calculer le taux de réalisation (%) */
+ public double getRealizationRate() {
+ if (amountPlanned.compareTo(BigDecimal.ZERO) == 0) {
+ return 0.0;
+ }
+ return amountRealized.divide(amountPlanned, 4, java.math.RoundingMode.HALF_UP)
+ .multiply(new BigDecimal("100"))
+ .doubleValue();
+ }
+
+ /** Méthode métier pour calculer l'écart */
+ public BigDecimal getVariance() {
+ return amountRealized.subtract(amountPlanned);
+ }
+
+ /** Méthode métier pour vérifier si la ligne est dépassée */
+ public boolean isOverBudget() {
+ return amountRealized.compareTo(amountPlanned) > 0;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java b/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java
index f7d83a7..ce007be 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java
@@ -1,122 +1,127 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité CompteComptable pour le plan comptable
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "comptes_comptables",
- indexes = {
- @Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true),
- @Index(name = "idx_compte_type", columnList = "type_compte"),
- @Index(name = "idx_compte_classe", columnList = "classe_comptable")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CompteComptable extends BaseEntity {
-
- /** Numéro de compte unique (ex: 411000, 512000) */
- @NotBlank
- @Column(name = "numero_compte", unique = true, nullable = false, length = 10)
- private String numeroCompte;
-
- /** Libellé du compte */
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 200)
- private String libelle;
-
- /** Type de compte */
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_compte", nullable = false, length = 30)
- private TypeCompteComptable typeCompte;
-
- /** Classe comptable (1-7) */
- @NotNull
- @Min(value = 1, message = "La classe comptable doit être entre 1 et 7")
- @Max(value = 7, message = "La classe comptable doit être entre 1 et 7")
- @Column(name = "classe_comptable", nullable = false)
- private Integer classeComptable;
-
- /** Solde initial */
- @Builder.Default
- @DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "solde_initial", precision = 14, scale = 2)
- private BigDecimal soldeInitial = BigDecimal.ZERO;
-
- /** Solde actuel (calculé) */
- @Builder.Default
- @Digits(integer = 12, fraction = 2)
- @Column(name = "solde_actuel", precision = 14, scale = 2)
- private BigDecimal soldeActuel = BigDecimal.ZERO;
-
- /** Compte collectif (regroupe plusieurs sous-comptes) */
- @Builder.Default
- @Column(name = "compte_collectif", nullable = false)
- private Boolean compteCollectif = false;
-
- /** Compte analytique */
- @Builder.Default
- @Column(name = "compte_analytique", nullable = false)
- private Boolean compteAnalytique = false;
-
- /** Description du compte */
- @Column(name = "description", length = 500)
- private String description;
-
- /** Lignes d'écriture associées */
- @JsonIgnore
- @OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List lignesEcriture = new ArrayList<>();
-
- /** Méthode métier pour obtenir le numéro formaté */
- public String getNumeroFormate() {
- return String.format("%-10s", numeroCompte);
- }
-
- /** Méthode métier pour vérifier si c'est un compte de trésorerie */
- public boolean isTresorerie() {
- return TypeCompteComptable.TRESORERIE.equals(typeCompte);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (soldeInitial == null) {
- soldeInitial = BigDecimal.ZERO;
- }
- if (soldeActuel == null) {
- soldeActuel = soldeInitial;
- }
- if (compteCollectif == null) {
- compteCollectif = false;
- }
- if (compteAnalytique == null) {
- compteAnalytique = false;
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité CompteComptable pour le plan comptable
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "comptes_comptables",
+ indexes = {
+ @Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true),
+ @Index(name = "idx_compte_type", columnList = "type_compte"),
+ @Index(name = "idx_compte_classe", columnList = "classe_comptable")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CompteComptable extends BaseEntity {
+
+ /** Numéro de compte unique (ex: 411000, 512000) */
+ @NotBlank
+ @Column(name = "numero_compte", unique = true, nullable = false, length = 10)
+ private String numeroCompte;
+
+ /** Libellé du compte */
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 200)
+ private String libelle;
+
+ /** Type de compte */
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_compte", nullable = false, length = 30)
+ private TypeCompteComptable typeCompte;
+
+ /** Classe comptable (1-7) */
+ @NotNull
+ @Min(value = 1, message = "La classe comptable doit être entre 1 et 9")
+ @Max(value = 9, message = "La classe comptable doit être entre 1 et 9")
+ @Column(name = "classe_comptable", nullable = false)
+ private Integer classeComptable;
+
+ /** Solde initial */
+ @Builder.Default
+ @DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "solde_initial", precision = 14, scale = 2)
+ private BigDecimal soldeInitial = BigDecimal.ZERO;
+
+ /** Solde actuel (calculé) */
+ @Builder.Default
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "solde_actuel", precision = 14, scale = 2)
+ private BigDecimal soldeActuel = BigDecimal.ZERO;
+
+ /** Compte collectif (regroupe plusieurs sous-comptes) */
+ @Builder.Default
+ @Column(name = "compte_collectif", nullable = false)
+ private Boolean compteCollectif = false;
+
+ /** Compte analytique */
+ @Builder.Default
+ @Column(name = "compte_analytique", nullable = false)
+ private Boolean compteAnalytique = false;
+
+ /** Description du compte */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Organisation propriétaire (null = compte standard global) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /** Lignes d'écriture associées */
+ @JsonIgnore
+ @OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List lignesEcriture = new ArrayList<>();
+
+ /** Méthode métier pour obtenir le numéro formaté */
+ public String getNumeroFormate() {
+ return String.format("%-10s", numeroCompte);
+ }
+
+ /** Méthode métier pour vérifier si c'est un compte de trésorerie */
+ public boolean isTresorerie() {
+ return TypeCompteComptable.TRESORERIE.equals(typeCompte);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (soldeInitial == null) {
+ soldeInitial = BigDecimal.ZERO;
+ }
+ if (soldeActuel == null) {
+ soldeActuel = soldeInitial;
+ }
+ if (compteCollectif == null) {
+ compteCollectif = false;
+ }
+ if (compteAnalytique == null) {
+ compteAnalytique = false;
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java b/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java
index cdcf9ed..c93d5bf 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java
@@ -1,105 +1,105 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Pattern;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité CompteWave pour la gestion des comptes Wave Mobile Money
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(name = "comptes_wave", indexes = {
- @Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true),
- @Index(name = "idx_compte_wave_statut", columnList = "statut_compte"),
- @Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"),
- @Index(name = "idx_compte_wave_membre", columnList = "membre_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CompteWave extends BaseEntity {
-
- /** Numéro de téléphone Wave (format +225XXXXXXXX) */
- @NotBlank
- @Pattern(regexp = "^\\+225[0-9]{8}$", message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
- @Column(name = "numero_telephone", unique = true, nullable = false, length = 13)
- private String numeroTelephone;
-
- /** Statut du compte */
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut_compte", nullable = false, length = 30)
- private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE;
-
- /** Identifiant Wave API (encrypté) */
- @Column(name = "wave_account_id", length = 255)
- private String waveAccountId;
-
- /** Clé API Wave (encryptée) */
- @Column(name = "wave_api_key", length = 500)
- private String waveApiKey;
-
- /** Environnement (SANDBOX ou PRODUCTION) */
- @Column(name = "environnement", length = 20)
- private String environnement;
-
- /** Date de dernière vérification */
- @Column(name = "date_derniere_verification")
- private java.time.LocalDateTime dateDerniereVerification;
-
- /** Commentaires */
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-
- // Relations
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id")
- private Membre membre;
-
- @JsonIgnore
-
- @OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List transactions = new ArrayList<>();
-
- /** Méthode métier pour vérifier si le compte est vérifié */
- public boolean isVerifie() {
- return StatutCompteWave.VERIFIE.equals(statutCompte);
- }
-
- /** Méthode métier pour vérifier si le compte peut être utilisé */
- public boolean peutEtreUtilise() {
- return StatutCompteWave.VERIFIE.equals(statutCompte);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statutCompte == null) {
- statutCompte = StatutCompteWave.NON_VERIFIE;
- }
- if (environnement == null || environnement.isEmpty()) {
- environnement = "SANDBOX";
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité CompteWave pour la gestion des comptes Wave Mobile Money
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(name = "comptes_wave", indexes = {
+ @Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true),
+ @Index(name = "idx_compte_wave_statut", columnList = "statut_compte"),
+ @Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_compte_wave_membre", columnList = "membre_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CompteWave extends BaseEntity {
+
+ /** Numéro de téléphone Wave (format +225XXXXXXXX) */
+ @NotBlank
+ @Pattern(regexp = "^\\+225[0-9]{8}$", message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
+ @Column(name = "numero_telephone", unique = true, nullable = false, length = 13)
+ private String numeroTelephone;
+
+ /** Statut du compte */
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut_compte", nullable = false, length = 30)
+ private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE;
+
+ /** Identifiant Wave API (encrypté) */
+ @Column(name = "wave_account_id", length = 255)
+ private String waveAccountId;
+
+ /** Clé API Wave (encryptée) */
+ @Column(name = "wave_api_key", length = 500)
+ private String waveApiKey;
+
+ /** Environnement (SANDBOX ou PRODUCTION) */
+ @Column(name = "environnement", length = 20)
+ private String environnement;
+
+ /** Date de dernière vérification */
+ @Column(name = "date_derniere_verification")
+ private java.time.LocalDateTime dateDerniereVerification;
+
+ /** Commentaires */
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+
+ // Relations
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id")
+ private Membre membre;
+
+ @JsonIgnore
+
+ @OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List transactions = new ArrayList<>();
+
+ /** Méthode métier pour vérifier si le compte est vérifié */
+ public boolean isVerifie() {
+ return StatutCompteWave.VERIFIE.equals(statutCompte);
+ }
+
+ /** Méthode métier pour vérifier si le compte peut être utilisé */
+ public boolean peutEtreUtilise() {
+ return StatutCompteWave.VERIFIE.equals(statutCompte);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statutCompte == null) {
+ statutCompte = StatutCompteWave.NON_VERIFIE;
+ }
+ if (environnement == null || environnement.isEmpty()) {
+ environnement = "SANDBOX";
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Configuration.java b/src/main/java/dev/lions/unionflow/server/entity/Configuration.java
index ee9b3ea..a7f7abb 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Configuration.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Configuration.java
@@ -1,53 +1,53 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Configuration pour la gestion de la configuration système
- *
- * @author UnionFlow Team
- * @version 1.0
- */
-@Entity
-@Table(name = "configurations")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Configuration extends BaseEntity {
-
- @NotBlank
- @Column(name = "cle", nullable = false, unique = true, length = 255)
- private String cle;
-
- @Column(name = "valeur", columnDefinition = "TEXT")
- private String valeur;
-
- @Column(name = "type", length = 50)
- private String type; // STRING, NUMBER, BOOLEAN, JSON, DATE
-
- @Column(name = "categorie", length = 50)
- private String categorie; // SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE
-
- @Column(name = "description", length = 1000)
- private String description;
-
- @Column(name = "modifiable")
- @Builder.Default
- private Boolean modifiable = true;
-
- @Column(name = "visible")
- @Builder.Default
- private Boolean visible = true;
-
- @Column(name = "metadonnees", columnDefinition = "TEXT")
- private String metadonnees; // JSON string pour stocker les métadonnées
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Configuration pour la gestion de la configuration système
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ */
+@Entity
+@Table(name = "configurations")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Configuration extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "cle", nullable = false, unique = true, length = 255)
+ private String cle;
+
+ @Column(name = "valeur", columnDefinition = "TEXT")
+ private String valeur;
+
+ @Column(name = "type", length = 50)
+ private String type; // STRING, NUMBER, BOOLEAN, JSON, DATE
+
+ @Column(name = "categorie", length = 50)
+ private String categorie; // SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE
+
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ @Column(name = "modifiable")
+ @Builder.Default
+ private Boolean modifiable = true;
+
+ @Column(name = "visible")
+ @Builder.Default
+ private Boolean visible = true;
+
+ @Column(name = "metadonnees", columnDefinition = "TEXT")
+ private String metadonnees; // JSON string pour stocker les métadonnées
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java b/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java
index 6adca19..965acb3 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java
@@ -1,69 +1,69 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité ConfigurationWave pour la configuration de l'intégration Wave
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "configurations_wave",
- indexes = {
- @Index(name = "idx_config_wave_cle", columnList = "cle", unique = true)
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ConfigurationWave extends BaseEntity {
-
- /** Clé de configuration */
- @NotBlank
- @Column(name = "cle", unique = true, nullable = false, length = 100)
- private String cle;
-
- /** Valeur de configuration (peut être encryptée) */
- @Column(name = "valeur", columnDefinition = "TEXT")
- private String valeur;
-
- /** Description de la configuration */
- @Column(name = "description", length = 500)
- private String description;
-
- /** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */
- @Column(name = "type_valeur", length = 20)
- private String typeValeur;
-
- /** Environnement (SANDBOX, PRODUCTION, COMMON) */
- @Column(name = "environnement", length = 20)
- private String environnement;
-
- /** Méthode métier pour vérifier si la valeur est encryptée */
- public boolean isEncryptee() {
- return "ENCRYPTED".equals(typeValeur);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (typeValeur == null || typeValeur.isEmpty()) {
- typeValeur = "STRING";
- }
- if (environnement == null || environnement.isEmpty()) {
- environnement = "COMMON";
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité ConfigurationWave pour la configuration de l'intégration Wave
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "configurations_wave",
+ indexes = {
+ @Index(name = "idx_config_wave_cle", columnList = "cle", unique = true)
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ConfigurationWave extends BaseEntity {
+
+ /** Clé de configuration */
+ @NotBlank
+ @Column(name = "cle", unique = true, nullable = false, length = 100)
+ private String cle;
+
+ /** Valeur de configuration (peut être encryptée) */
+ @Column(name = "valeur", columnDefinition = "TEXT")
+ private String valeur;
+
+ /** Description de la configuration */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */
+ @Column(name = "type_valeur", length = 20)
+ private String typeValeur;
+
+ /** Environnement (SANDBOX, PRODUCTION, COMMON) */
+ @Column(name = "environnement", length = 20)
+ private String environnement;
+
+ /** Méthode métier pour vérifier si la valeur est encryptée */
+ public boolean isEncryptee() {
+ return "ENCRYPTED".equals(typeValeur);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (typeValeur == null || typeValeur.isEmpty()) {
+ typeValeur = "STRING";
+ }
+ if (environnement == null || environnement.isEmpty()) {
+ environnement = "COMMON";
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java b/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java
index b8dfda9..50502cc 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java
@@ -1,194 +1,194 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicLong;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Cotisation avec UUID Représente une cotisation d'un membre à son
- * organisation
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-01-16
- */
-@Entity
-@Table(name = "cotisations", indexes = {
- @Index(name = "idx_cotisation_membre", columnList = "membre_id"),
- @Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
- @Index(name = "idx_cotisation_statut", columnList = "statut"),
- @Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
- @Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
- @Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Cotisation extends BaseEntity {
-
- @NotBlank
- @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
- private String numeroReference;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- /** Organisation pour laquelle la cotisation est due */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- /** Intention de paiement Wave associée (null si cotisation en attente) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "intention_paiement_id")
- private IntentionPaiement intentionPaiement;
-
- @NotBlank
- @Column(name = "type_cotisation", nullable = false, length = 50)
- private String typeCotisation;
-
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 100)
- private String libelle;
-
- @NotNull
- @DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
- private BigDecimal montantDu;
-
- @Builder.Default
- @DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
- private BigDecimal montantPaye = BigDecimal.ZERO;
-
- @NotBlank
- @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
- @Column(name = "code_devise", nullable = false, length = 3)
- private String codeDevise;
-
- @NotBlank
- @Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
- @Column(name = "statut", nullable = false, length = 30)
- private String statut;
-
- @NotNull
- @Column(name = "date_echeance", nullable = false)
- private LocalDate dateEcheance;
-
- @Column(name = "date_paiement")
- private LocalDateTime datePaiement;
-
- @Size(max = 500)
- @Column(name = "description", length = 500)
- private String description;
-
- @Size(max = 20)
- @Column(name = "periode", length = 20)
- private String periode;
-
- @NotNull
- @Min(value = 2020, message = "L'année doit être supérieure à 2020")
- @Max(value = 2100, message = "L'année doit être inférieure à 2100")
- @Column(name = "annee", nullable = false)
- private Integer annee;
-
- @Min(value = 1, message = "Le mois doit être entre 1 et 12")
- @Max(value = 12, message = "Le mois doit être entre 1 et 12")
- @Column(name = "mois")
- private Integer mois;
-
- @Size(max = 1000)
- @Column(name = "observations", length = 1000)
- private String observations;
-
- @Builder.Default
- @Column(name = "recurrente", nullable = false)
- private Boolean recurrente = false;
-
- @Builder.Default
- @Min(value = 0, message = "Le nombre de rappels doit être positif")
- @Column(name = "nombre_rappels", nullable = false)
- private Integer nombreRappels = 0;
-
- @Column(name = "date_dernier_rappel")
- private LocalDateTime dateDernierRappel;
-
- @Column(name = "valide_par_id")
- private UUID valideParId;
-
- @Size(max = 100)
- @Column(name = "nom_validateur", length = 100)
- private String nomValidateur;
-
- @Column(name = "date_validation")
- private LocalDateTime dateValidation;
-
- /** Méthode métier pour calculer le montant restant à payer */
- public BigDecimal getMontantRestant() {
- if (montantDu == null || montantPaye == null) {
- return BigDecimal.ZERO;
- }
- return montantDu.subtract(montantPaye);
- }
-
- /** Méthode métier pour vérifier si la cotisation est entièrement payée */
- public boolean isEntierementPayee() {
- return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
- }
-
- /** Méthode métier pour vérifier si la cotisation est en retard */
- public boolean isEnRetard() {
- return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
- }
-
- private static final AtomicLong REFERENCE_COUNTER =
- new AtomicLong(System.currentTimeMillis() % 100000000L);
-
- /** Méthode métier pour générer un numéro de référence unique */
- public static String genererNumeroReference() {
- return "COT-"
- + LocalDate.now().getYear()
- + "-"
- + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate(); // Appelle le onCreate de BaseEntity
- if (numeroReference == null || numeroReference.isEmpty()) {
- numeroReference = genererNumeroReference();
- }
- if (codeDevise == null) {
- codeDevise = "XOF";
- }
- if (statut == null) {
- statut = "EN_ATTENTE";
- }
- if (montantPaye == null) {
- montantPaye = BigDecimal.ZERO;
- }
- if (nombreRappels == null) {
- nombreRappels = 0;
- }
- if (recurrente == null) {
- recurrente = false;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Cotisation avec UUID Représente une cotisation d'un membre à son
+ * organisation
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-01-16
+ */
+@Entity
+@Table(name = "cotisations", indexes = {
+ @Index(name = "idx_cotisation_membre", columnList = "membre_id"),
+ @Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
+ @Index(name = "idx_cotisation_statut", columnList = "statut"),
+ @Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
+ @Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
+ @Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Cotisation extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
+ private String numeroReference;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ /** Organisation pour laquelle la cotisation est due */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /** Intention de paiement Wave associée (null si cotisation en attente) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "intention_paiement_id")
+ private IntentionPaiement intentionPaiement;
+
+ @NotBlank
+ @Column(name = "type_cotisation", nullable = false, length = 50)
+ private String typeCotisation;
+
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 100)
+ private String libelle;
+
+ @NotNull
+ @DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantDu;
+
+ @Builder.Default
+ @DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantPaye = BigDecimal.ZERO;
+
+ @NotBlank
+ @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
+ @Column(name = "code_devise", nullable = false, length = 3)
+ private String codeDevise;
+
+ @NotBlank
+ @Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
+ @Column(name = "statut", nullable = false, length = 30)
+ private String statut;
+
+ @NotNull
+ @Column(name = "date_echeance", nullable = false)
+ private LocalDate dateEcheance;
+
+ @Column(name = "date_paiement")
+ private LocalDateTime datePaiement;
+
+ @Size(max = 500)
+ @Column(name = "description", length = 500)
+ private String description;
+
+ @Size(max = 20)
+ @Column(name = "periode", length = 20)
+ private String periode;
+
+ @NotNull
+ @Min(value = 2020, message = "L'année doit être supérieure à 2020")
+ @Max(value = 2100, message = "L'année doit être inférieure à 2100")
+ @Column(name = "annee", nullable = false)
+ private Integer annee;
+
+ @Min(value = 1, message = "Le mois doit être entre 1 et 12")
+ @Max(value = 12, message = "Le mois doit être entre 1 et 12")
+ @Column(name = "mois")
+ private Integer mois;
+
+ @Size(max = 1000)
+ @Column(name = "observations", length = 1000)
+ private String observations;
+
+ @Builder.Default
+ @Column(name = "recurrente", nullable = false)
+ private Boolean recurrente = false;
+
+ @Builder.Default
+ @Min(value = 0, message = "Le nombre de rappels doit être positif")
+ @Column(name = "nombre_rappels", nullable = false)
+ private Integer nombreRappels = 0;
+
+ @Column(name = "date_dernier_rappel")
+ private LocalDateTime dateDernierRappel;
+
+ @Column(name = "valide_par_id")
+ private UUID valideParId;
+
+ @Size(max = 100)
+ @Column(name = "nom_validateur", length = 100)
+ private String nomValidateur;
+
+ @Column(name = "date_validation")
+ private LocalDateTime dateValidation;
+
+ /** Méthode métier pour calculer le montant restant à payer */
+ public BigDecimal getMontantRestant() {
+ if (montantDu == null || montantPaye == null) {
+ return BigDecimal.ZERO;
+ }
+ return montantDu.subtract(montantPaye);
+ }
+
+ /** Méthode métier pour vérifier si la cotisation est entièrement payée */
+ public boolean isEntierementPayee() {
+ return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
+ }
+
+ /** Méthode métier pour vérifier si la cotisation est en retard */
+ public boolean isEnRetard() {
+ return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
+ }
+
+ private static final AtomicLong REFERENCE_COUNTER =
+ new AtomicLong(System.currentTimeMillis() % 100000000L);
+
+ /** Méthode métier pour générer un numéro de référence unique */
+ public static String genererNumeroReference() {
+ return "COT-"
+ + LocalDate.now().getYear()
+ + "-"
+ + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate(); // Appelle le onCreate de BaseEntity
+ if (numeroReference == null || numeroReference.isEmpty()) {
+ numeroReference = genererNumeroReference();
+ }
+ if (codeDevise == null) {
+ codeDevise = "XOF";
+ }
+ if (statut == null) {
+ statut = "EN_ATTENTE";
+ }
+ if (montantPaye == null) {
+ montantPaye = BigDecimal.ZERO;
+ }
+ if (nombreRappels == null) {
+ nombreRappels = 0;
+ }
+ if (recurrente == null) {
+ recurrente = false;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/DemandeAdhesion.java b/src/main/java/dev/lions/unionflow/server/entity/DemandeAdhesion.java
index c08403f..9cfc961 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/DemandeAdhesion.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/DemandeAdhesion.java
@@ -1,132 +1,132 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.concurrent.atomic.AtomicLong;
-import lombok.*;
-
-/**
- * Demande d'adhésion d'un utilisateur à une organisation.
- *
- * Flux :
- *
- * - L'utilisateur crée son compte et choisit une organisation
- * - Une {@code DemandeAdhesion} est créée (statut EN_ATTENTE)
- * - Si frais d'adhésion : une {@link IntentionPaiement} est créée et liée
- * - Le manager valide → {@link MembreOrganisation} créé, quota souscription décrémenté
- *
- *
- * Remplace l'ancienne entité {@code Adhesion}.
- * Table : {@code demandes_adhesion}
- */
-@Entity
-@Table(
- name = "demandes_adhesion",
- indexes = {
- @Index(name = "idx_da_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_da_organisation", columnList = "organisation_id"),
- @Index(name = "idx_da_statut", columnList = "statut"),
- @Index(name = "idx_da_date", columnList = "date_demande")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class DemandeAdhesion extends BaseEntity {
-
- @NotBlank
- @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
- private String numeroReference;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "utilisateur_id", nullable = false)
- private Membre utilisateur;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Pattern(regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE)$")
- @Builder.Default
- @Column(name = "statut", nullable = false, length = 20)
- private String statut = "EN_ATTENTE";
-
- @Builder.Default
- @DecimalMin("0.00")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
- private BigDecimal fraisAdhesion = BigDecimal.ZERO;
-
- @Builder.Default
- @DecimalMin("0.00")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
- private BigDecimal montantPaye = BigDecimal.ZERO;
-
- @Builder.Default
- @Pattern(regexp = "^[A-Z]{3}$")
- @Column(name = "code_devise", nullable = false, length = 3)
- private String codeDevise = "XOF";
-
- /** Intention de paiement Wave liée aux frais d'adhésion */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "intention_paiement_id")
- private IntentionPaiement intentionPaiement;
-
- @Builder.Default
- @Column(name = "date_demande", nullable = false)
- private LocalDateTime dateDemande = LocalDateTime.now();
-
- @Column(name = "date_traitement")
- private LocalDateTime dateTraitement;
-
- /** Manager/Admin qui a approuvé ou rejeté */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "traite_par_id")
- private Membre traitePar;
-
- @Column(name = "motif_rejet", length = 1000)
- private String motifRejet;
-
- @Column(name = "observations", length = 1000)
- private String observations;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean isEnAttente() { return "EN_ATTENTE".equals(statut); }
- public boolean isApprouvee() { return "APPROUVEE".equals(statut); }
- public boolean isRejetee() { return "REJETEE".equals(statut); }
-
- public boolean isPayeeIntegralement() {
- return fraisAdhesion != null
- && montantPaye != null
- && montantPaye.compareTo(fraisAdhesion) >= 0;
- }
-
- private static final AtomicLong REFERENCE_COUNTER =
- new AtomicLong(System.currentTimeMillis() % 100000000L);
-
- public static String genererNumeroReference() {
- return "ADH-" + java.time.LocalDate.now().getYear()
- + "-" + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (dateDemande == null) dateDemande = LocalDateTime.now();
- if (statut == null) statut = "EN_ATTENTE";
- if (codeDevise == null) codeDevise = "XOF";
- if (fraisAdhesion == null) fraisAdhesion = BigDecimal.ZERO;
- if (montantPaye == null) montantPaye = BigDecimal.ZERO;
- if (numeroReference == null || numeroReference.isEmpty()) {
- numeroReference = genererNumeroReference();
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.concurrent.atomic.AtomicLong;
+import lombok.*;
+
+/**
+ * Demande d'adhésion d'un utilisateur à une organisation.
+ *
+ *
Flux :
+ *
+ * - L'utilisateur crée son compte et choisit une organisation
+ * - Une {@code DemandeAdhesion} est créée (statut EN_ATTENTE)
+ * - Si frais d'adhésion : une {@link IntentionPaiement} est créée et liée
+ * - Le manager valide → {@link MembreOrganisation} créé, quota souscription décrémenté
+ *
+ *
+ * Remplace l'ancienne entité {@code Adhesion}.
+ * Table : {@code demandes_adhesion}
+ */
+@Entity
+@Table(
+ name = "demandes_adhesion",
+ indexes = {
+ @Index(name = "idx_da_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_da_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_da_statut", columnList = "statut"),
+ @Index(name = "idx_da_date", columnList = "date_demande")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class DemandeAdhesion extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
+ private String numeroReference;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "utilisateur_id", nullable = false)
+ private Membre utilisateur;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Pattern(regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE)$")
+ @Builder.Default
+ @Column(name = "statut", nullable = false, length = 20)
+ private String statut = "EN_ATTENTE";
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
+ private BigDecimal fraisAdhesion = BigDecimal.ZERO;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantPaye = BigDecimal.ZERO;
+
+ @Builder.Default
+ @Pattern(regexp = "^[A-Z]{3}$")
+ @Column(name = "code_devise", nullable = false, length = 3)
+ private String codeDevise = "XOF";
+
+ /** Intention de paiement Wave liée aux frais d'adhésion */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "intention_paiement_id")
+ private IntentionPaiement intentionPaiement;
+
+ @Builder.Default
+ @Column(name = "date_demande", nullable = false)
+ private LocalDateTime dateDemande = LocalDateTime.now();
+
+ @Column(name = "date_traitement")
+ private LocalDateTime dateTraitement;
+
+ /** Manager/Admin qui a approuvé ou rejeté */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "traite_par_id")
+ private Membre traitePar;
+
+ @Column(name = "motif_rejet", length = 1000)
+ private String motifRejet;
+
+ @Column(name = "observations", length = 1000)
+ private String observations;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean isEnAttente() { return "EN_ATTENTE".equals(statut); }
+ public boolean isApprouvee() { return "APPROUVEE".equals(statut); }
+ public boolean isRejetee() { return "REJETEE".equals(statut); }
+
+ public boolean isPayeeIntegralement() {
+ return fraisAdhesion != null
+ && montantPaye != null
+ && montantPaye.compareTo(fraisAdhesion) >= 0;
+ }
+
+ private static final AtomicLong REFERENCE_COUNTER =
+ new AtomicLong(System.currentTimeMillis() % 100000000L);
+
+ public static String genererNumeroReference() {
+ return "ADH-" + java.time.LocalDate.now().getYear()
+ + "-" + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (dateDemande == null) dateDemande = LocalDateTime.now();
+ if (statut == null) statut = "EN_ATTENTE";
+ if (codeDevise == null) codeDevise = "XOF";
+ if (fraisAdhesion == null) fraisAdhesion = BigDecimal.ZERO;
+ if (montantPaye == null) montantPaye = BigDecimal.ZERO;
+ if (numeroReference == null || numeroReference.isEmpty()) {
+ numeroReference = genererNumeroReference();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java b/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java
index 5e6994c..a1acdb8 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java
@@ -1,130 +1,130 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
-import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
-import jakarta.persistence.*;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDateTime;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/** Entité représentant une demande d'aide dans le système de solidarité */
-@Entity
-@Table(name = "demandes_aide")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class DemandeAide extends BaseEntity {
-
- @Column(name = "titre", nullable = false, length = 200)
- private String titre;
-
- @Column(name = "description", nullable = false, columnDefinition = "TEXT")
- private String description;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "type_aide", nullable = false)
- private TypeAide typeAide;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false)
- private StatutAide statut;
-
- @Column(name = "montant_demande", precision = 10, scale = 2)
- private BigDecimal montantDemande;
-
- @Column(name = "montant_approuve", precision = 10, scale = 2)
- private BigDecimal montantApprouve;
-
- @Column(name = "date_demande", nullable = false)
- private LocalDateTime dateDemande;
-
- @Column(name = "date_evaluation")
- private LocalDateTime dateEvaluation;
-
- @Column(name = "date_versement")
- private LocalDateTime dateVersement;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "demandeur_id", nullable = false)
- private Membre demandeur;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "evaluateur_id")
- private Membre evaluateur;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @Column(name = "justification", columnDefinition = "TEXT")
- private String justification;
-
- @Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
- private String commentaireEvaluation;
-
- @Column(name = "urgence", nullable = false)
- @Builder.Default
- private Boolean urgence = false;
-
- @Column(name = "documents_fournis")
- private String documentsFournis;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate(); // Appelle le onCreate de BaseEntity
- if (dateDemande == null) {
- dateDemande = LocalDateTime.now();
- }
- if (statut == null) {
- statut = StatutAide.EN_ATTENTE;
- }
- if (urgence == null) {
- urgence = false;
- }
- }
-
- @PreUpdate
- protected void onUpdate() {
- // Méthode appelée avant mise à jour
- }
-
- /** Vérifie si la demande est en attente */
- public boolean isEnAttente() {
- return StatutAide.EN_ATTENTE.equals(statut);
- }
-
- /** Vérifie si la demande est approuvée */
- public boolean isApprouvee() {
- return StatutAide.APPROUVEE.equals(statut);
- }
-
- /** Vérifie si la demande est rejetée */
- public boolean isRejetee() {
- return StatutAide.REJETEE.equals(statut);
- }
-
- /** Vérifie si la demande est urgente */
- public boolean isUrgente() {
- return Boolean.TRUE.equals(urgence);
- }
-
- /** Calcule le pourcentage d'approbation par rapport au montant demandé */
- public BigDecimal getPourcentageApprobation() {
- if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
- return BigDecimal.ZERO;
- }
- if (montantApprouve == null) {
- return BigDecimal.ZERO;
- }
- return montantApprouve
- .divide(montantDemande, 4, RoundingMode.HALF_UP)
- .multiply(BigDecimal.valueOf(100));
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
+import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
+import jakarta.persistence.*;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/** Entité représentant une demande d'aide dans le système de solidarité */
+@Entity
+@Table(name = "demandes_aide")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class DemandeAide extends BaseEntity {
+
+ @Column(name = "titre", nullable = false, length = 200)
+ private String titre;
+
+ @Column(name = "description", nullable = false, columnDefinition = "TEXT")
+ private String description;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_aide", nullable = false)
+ private TypeAide typeAide;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false)
+ private StatutAide statut;
+
+ @Column(name = "montant_demande", precision = 10, scale = 2)
+ private BigDecimal montantDemande;
+
+ @Column(name = "montant_approuve", precision = 10, scale = 2)
+ private BigDecimal montantApprouve;
+
+ @Column(name = "date_demande", nullable = false)
+ private LocalDateTime dateDemande;
+
+ @Column(name = "date_evaluation")
+ private LocalDateTime dateEvaluation;
+
+ @Column(name = "date_versement")
+ private LocalDateTime dateVersement;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "demandeur_id", nullable = false)
+ private Membre demandeur;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "evaluateur_id")
+ private Membre evaluateur;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @Column(name = "justification", columnDefinition = "TEXT")
+ private String justification;
+
+ @Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
+ private String commentaireEvaluation;
+
+ @Column(name = "urgence", nullable = false)
+ @Builder.Default
+ private Boolean urgence = false;
+
+ @Column(name = "documents_fournis")
+ private String documentsFournis;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate(); // Appelle le onCreate de BaseEntity
+ if (dateDemande == null) {
+ dateDemande = LocalDateTime.now();
+ }
+ if (statut == null) {
+ statut = StatutAide.EN_ATTENTE;
+ }
+ if (urgence == null) {
+ urgence = false;
+ }
+ }
+
+ @PreUpdate
+ protected void onUpdate() {
+ // Méthode appelée avant mise à jour
+ }
+
+ /** Vérifie si la demande est en attente */
+ public boolean isEnAttente() {
+ return StatutAide.EN_ATTENTE.equals(statut);
+ }
+
+ /** Vérifie si la demande est approuvée */
+ public boolean isApprouvee() {
+ return StatutAide.APPROUVEE.equals(statut);
+ }
+
+ /** Vérifie si la demande est rejetée */
+ public boolean isRejetee() {
+ return StatutAide.REJETEE.equals(statut);
+ }
+
+ /** Vérifie si la demande est urgente */
+ public boolean isUrgente() {
+ return Boolean.TRUE.equals(urgence);
+ }
+
+ /** Calcule le pourcentage d'approbation par rapport au montant demandé */
+ public BigDecimal getPourcentageApprobation() {
+ if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
+ return BigDecimal.ZERO;
+ }
+ if (montantApprouve == null) {
+ return BigDecimal.ZERO;
+ }
+ return montantApprouve
+ .divide(montantDemande, 4, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100));
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Document.java b/src/main/java/dev/lions/unionflow/server/entity/Document.java
index 4bcadf3..26c86ee 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Document.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Document.java
@@ -1,130 +1,130 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.document.TypeDocument;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Document pour la gestion documentaire sécurisée
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "documents",
- indexes = {
- @Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"),
- @Index(name = "idx_document_type", columnList = "type_document"),
- @Index(name = "idx_document_hash_md5", columnList = "hash_md5"),
- @Index(name = "idx_document_hash_sha256", columnList = "hash_sha256")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Document extends BaseEntity {
-
- /** Nom du fichier original */
- @NotBlank
- @Column(name = "nom_fichier", nullable = false, length = 255)
- private String nomFichier;
-
- /** Nom original du fichier (tel que téléchargé) */
- @Column(name = "nom_original", length = 255)
- private String nomOriginal;
-
- /** Chemin de stockage */
- @NotBlank
- @Column(name = "chemin_stockage", nullable = false, length = 1000)
- private String cheminStockage;
-
- /** Type MIME */
- @Column(name = "type_mime", length = 100)
- private String typeMime;
-
- /** Taille du fichier en octets */
- @NotNull
- @Min(value = 0, message = "La taille doit être positive")
- @Column(name = "taille_octets", nullable = false)
- private Long tailleOctets;
-
- /** Type de document */
- @Enumerated(EnumType.STRING)
- @Column(name = "type_document", length = 50)
- private TypeDocument typeDocument;
-
- /** Hash MD5 pour vérification d'intégrité */
- @Column(name = "hash_md5", length = 32)
- private String hashMd5;
-
- /** Hash SHA256 pour vérification d'intégrité */
- @Column(name = "hash_sha256", length = 64)
- private String hashSha256;
-
- /** Description du document */
- @Column(name = "description", length = 1000)
- private String description;
-
- /** Nombre de téléchargements */
- @Builder.Default
- @Column(name = "nombre_telechargements", nullable = false)
- private Integer nombreTelechargements = 0;
-
- /** Date de dernier téléchargement */
- @Column(name = "date_dernier_telechargement")
- private java.time.LocalDateTime dateDernierTelechargement;
-
- /** Pièces jointes associées */
- @JsonIgnore
- @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List piecesJointes = new ArrayList<>();
-
- /** Méthode métier pour vérifier l'intégrité avec MD5 */
- public boolean verifierIntegriteMd5(String hashAttendu) {
- return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu);
- }
-
- /** Méthode métier pour vérifier l'intégrité avec SHA256 */
- public boolean verifierIntegriteSha256(String hashAttendu) {
- return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu);
- }
-
- /** Méthode métier pour obtenir la taille formatée */
- public String getTailleFormatee() {
- if (tailleOctets == null) {
- return "0 B";
- }
- if (tailleOctets < 1024) {
- return tailleOctets + " B";
- } else if (tailleOctets < 1024 * 1024) {
- return String.format("%.2f KB", tailleOctets / 1024.0);
- } else {
- return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0));
- }
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (nombreTelechargements == null) {
- nombreTelechargements = 0;
- }
- if (typeDocument == null) {
- typeDocument = TypeDocument.AUTRE;
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.document.TypeDocument;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Document pour la gestion documentaire sécurisée
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "documents",
+ indexes = {
+ @Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"),
+ @Index(name = "idx_document_type", columnList = "type_document"),
+ @Index(name = "idx_document_hash_md5", columnList = "hash_md5"),
+ @Index(name = "idx_document_hash_sha256", columnList = "hash_sha256")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Document extends BaseEntity {
+
+ /** Nom du fichier original */
+ @NotBlank
+ @Column(name = "nom_fichier", nullable = false, length = 255)
+ private String nomFichier;
+
+ /** Nom original du fichier (tel que téléchargé) */
+ @Column(name = "nom_original", length = 255)
+ private String nomOriginal;
+
+ /** Chemin de stockage */
+ @NotBlank
+ @Column(name = "chemin_stockage", nullable = false, length = 1000)
+ private String cheminStockage;
+
+ /** Type MIME */
+ @Column(name = "type_mime", length = 100)
+ private String typeMime;
+
+ /** Taille du fichier en octets */
+ @NotNull
+ @Min(value = 0, message = "La taille doit être positive")
+ @Column(name = "taille_octets", nullable = false)
+ private Long tailleOctets;
+
+ /** Type de document */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_document", length = 50)
+ private TypeDocument typeDocument;
+
+ /** Hash MD5 pour vérification d'intégrité */
+ @Column(name = "hash_md5", length = 32)
+ private String hashMd5;
+
+ /** Hash SHA256 pour vérification d'intégrité */
+ @Column(name = "hash_sha256", length = 64)
+ private String hashSha256;
+
+ /** Description du document */
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ /** Nombre de téléchargements */
+ @Builder.Default
+ @Column(name = "nombre_telechargements", nullable = false)
+ private Integer nombreTelechargements = 0;
+
+ /** Date de dernier téléchargement */
+ @Column(name = "date_dernier_telechargement")
+ private java.time.LocalDateTime dateDernierTelechargement;
+
+ /** Pièces jointes associées */
+ @JsonIgnore
+ @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List piecesJointes = new ArrayList<>();
+
+ /** Méthode métier pour vérifier l'intégrité avec MD5 */
+ public boolean verifierIntegriteMd5(String hashAttendu) {
+ return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu);
+ }
+
+ /** Méthode métier pour vérifier l'intégrité avec SHA256 */
+ public boolean verifierIntegriteSha256(String hashAttendu) {
+ return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu);
+ }
+
+ /** Méthode métier pour obtenir la taille formatée */
+ public String getTailleFormatee() {
+ if (tailleOctets == null) {
+ return "0 B";
+ }
+ if (tailleOctets < 1024) {
+ return tailleOctets + " B";
+ } else if (tailleOctets < 1024 * 1024) {
+ return String.format("%.2f KB", tailleOctets / 1024.0);
+ } else {
+ return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0));
+ }
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (nombreTelechargements == null) {
+ nombreTelechargements = 0;
+ }
+ if (typeDocument == null) {
+ typeDocument = TypeDocument.AUTRE;
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java b/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java
index d4b3a5a..41a4983 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java
@@ -1,174 +1,174 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité EcritureComptable pour les écritures comptables
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "ecritures_comptables",
- indexes = {
- @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
- @Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
- @Index(name = "idx_ecriture_journal", columnList = "journal_id"),
- @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
- @Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class EcritureComptable extends BaseEntity {
-
- /** Numéro de pièce unique */
- @NotBlank
- @Column(name = "numero_piece", unique = true, nullable = false, length = 50)
- private String numeroPiece;
-
- /** Date de l'écriture */
- @NotNull
- @Column(name = "date_ecriture", nullable = false)
- private LocalDate dateEcriture;
-
- /** Libellé de l'écriture */
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 500)
- private String libelle;
-
- /** Référence externe */
- @Column(name = "reference", length = 100)
- private String reference;
-
- /** Lettrage (pour rapprochement) */
- @Column(name = "lettrage", length = 20)
- private String lettrage;
-
- /** Pointage (pour rapprochement bancaire) */
- @Builder.Default
- @Column(name = "pointe", nullable = false)
- private Boolean pointe = false;
-
- /** Montant total débit (somme des lignes) */
- @Builder.Default
- @DecimalMin(value = "0.0")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_debit", precision = 14, scale = 2)
- private BigDecimal montantDebit = BigDecimal.ZERO;
-
- /** Montant total crédit (somme des lignes) */
- @Builder.Default
- @DecimalMin(value = "0.0")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_credit", precision = 14, scale = 2)
- private BigDecimal montantCredit = BigDecimal.ZERO;
-
- /** Commentaires */
- @Column(name = "commentaire", length = 1000)
- private String commentaire;
-
- // Relations
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "journal_id", nullable = false)
- private JournalComptable journal;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "paiement_id")
- private Paiement paiement;
-
- /** Lignes d'écriture */
- @JsonIgnore
- @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
- @Builder.Default
- private List lignes = new ArrayList<>();
-
- /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
- public boolean isEquilibree() {
- if (montantDebit == null || montantCredit == null) {
- return false;
- }
- return montantDebit.compareTo(montantCredit) == 0;
- }
-
- /** Méthode métier pour calculer les totaux à partir des lignes */
- public void calculerTotaux() {
- if (lignes == null || lignes.isEmpty()) {
- montantDebit = BigDecimal.ZERO;
- montantCredit = BigDecimal.ZERO;
- return;
- }
-
- montantDebit =
- lignes.stream()
- .map(LigneEcriture::getMontantDebit)
- .filter(amount -> amount != null)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- montantCredit =
- lignes.stream()
- .map(LigneEcriture::getMontantCredit)
- .filter(amount -> amount != null)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- /** Méthode métier pour générer un numéro de pièce unique */
- public static String genererNumeroPiece(String prefixe, LocalDate date) {
- return String.format(
- "%s-%04d%02d%02d-%012d",
- prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
- System.currentTimeMillis() % 1000000000000L);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (numeroPiece == null || numeroPiece.isEmpty()) {
- numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
- }
- if (dateEcriture == null) {
- dateEcriture = LocalDate.now();
- }
- if (montantDebit == null) {
- montantDebit = BigDecimal.ZERO;
- }
- if (montantCredit == null) {
- montantCredit = BigDecimal.ZERO;
- }
- if (pointe == null) {
- pointe = false;
- }
- // Calculer les totaux si les lignes sont déjà présentes
- if (lignes != null && !lignes.isEmpty()) {
- calculerTotaux();
- }
- }
-
- /** Callback JPA avant la mise à jour */
- @PreUpdate
- protected void onUpdate() {
- calculerTotaux();
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité EcritureComptable pour les écritures comptables
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "ecritures_comptables",
+ indexes = {
+ @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
+ @Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
+ @Index(name = "idx_ecriture_journal", columnList = "journal_id"),
+ @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class EcritureComptable extends BaseEntity {
+
+ /** Numéro de pièce unique */
+ @NotBlank
+ @Column(name = "numero_piece", unique = true, nullable = false, length = 50)
+ private String numeroPiece;
+
+ /** Date de l'écriture */
+ @NotNull
+ @Column(name = "date_ecriture", nullable = false)
+ private LocalDate dateEcriture;
+
+ /** Libellé de l'écriture */
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 500)
+ private String libelle;
+
+ /** Référence externe */
+ @Column(name = "reference", length = 100)
+ private String reference;
+
+ /** Lettrage (pour rapprochement) */
+ @Column(name = "lettrage", length = 20)
+ private String lettrage;
+
+ /** Pointage (pour rapprochement bancaire) */
+ @Builder.Default
+ @Column(name = "pointe", nullable = false)
+ private Boolean pointe = false;
+
+ /** Montant total débit (somme des lignes) */
+ @Builder.Default
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_debit", precision = 14, scale = 2)
+ private BigDecimal montantDebit = BigDecimal.ZERO;
+
+ /** Montant total crédit (somme des lignes) */
+ @Builder.Default
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_credit", precision = 14, scale = 2)
+ private BigDecimal montantCredit = BigDecimal.ZERO;
+
+ /** Commentaires */
+ @Column(name = "commentaire", length = 1000)
+ private String commentaire;
+
+ // Relations
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "journal_id", nullable = false)
+ private JournalComptable journal;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "paiement_id")
+ private Paiement paiement;
+
+ /** Lignes d'écriture */
+ @JsonIgnore
+ @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List lignes = new ArrayList<>();
+
+ /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
+ public boolean isEquilibree() {
+ if (montantDebit == null || montantCredit == null) {
+ return false;
+ }
+ return montantDebit.compareTo(montantCredit) == 0;
+ }
+
+ /** Méthode métier pour calculer les totaux à partir des lignes */
+ public void calculerTotaux() {
+ if (lignes == null || lignes.isEmpty()) {
+ montantDebit = BigDecimal.ZERO;
+ montantCredit = BigDecimal.ZERO;
+ return;
+ }
+
+ montantDebit =
+ lignes.stream()
+ .map(LigneEcriture::getMontantDebit)
+ .filter(amount -> amount != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ montantCredit =
+ lignes.stream()
+ .map(LigneEcriture::getMontantCredit)
+ .filter(amount -> amount != null)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ /** Méthode métier pour générer un numéro de pièce unique */
+ public static String genererNumeroPiece(String prefixe, LocalDate date) {
+ return String.format(
+ "%s-%04d%02d%02d-%012d",
+ prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
+ System.currentTimeMillis() % 1000000000000L);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (numeroPiece == null || numeroPiece.isEmpty()) {
+ numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
+ }
+ if (dateEcriture == null) {
+ dateEcriture = LocalDate.now();
+ }
+ if (montantDebit == null) {
+ montantDebit = BigDecimal.ZERO;
+ }
+ if (montantCredit == null) {
+ montantCredit = BigDecimal.ZERO;
+ }
+ if (pointe == null) {
+ pointe = false;
+ }
+ // Calculer les totaux si les lignes sont déjà présentes
+ if (lignes != null && !lignes.isEmpty()) {
+ calculerTotaux();
+ }
+ }
+
+ /** Callback JPA avant la mise à jour */
+ @PreUpdate
+ protected void onUpdate() {
+ calculerTotaux();
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Evenement.java b/src/main/java/dev/lions/unionflow/server/entity/Evenement.java
index d65ced3..da15873 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Evenement.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Evenement.java
@@ -1,259 +1,259 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import lombok.*;
-
-/**
- * Entité Événement pour la gestion des événements de l'union
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-01-16
- */
-@Entity
-@Table(name = "evenements", indexes = {
- @Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
- @Index(name = "idx_evenement_statut", columnList = "statut"),
- @Index(name = "idx_evenement_type", columnList = "type_evenement"),
- @Index(name = "idx_evenement_organisation", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Evenement extends BaseEntity {
-
- @NotBlank
- @Size(min = 3, max = 200)
- @Column(name = "titre", nullable = false, length = 200)
- private String titre;
-
- @Size(max = 2000)
- @Column(name = "description", length = 2000)
- private String description;
-
- @NotNull
- @Column(name = "date_debut", nullable = false)
- private LocalDateTime dateDebut;
-
- @Column(name = "date_fin")
- private LocalDateTime dateFin;
-
- @Size(max = 500)
- @Column(name = "lieu", length = 500)
- private String lieu;
-
- @Size(max = 1000)
- @Column(name = "adresse", length = 1000)
- private String adresse;
-
- @Column(name = "type_evenement", length = 50)
- private String typeEvenement;
-
- @Builder.Default
- @Column(name = "statut", nullable = false, length = 30)
- private String statut = "PLANIFIE";
-
- @Min(0)
- @Column(name = "capacite_max")
- private Integer capaciteMax;
-
- @DecimalMin("0.00")
- @Digits(integer = 8, fraction = 2)
- @Column(name = "prix", precision = 10, scale = 2)
- private BigDecimal prix;
-
- @Builder.Default
- @Column(name = "inscription_requise", nullable = false)
- private Boolean inscriptionRequise = false;
-
- @Column(name = "date_limite_inscription")
- private LocalDateTime dateLimiteInscription;
-
- @Size(max = 1000)
- @Column(name = "instructions_particulieres", length = 1000)
- private String instructionsParticulieres;
-
- @Size(max = 500)
- @Column(name = "contact_organisateur", length = 500)
- private String contactOrganisateur;
-
- @Size(max = 2000)
- @Column(name = "materiel_requis", length = 2000)
- private String materielRequis;
-
- @Builder.Default
- @Column(name = "visible_public", nullable = false)
- private Boolean visiblePublic = true;
-
- // Relations
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisateur_id")
- private Membre organisateur;
-
- @JsonIgnore
- @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
- @Builder.Default
- private List inscriptions = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List adresses = new ArrayList<>();
-
- /** Types d'événements */
- public enum TypeEvenement {
- ASSEMBLEE_GENERALE("Assemblée Générale"),
- REUNION("Réunion"),
- FORMATION("Formation"),
- CONFERENCE("Conférence"),
- ATELIER("Atelier"),
- SEMINAIRE("Séminaire"),
- EVENEMENT_SOCIAL("Événement Social"),
- MANIFESTATION("Manifestation"),
- CELEBRATION("Célébration"),
- AUTRE("Autre");
-
- private final String libelle;
-
- TypeEvenement(String libelle) {
- this.libelle = libelle;
- }
-
- public String getLibelle() {
- return libelle;
- }
- }
-
- /** Statuts d'événement */
- public enum StatutEvenement {
- PLANIFIE("Planifié"),
- CONFIRME("Confirmé"),
- EN_COURS("En cours"),
- TERMINE("Terminé"),
- ANNULE("Annulé"),
- REPORTE("Reporté");
-
- private final String libelle;
-
- StatutEvenement(String libelle) {
- this.libelle = libelle;
- }
-
- public String getLibelle() {
- return libelle;
- }
- }
-
- // Méthodes métier
-
- /** Vérifie si l'événement est ouvert aux inscriptions */
- @JsonIgnore
- public boolean isOuvertAuxInscriptions() {
- if (!inscriptionRequise || !getActif()) {
- return false;
- }
-
- LocalDateTime maintenant = LocalDateTime.now();
-
- // Vérifier si la date limite d'inscription n'est pas dépassée
- if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
- return false;
- }
-
- // Vérifier si l'événement n'a pas déjà commencé
- if (maintenant.isAfter(dateDebut)) {
- return false;
- }
-
- // Vérifier la capacité
- if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
- return false;
- }
-
- return "PLANIFIE".equals(statut) || "CONFIRME".equals(statut);
- }
-
- /** Obtient le nombre d'inscrits à l'événement */
- @JsonIgnore
- public int getNombreInscrits() {
- return inscriptions != null
- ? (int) inscriptions.stream()
- .filter(
- inscription -> InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()))
- .count()
- : 0;
- }
-
- /** Vérifie si l'événement est complet */
- @JsonIgnore
- public boolean isComplet() {
- return capaciteMax != null && getNombreInscrits() >= capaciteMax;
- }
-
- /** Vérifie si l'événement est en cours */
- public boolean isEnCours() {
- LocalDateTime maintenant = LocalDateTime.now();
- return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
- }
-
- /** Vérifie si l'événement est terminé */
- public boolean isTermine() {
- if ("TERMINE".equals(statut)) {
- return true;
- }
-
- LocalDateTime maintenant = LocalDateTime.now();
- return dateFin != null && maintenant.isAfter(dateFin);
- }
-
- /** Calcule la durée de l'événement en heures */
- public Long getDureeEnHeures() {
- if (dateFin == null) {
- return null;
- }
-
- return java.time.Duration.between(dateDebut, dateFin).toHours();
- }
-
- /** Obtient le nombre de places restantes */
- @JsonIgnore
- public Integer getPlacesRestantes() {
- if (capaciteMax == null) {
- return null; // Capacité illimitée
- }
-
- return Math.max(0, capaciteMax - getNombreInscrits());
- }
-
- /** Vérifie si un membre est inscrit à l'événement */
- public boolean isMemberInscrit(UUID membreId) {
- return inscriptions != null
- && inscriptions.stream()
- .anyMatch(
- inscription -> inscription.getMembre().getId().equals(membreId)
- && InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()));
- }
-
- /** Obtient le taux de remplissage en pourcentage */
- @JsonIgnore
- public Double getTauxRemplissage() {
- if (capaciteMax == null || capaciteMax == 0) {
- return null;
- }
-
- return (double) getNombreInscrits() / capaciteMax * 100;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.*;
+
+/**
+ * Entité Événement pour la gestion des événements de l'union
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-01-16
+ */
+@Entity
+@Table(name = "evenements", indexes = {
+ @Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
+ @Index(name = "idx_evenement_statut", columnList = "statut"),
+ @Index(name = "idx_evenement_type", columnList = "type_evenement"),
+ @Index(name = "idx_evenement_organisation", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Evenement extends BaseEntity {
+
+ @NotBlank
+ @Size(min = 3, max = 200)
+ @Column(name = "titre", nullable = false, length = 200)
+ private String titre;
+
+ @Size(max = 2000)
+ @Column(name = "description", length = 2000)
+ private String description;
+
+ @NotNull
+ @Column(name = "date_debut", nullable = false)
+ private LocalDateTime dateDebut;
+
+ @Column(name = "date_fin")
+ private LocalDateTime dateFin;
+
+ @Size(max = 500)
+ @Column(name = "lieu", length = 500)
+ private String lieu;
+
+ @Size(max = 1000)
+ @Column(name = "adresse", length = 1000)
+ private String adresse;
+
+ @Column(name = "type_evenement", length = 50)
+ private String typeEvenement;
+
+ @Builder.Default
+ @Column(name = "statut", nullable = false, length = 30)
+ private String statut = "PLANIFIE";
+
+ @Min(0)
+ @Column(name = "capacite_max")
+ private Integer capaciteMax;
+
+ @DecimalMin("0.00")
+ @Digits(integer = 8, fraction = 2)
+ @Column(name = "prix", precision = 10, scale = 2)
+ private BigDecimal prix;
+
+ @Builder.Default
+ @Column(name = "inscription_requise", nullable = false)
+ private Boolean inscriptionRequise = false;
+
+ @Column(name = "date_limite_inscription")
+ private LocalDateTime dateLimiteInscription;
+
+ @Size(max = 1000)
+ @Column(name = "instructions_particulieres", length = 1000)
+ private String instructionsParticulieres;
+
+ @Size(max = 500)
+ @Column(name = "contact_organisateur", length = 500)
+ private String contactOrganisateur;
+
+ @Size(max = 2000)
+ @Column(name = "materiel_requis", length = 2000)
+ private String materielRequis;
+
+ @Builder.Default
+ @Column(name = "visible_public", nullable = false)
+ private Boolean visiblePublic = true;
+
+ // Relations
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisateur_id")
+ private Membre organisateur;
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List inscriptions = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List adresses = new ArrayList<>();
+
+ /** Types d'événements */
+ public enum TypeEvenement {
+ ASSEMBLEE_GENERALE("Assemblée Générale"),
+ REUNION("Réunion"),
+ FORMATION("Formation"),
+ CONFERENCE("Conférence"),
+ ATELIER("Atelier"),
+ SEMINAIRE("Séminaire"),
+ EVENEMENT_SOCIAL("Événement Social"),
+ MANIFESTATION("Manifestation"),
+ CELEBRATION("Célébration"),
+ AUTRE("Autre");
+
+ private final String libelle;
+
+ TypeEvenement(String libelle) {
+ this.libelle = libelle;
+ }
+
+ public String getLibelle() {
+ return libelle;
+ }
+ }
+
+ /** Statuts d'événement */
+ public enum StatutEvenement {
+ PLANIFIE("Planifié"),
+ CONFIRME("Confirmé"),
+ EN_COURS("En cours"),
+ TERMINE("Terminé"),
+ ANNULE("Annulé"),
+ REPORTE("Reporté");
+
+ private final String libelle;
+
+ StatutEvenement(String libelle) {
+ this.libelle = libelle;
+ }
+
+ public String getLibelle() {
+ return libelle;
+ }
+ }
+
+ // Méthodes métier
+
+ /** Vérifie si l'événement est ouvert aux inscriptions */
+ @JsonIgnore
+ public boolean isOuvertAuxInscriptions() {
+ if (!inscriptionRequise || !getActif()) {
+ return false;
+ }
+
+ LocalDateTime maintenant = LocalDateTime.now();
+
+ // Vérifier si la date limite d'inscription n'est pas dépassée
+ if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
+ return false;
+ }
+
+ // Vérifier si l'événement n'a pas déjà commencé
+ if (maintenant.isAfter(dateDebut)) {
+ return false;
+ }
+
+ // Vérifier la capacité
+ if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
+ return false;
+ }
+
+ return "PLANIFIE".equals(statut) || "CONFIRME".equals(statut);
+ }
+
+ /** Obtient le nombre d'inscrits à l'événement */
+ @JsonIgnore
+ public int getNombreInscrits() {
+ return inscriptions != null
+ ? (int) inscriptions.stream()
+ .filter(
+ inscription -> InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()))
+ .count()
+ : 0;
+ }
+
+ /** Vérifie si l'événement est complet */
+ @JsonIgnore
+ public boolean isComplet() {
+ return capaciteMax != null && getNombreInscrits() >= capaciteMax;
+ }
+
+ /** Vérifie si l'événement est en cours */
+ public boolean isEnCours() {
+ LocalDateTime maintenant = LocalDateTime.now();
+ return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
+ }
+
+ /** Vérifie si l'événement est terminé */
+ public boolean isTermine() {
+ if ("TERMINE".equals(statut)) {
+ return true;
+ }
+
+ LocalDateTime maintenant = LocalDateTime.now();
+ return dateFin != null && maintenant.isAfter(dateFin);
+ }
+
+ /** Calcule la durée de l'événement en heures */
+ public Long getDureeEnHeures() {
+ if (dateFin == null) {
+ return null;
+ }
+
+ return java.time.Duration.between(dateDebut, dateFin).toHours();
+ }
+
+ /** Obtient le nombre de places restantes */
+ @JsonIgnore
+ public Integer getPlacesRestantes() {
+ if (capaciteMax == null) {
+ return null; // Capacité illimitée
+ }
+
+ return Math.max(0, capaciteMax - getNombreInscrits());
+ }
+
+ /** Vérifie si un membre est inscrit à l'événement */
+ public boolean isMemberInscrit(UUID membreId) {
+ return inscriptions != null
+ && inscriptions.stream()
+ .anyMatch(
+ inscription -> inscription.getMembre().getId().equals(membreId)
+ && InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()));
+ }
+
+ /** Obtient le taux de remplissage en pourcentage */
+ @JsonIgnore
+ public Double getTauxRemplissage() {
+ if (capaciteMax == null || capaciteMax == 0) {
+ return null;
+ }
+
+ return (double) getNombreInscrits() / capaciteMax * 100;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Favori.java b/src/main/java/dev/lions/unionflow/server/entity/Favori.java
index 09e6edc..d9f780d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Favori.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Favori.java
@@ -1,79 +1,79 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-/**
- * Entité Favori pour la gestion des favoris utilisateur
- *
- * @author UnionFlow Team
- * @version 1.0
- */
-@Entity
-@Table(
- name = "favoris",
- indexes = {
- @Index(name = "idx_favori_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_favori_type", columnList = "type_favori"),
- @Index(name = "idx_favori_categorie", columnList = "categorie")
- }
-)
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Favori extends BaseEntity {
-
- @NotNull
- @Column(name = "utilisateur_id", nullable = false)
- private UUID utilisateurId;
-
- @NotBlank
- @Column(name = "type_favori", nullable = false, length = 50)
- private String typeFavori; // PAGE, DOCUMENT, CONTACT, RACCOURCI
-
- @NotBlank
- @Column(name = "titre", nullable = false, length = 255)
- private String titre;
-
- @Column(name = "description", length = 1000)
- private String description;
-
- @Column(name = "url", length = 1000)
- private String url;
-
- @Column(name = "icon", length = 100)
- private String icon;
-
- @Column(name = "couleur", length = 50)
- private String couleur;
-
- @Column(name = "categorie", length = 100)
- private String categorie;
-
- @Column(name = "ordre")
- @Builder.Default
- private Integer ordre = 0;
-
- @Column(name = "nb_visites")
- @Builder.Default
- private Integer nbVisites = 0;
-
- @Column(name = "derniere_visite")
- private LocalDateTime derniereVisite;
-
- @Column(name = "est_plus_utilise")
- @Builder.Default
- private Boolean estPlusUtilise = false;
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Entité Favori pour la gestion des favoris utilisateur
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ */
+@Entity
+@Table(
+ name = "favoris",
+ indexes = {
+ @Index(name = "idx_favori_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_favori_type", columnList = "type_favori"),
+ @Index(name = "idx_favori_categorie", columnList = "categorie")
+ }
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Favori extends BaseEntity {
+
+ @NotNull
+ @Column(name = "utilisateur_id", nullable = false)
+ private UUID utilisateurId;
+
+ @NotBlank
+ @Column(name = "type_favori", nullable = false, length = 50)
+ private String typeFavori; // PAGE, DOCUMENT, CONTACT, RACCOURCI
+
+ @NotBlank
+ @Column(name = "titre", nullable = false, length = 255)
+ private String titre;
+
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ @Column(name = "url", length = 1000)
+ private String url;
+
+ @Column(name = "icon", length = 100)
+ private String icon;
+
+ @Column(name = "couleur", length = 50)
+ private String couleur;
+
+ @Column(name = "categorie", length = 100)
+ private String categorie;
+
+ @Column(name = "ordre")
+ @Builder.Default
+ private Integer ordre = 0;
+
+ @Column(name = "nb_visites")
+ @Builder.Default
+ private Integer nbVisites = 0;
+
+ @Column(name = "derniere_visite")
+ private LocalDateTime derniereVisite;
+
+ @Column(name = "est_plus_utilise")
+ @Builder.Default
+ private Boolean estPlusUtilise = false;
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java b/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java
index b90d5d3..169038d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java
@@ -1,117 +1,117 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.Max;
-import jakarta.validation.constraints.Min;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDateTime;
-import lombok.*;
-
-/**
- * Entité FeedbackEvenement représentant l'évaluation d'un membre sur un événement
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-16
- */
-@Entity
-@Table(
- name = "feedbacks_evenement",
- indexes = {
- @Index(name = "idx_feedback_membre", columnList = "membre_id"),
- @Index(name = "idx_feedback_evenement", columnList = "evenement_id"),
- @Index(name = "idx_feedback_date", columnList = "date_feedback")
- },
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_feedback_membre_evenement",
- columnNames = {"membre_id", "evenement_id"})
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class FeedbackEvenement extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "evenement_id", nullable = false)
- private Evenement evenement;
-
- @NotNull
- @Min(1)
- @Max(5)
- @Column(name = "note", nullable = false)
- private Integer note;
-
- @Column(name = "commentaire", length = 1000)
- private String commentaire;
-
- @Builder.Default
- @Column(name = "date_feedback", nullable = false)
- private LocalDateTime dateFeedback = LocalDateTime.now();
-
- @Column(name = "moderation_statut", length = 20)
- @Builder.Default
- private String moderationStatut = ModerationStatut.PUBLIE.name();
-
- @Column(name = "raison_moderation", length = 500)
- private String raisonModeration;
-
- /** Énumération des statuts de modération */
- public enum ModerationStatut {
- PUBLIE, // Visible publiquement
- EN_ATTENTE, // En attente de modération
- REJETE // Rejeté par modération
- }
-
- // Méthodes utilitaires
-
- /** Vérifie si le feedback est publié */
- public boolean isPublie() {
- return ModerationStatut.PUBLIE.name().equals(this.moderationStatut);
- }
-
- /** Marque le feedback comme en attente de modération */
- public void mettreEnAttente(String raison) {
- this.moderationStatut = ModerationStatut.EN_ATTENTE.name();
- this.raisonModeration = raison;
- setDateModification(LocalDateTime.now());
- }
-
- /** Publie le feedback */
- public void publier() {
- this.moderationStatut = ModerationStatut.PUBLIE.name();
- this.raisonModeration = null;
- setDateModification(LocalDateTime.now());
- }
-
- /** Rejette le feedback */
- public void rejeter(String raison) {
- this.moderationStatut = ModerationStatut.REJETE.name();
- this.raisonModeration = raison;
- setDateModification(LocalDateTime.now());
- }
-
- @PreUpdate
- public void preUpdate() {
- super.onUpdate();
- }
-
- @Override
- public String toString() {
- return String.format(
- "FeedbackEvenement{id=%s, membre=%s, evenement=%s, note=%d, dateFeedback=%s}",
- getId(),
- membre != null ? membre.getEmail() : "null",
- evenement != null ? evenement.getTitre() : "null",
- note,
- dateFeedback);
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import lombok.*;
+
+/**
+ * Entité FeedbackEvenement représentant l'évaluation d'un membre sur un événement
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-16
+ */
+@Entity
+@Table(
+ name = "feedbacks_evenement",
+ indexes = {
+ @Index(name = "idx_feedback_membre", columnList = "membre_id"),
+ @Index(name = "idx_feedback_evenement", columnList = "evenement_id"),
+ @Index(name = "idx_feedback_date", columnList = "date_feedback")
+ },
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_feedback_membre_evenement",
+ columnNames = {"membre_id", "evenement_id"})
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class FeedbackEvenement extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "evenement_id", nullable = false)
+ private Evenement evenement;
+
+ @NotNull
+ @Min(1)
+ @Max(5)
+ @Column(name = "note", nullable = false)
+ private Integer note;
+
+ @Column(name = "commentaire", length = 1000)
+ private String commentaire;
+
+ @Builder.Default
+ @Column(name = "date_feedback", nullable = false)
+ private LocalDateTime dateFeedback = LocalDateTime.now();
+
+ @Column(name = "moderation_statut", length = 20)
+ @Builder.Default
+ private String moderationStatut = ModerationStatut.PUBLIE.name();
+
+ @Column(name = "raison_moderation", length = 500)
+ private String raisonModeration;
+
+ /** Énumération des statuts de modération */
+ public enum ModerationStatut {
+ PUBLIE, // Visible publiquement
+ EN_ATTENTE, // En attente de modération
+ REJETE // Rejeté par modération
+ }
+
+ // Méthodes utilitaires
+
+ /** Vérifie si le feedback est publié */
+ public boolean isPublie() {
+ return ModerationStatut.PUBLIE.name().equals(this.moderationStatut);
+ }
+
+ /** Marque le feedback comme en attente de modération */
+ public void mettreEnAttente(String raison) {
+ this.moderationStatut = ModerationStatut.EN_ATTENTE.name();
+ this.raisonModeration = raison;
+ setDateModification(LocalDateTime.now());
+ }
+
+ /** Publie le feedback */
+ public void publier() {
+ this.moderationStatut = ModerationStatut.PUBLIE.name();
+ this.raisonModeration = null;
+ setDateModification(LocalDateTime.now());
+ }
+
+ /** Rejette le feedback */
+ public void rejeter(String raison) {
+ this.moderationStatut = ModerationStatut.REJETE.name();
+ this.raisonModeration = raison;
+ setDateModification(LocalDateTime.now());
+ }
+
+ @PreUpdate
+ public void preUpdate() {
+ super.onUpdate();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "FeedbackEvenement{id=%s, membre=%s, evenement=%s, note=%d, dateFeedback=%s}",
+ getId(),
+ membre != null ? membre.getEmail() : "null",
+ evenement != null ? evenement.getTitre() : "null",
+ note,
+ dateFeedback);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java b/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java
index 7de9043..a1305de 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/FormuleAbonnement.java
@@ -1,120 +1,124 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
-import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import lombok.*;
-
-/**
- * Catalogue des forfaits SaaS UnionFlow.
- *
- * Starter (≤50) → Standard (≤200) → Premium (≤500) → Crystal (illimité)
- * Fourchette tarifaire : 5 000 à 10 000 XOF/mois. Stockage max : 1 Go.
- *
- *
Table : {@code formules_abonnement}
- */
-@Entity
-@Table(
- name = "formules_abonnement",
- indexes = {
- @Index(name = "idx_formule_code_plage", columnList = "code, plage", unique = true),
- @Index(name = "idx_formule_code", columnList = "code"),
- @Index(name = "idx_formule_plage", columnList = "plage"),
- @Index(name = "idx_formule_actif", columnList = "actif")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class FormuleAbonnement extends BaseEntity {
-
- @Enumerated(EnumType.STRING)
- @NotNull
- @Column(name = "code", nullable = false, length = 20)
- private TypeFormule code;
-
- /**
- * Plage de taille d'organisation à laquelle cette formule s'applique.
- * Combinée avec le code de formule, forme une clé unique dans le catalogue.
- */
- @Enumerated(EnumType.STRING)
- @NotNull
- @Column(name = "plage", nullable = false, length = 20)
- private PlageMembres plage;
-
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 100)
- private String libelle;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String description;
-
- /** Nombre maximum de membres. NULL = illimité (Crystal) */
- @Column(name = "max_membres")
- private Integer maxMembres;
-
- /** Stockage maximum en Mo — 1 024 Mo (1 Go) par défaut */
- @Builder.Default
- @Column(name = "max_stockage_mo", nullable = false)
- private Integer maxStockageMo = 1024;
-
- @NotNull
- @DecimalMin("0.00")
- @Digits(integer = 8, fraction = 2)
- @Column(name = "prix_mensuel", nullable = false, precision = 10, scale = 2)
- private BigDecimal prixMensuel;
-
- @NotNull
- @DecimalMin("0.00")
- @Digits(integer = 8, fraction = 2)
- @Column(name = "prix_annuel", nullable = false, precision = 10, scale = 2)
- private BigDecimal prixAnnuel;
-
- @Builder.Default
- @Column(name = "ordre_affichage", nullable = false)
- private Integer ordreAffichage = 0;
-
- // ── Champs Option C (ajoutés en V19) ──────────────────────────────────────
-
- /** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */
- @Column(name = "plan_commercial", length = 30)
- private String planCommercial;
-
- /** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */
- @Column(name = "niveau_reporting", length = 20)
- private String niveauReporting;
-
- /** Accès à l'API REST (false pour les plans de base) */
- @Builder.Default
- @Column(name = "api_access", nullable = false)
- private Boolean apiAccess = false;
-
- /** Accès au module de fédération multi-org (ENTERPRISE uniquement) */
- @Builder.Default
- @Column(name = "federation_access", nullable = false)
- private Boolean federationAccess = false;
-
- /** Support prioritaire inclus */
- @Builder.Default
- @Column(name = "support_prioritaire", nullable = false)
- private Boolean supportPrioritaire = false;
-
- /** SLA garanti (ex: "99.0%", "99.9%") */
- @Column(name = "sla_garanti", length = 10)
- private String slaGaranti;
-
- /** Nombre maximum d'administrateurs. NULL = illimité */
- @Column(name = "max_admins")
- private Integer maxAdmins;
-
- public boolean isIllimitee() {
- return maxMembres == null;
- }
-
- public boolean accepteNouveauMembre(int quotaActuel) {
- return isIllimitee() || quotaActuel < maxMembres;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
+import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.*;
+
+/**
+ * Catalogue des forfaits SaaS UnionFlow.
+ *
+ *
Starter (≤50) → Standard (≤200) → Premium (≤500) → Crystal (illimité)
+ * Fourchette tarifaire : 5 000 à 10 000 XOF/mois. Stockage max : 1 Go.
+ *
+ *
Table : {@code formules_abonnement}
+ */
+@Entity
+@Table(
+ name = "formules_abonnement",
+ indexes = {
+ @Index(name = "idx_formule_code_plage", columnList = "code, plage", unique = true),
+ @Index(name = "idx_formule_code", columnList = "code"),
+ @Index(name = "idx_formule_plage", columnList = "plage"),
+ @Index(name = "idx_formule_actif", columnList = "actif")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class FormuleAbonnement extends BaseEntity {
+
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ @Column(name = "code", nullable = false, length = 20)
+ private TypeFormule code;
+
+ /**
+ * Plage de taille d'organisation à laquelle cette formule s'applique.
+ * Combinée avec le code de formule, forme une clé unique dans le catalogue.
+ */
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ @Column(name = "plage", nullable = false, length = 20)
+ private PlageMembres plage;
+
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 100)
+ private String libelle;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String description;
+
+ /** Nombre maximum de membres. NULL = illimité (Crystal) */
+ @Column(name = "max_membres")
+ private Integer maxMembres;
+
+ /** Stockage maximum en Mo — 1 024 Mo (1 Go) par défaut */
+ @Builder.Default
+ @Column(name = "max_stockage_mo", nullable = false)
+ private Integer maxStockageMo = 1024;
+
+ @NotNull
+ @DecimalMin("0.00")
+ @Digits(integer = 8, fraction = 2)
+ @Column(name = "prix_mensuel", nullable = false, precision = 10, scale = 2)
+ private BigDecimal prixMensuel;
+
+ @NotNull
+ @DecimalMin("0.00")
+ @Digits(integer = 8, fraction = 2)
+ @Column(name = "prix_annuel", nullable = false, precision = 10, scale = 2)
+ private BigDecimal prixAnnuel;
+
+ @Builder.Default
+ @Column(name = "ordre_affichage", nullable = false)
+ private Integer ordreAffichage = 0;
+
+ // ── Champs Option C (ajoutés en V19) ──────────────────────────────────────
+
+ /** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */
+ @Column(name = "plan_commercial", length = 30)
+ private String planCommercial;
+
+ /** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */
+ @Column(name = "niveau_reporting", length = 20)
+ private String niveauReporting;
+
+ /** Accès à l'API REST (false pour les plans de base) */
+ @Builder.Default
+ @Column(name = "api_access", nullable = false)
+ private Boolean apiAccess = false;
+
+ /** Accès au module de fédération multi-org (ENTERPRISE uniquement) */
+ @Builder.Default
+ @Column(name = "federation_access", nullable = false)
+ private Boolean federationAccess = false;
+
+ /** Support prioritaire inclus */
+ @Builder.Default
+ @Column(name = "support_prioritaire", nullable = false)
+ private Boolean supportPrioritaire = false;
+
+ /** SLA garanti (ex: "99.0%", "99.9%") */
+ @Column(name = "sla_garanti", length = 10)
+ private String slaGaranti;
+
+ /** Nombre maximum d'administrateurs. NULL = illimité */
+ @Column(name = "max_admins")
+ private Integer maxAdmins;
+
+ /** Code du provider de paiement par défaut (WAVE, ORANGE_MONEY, MTN_MOMO, PISPI). NULL = global. */
+ @Column(name = "provider_defaut", length = 20)
+ private String providerDefaut;
+
+ public boolean isIllimitee() {
+ return maxMembres == null;
+ }
+
+ public boolean accepteNouveauMembre(int quotaActuel) {
+ return isIllimitee() || quotaActuel < maxMembres;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java b/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java
index 94a1106..9c8b382 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java
@@ -1,143 +1,143 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDateTime;
-import lombok.*;
-
-/**
- * Entité InscriptionEvenement représentant l'inscription d'un membre à un
- * événement
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-01-16
- */
-@Entity
-@Table(name = "inscriptions_evenement", indexes = {
- @Index(name = "idx_inscription_membre", columnList = "membre_id"),
- @Index(name = "idx_inscription_evenement", columnList = "evenement_id"),
- @Index(name = "idx_inscription_date", columnList = "date_inscription")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class InscriptionEvenement extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "evenement_id", nullable = false)
- private Evenement evenement;
-
- @Builder.Default
- @Column(name = "date_inscription", nullable = false)
- private LocalDateTime dateInscription = LocalDateTime.now();
-
- @Column(name = "statut", length = 20)
- @Builder.Default
- private String statut = StatutInscription.CONFIRMEE.name();
-
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-
- /** Énumération des statuts d'inscription (pour constantes) */
- public enum StatutInscription {
- CONFIRMEE,
- EN_ATTENTE,
- ANNULEE,
- REFUSEE;
- }
-
- // Méthodes utilitaires
-
- /**
- * Vérifie si l'inscription est confirmée
- *
- * @return true si l'inscription est confirmée
- */
- public boolean isConfirmee() {
- return StatutInscription.CONFIRMEE.name().equals(this.statut);
- }
-
- /**
- * Vérifie si l'inscription est en attente
- *
- * @return true si l'inscription est en attente
- */
- public boolean isEnAttente() {
- return StatutInscription.EN_ATTENTE.name().equals(this.statut);
- }
-
- /**
- * Vérifie si l'inscription est annulée
- *
- * @return true si l'inscription est annulée
- */
- public boolean isAnnulee() {
- return StatutInscription.ANNULEE.name().equals(this.statut);
- }
-
- /** Confirme l'inscription */
- public void confirmer() {
- this.statut = StatutInscription.CONFIRMEE.name();
- setDateModification(LocalDateTime.now());
- }
-
- /**
- * Annule l'inscription
- *
- * @param commentaire le commentaire d'annulation
- */
- public void annuler(String commentaire) {
- this.statut = StatutInscription.ANNULEE.name();
- this.commentaire = commentaire;
- setDateModification(LocalDateTime.now());
- }
-
- /**
- * Met l'inscription en attente
- *
- * @param commentaire le commentaire de mise en attente
- */
- public void mettreEnAttente(String commentaire) {
- this.statut = StatutInscription.EN_ATTENTE.name();
- this.commentaire = commentaire;
- setDateModification(LocalDateTime.now());
- }
-
- /**
- * Refuser l'inscription
- *
- * @param commentaire le commentaire de refus
- */
- public void refuser(String commentaire) {
- this.statut = StatutInscription.REFUSEE.name();
- this.commentaire = commentaire;
- setDateModification(LocalDateTime.now());
- }
-
- // Callbacks JPA
-
- @PreUpdate
- public void preUpdate() {
- super.onUpdate();
- }
-
- @Override
- public String toString() {
- return String.format(
- "InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}",
- getId(),
- membre != null ? membre.getEmail() : "null",
- evenement != null ? evenement.getTitre() : "null",
- statut,
- dateInscription);
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import lombok.*;
+
+/**
+ * Entité InscriptionEvenement représentant l'inscription d'un membre à un
+ * événement
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-01-16
+ */
+@Entity
+@Table(name = "inscriptions_evenement", indexes = {
+ @Index(name = "idx_inscription_membre", columnList = "membre_id"),
+ @Index(name = "idx_inscription_evenement", columnList = "evenement_id"),
+ @Index(name = "idx_inscription_date", columnList = "date_inscription")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class InscriptionEvenement extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "evenement_id", nullable = false)
+ private Evenement evenement;
+
+ @Builder.Default
+ @Column(name = "date_inscription", nullable = false)
+ private LocalDateTime dateInscription = LocalDateTime.now();
+
+ @Column(name = "statut", length = 20)
+ @Builder.Default
+ private String statut = StatutInscription.CONFIRMEE.name();
+
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+
+ /** Énumération des statuts d'inscription (pour constantes) */
+ public enum StatutInscription {
+ CONFIRMEE,
+ EN_ATTENTE,
+ ANNULEE,
+ REFUSEE;
+ }
+
+ // Méthodes utilitaires
+
+ /**
+ * Vérifie si l'inscription est confirmée
+ *
+ * @return true si l'inscription est confirmée
+ */
+ public boolean isConfirmee() {
+ return StatutInscription.CONFIRMEE.name().equals(this.statut);
+ }
+
+ /**
+ * Vérifie si l'inscription est en attente
+ *
+ * @return true si l'inscription est en attente
+ */
+ public boolean isEnAttente() {
+ return StatutInscription.EN_ATTENTE.name().equals(this.statut);
+ }
+
+ /**
+ * Vérifie si l'inscription est annulée
+ *
+ * @return true si l'inscription est annulée
+ */
+ public boolean isAnnulee() {
+ return StatutInscription.ANNULEE.name().equals(this.statut);
+ }
+
+ /** Confirme l'inscription */
+ public void confirmer() {
+ this.statut = StatutInscription.CONFIRMEE.name();
+ setDateModification(LocalDateTime.now());
+ }
+
+ /**
+ * Annule l'inscription
+ *
+ * @param commentaire le commentaire d'annulation
+ */
+ public void annuler(String commentaire) {
+ this.statut = StatutInscription.ANNULEE.name();
+ this.commentaire = commentaire;
+ setDateModification(LocalDateTime.now());
+ }
+
+ /**
+ * Met l'inscription en attente
+ *
+ * @param commentaire le commentaire de mise en attente
+ */
+ public void mettreEnAttente(String commentaire) {
+ this.statut = StatutInscription.EN_ATTENTE.name();
+ this.commentaire = commentaire;
+ setDateModification(LocalDateTime.now());
+ }
+
+ /**
+ * Refuser l'inscription
+ *
+ * @param commentaire le commentaire de refus
+ */
+ public void refuser(String commentaire) {
+ this.statut = StatutInscription.REFUSEE.name();
+ this.commentaire = commentaire;
+ setDateModification(LocalDateTime.now());
+ }
+
+ // Callbacks JPA
+
+ @PreUpdate
+ public void preUpdate() {
+ super.onUpdate();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}",
+ getId(),
+ membre != null ? membre.getEmail() : "null",
+ evenement != null ? evenement.getTitre() : "null",
+ statut,
+ dateInscription);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/IntentionPaiement.java b/src/main/java/dev/lions/unionflow/server/entity/IntentionPaiement.java
index b94edbf..5ee9502 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/IntentionPaiement.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/IntentionPaiement.java
@@ -1,122 +1,122 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
-import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import lombok.*;
-
-/**
- * Hub centralisé pour tout paiement Wave initié depuis UnionFlow.
- *
- *
Flux :
- *
- * - UnionFlow crée une {@code IntentionPaiement} avec les objets cibles (cotisations, etc.)
- * - UnionFlow appelle l'API Wave → récupère {@code waveCheckoutSessionId}
- * - Le membre confirme dans l'app Wave
- * - Wave envoie un webhook → UnionFlow réconcilie via {@code waveCheckoutSessionId}
- * - UnionFlow valide automatiquement les objets listés dans {@code objetsCibles}
- *
- *
- * Table : {@code intentions_paiement}
- */
-@Entity
-@Table(
- name = "intentions_paiement",
- indexes = {
- @Index(name = "idx_intention_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_intention_statut", columnList = "statut"),
- @Index(name = "idx_intention_wave_session", columnList = "wave_checkout_session_id", unique = true),
- @Index(name = "idx_intention_expiration", columnList = "date_expiration")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class IntentionPaiement extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "utilisateur_id", nullable = false)
- private Membre utilisateur;
-
- /** NULL pour les abonnements UnionFlow SA (payés par l'organisation directement) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @NotNull
- @DecimalMin("0.01")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_total", nullable = false, precision = 14, scale = 2)
- private BigDecimal montantTotal;
-
- @NotBlank
- @Pattern(regexp = "^[A-Z]{3}$")
- @Builder.Default
- @Column(name = "code_devise", nullable = false, length = 3)
- private String codeDevise = "XOF";
-
- @Enumerated(EnumType.STRING)
- @NotNull
- @Column(name = "type_objet", nullable = false, length = 30)
- private TypeObjetIntentionPaiement typeObjet;
-
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut", nullable = false, length = 20)
- private StatutIntentionPaiement statut = StatutIntentionPaiement.INITIEE;
-
- /** ID de session Wave — clé de réconciliation sur webhook */
- @Column(name = "wave_checkout_session_id", unique = true, length = 255)
- private String waveCheckoutSessionId;
-
- /** URL de paiement Wave à rediriger l'utilisateur */
- @Column(name = "wave_launch_url", length = 1000)
- private String waveLaunchUrl;
-
- /** ID transaction Wave reçu via webhook */
- @Column(name = "wave_transaction_id", length = 100)
- private String waveTransactionId;
-
- /**
- * JSON : liste des objets couverts par ce paiement.
- * Exemple : [{\"type\":\"COTISATION\",\"id\":\"uuid\",\"montant\":5000}, ...]
- */
- @Column(name = "objets_cibles", columnDefinition = "TEXT")
- private String objetsCibles;
-
- @Column(name = "date_expiration")
- private LocalDateTime dateExpiration;
-
- @Column(name = "date_completion")
- private LocalDateTime dateCompletion;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean isActive() {
- return StatutIntentionPaiement.INITIEE.equals(statut)
- || StatutIntentionPaiement.EN_COURS.equals(statut);
- }
-
- public boolean isExpiree() {
- return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration);
- }
-
- public boolean isCompletee() {
- return StatutIntentionPaiement.COMPLETEE.equals(statut);
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statut == null) statut = StatutIntentionPaiement.INITIEE;
- if (codeDevise == null) codeDevise = "XOF";
- if (dateExpiration == null) {
- dateExpiration = LocalDateTime.now().plusMinutes(30);
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
+import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import lombok.*;
+
+/**
+ * Hub centralisé pour tout paiement Wave initié depuis UnionFlow.
+ *
+ *
Flux :
+ *
+ * - UnionFlow crée une {@code IntentionPaiement} avec les objets cibles (cotisations, etc.)
+ * - UnionFlow appelle l'API Wave → récupère {@code waveCheckoutSessionId}
+ * - Le membre confirme dans l'app Wave
+ * - Wave envoie un webhook → UnionFlow réconcilie via {@code waveCheckoutSessionId}
+ * - UnionFlow valide automatiquement les objets listés dans {@code objetsCibles}
+ *
+ *
+ * Table : {@code intentions_paiement}
+ */
+@Entity
+@Table(
+ name = "intentions_paiement",
+ indexes = {
+ @Index(name = "idx_intention_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_intention_statut", columnList = "statut"),
+ @Index(name = "idx_intention_wave_session", columnList = "wave_checkout_session_id", unique = true),
+ @Index(name = "idx_intention_expiration", columnList = "date_expiration")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class IntentionPaiement extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "utilisateur_id", nullable = false)
+ private Membre utilisateur;
+
+ /** NULL pour les abonnements UnionFlow SA (payés par l'organisation directement) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @NotNull
+ @DecimalMin("0.01")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_total", nullable = false, precision = 14, scale = 2)
+ private BigDecimal montantTotal;
+
+ @NotBlank
+ @Pattern(regexp = "^[A-Z]{3}$")
+ @Builder.Default
+ @Column(name = "code_devise", nullable = false, length = 3)
+ private String codeDevise = "XOF";
+
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ @Column(name = "type_objet", nullable = false, length = 30)
+ private TypeObjetIntentionPaiement typeObjet;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut", nullable = false, length = 20)
+ private StatutIntentionPaiement statut = StatutIntentionPaiement.INITIEE;
+
+ /** ID de session Wave — clé de réconciliation sur webhook */
+ @Column(name = "wave_checkout_session_id", unique = true, length = 255)
+ private String waveCheckoutSessionId;
+
+ /** URL de paiement Wave à rediriger l'utilisateur */
+ @Column(name = "wave_launch_url", length = 1000)
+ private String waveLaunchUrl;
+
+ /** ID transaction Wave reçu via webhook */
+ @Column(name = "wave_transaction_id", length = 100)
+ private String waveTransactionId;
+
+ /**
+ * JSON : liste des objets couverts par ce paiement.
+ * Exemple : [{\"type\":\"COTISATION\",\"id\":\"uuid\",\"montant\":5000}, ...]
+ */
+ @Column(name = "objets_cibles", columnDefinition = "TEXT")
+ private String objetsCibles;
+
+ @Column(name = "date_expiration")
+ private LocalDateTime dateExpiration;
+
+ @Column(name = "date_completion")
+ private LocalDateTime dateCompletion;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean isActive() {
+ return StatutIntentionPaiement.INITIEE.equals(statut)
+ || StatutIntentionPaiement.EN_COURS.equals(statut);
+ }
+
+ public boolean isExpiree() {
+ return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration);
+ }
+
+ public boolean isCompletee() {
+ return StatutIntentionPaiement.COMPLETEE.equals(statut);
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statut == null) statut = StatutIntentionPaiement.INITIEE;
+ if (codeDevise == null) codeDevise = "XOF";
+ if (dateExpiration == null) {
+ dateExpiration = LocalDateTime.now().plusMinutes(30);
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java b/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java
index f3d9d5f..c3b3090 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java
@@ -1,100 +1,108 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité JournalComptable pour la gestion des journaux
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "journaux_comptables",
- indexes = {
- @Index(name = "idx_journal_code", columnList = "code", unique = true),
- @Index(name = "idx_journal_type", columnList = "type_journal"),
- @Index(name = "idx_journal_periode", columnList = "date_debut, date_fin")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class JournalComptable extends BaseEntity {
-
- /** Code unique du journal */
- @NotBlank
- @Column(name = "code", unique = true, nullable = false, length = 10)
- private String code;
-
- /** Libellé du journal */
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 100)
- private String libelle;
-
- /** Type de journal */
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_journal", nullable = false, length = 30)
- private TypeJournalComptable typeJournal;
-
- /** Date de début de la période */
- @Column(name = "date_debut")
- private LocalDate dateDebut;
-
- /** Date de fin de la période */
- @Column(name = "date_fin")
- private LocalDate dateFin;
-
- /** Statut du journal (OUVERT, FERME, ARCHIVE) */
- @Builder.Default
- @Column(name = "statut", length = 20)
- private String statut = "OUVERT";
-
- /** Description */
- @Column(name = "description", length = 500)
- private String description;
-
- /** Écritures comptables associées */
- @JsonIgnore
- @OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List ecritures = new ArrayList<>();
-
- /** Méthode métier pour vérifier si le journal est ouvert */
- public boolean isOuvert() {
- return "OUVERT".equals(statut);
- }
-
- /** Méthode métier pour vérifier si une date est dans la période */
- public boolean estDansPeriode(LocalDate date) {
- if (dateDebut == null || dateFin == null) {
- return true; // Période illimitée
- }
- return !date.isBefore(dateDebut) && !date.isAfter(dateFin);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statut == null || statut.isEmpty()) {
- statut = "OUVERT";
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité JournalComptable pour la gestion des journaux
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "journaux_comptables",
+ uniqueConstraints = {
+ @UniqueConstraint(name = "uk_journaux_org_code", columnNames = {"organisation_id", "code"})
+ },
+ indexes = {
+ @Index(name = "idx_journal_code", columnList = "code"),
+ @Index(name = "idx_journal_type", columnList = "type_journal"),
+ @Index(name = "idx_journal_periode", columnList = "date_debut, date_fin")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class JournalComptable extends BaseEntity {
+
+ /** Code du journal (unique par organisation). */
+ @NotBlank
+ @Column(name = "code", nullable = false, length = 10)
+ private String code;
+
+ /** Libellé du journal */
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 100)
+ private String libelle;
+
+ /** Type de journal */
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_journal", nullable = false, length = 30)
+ private TypeJournalComptable typeJournal;
+
+ /** Date de début de la période */
+ @Column(name = "date_debut")
+ private LocalDate dateDebut;
+
+ /** Date de fin de la période */
+ @Column(name = "date_fin")
+ private LocalDate dateFin;
+
+ /** Statut du journal (OUVERT, FERME, ARCHIVE) */
+ @Builder.Default
+ @Column(name = "statut", length = 20)
+ private String statut = "OUVERT";
+
+ /** Description */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Organisation propriétaire */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /** Écritures comptables associées */
+ @JsonIgnore
+ @OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List ecritures = new ArrayList<>();
+
+ /** Méthode métier pour vérifier si le journal est ouvert */
+ public boolean isOuvert() {
+ return "OUVERT".equals(statut);
+ }
+
+ /** Méthode métier pour vérifier si une date est dans la période */
+ public boolean estDansPeriode(LocalDate date) {
+ if (dateDebut == null || dateFin == null) {
+ return true; // Période illimitée
+ }
+ return !date.isBefore(dateDebut) && !date.isAfter(dateFin);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statut == null || statut.isEmpty()) {
+ statut = "OUVERT";
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java b/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java
index 08a170c..35e8322 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java
@@ -1,100 +1,100 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité LigneEcriture pour les lignes d'une écriture comptable
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "lignes_ecriture",
- indexes = {
- @Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"),
- @Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class LigneEcriture extends BaseEntity {
-
- /** Numéro de ligne */
- @NotNull
- @Min(value = 1, message = "Le numéro de ligne doit être positif")
- @Column(name = "numero_ligne", nullable = false)
- private Integer numeroLigne;
-
- /** Montant débit */
- @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_debit", precision = 14, scale = 2)
- private BigDecimal montantDebit;
-
- /** Montant crédit */
- @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_credit", precision = 14, scale = 2)
- private BigDecimal montantCredit;
-
- /** Libellé de la ligne */
- @Column(name = "libelle", length = 500)
- private String libelle;
-
- /** Référence */
- @Column(name = "reference", length = 100)
- private String reference;
-
- // Relations
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "ecriture_id", nullable = false)
- private EcritureComptable ecriture;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "compte_comptable_id", nullable = false)
- private CompteComptable compteComptable;
-
- /** Méthode métier pour vérifier que la ligne a soit un débit soit un crédit (pas les deux) */
- public boolean isValide() {
- boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0;
- boolean aCredit = montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0;
- return aDebit != aCredit; // XOR : soit débit, soit crédit, pas les deux
- }
-
- /** Méthode métier pour obtenir le montant (débit ou crédit) */
- public BigDecimal getMontant() {
- if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) {
- return montantDebit;
- }
- if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) {
- return montantCredit;
- }
- return BigDecimal.ZERO;
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (montantDebit == null) {
- montantDebit = BigDecimal.ZERO;
- }
- if (montantCredit == null) {
- montantCredit = BigDecimal.ZERO;
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité LigneEcriture pour les lignes d'une écriture comptable
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "lignes_ecriture",
+ indexes = {
+ @Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"),
+ @Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class LigneEcriture extends BaseEntity {
+
+ /** Numéro de ligne */
+ @NotNull
+ @Min(value = 1, message = "Le numéro de ligne doit être positif")
+ @Column(name = "numero_ligne", nullable = false)
+ private Integer numeroLigne;
+
+ /** Montant débit */
+ @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_debit", precision = 14, scale = 2)
+ private BigDecimal montantDebit;
+
+ /** Montant crédit */
+ @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_credit", precision = 14, scale = 2)
+ private BigDecimal montantCredit;
+
+ /** Libellé de la ligne */
+ @Column(name = "libelle", length = 500)
+ private String libelle;
+
+ /** Référence */
+ @Column(name = "reference", length = 100)
+ private String reference;
+
+ // Relations
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "ecriture_id", nullable = false)
+ private EcritureComptable ecriture;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "compte_comptable_id", nullable = false)
+ private CompteComptable compteComptable;
+
+ /** Méthode métier pour vérifier que la ligne a soit un débit soit un crédit (pas les deux) */
+ public boolean isValide() {
+ boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0;
+ boolean aCredit = montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0;
+ return aDebit != aCredit; // XOR : soit débit, soit crédit, pas les deux
+ }
+
+ /** Méthode métier pour obtenir le montant (débit ou crédit) */
+ public BigDecimal getMontant() {
+ if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) {
+ return montantDebit;
+ }
+ if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) {
+ return montantCredit;
+ }
+ return BigDecimal.ZERO;
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (montantDebit == null) {
+ montantDebit = BigDecimal.ZERO;
+ }
+ if (montantCredit == null) {
+ montantCredit = BigDecimal.ZERO;
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Membre.java b/src/main/java/dev/lions/unionflow/server/entity/Membre.java
index 08c5ceb..a8e0565 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Membre.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Membre.java
@@ -1,169 +1,173 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import lombok.*;
-
-/**
- * Identité globale unique d'un utilisateur UnionFlow.
- *
- *
- * Un utilisateur possède un seul compte sur toute la plateforme.
- * Ses adhésions aux organisations sont gérées dans {@link MembreOrganisation}.
- *
- *
- * Table : {@code utilisateurs}
- */
-@Entity
-@Table(name = "utilisateurs", indexes = {
- @Index(name = "idx_utilisateur_email", columnList = "email", unique = true),
- @Index(name = "idx_utilisateur_numero", columnList = "numero_membre", unique = true),
- @Index(name = "idx_utilisateur_keycloak", columnList = "keycloak_id", unique = true),
- @Index(name = "idx_utilisateur_actif", columnList = "actif"),
- @Index(name = "idx_utilisateur_statut", columnList = "statut_compte")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Membre extends BaseEntity {
-
- /** Identifiant Keycloak (UUID du compte OIDC) */
- @Column(name = "keycloak_id", unique = true)
- private UUID keycloakId;
-
- /** Numéro de membre — unique globalement sur toute la plateforme */
- @NotBlank
- @Column(name = "numero_membre", unique = true, nullable = false, length = 20)
- private String numeroMembre;
-
- @NotBlank
- @Column(name = "prenom", nullable = false, length = 100)
- private String prenom;
-
- @NotBlank
- @Column(name = "nom", nullable = false, length = 100)
- private String nom;
-
- @Email
- @NotBlank
- @Column(name = "email", unique = true, nullable = false, length = 255)
- private String email;
-
- @Column(name = "telephone", length = 20)
- private String telephone;
-
- @Pattern(regexp = "^\\+[1-9][0-9]{6,14}$", message = "Le numéro Wave doit être au format international E.164 (ex: +22507XXXXXXXX)")
- @Column(name = "telephone_wave", length = 20)
- private String telephoneWave;
-
- @NotNull
- @Column(name = "date_naissance", nullable = false)
- private LocalDate dateNaissance;
-
- @Column(name = "profession", length = 100)
- private String profession;
-
- @Column(name = "photo_url", length = 500)
- private String photoUrl;
-
- @Builder.Default
- @Column(name = "statut_compte", nullable = false, length = 30)
- private String statutCompte = "EN_ATTENTE_VALIDATION";
-
- /** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
- @Builder.Default
- @Column(name = "premiere_connexion", nullable = false)
- private Boolean premiereConnexion = true;
-
- /**
- * Statut matrimonial (domaine
- * {@code STATUT_MATRIMONIAL} dans
- * {@code types_reference}).
- */
- @Column(name = "statut_matrimonial", length = 50)
- private String statutMatrimonial;
-
- /** Nationalité. */
- @Column(name = "nationalite", length = 100)
- private String nationalite;
-
- /**
- * Type de pièce d'identité (domaine
- * {@code TYPE_IDENTITE} dans
- * {@code types_reference}).
- */
- @Column(name = "type_identite", length = 50)
- private String typeIdentite;
-
- /** Numéro de la pièce d'identité. */
- @Column(name = "numero_identite", length = 100)
- private String numeroIdentite;
-
- /** Notes / biographie libre du membre. */
- @Column(name = "notes", length = 1000)
- private String notes;
-
- /** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
- @Column(name = "niveau_vigilance_kyc", length = 20)
- private String niveauVigilanceKyc;
-
- /** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */
- @Column(name = "statut_kyc", length = 20)
- private String statutKyc;
-
- /** Date de dernière vérification d'identité. */
- @Column(name = "date_verification_identite")
- private LocalDate dateVerificationIdentite;
-
- // ── Relations ────────────────────────────────────────────────────────────
-
- /** Adhésions à des organisations */
- @JsonIgnore
- @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List membresOrganisations = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List adresses = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List comptesWave = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List paiements = new ArrayList<>();
-
- // ── Méthodes métier ───────────────────────────────────────────────────────
-
- public String getNomComplet() {
- return prenom + " " + nom;
- }
-
- public boolean isMajeur() {
- return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18));
- }
-
- public int getAge() {
- return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0;
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statutCompte == null) {
- statutCompte = "EN_ATTENTE_VALIDATION";
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.*;
+
+/**
+ * Identité globale unique d'un utilisateur UnionFlow.
+ *
+ *
+ * Un utilisateur possède un seul compte sur toute la plateforme.
+ * Ses adhésions aux organisations sont gérées dans {@link MembreOrganisation}.
+ *
+ *
+ * Table : {@code utilisateurs}
+ */
+@Entity
+@Table(name = "utilisateurs", indexes = {
+ @Index(name = "idx_utilisateur_email", columnList = "email", unique = true),
+ @Index(name = "idx_utilisateur_numero", columnList = "numero_membre", unique = true),
+ @Index(name = "idx_utilisateur_keycloak", columnList = "keycloak_id", unique = true),
+ @Index(name = "idx_utilisateur_actif", columnList = "actif"),
+ @Index(name = "idx_utilisateur_statut", columnList = "statut_compte")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Membre extends BaseEntity {
+
+ /** Identifiant Keycloak (UUID du compte OIDC) */
+ @Column(name = "keycloak_id", unique = true)
+ private UUID keycloakId;
+
+ /** Numéro de membre — unique globalement sur toute la plateforme */
+ @NotBlank
+ @Column(name = "numero_membre", unique = true, nullable = false, length = 20)
+ private String numeroMembre;
+
+ @NotBlank
+ @Column(name = "prenom", nullable = false, length = 100)
+ private String prenom;
+
+ @NotBlank
+ @Column(name = "nom", nullable = false, length = 100)
+ private String nom;
+
+ @Email
+ @NotBlank
+ @Column(name = "email", unique = true, nullable = false, length = 255)
+ private String email;
+
+ @Column(name = "telephone", length = 20)
+ private String telephone;
+
+ /** Token FCM pour les notifications push Firebase. NULL si l'app mobile n'est pas installée ou si le membre a refusé les notifications. */
+ @Column(name = "fcm_token", length = 500)
+ private String fcmToken;
+
+ @Pattern(regexp = "^\\+[1-9][0-9]{6,14}$", message = "Le numéro Wave doit être au format international E.164 (ex: +22507XXXXXXXX)")
+ @Column(name = "telephone_wave", length = 20)
+ private String telephoneWave;
+
+ @NotNull
+ @Column(name = "date_naissance", nullable = false)
+ private LocalDate dateNaissance;
+
+ @Column(name = "profession", length = 100)
+ private String profession;
+
+ @Column(name = "photo_url", length = 500)
+ private String photoUrl;
+
+ @Builder.Default
+ @Column(name = "statut_compte", nullable = false, length = 30)
+ private String statutCompte = "EN_ATTENTE_VALIDATION";
+
+ /** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
+ @Builder.Default
+ @Column(name = "premiere_connexion", nullable = false)
+ private Boolean premiereConnexion = true;
+
+ /**
+ * Statut matrimonial (domaine
+ * {@code STATUT_MATRIMONIAL} dans
+ * {@code types_reference}).
+ */
+ @Column(name = "statut_matrimonial", length = 50)
+ private String statutMatrimonial;
+
+ /** Nationalité. */
+ @Column(name = "nationalite", length = 100)
+ private String nationalite;
+
+ /**
+ * Type de pièce d'identité (domaine
+ * {@code TYPE_IDENTITE} dans
+ * {@code types_reference}).
+ */
+ @Column(name = "type_identite", length = 50)
+ private String typeIdentite;
+
+ /** Numéro de la pièce d'identité. */
+ @Column(name = "numero_identite", length = 100)
+ private String numeroIdentite;
+
+ /** Notes / biographie libre du membre. */
+ @Column(name = "notes", length = 1000)
+ private String notes;
+
+ /** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
+ @Column(name = "niveau_vigilance_kyc", length = 20)
+ private String niveauVigilanceKyc;
+
+ /** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */
+ @Column(name = "statut_kyc", length = 20)
+ private String statutKyc;
+
+ /** Date de dernière vérification d'identité. */
+ @Column(name = "date_verification_identite")
+ private LocalDate dateVerificationIdentite;
+
+ // ── Relations ────────────────────────────────────────────────────────────
+
+ /** Adhésions à des organisations */
+ @JsonIgnore
+ @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List membresOrganisations = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List adresses = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List comptesWave = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List paiements = new ArrayList<>();
+
+ // ── Méthodes métier ───────────────────────────────────────────────────────
+
+ public String getNomComplet() {
+ return prenom + " " + nom;
+ }
+
+ public boolean isMajeur() {
+ return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18));
+ }
+
+ public int getAge() {
+ return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0;
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statutCompte == null) {
+ statutCompte = "EN_ATTENTE_VALIDATION";
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java
index 05809e3..b13c19a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/MembreOrganisation.java
@@ -1,141 +1,141 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import lombok.*;
-
-/**
- * Lien entre un utilisateur et une organisation.
- *
- * Un utilisateur peut adhérer à plusieurs organisations simultanément.
- * Chaque adhésion a son propre statut, date et unité d'affectation.
- *
- *
Table : {@code membres_organisations}
- */
-@Entity
-@Table(
- name = "membres_organisations",
- indexes = {
- @Index(name = "idx_mo_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_mo_organisation", columnList = "organisation_id"),
- @Index(name = "idx_mo_statut", columnList = "statut_membre"),
- @Index(name = "idx_mo_unite", columnList = "unite_id")
- },
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_mo_utilisateur_organisation",
- columnNames = {"utilisateur_id", "organisation_id"})
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class MembreOrganisation extends BaseEntity {
-
- /** L'utilisateur (identité globale) */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "utilisateur_id", nullable = false)
- private Membre membre;
-
- /** L'organisation racine à laquelle appartient ce membre */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- /**
- * Unité d'affectation (agence/bureau).
- * NULL = affecté au siège.
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "unite_id")
- private Organisation unite;
-
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut_membre", nullable = false, length = 30)
- private StatutMembre statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
-
- @Column(name = "date_adhesion")
- private LocalDate dateAdhesion;
-
- @Column(name = "date_changement_statut")
- private LocalDate dateChangementStatut;
-
- @Column(name = "motif_statut", length = 500)
- private String motifStatut;
-
- /** Utilisateur qui a approuvé ou traité ce changement de statut */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "approuve_par_id")
- private Membre approuvePar;
-
- // ── Champs d'invitation (StatutMembre.INVITE) ──────────────────────────────
-
- /** Date à laquelle l'invitation a été envoyée. */
- @Column(name = "date_invitation")
- private LocalDateTime dateInvitation;
-
- /** Date d'expiration de l'invitation (null = pas d'expiration). */
- @Column(name = "date_expiration_invitation")
- private LocalDateTime dateExpirationInvitation;
-
- /** Token opaque utilisé dans le lien d'invitation envoyé par email. */
- @Column(name = "token_invitation", length = 64)
- private String tokenInvitation;
-
- /** ID de l'administrateur qui a envoyé l'invitation. */
- @Column(name = "invite_par")
- private UUID invitePar;
-
- /** Motif d'archivage (pour StatutMembre.ARCHIVE). */
- @Column(name = "motif_archivage", length = 500)
- private String motifArchivage;
-
- // ── Rôle fonctionnel dans l'organisation ─────────────────────────────────
-
- /** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */
- @Column(name = "role_org", length = 50)
- private String roleOrg;
-
- // ── Relations ─────────────────────────────────────────────────────────────
-
- /** Rôles de ce membre dans cette organisation */
- @JsonIgnore
- @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List roles = new ArrayList<>();
-
- /** Ayants droit (mutuelles de santé uniquement) */
- @JsonIgnore
- @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List ayantsDroit = new ArrayList<>();
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean isActif() {
- return StatutMembre.ACTIF.equals(statutMembre) && Boolean.TRUE.equals(getActif());
- }
-
- public boolean peutDemanderAide() {
- return StatutMembre.ACTIF.equals(statutMembre);
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statutMembre == null) {
- statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.*;
+
+/**
+ * Lien entre un utilisateur et une organisation.
+ *
+ * Un utilisateur peut adhérer à plusieurs organisations simultanément.
+ * Chaque adhésion a son propre statut, date et unité d'affectation.
+ *
+ *
Table : {@code membres_organisations}
+ */
+@Entity
+@Table(
+ name = "membres_organisations",
+ indexes = {
+ @Index(name = "idx_mo_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_mo_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_mo_statut", columnList = "statut_membre"),
+ @Index(name = "idx_mo_unite", columnList = "unite_id")
+ },
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_mo_utilisateur_organisation",
+ columnNames = {"utilisateur_id", "organisation_id"})
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class MembreOrganisation extends BaseEntity {
+
+ /** L'utilisateur (identité globale) */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "utilisateur_id", nullable = false)
+ private Membre membre;
+
+ /** L'organisation racine à laquelle appartient ce membre */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /**
+ * Unité d'affectation (agence/bureau).
+ * NULL = affecté au siège.
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "unite_id")
+ private Organisation unite;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut_membre", nullable = false, length = 30)
+ private StatutMembre statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
+
+ @Column(name = "date_adhesion")
+ private LocalDate dateAdhesion;
+
+ @Column(name = "date_changement_statut")
+ private LocalDate dateChangementStatut;
+
+ @Column(name = "motif_statut", length = 500)
+ private String motifStatut;
+
+ /** Utilisateur qui a approuvé ou traité ce changement de statut */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "approuve_par_id")
+ private Membre approuvePar;
+
+ // ── Champs d'invitation (StatutMembre.INVITE) ──────────────────────────────
+
+ /** Date à laquelle l'invitation a été envoyée. */
+ @Column(name = "date_invitation")
+ private LocalDateTime dateInvitation;
+
+ /** Date d'expiration de l'invitation (null = pas d'expiration). */
+ @Column(name = "date_expiration_invitation")
+ private LocalDateTime dateExpirationInvitation;
+
+ /** Token opaque utilisé dans le lien d'invitation envoyé par email. */
+ @Column(name = "token_invitation", length = 64)
+ private String tokenInvitation;
+
+ /** ID de l'administrateur qui a envoyé l'invitation. */
+ @Column(name = "invite_par")
+ private UUID invitePar;
+
+ /** Motif d'archivage (pour StatutMembre.ARCHIVE). */
+ @Column(name = "motif_archivage", length = 500)
+ private String motifArchivage;
+
+ // ── Rôle fonctionnel dans l'organisation ─────────────────────────────────
+
+ /** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */
+ @Column(name = "role_org", length = 50)
+ private String roleOrg;
+
+ // ── Relations ─────────────────────────────────────────────────────────────
+
+ /** Rôles de ce membre dans cette organisation */
+ @JsonIgnore
+ @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List roles = new ArrayList<>();
+
+ /** Ayants droit (mutuelles de santé uniquement) */
+ @JsonIgnore
+ @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List ayantsDroit = new ArrayList<>();
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean isActif() {
+ return StatutMembre.ACTIF.equals(statutMembre) && Boolean.TRUE.equals(getActif());
+ }
+
+ public boolean peutDemanderAide() {
+ return StatutMembre.ACTIF.equals(statutMembre);
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statutMembre == null) {
+ statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java b/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java
index 0636a9d..1d5e69d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java
@@ -1,94 +1,94 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDate;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Table de liaison entre Membre et Role
- * Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "membres_roles",
- indexes = {
- @Index(name = "idx_mr_membre_org", columnList = "membre_organisation_id"),
- @Index(name = "idx_mr_organisation", columnList = "organisation_id"),
- @Index(name = "idx_mr_role", columnList = "role_id"),
- @Index(name = "idx_mr_actif", columnList = "actif")
- },
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_mr_membre_org_role",
- columnNames = {"membre_organisation_id", "role_id"})
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class MembreRole extends BaseEntity {
-
- /** Lien membership (utilisateur dans le contexte de son organisation) */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_organisation_id", nullable = false)
- private MembreOrganisation membreOrganisation;
-
- /** Organisation dans laquelle ce rôle est actif (dénormalisé pour les requêtes) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- /** Rôle */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "role_id", nullable = false)
- private Role role;
-
- /** Date de début d'attribution */
- @Column(name = "date_debut")
- private LocalDate dateDebut;
-
- /** Date de fin d'attribution (null = permanent) */
- @Column(name = "date_fin")
- private LocalDate dateFin;
-
- /** Commentaire sur l'attribution */
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-
- /** Méthode métier pour vérifier si l'attribution est active */
- public boolean isActif() {
- if (!Boolean.TRUE.equals(getActif())) {
- return false;
- }
- LocalDate aujourdhui = LocalDate.now();
- if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
- return false;
- }
- if (dateFin != null && aujourdhui.isAfter(dateFin)) {
- return false;
- }
- return true;
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (dateDebut == null) {
- dateDebut = LocalDate.now();
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Table de liaison entre Membre et Role
+ * Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "membres_roles",
+ indexes = {
+ @Index(name = "idx_mr_membre_org", columnList = "membre_organisation_id"),
+ @Index(name = "idx_mr_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_mr_role", columnList = "role_id"),
+ @Index(name = "idx_mr_actif", columnList = "actif")
+ },
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_mr_membre_org_role",
+ columnNames = {"membre_organisation_id", "role_id"})
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class MembreRole extends BaseEntity {
+
+ /** Lien membership (utilisateur dans le contexte de son organisation) */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_organisation_id", nullable = false)
+ private MembreOrganisation membreOrganisation;
+
+ /** Organisation dans laquelle ce rôle est actif (dénormalisé pour les requêtes) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /** Rôle */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "role_id", nullable = false)
+ private Role role;
+
+ /** Date de début d'attribution */
+ @Column(name = "date_debut")
+ private LocalDate dateDebut;
+
+ /** Date de fin d'attribution (null = permanent) */
+ @Column(name = "date_fin")
+ private LocalDate dateFin;
+
+ /** Commentaire sur l'attribution */
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+
+ /** Méthode métier pour vérifier si l'attribution est active */
+ public boolean isActif() {
+ if (!Boolean.TRUE.equals(getActif())) {
+ return false;
+ }
+ LocalDate aujourdhui = LocalDate.now();
+ if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
+ return false;
+ }
+ if (dateFin != null && aujourdhui.isAfter(dateFin)) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (dateDebut == null) {
+ dateDebut = LocalDate.now();
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/MembreSuivi.java b/src/main/java/dev/lions/unionflow/server/entity/MembreSuivi.java
index 4ab147f..5f6579e 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/MembreSuivi.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/MembreSuivi.java
@@ -1,38 +1,38 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.util.UUID;
-
-/**
- * Lien « suivi » entre deux membres : le membre connecté (follower) suit un autre membre (suivi).
- * Utilisé pour la fonctionnalité Réseau / Suivre dans l’app mobile.
- */
-@Entity
-@Table(
- name = "membre_suivi",
- uniqueConstraints = @UniqueConstraint(columnNames = { "follower_utilisateur_id", "suivi_utilisateur_id" }),
- indexes = {
- @Index(name = "idx_membre_suivi_follower", columnList = "follower_utilisateur_id"),
- @Index(name = "idx_membre_suivi_suivi", columnList = "suivi_utilisateur_id")
- }
-)
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class MembreSuivi extends BaseEntity {
-
- /** Utilisateur qui suit (membre connecté). */
- @NotNull
- @Column(name = "follower_utilisateur_id", nullable = false)
- private UUID followerUtilisateurId;
-
- /** Utilisateur suivi (membre cible). */
- @NotNull
- @Column(name = "suivi_utilisateur_id", nullable = false)
- private UUID suiviUtilisateurId;
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.util.UUID;
+
+/**
+ * Lien « suivi » entre deux membres : le membre connecté (follower) suit un autre membre (suivi).
+ * Utilisé pour la fonctionnalité Réseau / Suivre dans l’app mobile.
+ */
+@Entity
+@Table(
+ name = "membre_suivi",
+ uniqueConstraints = @UniqueConstraint(columnNames = { "follower_utilisateur_id", "suivi_utilisateur_id" }),
+ indexes = {
+ @Index(name = "idx_membre_suivi_follower", columnList = "follower_utilisateur_id"),
+ @Index(name = "idx_membre_suivi_suivi", columnList = "suivi_utilisateur_id")
+ }
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class MembreSuivi extends BaseEntity {
+
+ /** Utilisateur qui suit (membre connecté). */
+ @NotNull
+ @Column(name = "follower_utilisateur_id", nullable = false)
+ private UUID followerUtilisateurId;
+
+ /** Utilisateur suivi (membre cible). */
+ @NotNull
+ @Column(name = "suivi_utilisateur_id", nullable = false)
+ private UUID suiviUtilisateurId;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ModuleDisponible.java b/src/main/java/dev/lions/unionflow/server/entity/ModuleDisponible.java
index 208ceb0..2d0b972 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ModuleDisponible.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ModuleDisponible.java
@@ -1,56 +1,56 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import lombok.*;
-
-/**
- * Catalogue des modules métier activables par type d'organisation.
- *
- * Géré uniquement par le Super Admin UnionFlow.
- * Les organisations ne peuvent pas créer de nouveaux modules.
- *
- *
Table : {@code modules_disponibles}
- */
-@Entity
-@Table(
- name = "modules_disponibles",
- indexes = {
- @Index(name = "idx_module_code", columnList = "code", unique = true),
- @Index(name = "idx_module_actif", columnList = "actif")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ModuleDisponible extends BaseEntity {
-
- @NotBlank
- @Column(name = "code", unique = true, nullable = false, length = 50)
- private String code;
-
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 150)
- private String libelle;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String description;
-
- /**
- * JSON array des types d'organisations compatibles.
- * Exemple : ["MUTUELLE_SANTE","ONG"] ou ["ALL"] pour tous.
- */
- @Column(name = "types_org_compatibles", columnDefinition = "TEXT")
- private String typesOrgCompatibles;
-
- @Builder.Default
- @Column(name = "ordre_affichage", nullable = false)
- private Integer ordreAffichage = 0;
-
- public boolean estCompatibleAvec(String typeOrganisation) {
- if (typesOrgCompatibles == null) return false;
- return typesOrgCompatibles.contains("ALL")
- || typesOrgCompatibles.contains(typeOrganisation);
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import lombok.*;
+
+/**
+ * Catalogue des modules métier activables par type d'organisation.
+ *
+ *
Géré uniquement par le Super Admin UnionFlow.
+ * Les organisations ne peuvent pas créer de nouveaux modules.
+ *
+ *
Table : {@code modules_disponibles}
+ */
+@Entity
+@Table(
+ name = "modules_disponibles",
+ indexes = {
+ @Index(name = "idx_module_code", columnList = "code", unique = true),
+ @Index(name = "idx_module_actif", columnList = "actif")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ModuleDisponible extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "code", unique = true, nullable = false, length = 50)
+ private String code;
+
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 150)
+ private String libelle;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String description;
+
+ /**
+ * JSON array des types d'organisations compatibles.
+ * Exemple : ["MUTUELLE_SANTE","ONG"] ou ["ALL"] pour tous.
+ */
+ @Column(name = "types_org_compatibles", columnDefinition = "TEXT")
+ private String typesOrgCompatibles;
+
+ @Builder.Default
+ @Column(name = "ordre_affichage", nullable = false)
+ private Integer ordreAffichage = 0;
+
+ public boolean estCompatibleAvec(String typeOrganisation) {
+ if (typesOrgCompatibles == null) return false;
+ return typesOrgCompatibles.contains("ALL")
+ || typesOrgCompatibles.contains(typeOrganisation);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ModuleOrganisationActif.java b/src/main/java/dev/lions/unionflow/server/entity/ModuleOrganisationActif.java
index 5435af1..1e465c7 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ModuleOrganisationActif.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ModuleOrganisationActif.java
@@ -1,64 +1,64 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.time.LocalDateTime;
-import lombok.*;
-
-/**
- * Module activé pour une organisation donnée.
- *
- *
- * Les modules sont activés automatiquement selon le type d'organisation
- * lors de la première souscription, et peuvent être désactivés par le manager.
- *
- *
- * Table : {@code modules_organisation_actifs}
- */
-@Entity
-@Table(name = "modules_organisation_actifs", indexes = {
- @Index(name = "idx_moa_organisation", columnList = "organisation_id"),
- @Index(name = "idx_moa_module", columnList = "module_code")
-}, uniqueConstraints = {
- @UniqueConstraint(name = "uk_moa_org_module", columnNames = { "organisation_id", "module_code" })
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ModuleOrganisationActif extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Column(name = "module_code", nullable = false, length = 50)
- private String moduleCode;
-
- /**
- * Référence vers le catalogue des modules.
- * Assure l'intégrité référentielle avec
- * {@code modules_disponibles}.
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "module_disponible_id")
- private ModuleDisponible moduleDisponible;
-
- @Builder.Default
- @Column(name = "date_activation", nullable = false)
- private LocalDateTime dateActivation = LocalDateTime.now();
-
- /**
- * Configuration JSON spécifique au module pour cette organisation.
- * Exemple pour CREDIT_EPARGNE : {"taux_interet_max": 18, "duree_max_mois": 24}
- */
- @Column(name = "parametres", columnDefinition = "TEXT")
- private String parametres;
-
- public boolean isActif() {
- return Boolean.TRUE.equals(getActif());
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.time.LocalDateTime;
+import lombok.*;
+
+/**
+ * Module activé pour une organisation donnée.
+ *
+ *
+ * Les modules sont activés automatiquement selon le type d'organisation
+ * lors de la première souscription, et peuvent être désactivés par le manager.
+ *
+ *
+ * Table : {@code modules_organisation_actifs}
+ */
+@Entity
+@Table(name = "modules_organisation_actifs", indexes = {
+ @Index(name = "idx_moa_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_moa_module", columnList = "module_code")
+}, uniqueConstraints = {
+ @UniqueConstraint(name = "uk_moa_org_module", columnNames = { "organisation_id", "module_code" })
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ModuleOrganisationActif extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Column(name = "module_code", nullable = false, length = 50)
+ private String moduleCode;
+
+ /**
+ * Référence vers le catalogue des modules.
+ * Assure l'intégrité référentielle avec
+ * {@code modules_disponibles}.
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "module_disponible_id")
+ private ModuleDisponible moduleDisponible;
+
+ @Builder.Default
+ @Column(name = "date_activation", nullable = false)
+ private LocalDateTime dateActivation = LocalDateTime.now();
+
+ /**
+ * Configuration JSON spécifique au module pour cette organisation.
+ * Exemple pour CREDIT_EPARGNE : {"taux_interet_max": 18, "duree_max_mois": 24}
+ */
+ @Column(name = "parametres", columnDefinition = "TEXT")
+ private String parametres;
+
+ public boolean isActif() {
+ return Boolean.TRUE.equals(getActif());
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Notification.java b/src/main/java/dev/lions/unionflow/server/entity/Notification.java
index 21d6d71..edd8232 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Notification.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Notification.java
@@ -1,123 +1,123 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import java.time.LocalDateTime;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Notification pour la gestion des notifications
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(name = "notifications", indexes = {
- @Index(name = "idx_notification_type", columnList = "type_notification"),
- @Index(name = "idx_notification_statut", columnList = "statut"),
- @Index(name = "idx_notification_priorite", columnList = "priorite"),
- @Index(name = "idx_notification_membre", columnList = "membre_id"),
- @Index(name = "idx_notification_organisation", columnList = "organisation_id"),
- @Index(name = "idx_notification_date_envoi", columnList = "date_envoi")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Notification extends BaseEntity {
-
- /** Type de notification */
- @NotNull
- @Column(name = "type_notification", nullable = false, length = 30)
- private String typeNotification;
-
- /** Priorité */
- @Builder.Default
- @Column(name = "priorite", length = 20)
- private String priorite = "NORMALE";
-
- /** Statut */
- @Builder.Default
- @Column(name = "statut", length = 30)
- private String statut = "EN_ATTENTE";
-
- /** Sujet */
- @Column(name = "sujet", length = 500)
- private String sujet;
-
- /** Corps du message */
- @Column(name = "corps", columnDefinition = "TEXT")
- private String corps;
-
- /** Date d'envoi prévue */
- @Column(name = "date_envoi_prevue")
- private LocalDateTime dateEnvoiPrevue;
-
- /** Date d'envoi réelle */
- @Column(name = "date_envoi")
- private LocalDateTime dateEnvoi;
-
- /** Date de lecture */
- @Column(name = "date_lecture")
- private LocalDateTime dateLecture;
-
- /** Nombre de tentatives d'envoi */
- @Builder.Default
- @Column(name = "nombre_tentatives", nullable = false)
- private Integer nombreTentatives = 0;
-
- /** Message d'erreur (si échec) */
- @Column(name = "message_erreur", length = 1000)
- private String messageErreur;
-
- /** Données additionnelles (JSON) */
- @Column(name = "donnees_additionnelles", columnDefinition = "TEXT")
- private String donneesAdditionnelles;
-
- // Relations
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id")
- private Membre membre;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "template_id")
- private TemplateNotification template;
-
- /** Méthode métier pour vérifier si la notification est envoyée */
- public boolean isEnvoyee() {
- return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.name().equals(statut);
- }
-
- /** Méthode métier pour vérifier si la notification est lue */
- public boolean isLue() {
- return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.name().equals(statut);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (priorite == null) {
- priorite = "NORMALE";
- }
- if (statut == null) {
- statut = "EN_ATTENTE";
- }
- if (nombreTentatives == null) {
- nombreTentatives = 0;
- }
- if (dateEnvoiPrevue == null) {
- dateEnvoiPrevue = LocalDateTime.now();
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Notification pour la gestion des notifications
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(name = "notifications", indexes = {
+ @Index(name = "idx_notification_type", columnList = "type_notification"),
+ @Index(name = "idx_notification_statut", columnList = "statut"),
+ @Index(name = "idx_notification_priorite", columnList = "priorite"),
+ @Index(name = "idx_notification_membre", columnList = "membre_id"),
+ @Index(name = "idx_notification_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_notification_date_envoi", columnList = "date_envoi")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Notification extends BaseEntity {
+
+ /** Type de notification */
+ @NotNull
+ @Column(name = "type_notification", nullable = false, length = 30)
+ private String typeNotification;
+
+ /** Priorité */
+ @Builder.Default
+ @Column(name = "priorite", length = 20)
+ private String priorite = "NORMALE";
+
+ /** Statut */
+ @Builder.Default
+ @Column(name = "statut", length = 30)
+ private String statut = "EN_ATTENTE";
+
+ /** Sujet */
+ @Column(name = "sujet", length = 500)
+ private String sujet;
+
+ /** Corps du message */
+ @Column(name = "corps", columnDefinition = "TEXT")
+ private String corps;
+
+ /** Date d'envoi prévue */
+ @Column(name = "date_envoi_prevue")
+ private LocalDateTime dateEnvoiPrevue;
+
+ /** Date d'envoi réelle */
+ @Column(name = "date_envoi")
+ private LocalDateTime dateEnvoi;
+
+ /** Date de lecture */
+ @Column(name = "date_lecture")
+ private LocalDateTime dateLecture;
+
+ /** Nombre de tentatives d'envoi */
+ @Builder.Default
+ @Column(name = "nombre_tentatives", nullable = false)
+ private Integer nombreTentatives = 0;
+
+ /** Message d'erreur (si échec) */
+ @Column(name = "message_erreur", length = 1000)
+ private String messageErreur;
+
+ /** Données additionnelles (JSON) */
+ @Column(name = "donnees_additionnelles", columnDefinition = "TEXT")
+ private String donneesAdditionnelles;
+
+ // Relations
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id")
+ private Membre membre;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "template_id")
+ private TemplateNotification template;
+
+ /** Méthode métier pour vérifier si la notification est envoyée */
+ public boolean isEnvoyee() {
+ return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.name().equals(statut);
+ }
+
+ /** Méthode métier pour vérifier si la notification est lue */
+ public boolean isLue() {
+ return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.name().equals(statut);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (priorite == null) {
+ priorite = "NORMALE";
+ }
+ if (statut == null) {
+ statut = "EN_ATTENTE";
+ }
+ if (nombreTentatives == null) {
+ nombreTentatives = 0;
+ }
+ if (dateEnvoiPrevue == null) {
+ dateEnvoiPrevue = LocalDateTime.now();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Organisation.java b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java
index d22f363..eeb84ac 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Organisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java
@@ -1,326 +1,331 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.Period;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Organisation avec UUID Représente une organisation (Lions Club,
- * Association,
- * Coopérative, etc.)
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-01-16
- */
-@Entity
-@Table(name = "organisations", indexes = {
- @Index(name = "idx_organisation_nom", columnList = "nom"),
- @Index(name = "idx_organisation_email", columnList = "email", unique = true),
- @Index(name = "idx_organisation_statut", columnList = "statut"),
- @Index(name = "idx_organisation_type", columnList = "type_organisation"),
- @Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"),
- @Index(name = "idx_organisation_numero_enregistrement", columnList = "numero_enregistrement", unique = true)
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Organisation extends BaseEntity {
-
- @NotBlank
- @Column(name = "nom", nullable = false, length = 200)
- private String nom;
-
- @Column(name = "nom_court", length = 50)
- private String nomCourt;
-
- @NotBlank
- @Column(name = "type_organisation", nullable = false, length = 50)
- private String typeOrganisation;
-
- @NotBlank
- @Column(name = "statut", nullable = false, length = 50)
- private String statut;
-
- @Column(name = "description", length = 2000)
- private String description;
-
- @Column(name = "date_fondation")
- private LocalDate dateFondation;
-
- @Column(name = "numero_enregistrement", unique = true, length = 100)
- private String numeroEnregistrement;
-
- // Informations de contact
- @Email
- @NotBlank
- @Column(name = "email", unique = true, nullable = false, length = 255)
- private String email;
-
- @Column(name = "telephone", length = 20)
- private String telephone;
-
- @Column(name = "telephone_secondaire", length = 20)
- private String telephoneSecondaire;
-
- @Email
- @Column(name = "email_secondaire", length = 255)
- private String emailSecondaire;
-
- // Adresse principale (champs dénormalisés pour performance)
- @Column(name = "adresse", length = 500)
- private String adresse;
-
- @Column(name = "ville", length = 100)
- private String ville;
-
- @Column(name = "region", length = 100)
- private String region;
-
- @Column(name = "pays", length = 100)
- private String pays;
-
- @Column(name = "code_postal", length = 20)
- private String codePostal;
-
- // Coordonnées géographiques
- @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
- @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
- @Digits(integer = 3, fraction = 6)
- @Column(name = "latitude", precision = 9, scale = 6)
- private BigDecimal latitude;
-
- @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
- @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
- @Digits(integer = 3, fraction = 6)
- @Column(name = "longitude", precision = 9, scale = 6)
- private BigDecimal longitude;
-
- // Web et réseaux sociaux
- @Column(name = "site_web", length = 500)
- private String siteWeb;
-
- @Column(name = "logo", length = 500)
- private String logo;
-
- @Column(name = "reseaux_sociaux", length = 1000)
- private String reseauxSociaux;
-
- // ── Hiérarchie ──────────────────────────────────────────────────────────────
-
- /** Organisation parente — FK propre (null = organisation racine) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_parente_id")
- private Organisation organisationParente;
-
- @Builder.Default
- @Column(name = "niveau_hierarchique", nullable = false)
- private Integer niveauHierarchique = 0;
-
- /**
- * TRUE si c'est l'organisation racine qui porte la souscription SaaS
- * pour toute sa hiérarchie.
- */
- @Builder.Default
- @Column(name = "est_organisation_racine", nullable = false)
- private Boolean estOrganisationRacine = true;
-
- /**
- * Chemin hiérarchique complet — ex: /uuid-racine/uuid-intermediate/uuid-feuille
- * Permet des requêtes récursives optimisées sans CTE.
- */
- @Column(name = "chemin_hierarchique", length = 2000)
- private String cheminHierarchique;
-
- // Statistiques
- @Builder.Default
- @Column(name = "nombre_membres", nullable = false)
- private Integer nombreMembres = 0;
-
- @Builder.Default
- @Column(name = "nombre_administrateurs", nullable = false)
- private Integer nombreAdministrateurs = 0;
-
- // Finances
- @DecimalMin(value = "0.0", message = "Le budget annuel doit être positif")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "budget_annuel", precision = 14, scale = 2)
- private BigDecimal budgetAnnuel;
-
- @Builder.Default
- @Column(name = "devise", length = 3)
- private String devise = "XOF";
-
- @Builder.Default
- @Column(name = "cotisation_obligatoire", nullable = false)
- private Boolean cotisationObligatoire = false;
-
- @DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
- private BigDecimal montantCotisationAnnuelle;
-
- // Informations complémentaires
- @Column(name = "objectifs", length = 2000)
- private String objectifs;
-
- @Column(name = "activites_principales", length = 2000)
- private String activitesPrincipales;
-
- @Column(name = "certifications", length = 500)
- private String certifications;
-
- @Column(name = "partenaires", length = 1000)
- private String partenaires;
-
- @Column(name = "notes", length = 1000)
- private String notes;
-
- // Paramètres
- @Builder.Default
- @Column(name = "organisation_publique", nullable = false)
- private Boolean organisationPublique = true;
-
- @Builder.Default
- @Column(name = "accepte_nouveaux_membres", nullable = false)
- private Boolean accepteNouveauxMembres = true;
-
- /** Catégorie du type d'organisation (ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION) */
- @Column(name = "categorie_type", length = 50)
- private String categorieType;
-
- /** Modules activés pour cette organisation (liste CSV, ex: "MEMBRES,COTISATIONS,TONTINE") */
- @Column(name = "modules_actifs", length = 1000)
- private String modulesActifs;
-
- // Relations
-
- /** Adhésions des membres à cette organisation */
- @JsonIgnore
- @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List membresOrganisations = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List adresses = new ArrayList<>();
-
- @JsonIgnore
- @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List comptesWave = new ArrayList<>();
-
- /** Méthode métier pour obtenir le nom complet avec sigle */
- public String getNomComplet() {
- if (nomCourt != null && !nomCourt.isEmpty()) {
- return nom + " (" + nomCourt + ")";
- }
- return nom;
- }
-
- /** Méthode métier pour calculer l'ancienneté en années */
- public int getAncienneteAnnees() {
- if (dateFondation == null) {
- return 0;
- }
- return Period.between(dateFondation, LocalDate.now()).getYears();
- }
-
- /**
- * Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans)
- */
- public boolean isRecente() {
- return getAncienneteAnnees() < 2;
- }
-
- /** Méthode métier pour vérifier si l'organisation est active */
- public boolean isActive() {
- return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
- }
-
- /** Méthode métier pour ajouter un membre */
- public void ajouterMembre() {
- if (nombreMembres == null) {
- nombreMembres = 0;
- }
- nombreMembres++;
- }
-
- /** Méthode métier pour retirer un membre */
- public void retirerMembre() {
- if (nombreMembres != null && nombreMembres > 0) {
- nombreMembres--;
- }
- }
-
- /** Méthode métier pour activer l'organisation */
- public void activer(String utilisateur) {
- this.statut = "ACTIVE";
- this.setActif(true);
- marquerCommeModifie(utilisateur);
- }
-
- /** Méthode métier pour suspendre l'organisation */
- public void suspendre(String utilisateur) {
- this.statut = "SUSPENDUE";
- this.accepteNouveauxMembres = false;
- marquerCommeModifie(utilisateur);
- }
-
- /** Méthode métier pour dissoudre l'organisation */
- public void dissoudre(String utilisateur) {
- this.statut = "DISSOUTE";
- this.setActif(false);
- this.accepteNouveauxMembres = false;
- marquerCommeModifie(utilisateur);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate(); // Appelle le onCreate de BaseEntity
- if (statut == null) {
- statut = "ACTIVE";
- }
- if (typeOrganisation == null) {
- typeOrganisation = "ASSOCIATION";
- }
- if (devise == null) {
- devise = "XOF";
- }
- if (niveauHierarchique == null) {
- niveauHierarchique = 0;
- }
- if (estOrganisationRacine == null) {
- estOrganisationRacine = (organisationParente == null);
- }
- if (nombreMembres == null) {
- nombreMembres = 0;
- }
- if (nombreAdministrateurs == null) {
- nombreAdministrateurs = 0;
- }
- if (organisationPublique == null) {
- organisationPublique = true;
- }
- if (accepteNouveauxMembres == null) {
- accepteNouveauxMembres = true;
- }
- if (cotisationObligatoire == null) {
- cotisationObligatoire = false;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.Period;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Organisation avec UUID Représente une organisation (Lions Club,
+ * Association,
+ * Coopérative, etc.)
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-01-16
+ */
+@Entity
+@Table(name = "organisations", indexes = {
+ @Index(name = "idx_organisation_nom", columnList = "nom"),
+ @Index(name = "idx_organisation_email", columnList = "email", unique = true),
+ @Index(name = "idx_organisation_statut", columnList = "statut"),
+ @Index(name = "idx_organisation_type", columnList = "type_organisation"),
+ @Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"),
+ @Index(name = "idx_organisation_numero_enregistrement", columnList = "numero_enregistrement", unique = true)
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Organisation extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "nom", nullable = false, length = 200)
+ private String nom;
+
+ @Column(name = "nom_court", length = 50)
+ private String nomCourt;
+
+ @NotBlank
+ @Column(name = "type_organisation", nullable = false, length = 50)
+ private String typeOrganisation;
+
+ @NotBlank
+ @Column(name = "statut", nullable = false, length = 50)
+ private String statut;
+
+ @Column(name = "description", length = 2000)
+ private String description;
+
+ @Column(name = "date_fondation")
+ private LocalDate dateFondation;
+
+ @Column(name = "numero_enregistrement", unique = true, length = 100)
+ private String numeroEnregistrement;
+
+ // Informations de contact
+ @Email
+ @NotBlank
+ @Column(name = "email", unique = true, nullable = false, length = 255)
+ private String email;
+
+ @Column(name = "telephone", length = 20)
+ private String telephone;
+
+ @Column(name = "telephone_secondaire", length = 20)
+ private String telephoneSecondaire;
+
+ @Email
+ @Column(name = "email_secondaire", length = 255)
+ private String emailSecondaire;
+
+ // Adresse principale (champs dénormalisés pour performance)
+ @Column(name = "adresse", length = 500)
+ private String adresse;
+
+ @Column(name = "ville", length = 100)
+ private String ville;
+
+ @Column(name = "region", length = 100)
+ private String region;
+
+ @Column(name = "pays", length = 100)
+ private String pays;
+
+ @Column(name = "code_postal", length = 20)
+ private String codePostal;
+
+ // Coordonnées géographiques
+ @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
+ @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
+ @Digits(integer = 3, fraction = 6)
+ @Column(name = "latitude", precision = 9, scale = 6)
+ private BigDecimal latitude;
+
+ @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
+ @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
+ @Digits(integer = 3, fraction = 6)
+ @Column(name = "longitude", precision = 9, scale = 6)
+ private BigDecimal longitude;
+
+ // Web et réseaux sociaux
+ @Column(name = "site_web", length = 500)
+ private String siteWeb;
+
+ @Column(name = "logo", length = 500)
+ private String logo;
+
+ @Column(name = "reseaux_sociaux", length = 1000)
+ private String reseauxSociaux;
+
+ // ── Hiérarchie ──────────────────────────────────────────────────────────────
+
+ /** Organisation parente — FK propre (null = organisation racine) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_parente_id")
+ private Organisation organisationParente;
+
+ @Builder.Default
+ @Column(name = "niveau_hierarchique", nullable = false)
+ private Integer niveauHierarchique = 0;
+
+ /**
+ * TRUE si c'est l'organisation racine qui porte la souscription SaaS
+ * pour toute sa hiérarchie.
+ */
+ @Builder.Default
+ @Column(name = "est_organisation_racine", nullable = false)
+ private Boolean estOrganisationRacine = true;
+
+ /**
+ * Chemin hiérarchique complet — ex: /uuid-racine/uuid-intermediate/uuid-feuille
+ * Permet des requêtes récursives optimisées sans CTE.
+ */
+ @Column(name = "chemin_hierarchique", length = 2000)
+ private String cheminHierarchique;
+
+ // Statistiques
+ @Builder.Default
+ @Column(name = "nombre_membres", nullable = false)
+ private Integer nombreMembres = 0;
+
+ @Builder.Default
+ @Column(name = "nombre_administrateurs", nullable = false)
+ private Integer nombreAdministrateurs = 0;
+
+ // Finances
+ @DecimalMin(value = "0.0", message = "Le budget annuel doit être positif")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "budget_annuel", precision = 14, scale = 2)
+ private BigDecimal budgetAnnuel;
+
+ @Builder.Default
+ @Column(name = "devise", length = 3)
+ private String devise = "XOF";
+
+ @Builder.Default
+ @Column(name = "cotisation_obligatoire", nullable = false)
+ private Boolean cotisationObligatoire = false;
+
+ @DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
+ private BigDecimal montantCotisationAnnuelle;
+
+ // Informations complémentaires
+ @Column(name = "objectifs", length = 2000)
+ private String objectifs;
+
+ @Column(name = "activites_principales", length = 2000)
+ private String activitesPrincipales;
+
+ @Column(name = "certifications", length = 500)
+ private String certifications;
+
+ @Column(name = "partenaires", length = 1000)
+ private String partenaires;
+
+ @Column(name = "notes", length = 1000)
+ private String notes;
+
+ // Paramètres
+ @Builder.Default
+ @Column(name = "organisation_publique", nullable = false)
+ private Boolean organisationPublique = true;
+
+ @Builder.Default
+ @Column(name = "accepte_nouveaux_membres", nullable = false)
+ private Boolean accepteNouveauxMembres = true;
+
+ /** Catégorie du type d'organisation (ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION) */
+ @Column(name = "categorie_type", length = 50)
+ private String categorieType;
+
+ /** ID de l'Organization Keycloak 26 correspondante — null si pas encore migrée. */
+ @Column(name = "keycloak_org_id")
+ private UUID keycloakOrgId;
+
+ /** Modules activés pour cette organisation (liste CSV, ex: "MEMBRES,COTISATIONS,TONTINE") */
+ @Column(name = "modules_actifs", length = 1000)
+ private String modulesActifs;
+
+ // Relations
+
+ /** Adhésions des membres à cette organisation */
+ @JsonIgnore
+ @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List membresOrganisations = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List adresses = new ArrayList<>();
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List comptesWave = new ArrayList<>();
+
+ /** Méthode métier pour obtenir le nom complet avec sigle */
+ public String getNomComplet() {
+ if (nomCourt != null && !nomCourt.isEmpty()) {
+ return nom + " (" + nomCourt + ")";
+ }
+ return nom;
+ }
+
+ /** Méthode métier pour calculer l'ancienneté en années */
+ public int getAncienneteAnnees() {
+ if (dateFondation == null) {
+ return 0;
+ }
+ return Period.between(dateFondation, LocalDate.now()).getYears();
+ }
+
+ /**
+ * Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans)
+ */
+ public boolean isRecente() {
+ return getAncienneteAnnees() < 2;
+ }
+
+ /** Méthode métier pour vérifier si l'organisation est active */
+ public boolean isActive() {
+ return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
+ }
+
+ /** Méthode métier pour ajouter un membre */
+ public void ajouterMembre() {
+ if (nombreMembres == null) {
+ nombreMembres = 0;
+ }
+ nombreMembres++;
+ }
+
+ /** Méthode métier pour retirer un membre */
+ public void retirerMembre() {
+ if (nombreMembres != null && nombreMembres > 0) {
+ nombreMembres--;
+ }
+ }
+
+ /** Méthode métier pour activer l'organisation */
+ public void activer(String utilisateur) {
+ this.statut = "ACTIVE";
+ this.setActif(true);
+ marquerCommeModifie(utilisateur);
+ }
+
+ /** Méthode métier pour suspendre l'organisation */
+ public void suspendre(String utilisateur) {
+ this.statut = "SUSPENDUE";
+ this.accepteNouveauxMembres = false;
+ marquerCommeModifie(utilisateur);
+ }
+
+ /** Méthode métier pour dissoudre l'organisation */
+ public void dissoudre(String utilisateur) {
+ this.statut = "DISSOUTE";
+ this.setActif(false);
+ this.accepteNouveauxMembres = false;
+ marquerCommeModifie(utilisateur);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate(); // Appelle le onCreate de BaseEntity
+ if (statut == null) {
+ statut = "ACTIVE";
+ }
+ if (typeOrganisation == null) {
+ typeOrganisation = "ASSOCIATION";
+ }
+ if (devise == null) {
+ devise = "XOF";
+ }
+ if (niveauHierarchique == null) {
+ niveauHierarchique = 0;
+ }
+ if (estOrganisationRacine == null) {
+ estOrganisationRacine = (organisationParente == null);
+ }
+ if (nombreMembres == null) {
+ nombreMembres = 0;
+ }
+ if (nombreAdministrateurs == null) {
+ nombreAdministrateurs = 0;
+ }
+ if (organisationPublique == null) {
+ organisationPublique = true;
+ }
+ if (accepteNouveauxMembres == null) {
+ accepteNouveauxMembres = true;
+ }
+ if (cotisationObligatoire == null) {
+ cotisationObligatoire = false;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
index bd314eb..c07cfcd 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
@@ -1,94 +1,94 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import lombok.*;
-
-/**
- * Paramètres de cotisation configurés par le manager de chaque organisation.
- *
- *
- * Le manager peut définir :
- *
- * - Le montant mensuel et annuel fixé pour tous les membres
- * - La date de départ du calcul des impayés (configurable)
- * - Le délai en jours avant passage automatique en statut INACTIF
- *
- *
- *
- * Table : {@code parametres_cotisation_organisation}
- */
-@Entity
-@Table(name = "parametres_cotisation_organisation", indexes = {
- @Index(name = "idx_param_cot_org", columnList = "organisation_id", unique = true)
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ParametresCotisationOrganisation extends BaseEntity {
-
- @NotNull
- @OneToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false, unique = true)
- private Organisation organisation;
-
- @Builder.Default
- @DecimalMin("0.00")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_cotisation_mensuelle", precision = 12, scale = 2)
- private BigDecimal montantCotisationMensuelle = BigDecimal.ZERO;
-
- @Builder.Default
- @DecimalMin("0.00")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
- private BigDecimal montantCotisationAnnuelle = BigDecimal.ZERO;
-
- @Column(name = "devise", nullable = false, length = 3)
- private String devise;
-
- /**
- * Date de référence pour le calcul des membres «à jour».
- * Toutes les échéances depuis cette date doivent être payées.
- * Configurable par le manager.
- */
- @Column(name = "date_debut_calcul_ajour")
- private LocalDate dateDebutCalculAjour;
-
- /**
- * Nombre de jours de retard avant passage automatique du statut membre →
- * INACTIF.
- * Défaut : 30 jours.
- */
- @Builder.Default
- @Min(1)
- @Column(name = "delai_retard_avant_inactif_jours", nullable = false)
- private Integer delaiRetardAvantInactifJours = 30;
-
- @Builder.Default
- @Column(name = "cotisation_obligatoire", nullable = false)
- private Boolean cotisationObligatoire = true;
-
- /**
- * Active la génération automatique mensuelle des cotisations pour cette organisation.
- * Quand {@code true}, un job planifié crée automatiquement une cotisation par membre actif
- * le 1er de chaque mois, en utilisant les barèmes par rôle ou le montant par défaut.
- */
- @Builder.Default
- @Column(name = "generation_automatique_activee", nullable = false)
- private Boolean generationAutomatiqueActivee = false;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- /**
- * Vérifie si la date de référence pour les impayés est définie.
- * Sans cette date, aucun calcul d'ancienneté des impayés n'est possible.
- */
- public boolean isCalculAjourActive() {
- return dateDebutCalculAjour != null;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.*;
+
+/**
+ * Paramètres de cotisation configurés par le manager de chaque organisation.
+ *
+ *
+ * Le manager peut définir :
+ *
+ * - Le montant mensuel et annuel fixé pour tous les membres
+ * - La date de départ du calcul des impayés (configurable)
+ * - Le délai en jours avant passage automatique en statut INACTIF
+ *
+ *
+ *
+ * Table : {@code parametres_cotisation_organisation}
+ */
+@Entity
+@Table(name = "parametres_cotisation_organisation", indexes = {
+ @Index(name = "idx_param_cot_org", columnList = "organisation_id", unique = true)
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ParametresCotisationOrganisation extends BaseEntity {
+
+ @NotNull
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false, unique = true)
+ private Organisation organisation;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_cotisation_mensuelle", precision = 12, scale = 2)
+ private BigDecimal montantCotisationMensuelle = BigDecimal.ZERO;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
+ private BigDecimal montantCotisationAnnuelle = BigDecimal.ZERO;
+
+ @Column(name = "devise", nullable = false, length = 3)
+ private String devise;
+
+ /**
+ * Date de référence pour le calcul des membres «à jour».
+ * Toutes les échéances depuis cette date doivent être payées.
+ * Configurable par le manager.
+ */
+ @Column(name = "date_debut_calcul_ajour")
+ private LocalDate dateDebutCalculAjour;
+
+ /**
+ * Nombre de jours de retard avant passage automatique du statut membre →
+ * INACTIF.
+ * Défaut : 30 jours.
+ */
+ @Builder.Default
+ @Min(1)
+ @Column(name = "delai_retard_avant_inactif_jours", nullable = false)
+ private Integer delaiRetardAvantInactifJours = 30;
+
+ @Builder.Default
+ @Column(name = "cotisation_obligatoire", nullable = false)
+ private Boolean cotisationObligatoire = true;
+
+ /**
+ * Active la génération automatique mensuelle des cotisations pour cette organisation.
+ * Quand {@code true}, un job planifié crée automatiquement une cotisation par membre actif
+ * le 1er de chaque mois, en utilisant les barèmes par rôle ou le montant par défaut.
+ */
+ @Builder.Default
+ @Column(name = "generation_automatique_activee", nullable = false)
+ private Boolean generationAutomatiqueActivee = false;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ /**
+ * Vérifie si la date de référence pour les impayés est définie.
+ * Sans cette date, aucun calcul d'ancienneté des impayés n'est possible.
+ */
+ public boolean isCalculAjourActive() {
+ return dateDebutCalculAjour != null;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ParametresLcbFt.java b/src/main/java/dev/lions/unionflow/server/entity/ParametresLcbFt.java
index 85bef93..3aa7968 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ParametresLcbFt.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ParametresLcbFt.java
@@ -1,36 +1,36 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.util.UUID;
-
-/**
- * Paramètres LCB-FT par organisation ou globaux (organisationId null).
- * Seuils au-dessus desquels l'origine des fonds est obligatoire / validation manuelle.
- */
-@Entity
-@Table(name = "parametres_lcb_ft", indexes = {
- @Index(name = "idx_param_lcb_ft_org", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ParametresLcbFt extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- @Column(name = "code_devise", nullable = false, length = 3)
- private String codeDevise;
-
- @Column(name = "montant_seuil_justification", nullable = false, precision = 18, scale = 4)
- private BigDecimal montantSeuilJustification;
-
- @Column(name = "montant_seuil_validation_manuelle", precision = 18, scale = 4)
- private BigDecimal montantSeuilValidationManuelle;
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+/**
+ * Paramètres LCB-FT par organisation ou globaux (organisationId null).
+ * Seuils au-dessus desquels l'origine des fonds est obligatoire / validation manuelle.
+ */
+@Entity
+@Table(name = "parametres_lcb_ft", indexes = {
+ @Index(name = "idx_param_lcb_ft_org", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ParametresLcbFt extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ @Column(name = "code_devise", nullable = false, length = 3)
+ private String codeDevise;
+
+ @Column(name = "montant_seuil_justification", nullable = false, precision = 18, scale = 4)
+ private BigDecimal montantSeuilJustification;
+
+ @Column(name = "montant_seuil_validation_manuelle", precision = 18, scale = 4)
+ private BigDecimal montantSeuilValidationManuelle;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Permission.java b/src/main/java/dev/lions/unionflow/server/entity/Permission.java
index f0ca6fc..0058951 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Permission.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Permission.java
@@ -1,92 +1,92 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Permission pour la gestion des permissions granulaires
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "permissions",
- indexes = {
- @Index(name = "idx_permission_code", columnList = "code", unique = true),
- @Index(name = "idx_permission_module", columnList = "module"),
- @Index(name = "idx_permission_ressource", columnList = "ressource")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Permission extends BaseEntity {
-
- /** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
- @NotBlank
- @Column(name = "code", unique = true, nullable = false, length = 100)
- private String code;
-
- /** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
- @NotBlank
- @Column(name = "module", nullable = false, length = 50)
- private String module;
-
- /** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
- @NotBlank
- @Column(name = "ressource", nullable = false, length = 50)
- private String ressource;
-
- /** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
- @NotBlank
- @Column(name = "action", nullable = false, length = 50)
- private String action;
-
- /** Libellé de la permission */
- @Column(name = "libelle", length = 200)
- private String libelle;
-
- /** Description de la permission */
- @Column(name = "description", length = 500)
- private String description;
-
- /** Rôles associés */
- @JsonIgnore
- @OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List roles = new ArrayList<>();
-
- /** Méthode métier pour générer le code à partir des composants */
- public static String genererCode(String module, String ressource, String action) {
- return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
- }
-
- /** Méthode métier pour vérifier si le code est valide */
- public boolean isCodeValide() {
- return code != null && code.contains(" > ") && code.split(" > ").length == 3;
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- // Générer le code si non fourni
- if (code == null || code.isEmpty()) {
- if (module != null && ressource != null && action != null) {
- code = genererCode(module, ressource, action);
- }
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Permission pour la gestion des permissions granulaires
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "permissions",
+ indexes = {
+ @Index(name = "idx_permission_code", columnList = "code", unique = true),
+ @Index(name = "idx_permission_module", columnList = "module"),
+ @Index(name = "idx_permission_ressource", columnList = "ressource")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Permission extends BaseEntity {
+
+ /** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
+ @NotBlank
+ @Column(name = "code", unique = true, nullable = false, length = 100)
+ private String code;
+
+ /** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
+ @NotBlank
+ @Column(name = "module", nullable = false, length = 50)
+ private String module;
+
+ /** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
+ @NotBlank
+ @Column(name = "ressource", nullable = false, length = 50)
+ private String ressource;
+
+ /** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
+ @NotBlank
+ @Column(name = "action", nullable = false, length = 50)
+ private String action;
+
+ /** Libellé de la permission */
+ @Column(name = "libelle", length = 200)
+ private String libelle;
+
+ /** Description de la permission */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Rôles associés */
+ @JsonIgnore
+ @OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List roles = new ArrayList<>();
+
+ /** Méthode métier pour générer le code à partir des composants */
+ public static String genererCode(String module, String ressource, String action) {
+ return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
+ }
+
+ /** Méthode métier pour vérifier si le code est valide */
+ public boolean isCodeValide() {
+ return code != null && code.contains(" > ") && code.split(" > ").length == 3;
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ // Générer le code si non fourni
+ if (code == null || code.isEmpty()) {
+ if (module != null && ressource != null && action != null) {
+ code = genererCode(module, ressource, action);
+ }
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java b/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java
index 8a46403..444ea37 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java
@@ -1,122 +1,122 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.Index;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.PrePersist;
-import jakarta.persistence.Table;
-import jakarta.validation.constraints.Min;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
-import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Association polymorphique entre un document et
- * une entité métier quelconque.
- *
- *
- * Remplace les 6 FK nullables mutuellement
- * exclusives (membre, organisation, cotisation,
- * adhesion, demandeAide, transactionWave) par un
- * couple {@code (type_entite_rattachee,
- * entite_rattachee_id)}.
- *
- *
- * Les types autorisés sont définis dans le
- * domaine {@code ENTITE_RATTACHEE} de la table
- * {@code types_reference} (ex: MEMBRE,
- * ORGANISATION, COTISATION, ADHESION, AIDE,
- * TRANSACTION_WAVE).
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2026-02-21
- */
-@Entity
-@Table(name = "pieces_jointes", indexes = {
- @Index(name = "idx_pj_document", columnList = "document_id"),
- @Index(name = "idx_pj_entite", columnList = "type_entite_rattachee,"
- + " entite_rattachee_id"),
- @Index(name = "idx_pj_type_entite", columnList = "type_entite_rattachee")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class PieceJointe extends BaseEntity {
-
- /** Ordre d'affichage. */
- @NotNull
- @Min(value = 1, message = "L'ordre doit être positif")
- @Column(name = "ordre", nullable = false)
- private Integer ordre;
-
- /** Libellé de la pièce jointe. */
- @Size(max = 200)
- @Column(name = "libelle", length = 200)
- private String libelle;
-
- /** Commentaire. */
- @Size(max = 500)
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-
- /** Document associé (obligatoire). */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "document_id", nullable = false)
- private Document document;
-
- /**
- * Type de l'entité rattachée (code du domaine
- * {@code ENTITE_RATTACHEE} dans
- * {@code types_reference}).
- *
- *
- * Valeurs attendues : {@code MEMBRE},
- * {@code ORGANISATION}, {@code COTISATION},
- * {@code ADHESION}, {@code AIDE},
- * {@code TRANSACTION_WAVE}.
- */
- @NotBlank
- @Size(max = 50)
- @Column(name = "type_entite_rattachee", nullable = false, length = 50)
- private String typeEntiteRattachee;
-
- /**
- * UUID de l'entité rattachée (membre,
- * organisation, cotisation, etc.).
- */
- @NotNull
- @Column(name = "entite_rattachee_id", nullable = false)
- private UUID entiteRattacheeId;
-
- /**
- * Callback JPA avant la persistance.
- *
- *
- * Initialise {@code ordre} à 1 si non
- * renseigné. Normalise le type en majuscules.
- */
- @Override
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (ordre == null) {
- ordre = 1;
- }
- if (typeEntiteRattachee != null) {
- typeEntiteRattachee = typeEntiteRattachee.toUpperCase();
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Index;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Association polymorphique entre un document et
+ * une entité métier quelconque.
+ *
+ *
+ * Remplace les 6 FK nullables mutuellement
+ * exclusives (membre, organisation, cotisation,
+ * adhesion, demandeAide, transactionWave) par un
+ * couple {@code (type_entite_rattachee,
+ * entite_rattachee_id)}.
+ *
+ *
+ * Les types autorisés sont définis dans le
+ * domaine {@code ENTITE_RATTACHEE} de la table
+ * {@code types_reference} (ex: MEMBRE,
+ * ORGANISATION, COTISATION, ADHESION, AIDE,
+ * TRANSACTION_WAVE).
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2026-02-21
+ */
+@Entity
+@Table(name = "pieces_jointes", indexes = {
+ @Index(name = "idx_pj_document", columnList = "document_id"),
+ @Index(name = "idx_pj_entite", columnList = "type_entite_rattachee,"
+ + " entite_rattachee_id"),
+ @Index(name = "idx_pj_type_entite", columnList = "type_entite_rattachee")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class PieceJointe extends BaseEntity {
+
+ /** Ordre d'affichage. */
+ @NotNull
+ @Min(value = 1, message = "L'ordre doit être positif")
+ @Column(name = "ordre", nullable = false)
+ private Integer ordre;
+
+ /** Libellé de la pièce jointe. */
+ @Size(max = 200)
+ @Column(name = "libelle", length = 200)
+ private String libelle;
+
+ /** Commentaire. */
+ @Size(max = 500)
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+
+ /** Document associé (obligatoire). */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "document_id", nullable = false)
+ private Document document;
+
+ /**
+ * Type de l'entité rattachée (code du domaine
+ * {@code ENTITE_RATTACHEE} dans
+ * {@code types_reference}).
+ *
+ *
+ * Valeurs attendues : {@code MEMBRE},
+ * {@code ORGANISATION}, {@code COTISATION},
+ * {@code ADHESION}, {@code AIDE},
+ * {@code TRANSACTION_WAVE}.
+ */
+ @NotBlank
+ @Size(max = 50)
+ @Column(name = "type_entite_rattachee", nullable = false, length = 50)
+ private String typeEntiteRattachee;
+
+ /**
+ * UUID de l'entité rattachée (membre,
+ * organisation, cotisation, etc.).
+ */
+ @NotNull
+ @Column(name = "entite_rattachee_id", nullable = false)
+ private UUID entiteRattacheeId;
+
+ /**
+ * Callback JPA avant la persistance.
+ *
+ *
+ * Initialise {@code ordre} à 1 si non
+ * renseigné. Normalise le type en majuscules.
+ */
+ @Override
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (ordre == null) {
+ ordre = 1;
+ }
+ if (typeEntiteRattachee != null) {
+ typeEntiteRattachee = typeEntiteRattachee.toUpperCase();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Role.java b/src/main/java/dev/lions/unionflow/server/entity/Role.java
index 4b742ea..a33823d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Role.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Role.java
@@ -1,98 +1,98 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Role pour la gestion des rôles dans le système
- *
- * @author UnionFlow Team
- * @version 3.1
- * @since 2025-01-29
- */
-@Entity
-@Table(name = "roles", indexes = {
- @Index(name = "idx_role_code", columnList = "code", unique = true),
- @Index(name = "idx_role_actif", columnList = "actif"),
- @Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Role extends BaseEntity {
-
- /** Code unique du rôle */
- @NotBlank
- @Column(name = "code", unique = true, nullable = false, length = 50)
- private String code;
-
- /** Libellé du rôle */
- @NotBlank
- @Column(name = "libelle", nullable = false, length = 100)
- private String libelle;
-
- /** Description du rôle */
- @Column(name = "description", length = 500)
- private String description;
-
- /** Niveau hiérarchique (plus bas = plus prioritaire) */
- @NotNull
- @Builder.Default
- @Column(name = "niveau_hierarchique", nullable = false)
- private Integer niveauHierarchique = 100;
-
- /** Type de rôle (SYSTEME, ORGANISATION, PERSONNALISE) */
- @Column(name = "type_role", nullable = false, length = 50)
- private String typeRole;
-
- /** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */
- @Column(name = "categorie", length = 30)
- @Builder.Default
- private String categorie = "FONCTIONNEL";
-
- /** Organisation propriétaire (null pour rôles système) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- /** Permissions associées */
- @JsonIgnore
- @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List permissions = new ArrayList<>();
-
- /** Énumération des constantes de types de rôle */
- public enum TypeRole {
- SYSTEME,
- ORGANISATION,
- PERSONNALISE;
- }
-
- /** Méthode métier pour vérifier si c'est un rôle système */
- public boolean isRoleSysteme() {
- return TypeRole.SYSTEME.name().equals(typeRole);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (typeRole == null) {
- typeRole = TypeRole.PERSONNALISE.name();
- }
- if (niveauHierarchique == null) {
- niveauHierarchique = 100;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Role pour la gestion des rôles dans le système
+ *
+ * @author UnionFlow Team
+ * @version 3.1
+ * @since 2025-01-29
+ */
+@Entity
+@Table(name = "roles", indexes = {
+ @Index(name = "idx_role_code", columnList = "code", unique = true),
+ @Index(name = "idx_role_actif", columnList = "actif"),
+ @Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Role extends BaseEntity {
+
+ /** Code unique du rôle */
+ @NotBlank
+ @Column(name = "code", unique = true, nullable = false, length = 50)
+ private String code;
+
+ /** Libellé du rôle */
+ @NotBlank
+ @Column(name = "libelle", nullable = false, length = 100)
+ private String libelle;
+
+ /** Description du rôle */
+ @Column(name = "description", length = 500)
+ private String description;
+
+ /** Niveau hiérarchique (plus bas = plus prioritaire) */
+ @NotNull
+ @Builder.Default
+ @Column(name = "niveau_hierarchique", nullable = false)
+ private Integer niveauHierarchique = 100;
+
+ /** Type de rôle (SYSTEME, ORGANISATION, PERSONNALISE) */
+ @Column(name = "type_role", nullable = false, length = 50)
+ private String typeRole;
+
+ /** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */
+ @Column(name = "categorie", length = 30)
+ @Builder.Default
+ private String categorie = "FONCTIONNEL";
+
+ /** Organisation propriétaire (null pour rôles système) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /** Permissions associées */
+ @JsonIgnore
+ @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List permissions = new ArrayList<>();
+
+ /** Énumération des constantes de types de rôle */
+ public enum TypeRole {
+ SYSTEME,
+ ORGANISATION,
+ PERSONNALISE;
+ }
+
+ /** Méthode métier pour vérifier si c'est un rôle système */
+ public boolean isRoleSysteme() {
+ return TypeRole.SYSTEME.name().equals(typeRole);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (typeRole == null) {
+ typeRole = TypeRole.PERSONNALISE.name();
+ }
+ if (niveauHierarchique == null) {
+ niveauHierarchique = 100;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java b/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java
index 6f07add..ccabf49 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java
@@ -1,54 +1,54 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Table de liaison entre Role et Permission
- * Permet à un rôle d'avoir plusieurs permissions
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "roles_permissions",
- indexes = {
- @Index(name = "idx_role_permission_role", columnList = "role_id"),
- @Index(name = "idx_role_permission_permission", columnList = "permission_id")
- },
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_role_permission",
- columnNames = {"role_id", "permission_id"})
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class RolePermission extends BaseEntity {
-
- /** Rôle */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "role_id", nullable = false)
- private Role role;
-
- /** Permission */
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "permission_id", nullable = false)
- private Permission permission;
-
- /** Commentaire sur l'association */
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Table de liaison entre Role et Permission
+ * Permet à un rôle d'avoir plusieurs permissions
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "roles_permissions",
+ indexes = {
+ @Index(name = "idx_role_permission_role", columnList = "role_id"),
+ @Index(name = "idx_role_permission_permission", columnList = "permission_id")
+ },
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_role_permission",
+ columnNames = {"role_id", "permission_id"})
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class RolePermission extends BaseEntity {
+
+ /** Rôle */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "role_id", nullable = false)
+ private Role role;
+
+ /** Permission */
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "permission_id", nullable = false)
+ private Permission permission;
+
+ /** Commentaire sur l'association */
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/SouscriptionOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/SouscriptionOrganisation.java
index 5d4f4e8..ea3668a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/SouscriptionOrganisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/SouscriptionOrganisation.java
@@ -1,171 +1,171 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
-import dev.lions.unionflow.server.api.enums.abonnement.StatutSouscription;
-import dev.lions.unionflow.server.api.enums.abonnement.StatutValidationSouscription;
-import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement;
-import dev.lions.unionflow.server.api.enums.abonnement.TypeOrganisationFacturation;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.UUID;
-import lombok.*;
-
-/**
- * Abonnement actif d'une organisation racine à un forfait UnionFlow.
- *
- * Règle clé : quand {@code quotaUtilise >= quotaMax}, toute nouvelle
- * validation d'adhésion est bloquée avec un message explicite.
- * Le manager peut upgrader son forfait à tout moment.
- *
- *
Table : {@code souscriptions_organisation}
- */
-@Entity
-@Table(
- name = "souscriptions_organisation",
- indexes = {
- @Index(name = "idx_souscription_org", columnList = "organisation_id", unique = true),
- @Index(name = "idx_souscription_statut", columnList = "statut"),
- @Index(name = "idx_souscription_fin", columnList = "date_fin")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class SouscriptionOrganisation extends BaseEntity {
-
- /** Organisation racine abonnée (une seule souscription active par org) */
- @NotNull
- @OneToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false, unique = true)
- private Organisation organisation;
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "formule_id", nullable = false)
- private FormuleAbonnement formule;
-
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "type_periode", nullable = false, length = 10)
- private TypePeriodeAbonnement typePeriode = TypePeriodeAbonnement.MENSUEL;
-
- @NotNull
- @Column(name = "date_debut", nullable = false)
- private LocalDate dateDebut;
-
- @NotNull
- @Column(name = "date_fin", nullable = false)
- private LocalDate dateFin;
-
- /** Snapshot du quota max au moment de la souscription */
- @Column(name = "quota_max")
- private Integer quotaMax;
-
- /** Compteur incrémenté à chaque adhésion validée */
- @Builder.Default
- @Min(0)
- @Column(name = "quota_utilise", nullable = false)
- private Integer quotaUtilise = 0;
-
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut", nullable = false, length = 30)
- private StatutSouscription statut = StatutSouscription.ACTIVE;
-
- @Column(name = "reference_paiement_wave", length = 100)
- private String referencePaiementWave;
-
- @Column(name = "wave_session_id", length = 255)
- private String waveSessionId;
-
- @Column(name = "wave_checkout_url", length = 1024)
- private String waveCheckoutUrl;
-
- @Column(name = "date_dernier_paiement")
- private LocalDate dateDernierPaiement;
-
- @Column(name = "date_prochain_paiement")
- private LocalDate dateProchainePaiement;
-
- // ── Champs workflow de validation (onboarding) ────────────────────────────
-
- /** Plage de membres choisie lors de la souscription. */
- @Enumerated(EnumType.STRING)
- @Column(name = "plage", length = 20)
- private PlageMembres plage;
-
- /** Type d'organisation déclaré, utilisé pour le coefficient tarifaire. */
- @Enumerated(EnumType.STRING)
- @Column(name = "type_organisation", length = 30)
- private TypeOrganisationFacturation typeOrganisationSouscription;
-
- /** Coefficient multiplicateur effectivement appliqué (org × période). */
- @Column(name = "coefficient_applique", precision = 4, scale = 2)
- private BigDecimal coefficientApplique;
-
- /** État du workflow de validation SuperAdmin. */
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut_validation", nullable = false, length = 40)
- private StatutValidationSouscription statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
-
- /** Montant total facturé pour la période choisie (en XOF). */
- @Column(name = "montant_total", precision = 12, scale = 2)
- private BigDecimal montantTotal;
-
- /** Date à laquelle le SuperAdmin a approuvé ou rejeté la souscription. */
- @Column(name = "date_validation")
- private LocalDate dateValidation;
-
- /** UUID du SuperAdmin ayant validé ou rejeté. */
- @Column(name = "validated_by_id")
- private UUID validatedById;
-
- /** Motif de rejet renseigné par le SuperAdmin. */
- @Column(name = "commentaire_rejet", length = 500)
- private String commentaireRejet;
-
- /** Mot de passe temporaire généré à l'activation du compte. */
- @Column(name = "mot_de_passe_temporaire", length = 100)
- private String motDePasseTemporaire;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean isActive() {
- return StatutSouscription.ACTIVE.equals(statut)
- && LocalDate.now().isBefore(dateFin.plusDays(1));
- }
-
- public boolean isQuotaDepasse() {
- return quotaMax != null && quotaUtilise >= quotaMax;
- }
-
- public int getPlacesRestantes() {
- if (quotaMax == null) return Integer.MAX_VALUE;
- return Math.max(0, quotaMax - quotaUtilise);
- }
-
- /** Incrémente le quota lors de la validation d'une adhésion */
- public void incrementerQuota() {
- if (quotaUtilise == null) quotaUtilise = 0;
- quotaUtilise++;
- }
-
- /** Décrémente le quota lors de la radiation d'un membre */
- public void decrementerQuota() {
- if (quotaUtilise != null && quotaUtilise > 0) quotaUtilise--;
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statut == null) statut = StatutSouscription.ACTIVE;
- if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL;
- if (quotaUtilise == null) quotaUtilise = 0;
- if (statutValidation == null) statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
- if (formule != null && quotaMax == null) quotaMax = formule.getMaxMembres();
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
+import dev.lions.unionflow.server.api.enums.abonnement.StatutSouscription;
+import dev.lions.unionflow.server.api.enums.abonnement.StatutValidationSouscription;
+import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement;
+import dev.lions.unionflow.server.api.enums.abonnement.TypeOrganisationFacturation;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.UUID;
+import lombok.*;
+
+/**
+ * Abonnement actif d'une organisation racine à un forfait UnionFlow.
+ *
+ *
Règle clé : quand {@code quotaUtilise >= quotaMax}, toute nouvelle
+ * validation d'adhésion est bloquée avec un message explicite.
+ * Le manager peut upgrader son forfait à tout moment.
+ *
+ *
Table : {@code souscriptions_organisation}
+ */
+@Entity
+@Table(
+ name = "souscriptions_organisation",
+ indexes = {
+ @Index(name = "idx_souscription_org", columnList = "organisation_id", unique = true),
+ @Index(name = "idx_souscription_statut", columnList = "statut"),
+ @Index(name = "idx_souscription_fin", columnList = "date_fin")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class SouscriptionOrganisation extends BaseEntity {
+
+ /** Organisation racine abonnée (une seule souscription active par org) */
+ @NotNull
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false, unique = true)
+ private Organisation organisation;
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "formule_id", nullable = false)
+ private FormuleAbonnement formule;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "type_periode", nullable = false, length = 10)
+ private TypePeriodeAbonnement typePeriode = TypePeriodeAbonnement.MENSUEL;
+
+ @NotNull
+ @Column(name = "date_debut", nullable = false)
+ private LocalDate dateDebut;
+
+ @NotNull
+ @Column(name = "date_fin", nullable = false)
+ private LocalDate dateFin;
+
+ /** Snapshot du quota max au moment de la souscription */
+ @Column(name = "quota_max")
+ private Integer quotaMax;
+
+ /** Compteur incrémenté à chaque adhésion validée */
+ @Builder.Default
+ @Min(0)
+ @Column(name = "quota_utilise", nullable = false)
+ private Integer quotaUtilise = 0;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut", nullable = false, length = 30)
+ private StatutSouscription statut = StatutSouscription.ACTIVE;
+
+ @Column(name = "reference_paiement_wave", length = 100)
+ private String referencePaiementWave;
+
+ @Column(name = "wave_session_id", length = 255)
+ private String waveSessionId;
+
+ @Column(name = "wave_checkout_url", length = 1024)
+ private String waveCheckoutUrl;
+
+ @Column(name = "date_dernier_paiement")
+ private LocalDate dateDernierPaiement;
+
+ @Column(name = "date_prochain_paiement")
+ private LocalDate dateProchainePaiement;
+
+ // ── Champs workflow de validation (onboarding) ────────────────────────────
+
+ /** Plage de membres choisie lors de la souscription. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "plage", length = 20)
+ private PlageMembres plage;
+
+ /** Type d'organisation déclaré, utilisé pour le coefficient tarifaire. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_organisation", length = 30)
+ private TypeOrganisationFacturation typeOrganisationSouscription;
+
+ /** Coefficient multiplicateur effectivement appliqué (org × période). */
+ @Column(name = "coefficient_applique", precision = 4, scale = 2)
+ private BigDecimal coefficientApplique;
+
+ /** État du workflow de validation SuperAdmin. */
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut_validation", nullable = false, length = 40)
+ private StatutValidationSouscription statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
+
+ /** Montant total facturé pour la période choisie (en XOF). */
+ @Column(name = "montant_total", precision = 12, scale = 2)
+ private BigDecimal montantTotal;
+
+ /** Date à laquelle le SuperAdmin a approuvé ou rejeté la souscription. */
+ @Column(name = "date_validation")
+ private LocalDate dateValidation;
+
+ /** UUID du SuperAdmin ayant validé ou rejeté. */
+ @Column(name = "validated_by_id")
+ private UUID validatedById;
+
+ /** Motif de rejet renseigné par le SuperAdmin. */
+ @Column(name = "commentaire_rejet", length = 500)
+ private String commentaireRejet;
+
+ /** Mot de passe temporaire généré à l'activation du compte. */
+ @Column(name = "mot_de_passe_temporaire", length = 100)
+ private String motDePasseTemporaire;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean isActive() {
+ return StatutSouscription.ACTIVE.equals(statut)
+ && LocalDate.now().isBefore(dateFin.plusDays(1));
+ }
+
+ public boolean isQuotaDepasse() {
+ return quotaMax != null && quotaUtilise >= quotaMax;
+ }
+
+ public int getPlacesRestantes() {
+ if (quotaMax == null) return Integer.MAX_VALUE;
+ return Math.max(0, quotaMax - quotaUtilise);
+ }
+
+ /** Incrémente le quota lors de la validation d'une adhésion */
+ public void incrementerQuota() {
+ if (quotaUtilise == null) quotaUtilise = 0;
+ quotaUtilise++;
+ }
+
+ /** Décrémente le quota lors de la radiation d'un membre */
+ public void decrementerQuota() {
+ if (quotaUtilise != null && quotaUtilise > 0) quotaUtilise--;
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statut == null) statut = StatutSouscription.ACTIVE;
+ if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL;
+ if (quotaUtilise == null) quotaUtilise = 0;
+ if (statutValidation == null) statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
+ if (formule != null && quotaMax == null) quotaMax = formule.getMaxMembres();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Suggestion.java b/src/main/java/dev/lions/unionflow/server/entity/Suggestion.java
index e288380..66fe390 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Suggestion.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Suggestion.java
@@ -1,91 +1,91 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-/**
- * Entité Suggestion pour la gestion des suggestions utilisateur
- *
- * @author UnionFlow Team
- * @version 1.0
- */
-@Entity
-@Table(
- name = "suggestions",
- indexes = {
- @Index(name = "idx_suggestion_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_suggestion_statut", columnList = "statut"),
- @Index(name = "idx_suggestion_categorie", columnList = "categorie")
- }
-)
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Suggestion extends BaseEntity {
-
- @NotNull
- @Column(name = "utilisateur_id", nullable = false)
- private UUID utilisateurId;
-
- @Column(name = "utilisateur_nom", length = 255)
- private String utilisateurNom;
-
- @NotBlank
- @Column(name = "titre", nullable = false, length = 255)
- private String titre;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String description;
-
- @Column(name = "justification", columnDefinition = "TEXT")
- private String justification;
-
- @Column(name = "categorie", length = 50)
- private String categorie; // UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING
-
- @Column(name = "priorite_estimee", length = 50)
- private String prioriteEstimee; // BASSE, MOYENNE, HAUTE, CRITIQUE
-
- @Column(name = "statut", length = 50)
- @Builder.Default
- private String statut = "NOUVELLE"; // NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE
-
- @Column(name = "nb_votes")
- @Builder.Default
- private Integer nbVotes = 0;
-
- @Column(name = "nb_commentaires")
- @Builder.Default
- private Integer nbCommentaires = 0;
-
- @Column(name = "nb_vues")
- @Builder.Default
- private Integer nbVues = 0;
-
- @Column(name = "date_soumission")
- private LocalDateTime dateSoumission;
-
- @Column(name = "date_evaluation")
- private LocalDateTime dateEvaluation;
-
- @Column(name = "date_implementation")
- private LocalDateTime dateImplementation;
-
- @Column(name = "version_ciblee", length = 50)
- private String versionCiblee;
-
- @Column(name = "mise_a_jour", columnDefinition = "TEXT")
- private String miseAJour;
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Entité Suggestion pour la gestion des suggestions utilisateur
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ */
+@Entity
+@Table(
+ name = "suggestions",
+ indexes = {
+ @Index(name = "idx_suggestion_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_suggestion_statut", columnList = "statut"),
+ @Index(name = "idx_suggestion_categorie", columnList = "categorie")
+ }
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Suggestion extends BaseEntity {
+
+ @NotNull
+ @Column(name = "utilisateur_id", nullable = false)
+ private UUID utilisateurId;
+
+ @Column(name = "utilisateur_nom", length = 255)
+ private String utilisateurNom;
+
+ @NotBlank
+ @Column(name = "titre", nullable = false, length = 255)
+ private String titre;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String description;
+
+ @Column(name = "justification", columnDefinition = "TEXT")
+ private String justification;
+
+ @Column(name = "categorie", length = 50)
+ private String categorie; // UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING
+
+ @Column(name = "priorite_estimee", length = 50)
+ private String prioriteEstimee; // BASSE, MOYENNE, HAUTE, CRITIQUE
+
+ @Column(name = "statut", length = 50)
+ @Builder.Default
+ private String statut = "NOUVELLE"; // NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE
+
+ @Column(name = "nb_votes")
+ @Builder.Default
+ private Integer nbVotes = 0;
+
+ @Column(name = "nb_commentaires")
+ @Builder.Default
+ private Integer nbCommentaires = 0;
+
+ @Column(name = "nb_vues")
+ @Builder.Default
+ private Integer nbVues = 0;
+
+ @Column(name = "date_soumission")
+ private LocalDateTime dateSoumission;
+
+ @Column(name = "date_evaluation")
+ private LocalDateTime dateEvaluation;
+
+ @Column(name = "date_implementation")
+ private LocalDateTime dateImplementation;
+
+ @Column(name = "version_ciblee", length = 50)
+ private String versionCiblee;
+
+ @Column(name = "mise_a_jour", columnDefinition = "TEXT")
+ private String miseAJour;
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/SuggestionVote.java b/src/main/java/dev/lions/unionflow/server/entity/SuggestionVote.java
index cfad461..110202d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/SuggestionVote.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/SuggestionVote.java
@@ -1,66 +1,66 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-/**
- * Entité SuggestionVote pour gérer les votes sur les suggestions
- *
- *
Permet d'éviter qu'un utilisateur vote plusieurs fois pour la même suggestion.
- * La contrainte d'unicité (suggestion_id, utilisateur_id) est gérée au niveau de la base de données.
- *
- * @author UnionFlow Team
- * @version 1.0
- */
-@Entity
-@Table(
- name = "suggestion_votes",
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_suggestion_vote",
- columnNames = {"suggestion_id", "utilisateur_id"}
- )
- },
- indexes = {
- @Index(name = "idx_vote_suggestion", columnList = "suggestion_id"),
- @Index(name = "idx_vote_utilisateur", columnList = "utilisateur_id")
- }
-)
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class SuggestionVote extends BaseEntity {
-
- @NotNull
- @Column(name = "suggestion_id", nullable = false)
- private UUID suggestionId;
-
- @NotNull
- @Column(name = "utilisateur_id", nullable = false)
- private UUID utilisateurId;
-
- @Column(name = "date_vote", nullable = false)
- @Builder.Default
- private LocalDateTime dateVote = LocalDateTime.now();
-
- @PrePersist
- protected void onPrePersist() {
- if (dateVote == null) {
- dateVote = LocalDateTime.now();
- }
- if (getDateCreation() == null) {
- setDateCreation(LocalDateTime.now());
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Entité SuggestionVote pour gérer les votes sur les suggestions
+ *
+ *
Permet d'éviter qu'un utilisateur vote plusieurs fois pour la même suggestion.
+ * La contrainte d'unicité (suggestion_id, utilisateur_id) est gérée au niveau de la base de données.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ */
+@Entity
+@Table(
+ name = "suggestion_votes",
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_suggestion_vote",
+ columnNames = {"suggestion_id", "utilisateur_id"}
+ )
+ },
+ indexes = {
+ @Index(name = "idx_vote_suggestion", columnList = "suggestion_id"),
+ @Index(name = "idx_vote_utilisateur", columnList = "utilisateur_id")
+ }
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class SuggestionVote extends BaseEntity {
+
+ @NotNull
+ @Column(name = "suggestion_id", nullable = false)
+ private UUID suggestionId;
+
+ @NotNull
+ @Column(name = "utilisateur_id", nullable = false)
+ private UUID utilisateurId;
+
+ @Column(name = "date_vote", nullable = false)
+ @Builder.Default
+ private LocalDateTime dateVote = LocalDateTime.now();
+
+ @PrePersist
+ protected void onPrePersist() {
+ if (dateVote == null) {
+ dateVote = LocalDateTime.now();
+ }
+ if (getDateCreation() == null) {
+ setDateCreation(LocalDateTime.now());
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/SystemAlert.java b/src/main/java/dev/lions/unionflow/server/entity/SystemAlert.java
index 5c60321..672aa4d 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/SystemAlert.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/SystemAlert.java
@@ -1,119 +1,119 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import lombok.Getter;
-import lombok.Setter;
-
-import java.time.LocalDateTime;
-
-/**
- * Entité pour les alertes système.
- * Enregistre les alertes de seuils dépassés, erreurs critiques, etc.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@Entity
-@Table(name = "system_alerts", indexes = {
- @Index(name = "idx_system_alert_timestamp", columnList = "timestamp"),
- @Index(name = "idx_system_alert_level", columnList = "level"),
- @Index(name = "idx_system_alert_acknowledged", columnList = "acknowledged"),
- @Index(name = "idx_system_alert_source", columnList = "source")
-})
-@Getter
-@Setter
-public class SystemAlert extends BaseEntity {
-
- /**
- * Niveau de l'alerte (CRITICAL, ERROR, WARNING, INFO)
- */
- @Column(name = "level", nullable = false, length = 20)
- private String level;
-
- /**
- * Titre court de l'alerte
- */
- @Column(name = "title", nullable = false, length = 255)
- private String title;
-
- /**
- * Message détaillé de l'alerte
- */
- @Column(name = "message", nullable = false, length = 1000)
- private String message;
-
- /**
- * Date/heure de création de l'alerte
- */
- @Column(name = "timestamp", nullable = false)
- private LocalDateTime timestamp;
-
- /**
- * Alerte acquittée ou non
- */
- @Column(name = "acknowledged", nullable = false)
- private Boolean acknowledged = false;
-
- /**
- * Email de l'utilisateur ayant acquitté l'alerte
- */
- @Column(name = "acknowledged_by", length = 255)
- private String acknowledgedBy;
-
- /**
- * Date/heure d'acquittement
- */
- @Column(name = "acknowledged_at")
- private LocalDateTime acknowledgedAt;
-
- /**
- * Source de l'alerte (CPU, MEMORY, DISK, DATABASE, etc.)
- */
- @Column(name = "source", length = 100)
- private String source;
-
- /**
- * Type d'alerte (THRESHOLD, INFO, ERROR, etc.)
- */
- @Column(name = "alert_type", length = 50)
- private String alertType;
-
- /**
- * Valeur actuelle ayant déclenché l'alerte
- */
- @Column(name = "current_value")
- private Double currentValue;
-
- /**
- * Valeur seuil dépassée
- */
- @Column(name = "threshold_value")
- private Double thresholdValue;
-
- /**
- * Unité de mesure (%, MB, GB, ms, etc.)
- */
- @Column(name = "unit", length = 20)
- private String unit;
-
- /**
- * Actions recommandées pour résoudre l'alerte
- */
- @Column(name = "recommended_actions", columnDefinition = "TEXT")
- private String recommendedActions;
-
- /**
- * Initialisation automatique du timestamp
- */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (timestamp == null) {
- timestamp = LocalDateTime.now();
- }
- if (acknowledged == null) {
- acknowledged = false;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+/**
+ * Entité pour les alertes système.
+ * Enregistre les alertes de seuils dépassés, erreurs critiques, etc.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@Entity
+@Table(name = "system_alerts", indexes = {
+ @Index(name = "idx_system_alert_timestamp", columnList = "timestamp"),
+ @Index(name = "idx_system_alert_level", columnList = "level"),
+ @Index(name = "idx_system_alert_acknowledged", columnList = "acknowledged"),
+ @Index(name = "idx_system_alert_source", columnList = "source")
+})
+@Getter
+@Setter
+public class SystemAlert extends BaseEntity {
+
+ /**
+ * Niveau de l'alerte (CRITICAL, ERROR, WARNING, INFO)
+ */
+ @Column(name = "level", nullable = false, length = 20)
+ private String level;
+
+ /**
+ * Titre court de l'alerte
+ */
+ @Column(name = "title", nullable = false, length = 255)
+ private String title;
+
+ /**
+ * Message détaillé de l'alerte
+ */
+ @Column(name = "message", nullable = false, length = 1000)
+ private String message;
+
+ /**
+ * Date/heure de création de l'alerte
+ */
+ @Column(name = "timestamp", nullable = false)
+ private LocalDateTime timestamp;
+
+ /**
+ * Alerte acquittée ou non
+ */
+ @Column(name = "acknowledged", nullable = false)
+ private Boolean acknowledged = false;
+
+ /**
+ * Email de l'utilisateur ayant acquitté l'alerte
+ */
+ @Column(name = "acknowledged_by", length = 255)
+ private String acknowledgedBy;
+
+ /**
+ * Date/heure d'acquittement
+ */
+ @Column(name = "acknowledged_at")
+ private LocalDateTime acknowledgedAt;
+
+ /**
+ * Source de l'alerte (CPU, MEMORY, DISK, DATABASE, etc.)
+ */
+ @Column(name = "source", length = 100)
+ private String source;
+
+ /**
+ * Type d'alerte (THRESHOLD, INFO, ERROR, etc.)
+ */
+ @Column(name = "alert_type", length = 50)
+ private String alertType;
+
+ /**
+ * Valeur actuelle ayant déclenché l'alerte
+ */
+ @Column(name = "current_value")
+ private Double currentValue;
+
+ /**
+ * Valeur seuil dépassée
+ */
+ @Column(name = "threshold_value")
+ private Double thresholdValue;
+
+ /**
+ * Unité de mesure (%, MB, GB, ms, etc.)
+ */
+ @Column(name = "unit", length = 20)
+ private String unit;
+
+ /**
+ * Actions recommandées pour résoudre l'alerte
+ */
+ @Column(name = "recommended_actions", columnDefinition = "TEXT")
+ private String recommendedActions;
+
+ /**
+ * Initialisation automatique du timestamp
+ */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (timestamp == null) {
+ timestamp = LocalDateTime.now();
+ }
+ if (acknowledged == null) {
+ acknowledged = false;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/SystemLog.java b/src/main/java/dev/lions/unionflow/server/entity/SystemLog.java
index 9dbe3bb..0c5d385 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/SystemLog.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/SystemLog.java
@@ -1,98 +1,98 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import lombok.Getter;
-import lombok.Setter;
-
-import java.time.LocalDateTime;
-
-/**
- * Entité pour les logs techniques du système.
- * Enregistre les erreurs, warnings, et événements système.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@Entity
-@Table(name = "system_logs", indexes = {
- @Index(name = "idx_system_log_timestamp", columnList = "timestamp"),
- @Index(name = "idx_system_log_level", columnList = "level"),
- @Index(name = "idx_system_log_source", columnList = "source"),
- @Index(name = "idx_system_log_user_id", columnList = "user_id")
-})
-@Getter
-@Setter
-public class SystemLog extends BaseEntity {
-
- /**
- * Niveau du log (CRITICAL, ERROR, WARNING, INFO, DEBUG)
- */
- @Column(name = "level", nullable = false, length = 20)
- private String level;
-
- /**
- * Source du log (Database, API, Auth, System, Cache, etc.)
- */
- @Column(name = "source", nullable = false, length = 100)
- private String source;
-
- /**
- * Message principal du log
- */
- @Column(name = "message", nullable = false, length = 1000)
- private String message;
-
- /**
- * Détails supplémentaires (stacktrace, contexte, etc.)
- */
- @Column(name = "details", columnDefinition = "TEXT")
- private String details;
-
- /**
- * Date/heure du log
- */
- @Column(name = "timestamp", nullable = false)
- private LocalDateTime timestamp;
-
- /**
- * Identifiant de l'utilisateur concerné (optionnel)
- */
- @Column(name = "user_id", length = 255)
- private String userId;
-
- /**
- * Adresse IP de la requête (optionnel)
- */
- @Column(name = "ip_address", length = 45)
- private String ipAddress;
-
- /**
- * Identifiant de session (optionnel)
- */
- @Column(name = "session_id", length = 255)
- private String sessionId;
-
- /**
- * Endpoint HTTP concerné (optionnel)
- */
- @Column(name = "endpoint", length = 500)
- private String endpoint;
-
- /**
- * Code de statut HTTP (optionnel)
- */
- @Column(name = "http_status_code")
- private Integer httpStatusCode;
-
- /**
- * Initialisation automatique du timestamp
- */
- @PrePersist
- protected void onCreate() {
- super.onCreate(); // Appel du @PrePersist de BaseEntity (dateCreation, actif)
- if (timestamp == null) {
- timestamp = LocalDateTime.now();
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+/**
+ * Entité pour les logs techniques du système.
+ * Enregistre les erreurs, warnings, et événements système.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@Entity
+@Table(name = "system_logs", indexes = {
+ @Index(name = "idx_system_log_timestamp", columnList = "timestamp"),
+ @Index(name = "idx_system_log_level", columnList = "level"),
+ @Index(name = "idx_system_log_source", columnList = "source"),
+ @Index(name = "idx_system_log_user_id", columnList = "user_id")
+})
+@Getter
+@Setter
+public class SystemLog extends BaseEntity {
+
+ /**
+ * Niveau du log (CRITICAL, ERROR, WARNING, INFO, DEBUG)
+ */
+ @Column(name = "level", nullable = false, length = 20)
+ private String level;
+
+ /**
+ * Source du log (Database, API, Auth, System, Cache, etc.)
+ */
+ @Column(name = "source", nullable = false, length = 100)
+ private String source;
+
+ /**
+ * Message principal du log
+ */
+ @Column(name = "message", nullable = false, length = 1000)
+ private String message;
+
+ /**
+ * Détails supplémentaires (stacktrace, contexte, etc.)
+ */
+ @Column(name = "details", columnDefinition = "TEXT")
+ private String details;
+
+ /**
+ * Date/heure du log
+ */
+ @Column(name = "timestamp", nullable = false)
+ private LocalDateTime timestamp;
+
+ /**
+ * Identifiant de l'utilisateur concerné (optionnel)
+ */
+ @Column(name = "user_id", length = 255)
+ private String userId;
+
+ /**
+ * Adresse IP de la requête (optionnel)
+ */
+ @Column(name = "ip_address", length = 45)
+ private String ipAddress;
+
+ /**
+ * Identifiant de session (optionnel)
+ */
+ @Column(name = "session_id", length = 255)
+ private String sessionId;
+
+ /**
+ * Endpoint HTTP concerné (optionnel)
+ */
+ @Column(name = "endpoint", length = 500)
+ private String endpoint;
+
+ /**
+ * Code de statut HTTP (optionnel)
+ */
+ @Column(name = "http_status_code")
+ private Integer httpStatusCode;
+
+ /**
+ * Initialisation automatique du timestamp
+ */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate(); // Appel du @PrePersist de BaseEntity (dateCreation, actif)
+ if (timestamp == null) {
+ timestamp = LocalDateTime.now();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java b/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java
index 1323634..89e9ff4 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java
@@ -1,83 +1,83 @@
-package dev.lions.unionflow.server.entity;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité TemplateNotification pour les templates de notifications réutilisables
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "templates_notifications",
- indexes = {
- @Index(name = "idx_template_code", columnList = "code", unique = true),
- @Index(name = "idx_template_actif", columnList = "actif")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class TemplateNotification extends BaseEntity {
-
- /** Code unique du template */
- @NotBlank
- @Column(name = "code", unique = true, nullable = false, length = 100)
- private String code;
-
- /** Sujet du template */
- @Column(name = "sujet", length = 500)
- private String sujet;
-
- /** Corps du template (texte) */
- @Column(name = "corps_texte", columnDefinition = "TEXT")
- private String corpsTexte;
-
- /** Corps du template (HTML) */
- @Column(name = "corps_html", columnDefinition = "TEXT")
- private String corpsHtml;
-
- /** Variables disponibles (JSON) */
- @Column(name = "variables_disponibles", columnDefinition = "TEXT")
- private String variablesDisponibles;
-
- /** Canaux supportés (JSON array) */
- @Column(name = "canaux_supportes", length = 500)
- private String canauxSupportes;
-
- /** Langue du template */
- @Column(name = "langue", length = 10)
- private String langue;
-
- /** Description */
- @Column(name = "description", length = 1000)
- private String description;
-
- /** Notifications utilisant ce template */
- @JsonIgnore
- @OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List notifications = new ArrayList<>();
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (langue == null || langue.isEmpty()) {
- langue = "fr";
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité TemplateNotification pour les templates de notifications réutilisables
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "templates_notifications",
+ indexes = {
+ @Index(name = "idx_template_code", columnList = "code", unique = true),
+ @Index(name = "idx_template_actif", columnList = "actif")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class TemplateNotification extends BaseEntity {
+
+ /** Code unique du template */
+ @NotBlank
+ @Column(name = "code", unique = true, nullable = false, length = 100)
+ private String code;
+
+ /** Sujet du template */
+ @Column(name = "sujet", length = 500)
+ private String sujet;
+
+ /** Corps du template (texte) */
+ @Column(name = "corps_texte", columnDefinition = "TEXT")
+ private String corpsTexte;
+
+ /** Corps du template (HTML) */
+ @Column(name = "corps_html", columnDefinition = "TEXT")
+ private String corpsHtml;
+
+ /** Variables disponibles (JSON) */
+ @Column(name = "variables_disponibles", columnDefinition = "TEXT")
+ private String variablesDisponibles;
+
+ /** Canaux supportés (JSON array) */
+ @Column(name = "canaux_supportes", length = 500)
+ private String canauxSupportes;
+
+ /** Langue du template */
+ @Column(name = "langue", length = 10)
+ private String langue;
+
+ /** Description */
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ /** Notifications utilisant ce template */
+ @JsonIgnore
+ @OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List notifications = new ArrayList<>();
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (langue == null || langue.isEmpty()) {
+ langue = "fr";
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/Ticket.java b/src/main/java/dev/lions/unionflow/server/entity/Ticket.java
index 2b52d92..2a5fe64 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/Ticket.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/Ticket.java
@@ -1,92 +1,92 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-/**
- * Entité Ticket pour la gestion des tickets support
- *
- * @author UnionFlow Team
- * @version 1.0
- */
-@Entity
-@Table(
- name = "tickets",
- indexes = {
- @Index(name = "idx_ticket_utilisateur", columnList = "utilisateur_id"),
- @Index(name = "idx_ticket_statut", columnList = "statut"),
- @Index(name = "idx_ticket_categorie", columnList = "categorie"),
- @Index(name = "idx_ticket_numero", columnList = "numero_ticket", unique = true)
- }
-)
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Ticket extends BaseEntity {
-
- @NotBlank
- @Column(name = "numero_ticket", nullable = false, unique = true, length = 50)
- private String numeroTicket;
-
- @NotNull
- @Column(name = "utilisateur_id", nullable = false)
- private UUID utilisateurId;
-
- @NotBlank
- @Column(name = "sujet", nullable = false, length = 255)
- private String sujet;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String description;
-
- @Column(name = "categorie", length = 50)
- private String categorie; // TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE
-
- @Column(name = "priorite", length = 50)
- private String priorite; // BASSE, NORMALE, HAUTE, URGENTE
-
- @Column(name = "statut", length = 50)
- @Builder.Default
- private String statut = "OUVERT"; // OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME
-
- @Column(name = "agent_id")
- private UUID agentId;
-
- @Column(name = "agent_nom", length = 255)
- private String agentNom;
-
- @Column(name = "date_derniere_reponse")
- private LocalDateTime dateDerniereReponse;
-
- @Column(name = "date_resolution")
- private LocalDateTime dateResolution;
-
- @Column(name = "date_fermeture")
- private LocalDateTime dateFermeture;
-
- @Column(name = "nb_messages")
- @Builder.Default
- private Integer nbMessages = 0;
-
- @Column(name = "nb_fichiers")
- @Builder.Default
- private Integer nbFichiers = 0;
-
- @Column(name = "note_satisfaction")
- private Integer noteSatisfaction;
-
- @Column(name = "resolution", columnDefinition = "TEXT")
- private String resolution;
-}
-
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Entité Ticket pour la gestion des tickets support
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ */
+@Entity
+@Table(
+ name = "tickets",
+ indexes = {
+ @Index(name = "idx_ticket_utilisateur", columnList = "utilisateur_id"),
+ @Index(name = "idx_ticket_statut", columnList = "statut"),
+ @Index(name = "idx_ticket_categorie", columnList = "categorie"),
+ @Index(name = "idx_ticket_numero", columnList = "numero_ticket", unique = true)
+ }
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Ticket extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "numero_ticket", nullable = false, unique = true, length = 50)
+ private String numeroTicket;
+
+ @NotNull
+ @Column(name = "utilisateur_id", nullable = false)
+ private UUID utilisateurId;
+
+ @NotBlank
+ @Column(name = "sujet", nullable = false, length = 255)
+ private String sujet;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String description;
+
+ @Column(name = "categorie", length = 50)
+ private String categorie; // TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE
+
+ @Column(name = "priorite", length = 50)
+ private String priorite; // BASSE, NORMALE, HAUTE, URGENTE
+
+ @Column(name = "statut", length = 50)
+ @Builder.Default
+ private String statut = "OUVERT"; // OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME
+
+ @Column(name = "agent_id")
+ private UUID agentId;
+
+ @Column(name = "agent_nom", length = 255)
+ private String agentNom;
+
+ @Column(name = "date_derniere_reponse")
+ private LocalDateTime dateDerniereReponse;
+
+ @Column(name = "date_resolution")
+ private LocalDateTime dateResolution;
+
+ @Column(name = "date_fermeture")
+ private LocalDateTime dateFermeture;
+
+ @Column(name = "nb_messages")
+ @Builder.Default
+ private Integer nbMessages = 0;
+
+ @Column(name = "nb_fichiers")
+ @Builder.Default
+ private Integer nbFichiers = 0;
+
+ @Column(name = "note_satisfaction")
+ private Integer noteSatisfaction;
+
+ @Column(name = "resolution", columnDefinition = "TEXT")
+ private String resolution;
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/TransactionApproval.java b/src/main/java/dev/lions/unionflow/server/entity/TransactionApproval.java
index ce4ed4a..77650e8 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/TransactionApproval.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/TransactionApproval.java
@@ -1,183 +1,183 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité Approbation de Transaction
- *
- * Représente une approbation dans le workflow financier multi-niveaux.
- * Chaque transaction financière au-dessus d'un certain seuil nécessite une ou plusieurs approbations.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-13
- */
-@Entity
-@Table(name = "transaction_approvals", indexes = {
- @Index(name = "idx_approval_transaction", columnList = "transaction_id"),
- @Index(name = "idx_approval_status", columnList = "status"),
- @Index(name = "idx_approval_requester", columnList = "requester_id"),
- @Index(name = "idx_approval_organisation", columnList = "organisation_id"),
- @Index(name = "idx_approval_created", columnList = "created_at"),
- @Index(name = "idx_approval_level", columnList = "required_level")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class TransactionApproval extends BaseEntity {
-
- /** ID de la transaction financière à approuver */
- @NotNull
- @Column(name = "transaction_id", nullable = false)
- private UUID transactionId;
-
- /** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */
- @NotBlank
- @Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$")
- @Column(name = "transaction_type", nullable = false, length = 20)
- private String transactionType;
-
- /** Montant de la transaction */
- @NotNull
- @DecimalMin(value = "0.0", message = "Le montant doit être positif")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "amount", nullable = false, precision = 14, scale = 2)
- private BigDecimal amount;
-
- /** Code devise ISO 3 lettres */
- @NotBlank
- @Pattern(regexp = "^[A-Z]{3}$")
- @Builder.Default
- @Column(name = "currency", nullable = false, length = 3)
- private String currency = "XOF";
-
- /** ID du membre demandeur */
- @NotNull
- @Column(name = "requester_id", nullable = false)
- private UUID requesterId;
-
- /** Nom complet du demandeur (cache pour performance) */
- @NotBlank
- @Column(name = "requester_name", nullable = false, length = 200)
- private String requesterName;
-
- /** Organisation concernée (peut être null pour transactions globales) */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- /** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */
- @NotBlank
- @Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$")
- @Column(name = "required_level", nullable = false, length = 10)
- private String requiredLevel;
-
- /** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */
- @NotBlank
- @Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$")
- @Builder.Default
- @Column(name = "status", nullable = false, length = 20)
- private String status = "PENDING";
-
- /** Liste des actions d'approbateurs */
- @OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
- @Builder.Default
- private List approvers = new ArrayList<>();
-
- /** Raison du rejet (si status = REJECTED) */
- @Size(max = 1000)
- @Column(name = "rejection_reason", length = 1000)
- private String rejectionReason;
-
- /** Date de création de la demande d'approbation */
- @NotNull
- @Column(name = "created_at", nullable = false)
- private LocalDateTime createdAt;
-
- /** Date d'expiration (timeout) */
- @Column(name = "expires_at")
- private LocalDateTime expiresAt;
-
- /** Date de completion (approbation finale ou rejet) */
- @Column(name = "completed_at")
- private LocalDateTime completedAt;
-
- /** Métadonnées additionnelles (JSON) */
- @Column(name = "metadata", columnDefinition = "TEXT")
- private String metadata;
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (createdAt == null) {
- createdAt = LocalDateTime.now();
- }
- if (currency == null) {
- currency = "XOF";
- }
- if (status == null) {
- status = "PENDING";
- }
- // Expiration par défaut: 7 jours
- if (expiresAt == null) {
- expiresAt = createdAt.plusDays(7);
- }
- }
-
- /** Méthode métier pour ajouter une action d'approbateur */
- public void addApproverAction(ApproverAction action) {
- approvers.add(action);
- action.setApproval(this);
- }
-
- /** Méthode métier pour compter les approbations */
- public long countApprovals() {
- return approvers.stream()
- .filter(a -> "APPROVED".equals(a.getDecision()))
- .count();
- }
-
- /** Méthode métier pour obtenir le nombre d'approbations requises */
- public int getRequiredApprovals() {
- return switch (requiredLevel) {
- case "NONE" -> 0;
- case "LEVEL1" -> 1;
- case "LEVEL2" -> 2;
- case "LEVEL3" -> 3;
- default -> 0;
- };
- }
-
- /** Méthode métier pour vérifier si toutes les approbations sont reçues */
- public boolean hasAllApprovals() {
- return countApprovals() >= getRequiredApprovals();
- }
-
- /** Méthode métier pour vérifier si l'approbation est expirée */
- public boolean isExpired() {
- return expiresAt != null && LocalDateTime.now().isAfter(expiresAt);
- }
-
- /** Méthode métier pour vérifier si l'approbation est en attente */
- public boolean isPending() {
- return "PENDING".equals(status);
- }
-
- /** Méthode métier pour vérifier si l'approbation est complétée */
- public boolean isCompleted() {
- return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status);
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité Approbation de Transaction
+ *
+ * Représente une approbation dans le workflow financier multi-niveaux.
+ * Chaque transaction financière au-dessus d'un certain seuil nécessite une ou plusieurs approbations.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-13
+ */
+@Entity
+@Table(name = "transaction_approvals", indexes = {
+ @Index(name = "idx_approval_transaction", columnList = "transaction_id"),
+ @Index(name = "idx_approval_status", columnList = "status"),
+ @Index(name = "idx_approval_requester", columnList = "requester_id"),
+ @Index(name = "idx_approval_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_approval_created", columnList = "created_at"),
+ @Index(name = "idx_approval_level", columnList = "required_level")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class TransactionApproval extends BaseEntity {
+
+ /** ID de la transaction financière à approuver */
+ @NotNull
+ @Column(name = "transaction_id", nullable = false)
+ private UUID transactionId;
+
+ /** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */
+ @NotBlank
+ @Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$")
+ @Column(name = "transaction_type", nullable = false, length = 20)
+ private String transactionType;
+
+ /** Montant de la transaction */
+ @NotNull
+ @DecimalMin(value = "0.0", message = "Le montant doit être positif")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "amount", nullable = false, precision = 14, scale = 2)
+ private BigDecimal amount;
+
+ /** Code devise ISO 3 lettres */
+ @NotBlank
+ @Pattern(regexp = "^[A-Z]{3}$")
+ @Builder.Default
+ @Column(name = "currency", nullable = false, length = 3)
+ private String currency = "XOF";
+
+ /** ID du membre demandeur */
+ @NotNull
+ @Column(name = "requester_id", nullable = false)
+ private UUID requesterId;
+
+ /** Nom complet du demandeur (cache pour performance) */
+ @NotBlank
+ @Column(name = "requester_name", nullable = false, length = 200)
+ private String requesterName;
+
+ /** Organisation concernée (peut être null pour transactions globales) */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */
+ @NotBlank
+ @Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$")
+ @Column(name = "required_level", nullable = false, length = 10)
+ private String requiredLevel;
+
+ /** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */
+ @NotBlank
+ @Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$")
+ @Builder.Default
+ @Column(name = "status", nullable = false, length = 20)
+ private String status = "PENDING";
+
+ /** Liste des actions d'approbateurs */
+ @OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List approvers = new ArrayList<>();
+
+ /** Raison du rejet (si status = REJECTED) */
+ @Size(max = 1000)
+ @Column(name = "rejection_reason", length = 1000)
+ private String rejectionReason;
+
+ /** Date de création de la demande d'approbation */
+ @NotNull
+ @Column(name = "created_at", nullable = false)
+ private LocalDateTime createdAt;
+
+ /** Date d'expiration (timeout) */
+ @Column(name = "expires_at")
+ private LocalDateTime expiresAt;
+
+ /** Date de completion (approbation finale ou rejet) */
+ @Column(name = "completed_at")
+ private LocalDateTime completedAt;
+
+ /** Métadonnées additionnelles (JSON) */
+ @Column(name = "metadata", columnDefinition = "TEXT")
+ private String metadata;
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (createdAt == null) {
+ createdAt = LocalDateTime.now();
+ }
+ if (currency == null) {
+ currency = "XOF";
+ }
+ if (status == null) {
+ status = "PENDING";
+ }
+ // Expiration par défaut: 7 jours
+ if (expiresAt == null) {
+ expiresAt = createdAt.plusDays(7);
+ }
+ }
+
+ /** Méthode métier pour ajouter une action d'approbateur */
+ public void addApproverAction(ApproverAction action) {
+ approvers.add(action);
+ action.setApproval(this);
+ }
+
+ /** Méthode métier pour compter les approbations */
+ public long countApprovals() {
+ return approvers.stream()
+ .filter(a -> "APPROVED".equals(a.getDecision()))
+ .count();
+ }
+
+ /** Méthode métier pour obtenir le nombre d'approbations requises */
+ public int getRequiredApprovals() {
+ return switch (requiredLevel) {
+ case "NONE" -> 0;
+ case "LEVEL1" -> 1;
+ case "LEVEL2" -> 2;
+ case "LEVEL3" -> 3;
+ default -> 0;
+ };
+ }
+
+ /** Méthode métier pour vérifier si toutes les approbations sont reçues */
+ public boolean hasAllApprovals() {
+ return countApprovals() >= getRequiredApprovals();
+ }
+
+ /** Méthode métier pour vérifier si l'approbation est expirée */
+ public boolean isExpired() {
+ return expiresAt != null && LocalDateTime.now().isAfter(expiresAt);
+ }
+
+ /** Méthode métier pour vérifier si l'approbation est en attente */
+ public boolean isPending() {
+ return "PENDING".equals(status);
+ }
+
+ /** Méthode métier pour vérifier si l'approbation est complétée */
+ public boolean isCompleted() {
+ return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java b/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java
index 8d85b12..83a0d08 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java
@@ -1,164 +1,164 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
-import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité TransactionWave pour le suivi des transactions Wave Mobile Money
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(
- name = "transactions_wave",
- indexes = {
- @Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true),
- @Index(name = "idx_transaction_wave_request_id", columnList = "wave_request_id"),
- @Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"),
- @Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"),
- @Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class TransactionWave extends BaseEntity {
-
- /** Identifiant Wave de la transaction (unique) */
- @NotBlank
- @Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100)
- private String waveTransactionId;
-
- /** Identifiant de requête Wave */
- @Column(name = "wave_request_id", length = 100)
- private String waveRequestId;
-
- /** Référence Wave */
- @Column(name = "wave_reference", length = 100)
- private String waveReference;
-
- /** Type de transaction */
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_transaction", nullable = false, length = 50)
- private TypeTransactionWave typeTransaction;
-
- /** Statut de la transaction */
- @NotNull
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut_transaction", nullable = false, length = 30)
- private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE;
-
- /** Montant de la transaction */
- @NotNull
- @DecimalMin(value = "0.0", message = "Le montant doit être positif")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant", nullable = false, precision = 14, scale = 2)
- private BigDecimal montant;
-
- /** Frais de transaction */
- @DecimalMin(value = "0.0")
- @Digits(integer = 10, fraction = 2)
- @Column(name = "frais", precision = 12, scale = 2)
- private BigDecimal frais;
-
- /** Montant net (montant - frais) */
- @DecimalMin(value = "0.0")
- @Digits(integer = 12, fraction = 2)
- @Column(name = "montant_net", precision = 14, scale = 2)
- private BigDecimal montantNet;
-
- /** Code devise */
- @NotBlank
- @Pattern(regexp = "^[A-Z]{3}$")
- @Column(name = "code_devise", nullable = false, length = 3)
- private String codeDevise;
-
- /** Numéro téléphone payeur */
- @Column(name = "telephone_payeur", length = 13)
- private String telephonePayeur;
-
- /** Numéro téléphone bénéficiaire */
- @Column(name = "telephone_beneficiaire", length = 13)
- private String telephoneBeneficiaire;
-
- /** Métadonnées JSON (réponse complète de Wave API) */
- @Column(name = "metadonnees", columnDefinition = "TEXT")
- private String metadonnees;
-
- /** Réponse complète de Wave API (JSON) */
- @Column(name = "reponse_wave_api", columnDefinition = "TEXT")
- private String reponseWaveApi;
-
- /** Nombre de tentatives */
- @Builder.Default
- @Column(name = "nombre_tentatives", nullable = false)
- private Integer nombreTentatives = 0;
-
- /** Date de dernière tentative */
- @Column(name = "date_derniere_tentative")
- private LocalDateTime dateDerniereTentative;
-
- /** Message d'erreur (si échec) */
- @Column(name = "message_erreur", length = 1000)
- private String messageErreur;
-
- // Relations
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "compte_wave_id", nullable = false)
- private CompteWave compteWave;
-
- @JsonIgnore
-
- @OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @Builder.Default
- private List webhooks = new ArrayList<>();
-
- /** Méthode métier pour vérifier si la transaction est réussie */
- public boolean isReussie() {
- return StatutTransactionWave.REUSSIE.equals(statutTransaction);
- }
-
- /** Méthode métier pour vérifier si la transaction peut être retentée */
- public boolean peutEtreRetentee() {
- return (statutTransaction == StatutTransactionWave.ECHOUE
- || statutTransaction == StatutTransactionWave.EXPIRED)
- && (nombreTentatives == null || nombreTentatives < 5);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statutTransaction == null) {
- statutTransaction = StatutTransactionWave.INITIALISE;
- }
- if (codeDevise == null || codeDevise.isEmpty()) {
- codeDevise = "XOF";
- }
- if (nombreTentatives == null) {
- nombreTentatives = 0;
- }
- if (montantNet == null && montant != null && frais != null) {
- montantNet = montant.subtract(frais);
- }
- }
-}
-
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
+import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité TransactionWave pour le suivi des transactions Wave Mobile Money
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(
+ name = "transactions_wave",
+ indexes = {
+ @Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true),
+ @Index(name = "idx_transaction_wave_request_id", columnList = "wave_request_id"),
+ @Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"),
+ @Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"),
+ @Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class TransactionWave extends BaseEntity {
+
+ /** Identifiant Wave de la transaction (unique) */
+ @NotBlank
+ @Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100)
+ private String waveTransactionId;
+
+ /** Identifiant de requête Wave */
+ @Column(name = "wave_request_id", length = 100)
+ private String waveRequestId;
+
+ /** Référence Wave */
+ @Column(name = "wave_reference", length = 100)
+ private String waveReference;
+
+ /** Type de transaction */
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_transaction", nullable = false, length = 50)
+ private TypeTransactionWave typeTransaction;
+
+ /** Statut de la transaction */
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut_transaction", nullable = false, length = 30)
+ private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE;
+
+ /** Montant de la transaction */
+ @NotNull
+ @DecimalMin(value = "0.0", message = "Le montant doit être positif")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant", nullable = false, precision = 14, scale = 2)
+ private BigDecimal montant;
+
+ /** Frais de transaction */
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "frais", precision = 12, scale = 2)
+ private BigDecimal frais;
+
+ /** Montant net (montant - frais) */
+ @DecimalMin(value = "0.0")
+ @Digits(integer = 12, fraction = 2)
+ @Column(name = "montant_net", precision = 14, scale = 2)
+ private BigDecimal montantNet;
+
+ /** Code devise */
+ @NotBlank
+ @Pattern(regexp = "^[A-Z]{3}$")
+ @Column(name = "code_devise", nullable = false, length = 3)
+ private String codeDevise;
+
+ /** Numéro téléphone payeur */
+ @Column(name = "telephone_payeur", length = 13)
+ private String telephonePayeur;
+
+ /** Numéro téléphone bénéficiaire */
+ @Column(name = "telephone_beneficiaire", length = 13)
+ private String telephoneBeneficiaire;
+
+ /** Métadonnées JSON (réponse complète de Wave API) */
+ @Column(name = "metadonnees", columnDefinition = "TEXT")
+ private String metadonnees;
+
+ /** Réponse complète de Wave API (JSON) */
+ @Column(name = "reponse_wave_api", columnDefinition = "TEXT")
+ private String reponseWaveApi;
+
+ /** Nombre de tentatives */
+ @Builder.Default
+ @Column(name = "nombre_tentatives", nullable = false)
+ private Integer nombreTentatives = 0;
+
+ /** Date de dernière tentative */
+ @Column(name = "date_derniere_tentative")
+ private LocalDateTime dateDerniereTentative;
+
+ /** Message d'erreur (si échec) */
+ @Column(name = "message_erreur", length = 1000)
+ private String messageErreur;
+
+ // Relations
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "compte_wave_id", nullable = false)
+ private CompteWave compteWave;
+
+ @JsonIgnore
+
+ @OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ @Builder.Default
+ private List webhooks = new ArrayList<>();
+
+ /** Méthode métier pour vérifier si la transaction est réussie */
+ public boolean isReussie() {
+ return StatutTransactionWave.REUSSIE.equals(statutTransaction);
+ }
+
+ /** Méthode métier pour vérifier si la transaction peut être retentée */
+ public boolean peutEtreRetentee() {
+ return (statutTransaction == StatutTransactionWave.ECHOUE
+ || statutTransaction == StatutTransactionWave.EXPIRED)
+ && (nombreTentatives == null || nombreTentatives < 5);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statutTransaction == null) {
+ statutTransaction = StatutTransactionWave.INITIALISE;
+ }
+ if (codeDevise == null || codeDevise.isEmpty()) {
+ codeDevise = "XOF";
+ }
+ if (nombreTentatives == null) {
+ nombreTentatives = 0;
+ }
+ if (montantNet == null && montant != null && frais != null) {
+ montantNet = montant.subtract(frais);
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/unionflow/server/entity/TypeReference.java b/src/main/java/dev/lions/unionflow/server/entity/TypeReference.java
index 98fc8b6..9e64401 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/TypeReference.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/TypeReference.java
@@ -1,206 +1,206 @@
-package dev.lions.unionflow.server.entity;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.Index;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.PrePersist;
-import jakarta.persistence.Table;
-import jakarta.persistence.UniqueConstraint;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Donnée de référence paramétrable via le client.
- *
- *
- * Remplace toutes les enums Java et valeurs hardcodées
- * par une table unique CRUD-able depuis l'interface
- * d'administration. Chaque ligne appartient à un
- * {@code domaine} (ex: STATUT_ORGANISATION, DEVISE)
- * et porte un {@code code} unique dans ce domaine.
- *
- *
- * Le champ {@code organisation} permet une
- * personnalisation par organisation. Lorsqu'il est
- * {@code null}, la valeur est globale à la plateforme.
- *
- *
- * Table : {@code types_reference}
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2026-02-21
- */
-@Entity
-@Table(name = "types_reference", indexes = {
- @Index(name = "idx_typeref_domaine", columnList = "domaine"),
- @Index(name = "idx_typeref_domaine_actif", columnList = "domaine, actif, ordre_affichage"),
- @Index(name = "idx_typeref_org", columnList = "organisation_id")
-}, uniqueConstraints = {
- @UniqueConstraint(name = "uk_typeref_domaine_code_org", columnNames = {
- "domaine", "code", "organisation_id"
- })
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class TypeReference extends BaseEntity {
-
- /**
- * Domaine fonctionnel de cette valeur de référence.
- *
- *
- * Exemples : {@code STATUT_ORGANISATION},
- * {@code TYPE_ORGANISATION}, {@code DEVISE}.
- */
- @NotBlank
- @Size(max = 50)
- @Column(name = "domaine", nullable = false, length = 50)
- private String domaine;
-
- /**
- * Code technique unique au sein du domaine.
- *
- *
- * Exemples : {@code ACTIVE}, {@code XOF},
- * {@code ASSOCIATION}.
- */
- @NotBlank
- @Size(max = 50)
- @Column(name = "code", nullable = false, length = 50)
- private String code;
-
- /**
- * Libellé affiché dans l'interface utilisateur.
- *
- *
- * Exemple : {@code "Franc CFA (UEMOA)"}.
- */
- @NotBlank
- @Size(max = 200)
- @Column(name = "libelle", nullable = false, length = 200)
- private String libelle;
-
- /** Description longue optionnelle. */
- @Size(max = 1000)
- @Column(name = "description", length = 1000)
- private String description;
-
- /**
- * Classe d'icône pour le rendu UI.
- *
- *
- * Exemple : {@code "pi-check-circle"}.
- */
- @Size(max = 100)
- @Column(name = "icone", length = 100)
- private String icone;
-
- /**
- * Code couleur hexadécimal pour le rendu UI.
- *
- *
- * Exemple : {@code "#22C55E"}.
- */
- @Size(max = 50)
- @Column(name = "couleur", length = 50)
- private String couleur;
-
- /**
- * Niveau de sévérité pour les badges PrimeFaces.
- *
- *
- * Valeurs typiques : {@code success},
- * {@code warning}, {@code danger}, {@code info}.
- */
- @Size(max = 20)
- @Column(name = "severity", length = 20)
- private String severity;
-
- /**
- * Ordre d'affichage dans les listes déroulantes.
- *
- *
- * Les valeurs avec un ordre inférieur
- * apparaissent en premier.
- */
- @Builder.Default
- @Column(name = "ordre_affichage", nullable = false)
- private Integer ordreAffichage = 0;
-
- /**
- * Indique si cette valeur est la valeur par défaut
- * pour son domaine. Une seule valeur par défaut
- * est autorisée par domaine et organisation.
- */
- @Builder.Default
- @Column(name = "est_defaut", nullable = false)
- private Boolean estDefaut = false;
-
- /**
- * Indique si cette valeur est protégée par le
- * système. Les valeurs système ne peuvent être
- * ni supprimées ni désactivées par un
- * administrateur.
- */
- @Builder.Default
- @Column(name = "est_systeme", nullable = false)
- private Boolean estSysteme = false;
-
- /**
- * Catégorie fonctionnelle (ex: ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX…).
- * Utilisée pour les types d'organisation (domaine TYPE_ORGANISATION).
- */
- @Size(max = 50)
- @Column(name = "categorie", length = 50)
- private String categorie;
-
- /**
- * Liste CSV des modules activés pour ce type d'organisation.
- * Exemple : "MEMBRES,COTISATIONS,TONTINE,FINANCE"
- * Utilisée pour initialiser {@code Organisation.modulesActifs} à la création.
- */
- @Column(name = "modules_requis", columnDefinition = "TEXT")
- private String modulesRequis;
-
- /**
- * Organisation propriétaire de cette valeur.
- *
- *
- * Lorsque {@code null}, la valeur est globale
- * à la plateforme et visible par toutes les
- * organisations.
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id")
- private Organisation organisation;
-
- /**
- * Callback JPA exécuté avant la persistance.
- *
- *
- * Normalise le code et le domaine en
- * majuscules pour garantir la cohérence.
- */
- @Override
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (domaine != null) {
- domaine = domaine.toUpperCase();
- }
- if (code != null) {
- code = code.toUpperCase();
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Index;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Donnée de référence paramétrable via le client.
+ *
+ *
+ * Remplace toutes les enums Java et valeurs hardcodées
+ * par une table unique CRUD-able depuis l'interface
+ * d'administration. Chaque ligne appartient à un
+ * {@code domaine} (ex: STATUT_ORGANISATION, DEVISE)
+ * et porte un {@code code} unique dans ce domaine.
+ *
+ *
+ * Le champ {@code organisation} permet une
+ * personnalisation par organisation. Lorsqu'il est
+ * {@code null}, la valeur est globale à la plateforme.
+ *
+ *
+ * Table : {@code types_reference}
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2026-02-21
+ */
+@Entity
+@Table(name = "types_reference", indexes = {
+ @Index(name = "idx_typeref_domaine", columnList = "domaine"),
+ @Index(name = "idx_typeref_domaine_actif", columnList = "domaine, actif, ordre_affichage"),
+ @Index(name = "idx_typeref_org", columnList = "organisation_id")
+}, uniqueConstraints = {
+ @UniqueConstraint(name = "uk_typeref_domaine_code_org", columnNames = {
+ "domaine", "code", "organisation_id"
+ })
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class TypeReference extends BaseEntity {
+
+ /**
+ * Domaine fonctionnel de cette valeur de référence.
+ *
+ *
+ * Exemples : {@code STATUT_ORGANISATION},
+ * {@code TYPE_ORGANISATION}, {@code DEVISE}.
+ */
+ @NotBlank
+ @Size(max = 50)
+ @Column(name = "domaine", nullable = false, length = 50)
+ private String domaine;
+
+ /**
+ * Code technique unique au sein du domaine.
+ *
+ *
+ * Exemples : {@code ACTIVE}, {@code XOF},
+ * {@code ASSOCIATION}.
+ */
+ @NotBlank
+ @Size(max = 50)
+ @Column(name = "code", nullable = false, length = 50)
+ private String code;
+
+ /**
+ * Libellé affiché dans l'interface utilisateur.
+ *
+ *
+ * Exemple : {@code "Franc CFA (UEMOA)"}.
+ */
+ @NotBlank
+ @Size(max = 200)
+ @Column(name = "libelle", nullable = false, length = 200)
+ private String libelle;
+
+ /** Description longue optionnelle. */
+ @Size(max = 1000)
+ @Column(name = "description", length = 1000)
+ private String description;
+
+ /**
+ * Classe d'icône pour le rendu UI.
+ *
+ *
+ * Exemple : {@code "pi-check-circle"}.
+ */
+ @Size(max = 100)
+ @Column(name = "icone", length = 100)
+ private String icone;
+
+ /**
+ * Code couleur hexadécimal pour le rendu UI.
+ *
+ *
+ * Exemple : {@code "#22C55E"}.
+ */
+ @Size(max = 50)
+ @Column(name = "couleur", length = 50)
+ private String couleur;
+
+ /**
+ * Niveau de sévérité pour les badges PrimeFaces.
+ *
+ *
+ * Valeurs typiques : {@code success},
+ * {@code warning}, {@code danger}, {@code info}.
+ */
+ @Size(max = 20)
+ @Column(name = "severity", length = 20)
+ private String severity;
+
+ /**
+ * Ordre d'affichage dans les listes déroulantes.
+ *
+ *
+ * Les valeurs avec un ordre inférieur
+ * apparaissent en premier.
+ */
+ @Builder.Default
+ @Column(name = "ordre_affichage", nullable = false)
+ private Integer ordreAffichage = 0;
+
+ /**
+ * Indique si cette valeur est la valeur par défaut
+ * pour son domaine. Une seule valeur par défaut
+ * est autorisée par domaine et organisation.
+ */
+ @Builder.Default
+ @Column(name = "est_defaut", nullable = false)
+ private Boolean estDefaut = false;
+
+ /**
+ * Indique si cette valeur est protégée par le
+ * système. Les valeurs système ne peuvent être
+ * ni supprimées ni désactivées par un
+ * administrateur.
+ */
+ @Builder.Default
+ @Column(name = "est_systeme", nullable = false)
+ private Boolean estSysteme = false;
+
+ /**
+ * Catégorie fonctionnelle (ex: ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX…).
+ * Utilisée pour les types d'organisation (domaine TYPE_ORGANISATION).
+ */
+ @Size(max = 50)
+ @Column(name = "categorie", length = 50)
+ private String categorie;
+
+ /**
+ * Liste CSV des modules activés pour ce type d'organisation.
+ * Exemple : "MEMBRES,COTISATIONS,TONTINE,FINANCE"
+ * Utilisée pour initialiser {@code Organisation.modulesActifs} à la création.
+ */
+ @Column(name = "modules_requis", columnDefinition = "TEXT")
+ private String modulesRequis;
+
+ /**
+ * Organisation propriétaire de cette valeur.
+ *
+ *
+ * Lorsque {@code null}, la valeur est globale
+ * à la plateforme et visible par toutes les
+ * organisations.
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id")
+ private Organisation organisation;
+
+ /**
+ * Callback JPA exécuté avant la persistance.
+ *
+ *
+ * Normalise le code et le domaine en
+ * majuscules pour garantir la cohérence.
+ */
+ @Override
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (domaine != null) {
+ domaine = domaine.toUpperCase();
+ }
+ if (code != null) {
+ code = code.toUpperCase();
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ValidationEtapeDemande.java b/src/main/java/dev/lions/unionflow/server/entity/ValidationEtapeDemande.java
index 82f0c98..f905a9a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ValidationEtapeDemande.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ValidationEtapeDemande.java
@@ -1,91 +1,91 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import java.time.LocalDateTime;
-import lombok.*;
-
-/**
- * Historique des validations pour une demande d'aide.
- *
- *
Chaque ligne représente l'état d'une étape du workflow pour une demande.
- * La délégation de véto (valideur absent) est tracée avec motif — conformité BCEAO/OHADA.
- *
- *
Table : {@code validation_etapes_demande}
- */
-@Entity
-@Table(
- name = "validation_etapes_demande",
- indexes = {
- @Index(name = "idx_ved_demande", columnList = "demande_aide_id"),
- @Index(name = "idx_ved_valideur", columnList = "valideur_id"),
- @Index(name = "idx_ved_statut", columnList = "statut")
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ValidationEtapeDemande extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "demande_aide_id", nullable = false)
- private DemandeAide demandeAide;
-
- @NotNull
- @Min(1) @Max(3)
- @Column(name = "etape_numero", nullable = false)
- private Integer etapeNumero;
-
- /** Valideur assigné à cette étape */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "valideur_id")
- private Membre valideur;
-
- @Enumerated(EnumType.STRING)
- @Builder.Default
- @Column(name = "statut", nullable = false, length = 20)
- private StatutValidationEtape statut = StatutValidationEtape.EN_ATTENTE;
-
- @Column(name = "date_validation")
- private LocalDateTime dateValidation;
-
- @Column(name = "commentaire", length = 1000)
- private String commentaire;
-
- /**
- * Valideur supérieur qui a désactivé le véto de {@code valideur}.
- * Renseigné uniquement en cas de délégation.
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "delegue_par_id")
- private Membre deleguePar;
-
- /**
- * Motif et trace de la délégation — obligatoire si {@code deleguePar} est renseigné.
- * Conservé 10 ans — exigence BCEAO/OHADA/Fiscalité ivoirienne.
- */
- @Column(name = "trace_delegation", columnDefinition = "TEXT")
- private String traceDelegation;
-
- // ── Méthodes métier ────────────────────────────────────────────────────────
-
- public boolean estEnAttente() {
- return StatutValidationEtape.EN_ATTENTE.equals(statut);
- }
-
- public boolean estFinalisee() {
- return StatutValidationEtape.APPROUVEE.equals(statut)
- || StatutValidationEtape.REJETEE.equals(statut)
- || StatutValidationEtape.DELEGUEE.equals(statut)
- || StatutValidationEtape.EXPIREE.equals(statut);
- }
-
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statut == null) statut = StatutValidationEtape.EN_ATTENTE;
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.time.LocalDateTime;
+import lombok.*;
+
+/**
+ * Historique des validations pour une demande d'aide.
+ *
+ *
Chaque ligne représente l'état d'une étape du workflow pour une demande.
+ * La délégation de véto (valideur absent) est tracée avec motif — conformité BCEAO/OHADA.
+ *
+ *
Table : {@code validation_etapes_demande}
+ */
+@Entity
+@Table(
+ name = "validation_etapes_demande",
+ indexes = {
+ @Index(name = "idx_ved_demande", columnList = "demande_aide_id"),
+ @Index(name = "idx_ved_valideur", columnList = "valideur_id"),
+ @Index(name = "idx_ved_statut", columnList = "statut")
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ValidationEtapeDemande extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "demande_aide_id", nullable = false)
+ private DemandeAide demandeAide;
+
+ @NotNull
+ @Min(1) @Max(3)
+ @Column(name = "etape_numero", nullable = false)
+ private Integer etapeNumero;
+
+ /** Valideur assigné à cette étape */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "valideur_id")
+ private Membre valideur;
+
+ @Enumerated(EnumType.STRING)
+ @Builder.Default
+ @Column(name = "statut", nullable = false, length = 20)
+ private StatutValidationEtape statut = StatutValidationEtape.EN_ATTENTE;
+
+ @Column(name = "date_validation")
+ private LocalDateTime dateValidation;
+
+ @Column(name = "commentaire", length = 1000)
+ private String commentaire;
+
+ /**
+ * Valideur supérieur qui a désactivé le véto de {@code valideur}.
+ * Renseigné uniquement en cas de délégation.
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "delegue_par_id")
+ private Membre deleguePar;
+
+ /**
+ * Motif et trace de la délégation — obligatoire si {@code deleguePar} est renseigné.
+ * Conservé 10 ans — exigence BCEAO/OHADA/Fiscalité ivoirienne.
+ */
+ @Column(name = "trace_delegation", columnDefinition = "TEXT")
+ private String traceDelegation;
+
+ // ── Méthodes métier ────────────────────────────────────────────────────────
+
+ public boolean estEnAttente() {
+ return StatutValidationEtape.EN_ATTENTE.equals(statut);
+ }
+
+ public boolean estFinalisee() {
+ return StatutValidationEtape.APPROUVEE.equals(statut)
+ || StatutValidationEtape.REJETEE.equals(statut)
+ || StatutValidationEtape.DELEGUEE.equals(statut)
+ || StatutValidationEtape.EXPIREE.equals(statut);
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statut == null) statut = StatutValidationEtape.EN_ATTENTE;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java b/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java
index a6f5c97..2d12a88 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java
@@ -1,114 +1,114 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
-import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import java.time.LocalDateTime;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-/**
- * Entité WebhookWave pour le traitement des événements Wave
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@Entity
-@Table(name = "webhooks_wave", indexes = {
- @Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true),
- @Index(name = "idx_webhook_wave_statut", columnList = "statut_traitement"),
- @Index(name = "idx_webhook_wave_type", columnList = "type_evenement"),
- @Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"),
- @Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class WebhookWave extends BaseEntity {
-
- /** Identifiant unique de l'événement Wave */
- @NotBlank
- @Column(name = "wave_event_id", unique = true, nullable = false, length = 100)
- private String waveEventId;
-
- /** Type d'événement */
- @Column(name = "type_evenement", length = 50)
- private String typeEvenement;
-
- /** Statut de traitement */
- @Builder.Default
- @Column(name = "statut_traitement", nullable = false, length = 30)
- private String statutTraitement = StatutWebhook.EN_ATTENTE.name();
-
- /** Payload JSON reçu */
- @Column(name = "payload", columnDefinition = "TEXT")
- private String payload;
-
- /** Signature de validation */
- @Column(name = "signature", length = 500)
- private String signature;
-
- /** Date de réception */
- @Column(name = "date_reception")
- private LocalDateTime dateReception;
-
- /** Date de traitement */
- @Column(name = "date_traitement")
- private LocalDateTime dateTraitement;
-
- /** Nombre de tentatives de traitement */
- @Builder.Default
- @Column(name = "nombre_tentatives", nullable = false)
- private Integer nombreTentatives = 0;
-
- /** Message d'erreur (si échec) */
- @Column(name = "message_erreur", length = 1000)
- private String messageErreur;
-
- /** Commentaires */
- @Column(name = "commentaire", length = 500)
- private String commentaire;
-
- // Relations
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "transaction_wave_id")
- private TransactionWave transactionWave;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "paiement_id")
- private Versement versement;
-
- /** Méthode métier pour vérifier si le webhook est traité */
- public boolean isTraite() {
- return StatutWebhook.TRAITE.name().equals(statutTraitement);
- }
-
- /** Méthode métier pour vérifier si le webhook peut être retenté */
- public boolean peutEtreRetente() {
- return (StatutWebhook.ECHOUE.name().equals(statutTraitement)
- || StatutWebhook.EN_ATTENTE.name().equals(statutTraitement))
- && (nombreTentatives == null || nombreTentatives < 5);
- }
-
- /** Callback JPA avant la persistance */
- @PrePersist
- protected void onCreate() {
- super.onCreate();
- if (statutTraitement == null) {
- statutTraitement = StatutWebhook.EN_ATTENTE.name();
- }
- if (dateReception == null) {
- dateReception = LocalDateTime.now();
- }
- if (nombreTentatives == null) {
- nombreTentatives = 0;
- }
- }
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
+import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import java.time.LocalDateTime;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Entité WebhookWave pour le traitement des événements Wave
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@Entity
+@Table(name = "webhooks_wave", indexes = {
+ @Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true),
+ @Index(name = "idx_webhook_wave_statut", columnList = "statut_traitement"),
+ @Index(name = "idx_webhook_wave_type", columnList = "type_evenement"),
+ @Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"),
+ @Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class WebhookWave extends BaseEntity {
+
+ /** Identifiant unique de l'événement Wave */
+ @NotBlank
+ @Column(name = "wave_event_id", unique = true, nullable = false, length = 100)
+ private String waveEventId;
+
+ /** Type d'événement */
+ @Column(name = "type_evenement", length = 50)
+ private String typeEvenement;
+
+ /** Statut de traitement */
+ @Builder.Default
+ @Column(name = "statut_traitement", nullable = false, length = 30)
+ private String statutTraitement = StatutWebhook.EN_ATTENTE.name();
+
+ /** Payload JSON reçu */
+ @Column(name = "payload", columnDefinition = "TEXT")
+ private String payload;
+
+ /** Signature de validation */
+ @Column(name = "signature", length = 500)
+ private String signature;
+
+ /** Date de réception */
+ @Column(name = "date_reception")
+ private LocalDateTime dateReception;
+
+ /** Date de traitement */
+ @Column(name = "date_traitement")
+ private LocalDateTime dateTraitement;
+
+ /** Nombre de tentatives de traitement */
+ @Builder.Default
+ @Column(name = "nombre_tentatives", nullable = false)
+ private Integer nombreTentatives = 0;
+
+ /** Message d'erreur (si échec) */
+ @Column(name = "message_erreur", length = 1000)
+ private String messageErreur;
+
+ /** Commentaires */
+ @Column(name = "commentaire", length = 500)
+ private String commentaire;
+
+ // Relations
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "transaction_wave_id")
+ private TransactionWave transactionWave;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "paiement_id")
+ private Versement versement;
+
+ /** Méthode métier pour vérifier si le webhook est traité */
+ public boolean isTraite() {
+ return StatutWebhook.TRAITE.name().equals(statutTraitement);
+ }
+
+ /** Méthode métier pour vérifier si le webhook peut être retenté */
+ public boolean peutEtreRetente() {
+ return (StatutWebhook.ECHOUE.name().equals(statutTraitement)
+ || StatutWebhook.EN_ATTENTE.name().equals(statutTraitement))
+ && (nombreTentatives == null || nombreTentatives < 5);
+ }
+
+ /** Callback JPA avant la persistance */
+ @PrePersist
+ protected void onCreate() {
+ super.onCreate();
+ if (statutTraitement == null) {
+ statutTraitement = StatutWebhook.EN_ATTENTE.name();
+ }
+ if (dateReception == null) {
+ dateReception = LocalDateTime.now();
+ }
+ if (nombreTentatives == null) {
+ nombreTentatives = 0;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/WorkflowValidationConfig.java b/src/main/java/dev/lions/unionflow/server/entity/WorkflowValidationConfig.java
index b622c43..0c6b470 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/WorkflowValidationConfig.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/WorkflowValidationConfig.java
@@ -1,66 +1,66 @@
-package dev.lions.unionflow.server.entity;
-
-import dev.lions.unionflow.server.api.enums.solidarite.TypeWorkflow;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.*;
-import lombok.*;
-
-/**
- * Configuration du workflow de validation pour une organisation.
- *
- *
Maximum 3 étapes ordonnées. Chaque étape requiert un rôle spécifique.
- * Exemple Mutuelle Y : Secrétaire (étape 1) → Trésorier (étape 2) → Président (étape 3).
- *
- *
Table : {@code workflow_validation_config}
- */
-@Entity
-@Table(
- name = "workflow_validation_config",
- indexes = {
- @Index(name = "idx_wf_organisation", columnList = "organisation_id"),
- @Index(name = "idx_wf_type", columnList = "type_workflow")
- },
- uniqueConstraints = {
- @UniqueConstraint(
- name = "uk_wf_org_type_etape",
- columnNames = {"organisation_id", "type_workflow", "etape_numero"})
- })
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class WorkflowValidationConfig extends BaseEntity {
-
- @NotNull
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @Enumerated(EnumType.STRING)
- @NotNull
- @Builder.Default
- @Column(name = "type_workflow", nullable = false, length = 30)
- private TypeWorkflow typeWorkflow = TypeWorkflow.DEMANDE_AIDE;
-
- /** Numéro d'ordre de l'étape (1, 2 ou 3) */
- @NotNull
- @Min(1) @Max(3)
- @Column(name = "etape_numero", nullable = false)
- private Integer etapeNumero;
-
- /** Rôle nécessaire pour valider cette étape */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "role_requis_id")
- private Role roleRequis;
-
- @NotBlank
- @Column(name = "libelle_etape", nullable = false, length = 200)
- private String libelleEtape;
-
- /** Délai maximum en heures avant expiration automatique (SLA) */
- @Builder.Default
- @Min(1)
- @Column(name = "delai_max_heures", nullable = false)
- private Integer delaiMaxHeures = 72;
-}
+package dev.lions.unionflow.server.entity;
+
+import dev.lions.unionflow.server.api.enums.solidarite.TypeWorkflow;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import lombok.*;
+
+/**
+ * Configuration du workflow de validation pour une organisation.
+ *
+ *
Maximum 3 étapes ordonnées. Chaque étape requiert un rôle spécifique.
+ * Exemple Mutuelle Y : Secrétaire (étape 1) → Trésorier (étape 2) → Président (étape 3).
+ *
+ *
Table : {@code workflow_validation_config}
+ */
+@Entity
+@Table(
+ name = "workflow_validation_config",
+ indexes = {
+ @Index(name = "idx_wf_organisation", columnList = "organisation_id"),
+ @Index(name = "idx_wf_type", columnList = "type_workflow")
+ },
+ uniqueConstraints = {
+ @UniqueConstraint(
+ name = "uk_wf_org_type_etape",
+ columnNames = {"organisation_id", "type_workflow", "etape_numero"})
+ })
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class WorkflowValidationConfig extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ @Builder.Default
+ @Column(name = "type_workflow", nullable = false, length = 30)
+ private TypeWorkflow typeWorkflow = TypeWorkflow.DEMANDE_AIDE;
+
+ /** Numéro d'ordre de l'étape (1, 2 ou 3) */
+ @NotNull
+ @Min(1) @Max(3)
+ @Column(name = "etape_numero", nullable = false)
+ private Integer etapeNumero;
+
+ /** Rôle nécessaire pour valider cette étape */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "role_requis_id")
+ private Role roleRequis;
+
+ @NotBlank
+ @Column(name = "libelle_etape", nullable = false, length = 200)
+ private String libelleEtape;
+
+ /** Délai maximum en heures avant expiration automatique (SLA) */
+ @Builder.Default
+ @Min(1)
+ @Column(name = "delai_max_heures", nullable = false)
+ private Integer delaiMaxHeures = 72;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/agricole/CampagneAgricole.java b/src/main/java/dev/lions/unionflow/server/entity/agricole/CampagneAgricole.java
index d0046a1..4564934 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/agricole/CampagneAgricole.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/agricole/CampagneAgricole.java
@@ -1,50 +1,50 @@
-package dev.lions.unionflow.server.entity.agricole;
-
-import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "campagnes_agricoles", indexes = {
- @Index(name = "idx_agricole_organisation", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CampagneAgricole extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Column(name = "designation", nullable = false, length = 200)
- private String designation;
-
- @Column(name = "type_culture", length = 100)
- private String typeCulturePrincipale;
-
- @Column(name = "surface_estimee_ha", precision = 19, scale = 4)
- private BigDecimal surfaceTotaleEstimeeHectares;
-
- @Column(name = "volume_prev_tonnes", precision = 19, scale = 4)
- private BigDecimal volumePrevisionnelTonnes;
-
- @Column(name = "volume_reel_tonnes", precision = 19, scale = 4)
- private BigDecimal volumeReelTonnes;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutCampagneAgricole statut = StatutCampagneAgricole.PREPARATION;
-}
+package dev.lions.unionflow.server.entity.agricole;
+
+import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "campagnes_agricoles", indexes = {
+ @Index(name = "idx_agricole_organisation", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CampagneAgricole extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Column(name = "designation", nullable = false, length = 200)
+ private String designation;
+
+ @Column(name = "type_culture", length = 100)
+ private String typeCulturePrincipale;
+
+ @Column(name = "surface_estimee_ha", precision = 19, scale = 4)
+ private BigDecimal surfaceTotaleEstimeeHectares;
+
+ @Column(name = "volume_prev_tonnes", precision = 19, scale = 4)
+ private BigDecimal volumePrevisionnelTonnes;
+
+ @Column(name = "volume_reel_tonnes", precision = 19, scale = 4)
+ private BigDecimal volumeReelTonnes;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutCampagneAgricole statut = StatutCampagneAgricole.PREPARATION;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/collectefonds/CampagneCollecte.java b/src/main/java/dev/lions/unionflow/server/entity/collectefonds/CampagneCollecte.java
index 10166d7..2969634 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/collectefonds/CampagneCollecte.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/collectefonds/CampagneCollecte.java
@@ -1,71 +1,71 @@
-package dev.lions.unionflow.server.entity.collectefonds;
-
-import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "campagnes_collecte", indexes = {
- @Index(name = "idx_collecte_organisation", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CampagneCollecte extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Column(name = "titre", nullable = false, length = 200)
- private String titre;
-
- @Column(name = "courte_description", length = 500)
- private String courteDescription;
-
- @Column(name = "html_description_complete", columnDefinition = "TEXT")
- private String htmlDescriptionComplete;
-
- @Column(name = "image_banniere_url", length = 500)
- private String imageBanniereUrl;
-
- @Column(name = "objectif_financier", precision = 19, scale = 4)
- private BigDecimal objectifFinancier;
-
- @Column(name = "montant_collecte_actuel", precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal montantCollecteActuel = BigDecimal.ZERO;
-
- @Column(name = "nombre_donateurs")
- @Builder.Default
- private Integer nombreDonateurs = 0;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutCampagneCollecte statut = StatutCampagneCollecte.BROUILLON;
-
- @NotNull
- @Column(name = "date_ouverture", nullable = false)
- @Builder.Default
- private LocalDateTime dateOuverture = LocalDateTime.now();
-
- @Column(name = "date_cloture_prevue")
- private LocalDateTime dateCloturePrevue;
-
- @Column(name = "est_publique", nullable = false)
- @Builder.Default
- private Boolean estPublique = true;
-}
+package dev.lions.unionflow.server.entity.collectefonds;
+
+import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "campagnes_collecte", indexes = {
+ @Index(name = "idx_collecte_organisation", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CampagneCollecte extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Column(name = "titre", nullable = false, length = 200)
+ private String titre;
+
+ @Column(name = "courte_description", length = 500)
+ private String courteDescription;
+
+ @Column(name = "html_description_complete", columnDefinition = "TEXT")
+ private String htmlDescriptionComplete;
+
+ @Column(name = "image_banniere_url", length = 500)
+ private String imageBanniereUrl;
+
+ @Column(name = "objectif_financier", precision = 19, scale = 4)
+ private BigDecimal objectifFinancier;
+
+ @Column(name = "montant_collecte_actuel", precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal montantCollecteActuel = BigDecimal.ZERO;
+
+ @Column(name = "nombre_donateurs")
+ @Builder.Default
+ private Integer nombreDonateurs = 0;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutCampagneCollecte statut = StatutCampagneCollecte.BROUILLON;
+
+ @NotNull
+ @Column(name = "date_ouverture", nullable = false)
+ @Builder.Default
+ private LocalDateTime dateOuverture = LocalDateTime.now();
+
+ @Column(name = "date_cloture_prevue")
+ private LocalDateTime dateCloturePrevue;
+
+ @Column(name = "est_publique", nullable = false)
+ @Builder.Default
+ private Boolean estPublique = true;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/collectefonds/ContributionCollecte.java b/src/main/java/dev/lions/unionflow/server/entity/collectefonds/ContributionCollecte.java
index 8af32d7..965b72a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/collectefonds/ContributionCollecte.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/collectefonds/ContributionCollecte.java
@@ -1,59 +1,59 @@
-package dev.lions.unionflow.server.entity.collectefonds;
-
-import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "contributions_collecte", indexes = {
- @Index(name = "idx_contribution_campagne", columnList = "campagne_id"),
- @Index(name = "idx_contribution_membre", columnList = "membre_donateur_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ContributionCollecte extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "campagne_id", nullable = false)
- private CampagneCollecte campagne;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_donateur_id")
- private Membre membreDonateur;
-
- @Column(name = "alias_donateur", length = 150)
- private String aliasDonateur;
-
- @Column(name = "est_anonyme", nullable = false)
- @Builder.Default
- private Boolean estAnonyme = false;
-
- @NotNull
- @Column(name = "montant_soutien", nullable = false, precision = 19, scale = 4)
- private BigDecimal montantSoutien;
-
- @Column(name = "message_soutien", length = 500)
- private String messageSoutien;
-
- @NotNull
- @Column(name = "date_contribution", nullable = false)
- @Builder.Default
- private LocalDateTime dateContribution = LocalDateTime.now();
-
- @Column(name = "transaction_paiement_id", length = 100)
- private String transactionPaiementId;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "statut_paiement", length = 50)
- private StatutTransactionWave statutPaiement;
-}
+package dev.lions.unionflow.server.entity.collectefonds;
+
+import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "contributions_collecte", indexes = {
+ @Index(name = "idx_contribution_campagne", columnList = "campagne_id"),
+ @Index(name = "idx_contribution_membre", columnList = "membre_donateur_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ContributionCollecte extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "campagne_id", nullable = false)
+ private CampagneCollecte campagne;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_donateur_id")
+ private Membre membreDonateur;
+
+ @Column(name = "alias_donateur", length = 150)
+ private String aliasDonateur;
+
+ @Column(name = "est_anonyme", nullable = false)
+ @Builder.Default
+ private Boolean estAnonyme = false;
+
+ @NotNull
+ @Column(name = "montant_soutien", nullable = false, precision = 19, scale = 4)
+ private BigDecimal montantSoutien;
+
+ @Column(name = "message_soutien", length = 500)
+ private String messageSoutien;
+
+ @NotNull
+ @Column(name = "date_contribution", nullable = false)
+ @Builder.Default
+ private LocalDateTime dateContribution = LocalDateTime.now();
+
+ @Column(name = "transaction_paiement_id", length = 100)
+ private String transactionPaiementId;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut_paiement", length = 50)
+ private StatutTransactionWave statutPaiement;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/culte/DonReligieux.java b/src/main/java/dev/lions/unionflow/server/entity/culte/DonReligieux.java
index 254c8df..e702bb1 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/culte/DonReligieux.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/culte/DonReligieux.java
@@ -1,51 +1,51 @@
-package dev.lions.unionflow.server.entity.culte;
-
-import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "dons_religieux", indexes = {
- @Index(name = "idx_don_c_organisation", columnList = "institution_id"),
- @Index(name = "idx_don_c_fidele", columnList = "fidele_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class DonReligieux extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "institution_id", nullable = false)
- private Organisation institution;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "fidele_id")
- private Membre fidele;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_don", nullable = false, length = 50)
- private TypeDonReligieux typeDon;
-
- @NotNull
- @Column(name = "montant", nullable = false, precision = 19, scale = 4)
- private BigDecimal montant;
-
- @NotNull
- @Column(name = "date_encaissement", nullable = false)
- @Builder.Default
- private LocalDateTime dateEncaissement = LocalDateTime.now();
-
- @Column(name = "periode_nature", length = 150)
- private String periodeOuNatureAssociee;
-}
+package dev.lions.unionflow.server.entity.culte;
+
+import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "dons_religieux", indexes = {
+ @Index(name = "idx_don_c_organisation", columnList = "institution_id"),
+ @Index(name = "idx_don_c_fidele", columnList = "fidele_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class DonReligieux extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "institution_id", nullable = false)
+ private Organisation institution;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "fidele_id")
+ private Membre fidele;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_don", nullable = false, length = 50)
+ private TypeDonReligieux typeDon;
+
+ @NotNull
+ @Column(name = "montant", nullable = false, precision = 19, scale = 4)
+ private BigDecimal montant;
+
+ @NotNull
+ @Column(name = "date_encaissement", nullable = false)
+ @Builder.Default
+ private LocalDateTime dateEncaissement = LocalDateTime.now();
+
+ @Column(name = "periode_nature", length = 150)
+ private String periodeOuNatureAssociee;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/gouvernance/EchelonOrganigramme.java b/src/main/java/dev/lions/unionflow/server/entity/gouvernance/EchelonOrganigramme.java
index 515cc30..66d5ff0 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/gouvernance/EchelonOrganigramme.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/gouvernance/EchelonOrganigramme.java
@@ -1,43 +1,43 @@
-package dev.lions.unionflow.server.entity.gouvernance;
-
-import dev.lions.unionflow.server.api.enums.gouvernance.NiveauEchelon;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-@Entity
-@Table(name = "echelons_organigramme", indexes = {
- @Index(name = "idx_echelon_org", columnList = "organisation_id"),
- @Index(name = "idx_echelon_parent", columnList = "echelon_parent_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class EchelonOrganigramme extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "echelon_parent_id")
- private Organisation echelonParent;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "niveau_echelon", nullable = false, length = 50)
- private NiveauEchelon niveau;
-
- @NotBlank
- @Column(name = "designation", nullable = false, length = 200)
- private String designation;
-
- @Column(name = "zone_delegation", length = 200)
- private String zoneGeographiqueOuDelegation;
-}
+package dev.lions.unionflow.server.entity.gouvernance;
+
+import dev.lions.unionflow.server.api.enums.gouvernance.NiveauEchelon;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+@Entity
+@Table(name = "echelons_organigramme", indexes = {
+ @Index(name = "idx_echelon_org", columnList = "organisation_id"),
+ @Index(name = "idx_echelon_parent", columnList = "echelon_parent_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class EchelonOrganigramme extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "echelon_parent_id")
+ private Organisation echelonParent;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "niveau_echelon", nullable = false, length = 50)
+ private NiveauEchelon niveau;
+
+ @NotBlank
+ @Column(name = "designation", nullable = false, length = 200)
+ private String designation;
+
+ @Column(name = "zone_delegation", length = 200)
+ private String zoneGeographiqueOuDelegation;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/listener/AuditEntityListener.java b/src/main/java/dev/lions/unionflow/server/entity/listener/AuditEntityListener.java
index bf159d8..9aa06e5 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/listener/AuditEntityListener.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/listener/AuditEntityListener.java
@@ -1,106 +1,106 @@
-package dev.lions.unionflow.server.entity.listener;
-
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.service.KeycloakService;
-import io.quarkus.arc.Arc;
-import jakarta.persistence.PrePersist;
-import jakarta.persistence.PreUpdate;
-import org.jboss.logging.Logger;
-
-/**
- * Listener JPA pour l'alimentation automatique
- * des champs d'audit.
- *
- *
- * Renseigne automatiquement {@code creePar} lors
- * de la création et {@code modifiePar} lors de la
- * mise à jour, en récupérant l'email de
- * l'utilisateur authentifié via
- * {@link KeycloakService}.
- *
- *
- * Ce listener est référencé via
- * {@code @EntityListeners} sur {@link BaseEntity},
- * garantissant que toutes les
- * entités héritent automatiquement de ce
- * comportement (WOU strict).
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2026-02-21
- */
-public class AuditEntityListener {
-
- /**
- * Utilisateur par défaut pour les opérations
- * système sans contexte de sécurité.
- */
- private static final String UTILISATEUR_SYSTEME = "system";
-
- private static final Logger LOG = Logger.getLogger(AuditEntityListener.class);
-
- /**
- * Callback exécuté avant la persistance.
- *
- *
- * Renseigne {@code creePar} avec l'email
- * de l'utilisateur authentifié, ou
- * {@code "system"} si aucun contexte de
- * sécurité n'est disponible.
- *
- * @param entity l'entité en cours de création
- */
- @PrePersist
- public void avantCreation(BaseEntity entity) {
- if (entity.getCreePar() == null
- || entity.getCreePar().isBlank()) {
- entity.setCreePar(
- obtenirUtilisateurCourant());
- }
- }
-
- /**
- * Callback exécuté avant la mise à jour.
- *
- *
- * Renseigne {@code modifiePar} avec l'email
- * de l'utilisateur authentifié.
- *
- * @param entity l'entité en cours de modification
- */
- @PreUpdate
- public void avantModification(BaseEntity entity) {
- entity.setModifiePar(
- obtenirUtilisateurCourant());
- }
-
- /**
- * Obtient l'email de l'utilisateur courant.
- *
- *
- * Utilise {@link Arc#container()} pour
- * résoudre le {@link KeycloakService} depuis
- * le conteneur CDI de Quarkus.
- *
- * @return l'email ou {@code "system"} en fallback
- */
- private String obtenirUtilisateurCourant() {
- try {
- KeycloakService keycloakService = Arc.container()
- .instance(KeycloakService.class)
- .get();
- if (keycloakService != null
- && keycloakService.isAuthenticated()) {
- String email = keycloakService.getCurrentUserEmail();
- if (email != null && !email.isBlank()) {
- return email;
- }
- }
- } catch (Exception e) {
- LOG.debugf(
- "Contexte de sécurité indisponible: %s",
- e.getMessage());
- }
- return UTILISATEUR_SYSTEME;
- }
-}
+package dev.lions.unionflow.server.entity.listener;
+
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.service.KeycloakService;
+import io.quarkus.arc.Arc;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
+import org.jboss.logging.Logger;
+
+/**
+ * Listener JPA pour l'alimentation automatique
+ * des champs d'audit.
+ *
+ *
+ * Renseigne automatiquement {@code creePar} lors
+ * de la création et {@code modifiePar} lors de la
+ * mise à jour, en récupérant l'email de
+ * l'utilisateur authentifié via
+ * {@link KeycloakService}.
+ *
+ *
+ * Ce listener est référencé via
+ * {@code @EntityListeners} sur {@link BaseEntity},
+ * garantissant que toutes les
+ * entités héritent automatiquement de ce
+ * comportement (WOU strict).
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2026-02-21
+ */
+public class AuditEntityListener {
+
+ /**
+ * Utilisateur par défaut pour les opérations
+ * système sans contexte de sécurité.
+ */
+ private static final String UTILISATEUR_SYSTEME = "system";
+
+ private static final Logger LOG = Logger.getLogger(AuditEntityListener.class);
+
+ /**
+ * Callback exécuté avant la persistance.
+ *
+ *
+ * Renseigne {@code creePar} avec l'email
+ * de l'utilisateur authentifié, ou
+ * {@code "system"} si aucun contexte de
+ * sécurité n'est disponible.
+ *
+ * @param entity l'entité en cours de création
+ */
+ @PrePersist
+ public void avantCreation(BaseEntity entity) {
+ if (entity.getCreePar() == null
+ || entity.getCreePar().isBlank()) {
+ entity.setCreePar(
+ obtenirUtilisateurCourant());
+ }
+ }
+
+ /**
+ * Callback exécuté avant la mise à jour.
+ *
+ *
+ * Renseigne {@code modifiePar} avec l'email
+ * de l'utilisateur authentifié.
+ *
+ * @param entity l'entité en cours de modification
+ */
+ @PreUpdate
+ public void avantModification(BaseEntity entity) {
+ entity.setModifiePar(
+ obtenirUtilisateurCourant());
+ }
+
+ /**
+ * Obtient l'email de l'utilisateur courant.
+ *
+ *
+ * Utilise {@link Arc#container()} pour
+ * résoudre le {@link KeycloakService} depuis
+ * le conteneur CDI de Quarkus.
+ *
+ * @return l'email ou {@code "system"} en fallback
+ */
+ private String obtenirUtilisateurCourant() {
+ try {
+ KeycloakService keycloakService = Arc.container()
+ .instance(KeycloakService.class)
+ .get();
+ if (keycloakService != null
+ && keycloakService.isAuthenticated()) {
+ String email = keycloakService.getCurrentUserEmail();
+ if (email != null && !email.isBlank()) {
+ return email;
+ }
+ }
+ } catch (Exception e) {
+ LOG.debugf(
+ "Contexte de sécurité indisponible: %s",
+ e.getMessage());
+ }
+ return UTILISATEUR_SYSTEME;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/DemandeCredit.java b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/DemandeCredit.java
index 0f23862..613f954 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/DemandeCredit.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/DemandeCredit.java
@@ -1,97 +1,97 @@
-package dev.lions.unionflow.server.entity.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutDemandeCredit;
-import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-
-@Entity
-@Table(name = "demandes_credit", indexes = {
- @Index(name = "idx_credit_membre", columnList = "membre_id"),
- @Index(name = "idx_credit_numero", columnList = "numero_dossier", unique = true)
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class DemandeCredit extends BaseEntity {
-
- @Column(name = "numero_dossier", unique = true, nullable = false, length = 50)
- private String numeroDossier;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_credit", nullable = false, length = 50)
- private TypeCredit typeCredit;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "compte_lie_id")
- private CompteEpargne compteLie;
-
- @NotNull
- @Column(name = "montant_demande", nullable = false, precision = 19, scale = 4)
- private BigDecimal montantDemande;
-
- @NotNull
- @Column(name = "duree_mois_demande", nullable = false)
- private Integer dureeMoisDemande;
-
- @Column(name = "justification_detaillee", columnDefinition = "TEXT")
- private String justificationDetaillee;
-
- @Column(name = "montant_approuve", precision = 19, scale = 4)
- private BigDecimal montantApprouve;
-
- @Column(name = "duree_mois_approuvee")
- private Integer dureeMoisApprouvee;
-
- @Column(name = "taux_interet_annuel", precision = 5, scale = 2)
- private BigDecimal tauxInteretAnnuel;
-
- @Column(name = "cout_total_credit", precision = 19, scale = 4)
- private BigDecimal coutTotalCredit;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutDemandeCredit statut = StatutDemandeCredit.SOUMISE;
-
- @Column(name = "notes_comite", columnDefinition = "TEXT")
- private String notesComite;
-
- @NotNull
- @Column(name = "date_soumission", nullable = false)
- @Builder.Default
- private LocalDate dateSoumission = LocalDate.now();
-
- @Column(name = "date_validation")
- private LocalDate dateValidation;
-
- @Column(name = "date_premier_echeance")
- private LocalDate datePremierEcheance;
-
- @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
- @Builder.Default
- private List garanties = new ArrayList<>();
-
- @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
- @OrderBy("ordre ASC")
- @Builder.Default
- private List echeancier = new ArrayList<>();
-}
+package dev.lions.unionflow.server.entity.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutDemandeCredit;
+import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "demandes_credit", indexes = {
+ @Index(name = "idx_credit_membre", columnList = "membre_id"),
+ @Index(name = "idx_credit_numero", columnList = "numero_dossier", unique = true)
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class DemandeCredit extends BaseEntity {
+
+ @Column(name = "numero_dossier", unique = true, nullable = false, length = 50)
+ private String numeroDossier;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_credit", nullable = false, length = 50)
+ private TypeCredit typeCredit;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "compte_lie_id")
+ private CompteEpargne compteLie;
+
+ @NotNull
+ @Column(name = "montant_demande", nullable = false, precision = 19, scale = 4)
+ private BigDecimal montantDemande;
+
+ @NotNull
+ @Column(name = "duree_mois_demande", nullable = false)
+ private Integer dureeMoisDemande;
+
+ @Column(name = "justification_detaillee", columnDefinition = "TEXT")
+ private String justificationDetaillee;
+
+ @Column(name = "montant_approuve", precision = 19, scale = 4)
+ private BigDecimal montantApprouve;
+
+ @Column(name = "duree_mois_approuvee")
+ private Integer dureeMoisApprouvee;
+
+ @Column(name = "taux_interet_annuel", precision = 5, scale = 2)
+ private BigDecimal tauxInteretAnnuel;
+
+ @Column(name = "cout_total_credit", precision = 19, scale = 4)
+ private BigDecimal coutTotalCredit;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutDemandeCredit statut = StatutDemandeCredit.SOUMISE;
+
+ @Column(name = "notes_comite", columnDefinition = "TEXT")
+ private String notesComite;
+
+ @NotNull
+ @Column(name = "date_soumission", nullable = false)
+ @Builder.Default
+ private LocalDate dateSoumission = LocalDate.now();
+
+ @Column(name = "date_validation")
+ private LocalDate dateValidation;
+
+ @Column(name = "date_premier_echeance")
+ private LocalDate datePremierEcheance;
+
+ @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
+ @Builder.Default
+ private List garanties = new ArrayList<>();
+
+ @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
+ @OrderBy("ordre ASC")
+ @Builder.Default
+ private List echeancier = new ArrayList<>();
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/EcheanceCredit.java b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/EcheanceCredit.java
index 01ad3a2..68c8568 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/EcheanceCredit.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/EcheanceCredit.java
@@ -1,69 +1,69 @@
-package dev.lions.unionflow.server.entity.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit;
-import dev.lions.unionflow.server.entity.BaseEntity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "echeances_credit", indexes = {
- @Index(name = "idx_echeance_demande", columnList = "demande_credit_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-@ToString(exclude = "demandeCredit")
-public class EcheanceCredit extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "demande_credit_id", nullable = false)
- private DemandeCredit demandeCredit;
-
- @NotNull
- @Column(name = "ordre", nullable = false)
- private Integer ordre;
-
- @NotNull
- @Column(name = "date_echeance_prevue", nullable = false)
- private LocalDate dateEcheancePrevue;
-
- @Column(name = "date_paiement_effectif")
- private LocalDate datePaiementEffectif;
-
- @NotNull
- @Column(name = "capital_amorti", nullable = false, precision = 19, scale = 4)
- private BigDecimal capitalAmorti;
-
- @NotNull
- @Column(name = "interets_periode", nullable = false, precision = 19, scale = 4)
- private BigDecimal interetsDeLaPeriode;
-
- @NotNull
- @Column(name = "montant_total_exigible", nullable = false, precision = 19, scale = 4)
- private BigDecimal montantTotalExigible;
-
- @NotNull
- @Column(name = "capital_restant_du", nullable = false, precision = 19, scale = 4)
- private BigDecimal capitalRestantDu;
-
- @Column(name = "penalites_retard", precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal penalitesRetard = BigDecimal.ZERO;
-
- @Column(name = "montant_regle", precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal montantRegle = BigDecimal.ZERO;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutEcheanceCredit statut = StatutEcheanceCredit.A_VENIR;
-}
+package dev.lions.unionflow.server.entity.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit;
+import dev.lions.unionflow.server.entity.BaseEntity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "echeances_credit", indexes = {
+ @Index(name = "idx_echeance_demande", columnList = "demande_credit_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@ToString(exclude = "demandeCredit")
+public class EcheanceCredit extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "demande_credit_id", nullable = false)
+ private DemandeCredit demandeCredit;
+
+ @NotNull
+ @Column(name = "ordre", nullable = false)
+ private Integer ordre;
+
+ @NotNull
+ @Column(name = "date_echeance_prevue", nullable = false)
+ private LocalDate dateEcheancePrevue;
+
+ @Column(name = "date_paiement_effectif")
+ private LocalDate datePaiementEffectif;
+
+ @NotNull
+ @Column(name = "capital_amorti", nullable = false, precision = 19, scale = 4)
+ private BigDecimal capitalAmorti;
+
+ @NotNull
+ @Column(name = "interets_periode", nullable = false, precision = 19, scale = 4)
+ private BigDecimal interetsDeLaPeriode;
+
+ @NotNull
+ @Column(name = "montant_total_exigible", nullable = false, precision = 19, scale = 4)
+ private BigDecimal montantTotalExigible;
+
+ @NotNull
+ @Column(name = "capital_restant_du", nullable = false, precision = 19, scale = 4)
+ private BigDecimal capitalRestantDu;
+
+ @Column(name = "penalites_retard", precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal penalitesRetard = BigDecimal.ZERO;
+
+ @Column(name = "montant_regle", precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal montantRegle = BigDecimal.ZERO;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutEcheanceCredit statut = StatutEcheanceCredit.A_VENIR;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/GarantieDemande.java b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/GarantieDemande.java
index 8ea7780..55157e6 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/GarantieDemande.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/credit/GarantieDemande.java
@@ -1,39 +1,39 @@
-package dev.lions.unionflow.server.entity.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie;
-import dev.lions.unionflow.server.entity.BaseEntity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "garanties_demande")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-@ToString(exclude = "demandeCredit")
-public class GarantieDemande extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "demande_credit_id", nullable = false)
- private DemandeCredit demandeCredit;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_garantie", nullable = false, length = 50)
- private TypeGarantie typeGarantie;
-
- @Column(name = "valeur_estimee", precision = 19, scale = 4)
- private BigDecimal valeurEstimee;
-
- @Column(name = "reference_description", length = 500)
- private String referenceOuDescription;
-
- @Column(name = "document_preuve_id", length = 36)
- private String documentPreuveId;
-}
+package dev.lions.unionflow.server.entity.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie;
+import dev.lions.unionflow.server.entity.BaseEntity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "garanties_demande")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@ToString(exclude = "demandeCredit")
+public class GarantieDemande extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "demande_credit_id", nullable = false)
+ private DemandeCredit demandeCredit;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_garantie", nullable = false, length = 50)
+ private TypeGarantie typeGarantie;
+
+ @Column(name = "valeur_estimee", precision = 19, scale = 4)
+ private BigDecimal valeurEstimee;
+
+ @Column(name = "reference_description", length = 500)
+ private String referenceOuDescription;
+
+ @Column(name = "document_preuve_id", length = 36)
+ private String documentPreuveId;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/CompteEpargne.java b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/CompteEpargne.java
index b86f276..0ee4a4b 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/CompteEpargne.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/CompteEpargne.java
@@ -1,73 +1,73 @@
-package dev.lions.unionflow.server.entity.mutuelle.epargne;
-
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-import dev.lions.unionflow.server.entity.Organisation;
-import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne;
-import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "comptes_epargne", indexes = {
- @Index(name = "idx_compte_epargne_numero", columnList = "numero_compte", unique = true),
- @Index(name = "idx_compte_epargne_membre", columnList = "membre_id"),
- @Index(name = "idx_compte_epargne_orga", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CompteEpargne extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Column(name = "numero_compte", unique = true, nullable = false, length = 50)
- private String numeroCompte;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_compte", nullable = false, length = 50)
- private TypeCompteEpargne typeCompte;
-
- @NotNull
- @Column(name = "solde_actuel", nullable = false, precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal soldeActuel = BigDecimal.ZERO;
-
- @NotNull
- @Column(name = "solde_bloque", nullable = false, precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal soldeBloque = BigDecimal.ZERO;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 30)
- @Builder.Default
- private StatutCompteEpargne statut = StatutCompteEpargne.ACTIF;
-
- @NotNull
- @Column(name = "date_ouverture", nullable = false)
- @Builder.Default
- private LocalDate dateOuverture = LocalDate.now();
-
- @Column(name = "date_derniere_transaction")
- private LocalDate dateDerniereTransaction;
-
- @Column(name = "description", length = 500)
- private String description;
-}
+package dev.lions.unionflow.server.entity.mutuelle.epargne;
+
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+import dev.lions.unionflow.server.entity.Organisation;
+import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne;
+import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "comptes_epargne", indexes = {
+ @Index(name = "idx_compte_epargne_numero", columnList = "numero_compte", unique = true),
+ @Index(name = "idx_compte_epargne_membre", columnList = "membre_id"),
+ @Index(name = "idx_compte_epargne_orga", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CompteEpargne extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Column(name = "numero_compte", unique = true, nullable = false, length = 50)
+ private String numeroCompte;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_compte", nullable = false, length = 50)
+ private TypeCompteEpargne typeCompte;
+
+ @NotNull
+ @Column(name = "solde_actuel", nullable = false, precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal soldeActuel = BigDecimal.ZERO;
+
+ @NotNull
+ @Column(name = "solde_bloque", nullable = false, precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal soldeBloque = BigDecimal.ZERO;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 30)
+ @Builder.Default
+ private StatutCompteEpargne statut = StatutCompteEpargne.ACTIF;
+
+ @NotNull
+ @Column(name = "date_ouverture", nullable = false)
+ @Builder.Default
+ private LocalDate dateOuverture = LocalDate.now();
+
+ @Column(name = "date_derniere_transaction")
+ private LocalDate dateDerniereTransaction;
+
+ @Column(name = "description", length = 500)
+ private String description;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/TransactionEpargne.java b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/TransactionEpargne.java
index a90a09a..b8d04c9 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/TransactionEpargne.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/mutuelle/epargne/TransactionEpargne.java
@@ -1,70 +1,70 @@
-package dev.lions.unionflow.server.entity.mutuelle.epargne;
-
-import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
-import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
-import dev.lions.unionflow.server.entity.BaseEntity;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "transactions_epargne", indexes = {
- @Index(name = "idx_tx_epargne_compte", columnList = "compte_id"),
- @Index(name = "idx_tx_epargne_reference", columnList = "reference_externe")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class TransactionEpargne extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "compte_id", nullable = false)
- private CompteEpargne compte;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_transaction", nullable = false, length = 50)
- private TypeTransactionEpargne type;
-
- @NotNull
- @Column(name = "montant", nullable = false, precision = 19, scale = 4)
- private BigDecimal montant;
-
- @Column(name = "solde_avant", precision = 19, scale = 4)
- private BigDecimal soldeAvant;
-
- @Column(name = "solde_apres", precision = 19, scale = 4)
- private BigDecimal soldeApres;
-
- @Column(name = "motif", length = 500)
- private String motif;
-
- @NotNull
- @Column(name = "date_transaction", nullable = false)
- @Builder.Default
- private LocalDateTime dateTransaction = LocalDateTime.now();
-
- @Column(name = "operateur_id", length = 36)
- private String operateurId;
-
- @Column(name = "reference_externe", length = 100)
- private String referenceExterne;
-
- @Enumerated(EnumType.STRING)
- @Column(name = "statut_execution", length = 50)
- private StatutTransactionWave statutExecution;
-
- /** Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré */
- @Column(name = "origine_fonds", length = 200)
- private String origineFonds;
-
- /** Pièce justificative (document) pour opérations au-dessus du seuil LCB-FT */
- @Column(name = "piece_justificative_id")
- private java.util.UUID pieceJustificativeId;
-}
+package dev.lions.unionflow.server.entity.mutuelle.epargne;
+
+import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
+import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
+import dev.lions.unionflow.server.entity.BaseEntity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "transactions_epargne", indexes = {
+ @Index(name = "idx_tx_epargne_compte", columnList = "compte_id"),
+ @Index(name = "idx_tx_epargne_reference", columnList = "reference_externe")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class TransactionEpargne extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "compte_id", nullable = false)
+ private CompteEpargne compte;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_transaction", nullable = false, length = 50)
+ private TypeTransactionEpargne type;
+
+ @NotNull
+ @Column(name = "montant", nullable = false, precision = 19, scale = 4)
+ private BigDecimal montant;
+
+ @Column(name = "solde_avant", precision = 19, scale = 4)
+ private BigDecimal soldeAvant;
+
+ @Column(name = "solde_apres", precision = 19, scale = 4)
+ private BigDecimal soldeApres;
+
+ @Column(name = "motif", length = 500)
+ private String motif;
+
+ @NotNull
+ @Column(name = "date_transaction", nullable = false)
+ @Builder.Default
+ private LocalDateTime dateTransaction = LocalDateTime.now();
+
+ @Column(name = "operateur_id", length = 36)
+ private String operateurId;
+
+ @Column(name = "reference_externe", length = 100)
+ private String referenceExterne;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut_execution", length = 50)
+ private StatutTransactionWave statutExecution;
+
+ /** Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré */
+ @Column(name = "origine_fonds", length = 200)
+ private String origineFonds;
+
+ /** Pièce justificative (document) pour opérations au-dessus du seuil LCB-FT */
+ @Column(name = "piece_justificative_id")
+ private java.util.UUID pieceJustificativeId;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ong/ProjetOng.java b/src/main/java/dev/lions/unionflow/server/entity/ong/ProjetOng.java
index cfa7160..93cdf7a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ong/ProjetOng.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ong/ProjetOng.java
@@ -1,58 +1,58 @@
-package dev.lions.unionflow.server.entity.ong;
-
-import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "projets_ong", indexes = {
- @Index(name = "idx_projet_ong_organisation", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class ProjetOng extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotBlank
- @Column(name = "nom_projet", nullable = false, length = 200)
- private String nomProjet;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String descriptionMandat;
-
- @Column(name = "zone_geographique", length = 200)
- private String zoneGeographiqueIntervention;
-
- @Column(name = "budget_previsionnel", precision = 19, scale = 4)
- private BigDecimal budgetPrevisionnel;
-
- @Column(name = "depenses_reelles", precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal depensesReelles = BigDecimal.ZERO;
-
- @Column(name = "date_lancement")
- private LocalDate dateLancement;
-
- @Column(name = "date_fin_estimee")
- private LocalDate dateFinEstimee;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutProjetOng statut = StatutProjetOng.EN_ETUDE;
-}
+package dev.lions.unionflow.server.entity.ong;
+
+import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "projets_ong", indexes = {
+ @Index(name = "idx_projet_ong_organisation", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class ProjetOng extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotBlank
+ @Column(name = "nom_projet", nullable = false, length = 200)
+ private String nomProjet;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String descriptionMandat;
+
+ @Column(name = "zone_geographique", length = 200)
+ private String zoneGeographiqueIntervention;
+
+ @Column(name = "budget_previsionnel", precision = 19, scale = 4)
+ private BigDecimal budgetPrevisionnel;
+
+ @Column(name = "depenses_reelles", precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal depensesReelles = BigDecimal.ZERO;
+
+ @Column(name = "date_lancement")
+ private LocalDate dateLancement;
+
+ @Column(name = "date_fin_estimee")
+ private LocalDate dateFinEstimee;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutProjetOng statut = StatutProjetOng.EN_ETUDE;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/registre/AgrementProfessionnel.java b/src/main/java/dev/lions/unionflow/server/entity/registre/AgrementProfessionnel.java
index bb48720..0fec483 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/registre/AgrementProfessionnel.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/registre/AgrementProfessionnel.java
@@ -1,54 +1,54 @@
-package dev.lions.unionflow.server.entity.registre;
-
-import dev.lions.unionflow.server.api.enums.registre.StatutAgrement;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "agrements_professionnels", indexes = {
- @Index(name = "idx_agrement_membre", columnList = "membre_id"),
- @Index(name = "idx_agrement_orga", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class AgrementProfessionnel extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_id", nullable = false)
- private Membre membre;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @Column(name = "secteur_ordre", length = 150)
- private String secteurOuOrdre;
-
- @Column(name = "numero_licence", length = 100)
- private String numeroLicenceOuRegistre;
-
- @Column(name = "categorie_classement", length = 100)
- private String categorieClassement;
-
- @Column(name = "date_delivrance")
- private LocalDate dateDelivrance;
-
- @Column(name = "date_expiration")
- private LocalDate dateExpiration;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutAgrement statut = StatutAgrement.PROVISOIRE;
-}
+package dev.lions.unionflow.server.entity.registre;
+
+import dev.lions.unionflow.server.api.enums.registre.StatutAgrement;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "agrements_professionnels", indexes = {
+ @Index(name = "idx_agrement_membre", columnList = "membre_id"),
+ @Index(name = "idx_agrement_orga", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class AgrementProfessionnel extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_id", nullable = false)
+ private Membre membre;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @Column(name = "secteur_ordre", length = 150)
+ private String secteurOuOrdre;
+
+ @Column(name = "numero_licence", length = 100)
+ private String numeroLicenceOuRegistre;
+
+ @Column(name = "categorie_classement", length = 100)
+ private String categorieClassement;
+
+ @Column(name = "date_delivrance")
+ private LocalDate dateDelivrance;
+
+ @Column(name = "date_expiration")
+ private LocalDate dateExpiration;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutAgrement statut = StatutAgrement.PROVISOIRE;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/tontine/Tontine.java b/src/main/java/dev/lions/unionflow/server/entity/tontine/Tontine.java
index 2cab2d7..bcf2d6a 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/tontine/Tontine.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/tontine/Tontine.java
@@ -1,73 +1,73 @@
-package dev.lions.unionflow.server.entity.tontine;
-
-import dev.lions.unionflow.server.api.enums.tontine.FrequenceTour;
-import dev.lions.unionflow.server.api.enums.tontine.StatutTontine;
-import dev.lions.unionflow.server.api.enums.tontine.TypeTontine;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-
-@Entity
-@Table(name = "tontines", indexes = {
- @Index(name = "idx_tontine_organisation", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class Tontine extends BaseEntity {
-
- @NotBlank
- @Column(name = "nom", nullable = false, length = 150)
- private String nom;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String description;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_tontine", nullable = false, length = 50)
- private TypeTontine typeTontine;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "frequence", nullable = false, length = 50)
- private FrequenceTour frequence;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutTontine statut = StatutTontine.PLANIFIEE;
-
- @Column(name = "date_debut_effective")
- private LocalDate dateDebutEffective;
-
- @Column(name = "date_fin_prevue")
- private LocalDate dateFinPrevue;
-
- @Column(name = "montant_mise_tour", precision = 19, scale = 4)
- private BigDecimal montantMiseParTour;
-
- @Column(name = "limite_participants")
- private Integer limiteParticipants;
-
- @OneToMany(mappedBy = "tontine", cascade = CascadeType.ALL, orphanRemoval = true)
- @OrderBy("ordreTour ASC")
- @Builder.Default
- private List calendrierTours = new ArrayList<>();
-}
+package dev.lions.unionflow.server.entity.tontine;
+
+import dev.lions.unionflow.server.api.enums.tontine.FrequenceTour;
+import dev.lions.unionflow.server.api.enums.tontine.StatutTontine;
+import dev.lions.unionflow.server.api.enums.tontine.TypeTontine;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "tontines", indexes = {
+ @Index(name = "idx_tontine_organisation", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class Tontine extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "nom", nullable = false, length = 150)
+ private String nom;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String description;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_tontine", nullable = false, length = 50)
+ private TypeTontine typeTontine;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "frequence", nullable = false, length = 50)
+ private FrequenceTour frequence;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutTontine statut = StatutTontine.PLANIFIEE;
+
+ @Column(name = "date_debut_effective")
+ private LocalDate dateDebutEffective;
+
+ @Column(name = "date_fin_prevue")
+ private LocalDate dateFinPrevue;
+
+ @Column(name = "montant_mise_tour", precision = 19, scale = 4)
+ private BigDecimal montantMiseParTour;
+
+ @Column(name = "limite_participants")
+ private Integer limiteParticipants;
+
+ @OneToMany(mappedBy = "tontine", cascade = CascadeType.ALL, orphanRemoval = true)
+ @OrderBy("ordreTour ASC")
+ @Builder.Default
+ private List calendrierTours = new ArrayList<>();
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/tontine/TourTontine.java b/src/main/java/dev/lions/unionflow/server/entity/tontine/TourTontine.java
index b3422e5..379b50b 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/tontine/TourTontine.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/tontine/TourTontine.java
@@ -1,56 +1,56 @@
-package dev.lions.unionflow.server.entity.tontine;
-
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Membre;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "tours_tontine", indexes = {
- @Index(name = "idx_tour_tontine", columnList = "tontine_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-@ToString(exclude = { "tontine", "membreBeneficiaire" })
-public class TourTontine extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "tontine_id", nullable = false)
- private Tontine tontine;
-
- @NotNull
- @Column(name = "ordre_tour", nullable = false)
- private Integer ordreTour;
-
- @NotNull
- @Column(name = "date_ouverture_cotisations", nullable = false)
- private LocalDate dateOuvertureCotisations;
-
- @Column(name = "date_tirage_remise")
- private LocalDate dateTirageOuRemise;
-
- @NotNull
- @Column(name = "montant_cible", nullable = false, precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal montantCible = BigDecimal.ZERO;
-
- @NotNull
- @Column(name = "cagnotte_collectee", nullable = false, precision = 19, scale = 4)
- @Builder.Default
- private BigDecimal cagnotteCollectee = BigDecimal.ZERO;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "membre_beneficiaire_id")
- private Membre membreBeneficiaire;
-
- @Column(name = "statut_interne", length = 30)
- private String statutInterne;
-}
+package dev.lions.unionflow.server.entity.tontine;
+
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Membre;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "tours_tontine", indexes = {
+ @Index(name = "idx_tour_tontine", columnList = "tontine_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@ToString(exclude = { "tontine", "membreBeneficiaire" })
+public class TourTontine extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "tontine_id", nullable = false)
+ private Tontine tontine;
+
+ @NotNull
+ @Column(name = "ordre_tour", nullable = false)
+ private Integer ordreTour;
+
+ @NotNull
+ @Column(name = "date_ouverture_cotisations", nullable = false)
+ private LocalDate dateOuvertureCotisations;
+
+ @Column(name = "date_tirage_remise")
+ private LocalDate dateTirageOuRemise;
+
+ @NotNull
+ @Column(name = "montant_cible", nullable = false, precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal montantCible = BigDecimal.ZERO;
+
+ @NotNull
+ @Column(name = "cagnotte_collectee", nullable = false, precision = 19, scale = 4)
+ @Builder.Default
+ private BigDecimal cagnotteCollectee = BigDecimal.ZERO;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "membre_beneficiaire_id")
+ private Membre membreBeneficiaire;
+
+ @Column(name = "statut_interne", length = 30)
+ private String statutInterne;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/vote/CampagneVote.java b/src/main/java/dev/lions/unionflow/server/entity/vote/CampagneVote.java
index 47a267e..3f4edbd 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/vote/CampagneVote.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/vote/CampagneVote.java
@@ -1,84 +1,84 @@
-package dev.lions.unionflow.server.entity.vote;
-
-import dev.lions.unionflow.server.api.enums.vote.ModeScrutin;
-import dev.lions.unionflow.server.api.enums.vote.StatutVote;
-import dev.lions.unionflow.server.api.enums.vote.TypeVote;
-import dev.lions.unionflow.server.entity.BaseEntity;
-import dev.lions.unionflow.server.entity.Organisation;
-
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import lombok.*;
-
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-
-@Entity
-@Table(name = "campagnes_vote", indexes = {
- @Index(name = "idx_vote_orga", columnList = "organisation_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class CampagneVote extends BaseEntity {
-
- @NotBlank
- @Column(name = "titre", nullable = false, length = 200)
- private String titre;
-
- @Column(name = "description", columnDefinition = "TEXT")
- private String descriptionOuResolution;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "organisation_id", nullable = false)
- private Organisation organisation;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "type_vote", nullable = false, length = 50)
- private TypeVote typeVote;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "mode_scrutin", nullable = false, length = 50)
- private ModeScrutin modeScrutin;
-
- @NotNull
- @Enumerated(EnumType.STRING)
- @Column(name = "statut", nullable = false, length = 50)
- @Builder.Default
- private StatutVote statut = StatutVote.BROUILLON;
-
- @NotNull
- @Column(name = "date_ouverture", nullable = false)
- private LocalDateTime dateOuverture;
-
- @NotNull
- @Column(name = "date_fermeture", nullable = false)
- private LocalDateTime dateFermeture;
-
- @Column(name = "restreindre_membres_ajour", nullable = false)
- @Builder.Default
- private Boolean restreindreMembresAJour = true;
-
- @Column(name = "autoriser_vote_blanc", nullable = false)
- @Builder.Default
- private Boolean autoriserVoteBlanc = true;
-
- @Column(name = "total_electeurs")
- private Integer totalElecteursInscrits;
-
- @Column(name = "total_votants")
- private Integer totalVotantsEffectifs;
-
- @Column(name = "total_blancs_nuls")
- private Integer totalVotesBlancsOuNuls;
-
- @OneToMany(mappedBy = "campagneVote", cascade = CascadeType.ALL, orphanRemoval = true)
- @Builder.Default
- private List candidats = new ArrayList<>();
-}
+package dev.lions.unionflow.server.entity.vote;
+
+import dev.lions.unionflow.server.api.enums.vote.ModeScrutin;
+import dev.lions.unionflow.server.api.enums.vote.StatutVote;
+import dev.lions.unionflow.server.api.enums.vote.TypeVote;
+import dev.lions.unionflow.server.entity.BaseEntity;
+import dev.lions.unionflow.server.entity.Organisation;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "campagnes_vote", indexes = {
+ @Index(name = "idx_vote_orga", columnList = "organisation_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class CampagneVote extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "titre", nullable = false, length = 200)
+ private String titre;
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ private String descriptionOuResolution;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "type_vote", nullable = false, length = 50)
+ private TypeVote typeVote;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "mode_scrutin", nullable = false, length = 50)
+ private ModeScrutin modeScrutin;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "statut", nullable = false, length = 50)
+ @Builder.Default
+ private StatutVote statut = StatutVote.BROUILLON;
+
+ @NotNull
+ @Column(name = "date_ouverture", nullable = false)
+ private LocalDateTime dateOuverture;
+
+ @NotNull
+ @Column(name = "date_fermeture", nullable = false)
+ private LocalDateTime dateFermeture;
+
+ @Column(name = "restreindre_membres_ajour", nullable = false)
+ @Builder.Default
+ private Boolean restreindreMembresAJour = true;
+
+ @Column(name = "autoriser_vote_blanc", nullable = false)
+ @Builder.Default
+ private Boolean autoriserVoteBlanc = true;
+
+ @Column(name = "total_electeurs")
+ private Integer totalElecteursInscrits;
+
+ @Column(name = "total_votants")
+ private Integer totalVotantsEffectifs;
+
+ @Column(name = "total_blancs_nuls")
+ private Integer totalVotesBlancsOuNuls;
+
+ @OneToMany(mappedBy = "campagneVote", cascade = CascadeType.ALL, orphanRemoval = true)
+ @Builder.Default
+ private List candidats = new ArrayList<>();
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/vote/Candidat.java b/src/main/java/dev/lions/unionflow/server/entity/vote/Candidat.java
index 13fea88..bcea6ce 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/vote/Candidat.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/vote/Candidat.java
@@ -1,45 +1,45 @@
-package dev.lions.unionflow.server.entity.vote;
-
-import dev.lions.unionflow.server.entity.BaseEntity;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.NotBlank;
-import lombok.*;
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "candidats", indexes = {
- @Index(name = "idx_candidat_campagne", columnList = "campagne_vote_id")
-})
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode(callSuper = true)
-@ToString(exclude = "campagneVote")
-public class Candidat extends BaseEntity {
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "campagne_vote_id", nullable = false)
- private CampagneVote campagneVote;
-
- @NotBlank
- @Column(name = "nom_candidature", nullable = false, length = 150)
- private String nomCandidatureOuChoix;
-
- @Column(name = "membre_associe_id", length = 36)
- private String membreIdAssocie;
-
- @Column(name = "profession_foi", columnDefinition = "TEXT")
- private String professionDeFoi;
-
- @Column(name = "photo_url", length = 500)
- private String photoUrl;
-
- @Column(name = "nombre_voix")
- @Builder.Default
- private Integer nombreDeVoix = 0;
-
- @Column(name = "pourcentage", precision = 5, scale = 2)
- @Builder.Default
- private BigDecimal pourcentageObtenu = BigDecimal.ZERO;
-}
+package dev.lions.unionflow.server.entity.vote;
+
+import dev.lions.unionflow.server.entity.BaseEntity;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import lombok.*;
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "candidats", indexes = {
+ @Index(name = "idx_candidat_campagne", columnList = "campagne_vote_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@ToString(exclude = "campagneVote")
+public class Candidat extends BaseEntity {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "campagne_vote_id", nullable = false)
+ private CampagneVote campagneVote;
+
+ @NotBlank
+ @Column(name = "nom_candidature", nullable = false, length = 150)
+ private String nomCandidatureOuChoix;
+
+ @Column(name = "membre_associe_id", length = 36)
+ private String membreIdAssocie;
+
+ @Column(name = "profession_foi", columnDefinition = "TEXT")
+ private String professionDeFoi;
+
+ @Column(name = "photo_url", length = 500)
+ private String photoUrl;
+
+ @Column(name = "nombre_voix")
+ @Builder.Default
+ private Integer nombreDeVoix = 0;
+
+ @Column(name = "pourcentage", precision = 5, scale = 2)
+ @Builder.Default
+ private BigDecimal pourcentageObtenu = BigDecimal.ZERO;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java b/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java
index 0bfc224..02023b8 100644
--- a/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java
@@ -98,7 +98,9 @@ public class GlobalExceptionMapper implements ExceptionMapper {
return exception instanceof NotFoundException
|| exception instanceof ForbiddenException
|| exception instanceof NotAuthorizedException
- || exception instanceof NotAllowedException;
+ || exception instanceof NotAllowedException
+ || exception instanceof IllegalArgumentException
+ || exception instanceof IllegalStateException;
}
private int determineStatusCode(Throwable exception) {
diff --git a/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java b/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java
index 2a1a74a..95fa809 100644
--- a/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java
@@ -1,40 +1,40 @@
-package dev.lions.unionflow.server.exception;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.ext.ExceptionMapper;
-import jakarta.ws.rs.ext.Provider;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Exception Mapper pour les erreurs de traitement JSON (parsing, format, etc.).
- * Retourne un 400 Bad Request avec un message détaillé.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-19
- */
-@Slf4j
-@Provider
-public class JsonProcessingExceptionMapper implements ExceptionMapper {
-
- @Override
- public Response toResponse(JsonProcessingException exception) {
- log.warn("JSON processing error: {}", exception.getMessage());
-
- Map errorBody = new HashMap<>();
- errorBody.put("message", "Erreur de traitement JSON");
- errorBody.put("details", exception.getOriginalMessage() != null
- ? exception.getOriginalMessage()
- : exception.getMessage());
- errorBody.put("status", 400);
- errorBody.put("timestamp", java.time.LocalDateTime.now().toString());
-
- return Response.status(Response.Status.BAD_REQUEST)
- .entity(errorBody)
- .build();
- }
-}
+package dev.lions.unionflow.server.exception;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Exception Mapper pour les erreurs de traitement JSON (parsing, format, etc.).
+ * Retourne un 400 Bad Request avec un message détaillé.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-19
+ */
+@Slf4j
+@Provider
+public class JsonProcessingExceptionMapper implements ExceptionMapper {
+
+ @Override
+ public Response toResponse(JsonProcessingException exception) {
+ log.warn("JSON processing error: {}", exception.getMessage());
+
+ Map errorBody = new HashMap<>();
+ errorBody.put("message", "Erreur de traitement JSON");
+ errorBody.put("details", exception.getOriginalMessage() != null
+ ? exception.getOriginalMessage()
+ : exception.getMessage());
+ errorBody.put("status", 400);
+ errorBody.put("timestamp", java.time.LocalDateTime.now().toString());
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(errorBody)
+ .build();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/filter/HttpLoggingFilter.java b/src/main/java/dev/lions/unionflow/server/filter/HttpLoggingFilter.java
index 3487c95..264c9d1 100644
--- a/src/main/java/dev/lions/unionflow/server/filter/HttpLoggingFilter.java
+++ b/src/main/java/dev/lions/unionflow/server/filter/HttpLoggingFilter.java
@@ -1,154 +1,154 @@
-package dev.lions.unionflow.server.filter;
-
-import dev.lions.unionflow.server.service.SystemLoggingService;
-import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.container.ContainerRequestContext;
-import jakarta.ws.rs.container.ContainerRequestFilter;
-import jakarta.ws.rs.container.ContainerResponseContext;
-import jakarta.ws.rs.container.ContainerResponseFilter;
-import jakarta.ws.rs.core.SecurityContext;
-import jakarta.ws.rs.ext.Provider;
-import lombok.extern.slf4j.Slf4j;
-
-import java.io.IOException;
-import java.security.Principal;
-
-/**
- * Filtre JAX-RS pour capturer toutes les requêtes HTTP et les persister dans system_logs.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@Slf4j
-@Provider
-@Priority(1000)
-public class HttpLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
-
- private static final String REQUEST_START_TIME = "REQUEST_START_TIME";
- private static final String REQUEST_METHOD = "REQUEST_METHOD";
- private static final String REQUEST_PATH = "REQUEST_PATH";
-
- @Inject
- SystemLoggingService systemLoggingService;
-
- @Override
- public void filter(ContainerRequestContext requestContext) throws IOException {
- // Enregistrer le timestamp de début de requête
- requestContext.setProperty(REQUEST_START_TIME, System.currentTimeMillis());
- requestContext.setProperty(REQUEST_METHOD, requestContext.getMethod());
- requestContext.setProperty(REQUEST_PATH, requestContext.getUriInfo().getPath());
- }
-
- @Override
- public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
- try {
- // Calculer la durée de la requête
- Long startTime = (Long) requestContext.getProperty(REQUEST_START_TIME);
- long durationMs = startTime != null ? System.currentTimeMillis() - startTime : 0;
-
- // Récupérer les informations de la requête
- String method = (String) requestContext.getProperty(REQUEST_METHOD);
- String path = (String) requestContext.getProperty(REQUEST_PATH);
- int statusCode = responseContext.getStatus();
-
- // Récupérer l'utilisateur connecté
- String userId = extractUserId(requestContext);
-
- // Récupérer l'IP
- String ipAddress = extractIpAddress(requestContext);
-
- // Récupérer le sessionId (optionnel)
- String sessionId = extractSessionId(requestContext);
-
- // Ne logger que les endpoints API (ignorer /q/*, /static/*, etc.)
- if (shouldLog(path)) {
- systemLoggingService.logRequest(
- method,
- "/" + path,
- statusCode,
- userId,
- ipAddress,
- sessionId,
- durationMs
- );
- }
-
- } catch (Exception e) {
- // Ne jamais laisser le logging casser l'application
- log.error("Error in HttpLoggingFilter", e);
- }
- }
-
- /**
- * Extraire l'ID utilisateur depuis le contexte de sécurité
- */
- private String extractUserId(ContainerRequestContext requestContext) {
- SecurityContext securityContext = requestContext.getSecurityContext();
- if (securityContext != null) {
- Principal principal = securityContext.getUserPrincipal();
- if (principal != null) {
- return principal.getName();
- }
- }
- return "anonymous";
- }
-
- /**
- * Extraire l'adresse IP du client
- */
- private String extractIpAddress(ContainerRequestContext requestContext) {
- // Essayer d'abord les headers de proxy
- String xForwardedFor = requestContext.getHeaderString("X-Forwarded-For");
- if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
- // Prendre la première IP de la liste
- return xForwardedFor.split(",")[0].trim();
- }
-
- String xRealIp = requestContext.getHeaderString("X-Real-IP");
- if (xRealIp != null && !xRealIp.isEmpty()) {
- return xRealIp;
- }
-
- // Sinon retourner "unknown"
- return "unknown";
- }
-
- /**
- * Extraire le session ID (si disponible)
- */
- private String extractSessionId(ContainerRequestContext requestContext) {
- // Essayer de récupérer depuis les cookies ou headers
- String sessionId = requestContext.getHeaderString("X-Session-ID");
- if (sessionId != null && !sessionId.isEmpty()) {
- return sessionId;
- }
-
- // Par défaut, retourner null
- return null;
- }
-
- /**
- * Déterminer si on doit logger cette requête
- * Ignorer les endpoints techniques (health, metrics, swagger, etc.)
- */
- private boolean shouldLog(String path) {
- if (path == null) {
- return false;
- }
-
- // Ignorer les endpoints techniques Quarkus
- if (path.startsWith("q/")) {
- return false;
- }
-
- // Ignorer les ressources statiques
- if (path.startsWith("static/") || path.startsWith("webjars/")) {
- return false;
- }
-
- // Logger uniquement les endpoints API
- return path.startsWith("api/");
- }
-}
+package dev.lions.unionflow.server.filter;
+
+import dev.lions.unionflow.server.service.SystemLoggingService;
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.security.Principal;
+
+/**
+ * Filtre JAX-RS pour capturer toutes les requêtes HTTP et les persister dans system_logs.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@Slf4j
+@Provider
+@Priority(1000)
+public class HttpLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+ private static final String REQUEST_START_TIME = "REQUEST_START_TIME";
+ private static final String REQUEST_METHOD = "REQUEST_METHOD";
+ private static final String REQUEST_PATH = "REQUEST_PATH";
+
+ @Inject
+ SystemLoggingService systemLoggingService;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ // Enregistrer le timestamp de début de requête
+ requestContext.setProperty(REQUEST_START_TIME, System.currentTimeMillis());
+ requestContext.setProperty(REQUEST_METHOD, requestContext.getMethod());
+ requestContext.setProperty(REQUEST_PATH, requestContext.getUriInfo().getPath());
+ }
+
+ @Override
+ public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+ try {
+ // Calculer la durée de la requête
+ Long startTime = (Long) requestContext.getProperty(REQUEST_START_TIME);
+ long durationMs = startTime != null ? System.currentTimeMillis() - startTime : 0;
+
+ // Récupérer les informations de la requête
+ String method = (String) requestContext.getProperty(REQUEST_METHOD);
+ String path = (String) requestContext.getProperty(REQUEST_PATH);
+ int statusCode = responseContext.getStatus();
+
+ // Récupérer l'utilisateur connecté
+ String userId = extractUserId(requestContext);
+
+ // Récupérer l'IP
+ String ipAddress = extractIpAddress(requestContext);
+
+ // Récupérer le sessionId (optionnel)
+ String sessionId = extractSessionId(requestContext);
+
+ // Ne logger que les endpoints API (ignorer /q/*, /static/*, etc.)
+ if (shouldLog(path)) {
+ systemLoggingService.logRequest(
+ method,
+ "/" + path,
+ statusCode,
+ userId,
+ ipAddress,
+ sessionId,
+ durationMs
+ );
+ }
+
+ } catch (Exception e) {
+ // Ne jamais laisser le logging casser l'application
+ log.error("Error in HttpLoggingFilter", e);
+ }
+ }
+
+ /**
+ * Extraire l'ID utilisateur depuis le contexte de sécurité
+ */
+ private String extractUserId(ContainerRequestContext requestContext) {
+ SecurityContext securityContext = requestContext.getSecurityContext();
+ if (securityContext != null) {
+ Principal principal = securityContext.getUserPrincipal();
+ if (principal != null) {
+ return principal.getName();
+ }
+ }
+ return "anonymous";
+ }
+
+ /**
+ * Extraire l'adresse IP du client
+ */
+ private String extractIpAddress(ContainerRequestContext requestContext) {
+ // Essayer d'abord les headers de proxy
+ String xForwardedFor = requestContext.getHeaderString("X-Forwarded-For");
+ if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
+ // Prendre la première IP de la liste
+ return xForwardedFor.split(",")[0].trim();
+ }
+
+ String xRealIp = requestContext.getHeaderString("X-Real-IP");
+ if (xRealIp != null && !xRealIp.isEmpty()) {
+ return xRealIp;
+ }
+
+ // Sinon retourner "unknown"
+ return "unknown";
+ }
+
+ /**
+ * Extraire le session ID (si disponible)
+ */
+ private String extractSessionId(ContainerRequestContext requestContext) {
+ // Essayer de récupérer depuis les cookies ou headers
+ String sessionId = requestContext.getHeaderString("X-Session-ID");
+ if (sessionId != null && !sessionId.isEmpty()) {
+ return sessionId;
+ }
+
+ // Par défaut, retourner null
+ return null;
+ }
+
+ /**
+ * Déterminer si on doit logger cette requête
+ * Ignorer les endpoints techniques (health, metrics, swagger, etc.)
+ */
+ private boolean shouldLog(String path) {
+ if (path == null) {
+ return false;
+ }
+
+ // Ignorer les endpoints techniques Quarkus
+ if (path.startsWith("q/")) {
+ return false;
+ }
+
+ // Ignorer les ressources statiques
+ if (path.startsWith("static/") || path.startsWith("webjars/")) {
+ return false;
+ }
+
+ // Logger uniquement les endpoints API
+ return path.startsWith("api/");
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/DemandeAideMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/DemandeAideMapper.java
index 7f9cd3d..bf7fcdd 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/DemandeAideMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/DemandeAideMapper.java
@@ -1,131 +1,131 @@
-package dev.lions.unionflow.server.mapper;
-
-import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
-import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
-import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
-import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
-import dev.lions.unionflow.server.entity.DemandeAide;
-import dev.lions.unionflow.server.entity.Membre;
-import dev.lions.unionflow.server.entity.Organisation;
-import jakarta.enterprise.context.ApplicationScoped;
-
-/**
- * Mapper entre l'entité DemandeAide et le DTO DemandeAideDTO.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-01-31
- */
-@ApplicationScoped
-public class DemandeAideMapper {
-
- /**
- * Convertit une entité DemandeAide en DTO Response.
- */
- public DemandeAideResponse toDTO(DemandeAide entity) {
- if (entity == null) {
- return null;
- }
- DemandeAideResponse dto = new DemandeAideResponse();
- dto.setId(entity.getId());
- dto.setDateCreation(entity.getDateCreation());
- dto.setDateModification(entity.getDateModification());
- dto.setVersion(entity.getVersion() != null ? entity.getVersion() : 0L);
- dto.setActif(entity.getActif());
-
- dto.setTitre(entity.getTitre());
- dto.setDescription(entity.getDescription());
- dto.setTypeAide(entity.getTypeAide());
- dto.setStatut(entity.getStatut());
- dto.setMontantDemande(entity.getMontantDemande());
- dto.setMontantApprouve(entity.getMontantApprouve());
- dto.setJustification(entity.getJustification());
- dto.setCommentairesEvaluateur(entity.getCommentaireEvaluation());
- dto.setDocumentsJoints(entity.getDocumentsFournis());
-
- dto.setDateSoumission(entity.getDateDemande());
- dto.setDateEvaluation(entity.getDateEvaluation());
- dto.setDateVersement(entity.getDateVersement());
-
- dto.setPriorite(entity.getUrgence() != null && entity.getUrgence()
- ? PrioriteAide.URGENTE
- : PrioriteAide.NORMALE);
-
- if (entity.getDemandeur() != null) {
- dto.setMembreDemandeurId(entity.getDemandeur().getId());
- dto.setNomDemandeur(entity.getDemandeur().getPrenom() + " " + entity.getDemandeur().getNom());
- dto.setNumeroMembreDemandeur(entity.getDemandeur().getNumeroMembre());
- }
- if (entity.getEvaluateur() != null) {
- dto.setEvaluateurId(entity.getEvaluateur().getId().toString());
- dto.setEvaluateurNom(entity.getEvaluateur().getPrenom() + " " + entity.getEvaluateur().getNom());
- }
- if (entity.getOrganisation() != null) {
- dto.setAssociationId(entity.getOrganisation().getId());
- dto.setNomAssociation(entity.getOrganisation().getNom());
- }
-
- return dto;
- }
-
- /**
- * Met à jour une entité existante à partir du DTO UpdateRequest.
- */
- public void updateEntityFromDTO(DemandeAide entity, UpdateDemandeAideRequest request) {
- if (entity == null || request == null) {
- return;
- }
- if (request.titre() != null) {
- entity.setTitre(request.titre());
- }
- if (request.description() != null) {
- entity.setDescription(request.description());
- }
- if (request.typeAide() != null) {
- entity.setTypeAide(request.typeAide());
- }
- if (request.statut() != null) {
- entity.setStatut(request.statut());
- }
- entity.setMontantDemande(request.montantDemande());
- entity.setMontantApprouve(request.montantApprouve());
- entity.setJustification(request.justification());
- entity.setCommentaireEvaluation(request.commentairesEvaluateur());
- entity.setDocumentsFournis(request.documentsJoints());
- entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
- if (request.dateSoumission() != null) {
- entity.setDateDemande(request.dateSoumission());
- }
- entity.setDateEvaluation(request.dateEvaluation());
- entity.setDateVersement(request.dateVersement());
- }
-
- /**
- * Crée une nouvelle entité à partir du DTO CreateRequest.
- */
- public DemandeAide toEntity(
- CreateDemandeAideRequest request,
- Membre demandeur,
- Membre evaluateur,
- Organisation organisation) {
- if (request == null) {
- return null;
- }
- DemandeAide entity = new DemandeAide();
- entity.setTitre(request.titre());
- entity.setDescription(request.description());
- entity.setTypeAide(request.typeAide());
- entity.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE);
- entity.setMontantDemande(request.montantDemande());
- entity.setJustification(request.justification());
- entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
-
- entity.setDateDemande(java.time.LocalDateTime.now());
-
- entity.setDemandeur(demandeur);
- entity.setEvaluateur(evaluateur);
- entity.setOrganisation(organisation);
-
- return entity;
- }
-}
+package dev.lions.unionflow.server.mapper;
+
+import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
+import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
+import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
+import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
+import dev.lions.unionflow.server.entity.DemandeAide;
+import dev.lions.unionflow.server.entity.Membre;
+import dev.lions.unionflow.server.entity.Organisation;
+import jakarta.enterprise.context.ApplicationScoped;
+
+/**
+ * Mapper entre l'entité DemandeAide et le DTO DemandeAideDTO.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-01-31
+ */
+@ApplicationScoped
+public class DemandeAideMapper {
+
+ /**
+ * Convertit une entité DemandeAide en DTO Response.
+ */
+ public DemandeAideResponse toDTO(DemandeAide entity) {
+ if (entity == null) {
+ return null;
+ }
+ DemandeAideResponse dto = new DemandeAideResponse();
+ dto.setId(entity.getId());
+ dto.setDateCreation(entity.getDateCreation());
+ dto.setDateModification(entity.getDateModification());
+ dto.setVersion(entity.getVersion() != null ? entity.getVersion() : 0L);
+ dto.setActif(entity.getActif());
+
+ dto.setTitre(entity.getTitre());
+ dto.setDescription(entity.getDescription());
+ dto.setTypeAide(entity.getTypeAide());
+ dto.setStatut(entity.getStatut());
+ dto.setMontantDemande(entity.getMontantDemande());
+ dto.setMontantApprouve(entity.getMontantApprouve());
+ dto.setJustification(entity.getJustification());
+ dto.setCommentairesEvaluateur(entity.getCommentaireEvaluation());
+ dto.setDocumentsJoints(entity.getDocumentsFournis());
+
+ dto.setDateSoumission(entity.getDateDemande());
+ dto.setDateEvaluation(entity.getDateEvaluation());
+ dto.setDateVersement(entity.getDateVersement());
+
+ dto.setPriorite(entity.getUrgence() != null && entity.getUrgence()
+ ? PrioriteAide.URGENTE
+ : PrioriteAide.NORMALE);
+
+ if (entity.getDemandeur() != null) {
+ dto.setMembreDemandeurId(entity.getDemandeur().getId());
+ dto.setNomDemandeur(entity.getDemandeur().getPrenom() + " " + entity.getDemandeur().getNom());
+ dto.setNumeroMembreDemandeur(entity.getDemandeur().getNumeroMembre());
+ }
+ if (entity.getEvaluateur() != null) {
+ dto.setEvaluateurId(entity.getEvaluateur().getId().toString());
+ dto.setEvaluateurNom(entity.getEvaluateur().getPrenom() + " " + entity.getEvaluateur().getNom());
+ }
+ if (entity.getOrganisation() != null) {
+ dto.setAssociationId(entity.getOrganisation().getId());
+ dto.setNomAssociation(entity.getOrganisation().getNom());
+ }
+
+ return dto;
+ }
+
+ /**
+ * Met à jour une entité existante à partir du DTO UpdateRequest.
+ */
+ public void updateEntityFromDTO(DemandeAide entity, UpdateDemandeAideRequest request) {
+ if (entity == null || request == null) {
+ return;
+ }
+ if (request.titre() != null) {
+ entity.setTitre(request.titre());
+ }
+ if (request.description() != null) {
+ entity.setDescription(request.description());
+ }
+ if (request.typeAide() != null) {
+ entity.setTypeAide(request.typeAide());
+ }
+ if (request.statut() != null) {
+ entity.setStatut(request.statut());
+ }
+ entity.setMontantDemande(request.montantDemande());
+ entity.setMontantApprouve(request.montantApprouve());
+ entity.setJustification(request.justification());
+ entity.setCommentaireEvaluation(request.commentairesEvaluateur());
+ entity.setDocumentsFournis(request.documentsJoints());
+ entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
+ if (request.dateSoumission() != null) {
+ entity.setDateDemande(request.dateSoumission());
+ }
+ entity.setDateEvaluation(request.dateEvaluation());
+ entity.setDateVersement(request.dateVersement());
+ }
+
+ /**
+ * Crée une nouvelle entité à partir du DTO CreateRequest.
+ */
+ public DemandeAide toEntity(
+ CreateDemandeAideRequest request,
+ Membre demandeur,
+ Membre evaluateur,
+ Organisation organisation) {
+ if (request == null) {
+ return null;
+ }
+ DemandeAide entity = new DemandeAide();
+ entity.setTitre(request.titre());
+ entity.setDescription(request.description());
+ entity.setTypeAide(request.typeAide());
+ entity.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE);
+ entity.setMontantDemande(request.montantDemande());
+ entity.setJustification(request.justification());
+ entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
+
+ entity.setDateDemande(java.time.LocalDateTime.now());
+
+ entity.setDemandeur(demandeur);
+ entity.setEvaluateur(evaluateur);
+ entity.setOrganisation(organisation);
+
+ return entity;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/agricole/CampagneAgricoleMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/agricole/CampagneAgricoleMapper.java
index 9f15670..c2c90e1 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/agricole/CampagneAgricoleMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/agricole/CampagneAgricoleMapper.java
@@ -1,34 +1,34 @@
-package dev.lions.unionflow.server.mapper.agricole;
-
-import dev.lions.unionflow.server.api.dto.agricole.CampagneAgricoleDTO;
-import dev.lions.unionflow.server.entity.agricole.CampagneAgricole;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface CampagneAgricoleMapper {
-
- @Mapping(target = "organisationCoopId", source = "organisation.id")
- CampagneAgricoleDTO toDto(CampagneAgricole entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- CampagneAgricole toEntity(CampagneAgricoleDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- void updateEntityFromDto(CampagneAgricoleDTO dto, @MappingTarget CampagneAgricole entity);
-}
+package dev.lions.unionflow.server.mapper.agricole;
+
+import dev.lions.unionflow.server.api.dto.agricole.CampagneAgricoleDTO;
+import dev.lions.unionflow.server.entity.agricole.CampagneAgricole;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface CampagneAgricoleMapper {
+
+ @Mapping(target = "organisationCoopId", source = "organisation.id")
+ CampagneAgricoleDTO toDto(CampagneAgricole entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ CampagneAgricole toEntity(CampagneAgricoleDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ void updateEntityFromDto(CampagneAgricoleDTO dto, @MappingTarget CampagneAgricole entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/CampagneCollecteMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/CampagneCollecteMapper.java
index b609cdb..2991b3a 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/CampagneCollecteMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/CampagneCollecteMapper.java
@@ -1,28 +1,28 @@
-package dev.lions.unionflow.server.mapper.collectefonds;
-
-import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse;
-import dev.lions.unionflow.server.entity.collectefonds.CampagneCollecte;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface CampagneCollecteMapper {
-
- @Mapping(target = "organisationId", source = "organisation.id")
- CampagneCollecteResponse toDto(CampagneCollecte entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "montantCollecteActuel", ignore = true)
- @Mapping(target = "nombreDonateurs", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateOuverture", ignore = true)
- void updateEntityFromDto(CampagneCollecteResponse dto, @MappingTarget CampagneCollecte entity);
-}
+package dev.lions.unionflow.server.mapper.collectefonds;
+
+import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse;
+import dev.lions.unionflow.server.entity.collectefonds.CampagneCollecte;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface CampagneCollecteMapper {
+
+ @Mapping(target = "organisationId", source = "organisation.id")
+ CampagneCollecteResponse toDto(CampagneCollecte entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "montantCollecteActuel", ignore = true)
+ @Mapping(target = "nombreDonateurs", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateOuverture", ignore = true)
+ void updateEntityFromDto(CampagneCollecteResponse dto, @MappingTarget CampagneCollecte entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/ContributionCollecteMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/ContributionCollecteMapper.java
index 3cb83b8..8cb64d1 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/ContributionCollecteMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/collectefonds/ContributionCollecteMapper.java
@@ -1,37 +1,37 @@
-package dev.lions.unionflow.server.mapper.collectefonds;
-
-import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO;
-import dev.lions.unionflow.server.entity.collectefonds.ContributionCollecte;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface ContributionCollecteMapper {
-
- @Mapping(target = "campagneId", source = "campagne.id")
- @Mapping(target = "membreDonateurId", source = "membreDonateur.id")
- ContributionCollecteDTO toDto(ContributionCollecte entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "campagne", ignore = true)
- @Mapping(target = "membreDonateur", ignore = true)
- ContributionCollecte toEntity(ContributionCollecteDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "campagne", ignore = true)
- @Mapping(target = "membreDonateur", ignore = true)
- void updateEntityFromDto(ContributionCollecteDTO dto, @MappingTarget ContributionCollecte entity);
-}
+package dev.lions.unionflow.server.mapper.collectefonds;
+
+import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO;
+import dev.lions.unionflow.server.entity.collectefonds.ContributionCollecte;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface ContributionCollecteMapper {
+
+ @Mapping(target = "campagneId", source = "campagne.id")
+ @Mapping(target = "membreDonateurId", source = "membreDonateur.id")
+ ContributionCollecteDTO toDto(ContributionCollecte entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "campagne", ignore = true)
+ @Mapping(target = "membreDonateur", ignore = true)
+ ContributionCollecte toEntity(ContributionCollecteDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "campagne", ignore = true)
+ @Mapping(target = "membreDonateur", ignore = true)
+ void updateEntityFromDto(ContributionCollecteDTO dto, @MappingTarget ContributionCollecte entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/culte/DonReligieuxMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/culte/DonReligieuxMapper.java
index 61db0e1..d93e77b 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/culte/DonReligieuxMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/culte/DonReligieuxMapper.java
@@ -1,37 +1,37 @@
-package dev.lions.unionflow.server.mapper.culte;
-
-import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO;
-import dev.lions.unionflow.server.entity.culte.DonReligieux;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface DonReligieuxMapper {
-
- @Mapping(target = "institutionId", source = "institution.id")
- @Mapping(target = "fideleId", source = "fidele.id")
- DonReligieuxDTO toDto(DonReligieux entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "institution", ignore = true)
- @Mapping(target = "fidele", ignore = true)
- DonReligieux toEntity(DonReligieuxDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "institution", ignore = true)
- @Mapping(target = "fidele", ignore = true)
- void updateEntityFromDto(DonReligieuxDTO dto, @MappingTarget DonReligieux entity);
-}
+package dev.lions.unionflow.server.mapper.culte;
+
+import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO;
+import dev.lions.unionflow.server.entity.culte.DonReligieux;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface DonReligieuxMapper {
+
+ @Mapping(target = "institutionId", source = "institution.id")
+ @Mapping(target = "fideleId", source = "fidele.id")
+ DonReligieuxDTO toDto(DonReligieux entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "institution", ignore = true)
+ @Mapping(target = "fidele", ignore = true)
+ DonReligieux toEntity(DonReligieuxDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "institution", ignore = true)
+ @Mapping(target = "fidele", ignore = true)
+ void updateEntityFromDto(DonReligieuxDTO dto, @MappingTarget DonReligieux entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/gouvernance/EchelonOrganigrammeMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/gouvernance/EchelonOrganigrammeMapper.java
index 6120887..084b6fe 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/gouvernance/EchelonOrganigrammeMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/gouvernance/EchelonOrganigrammeMapper.java
@@ -1,37 +1,37 @@
-package dev.lions.unionflow.server.mapper.gouvernance;
-
-import dev.lions.unionflow.server.api.dto.gouvernance.EchelonOrganigrammeDTO;
-import dev.lions.unionflow.server.entity.gouvernance.EchelonOrganigramme;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface EchelonOrganigrammeMapper {
-
- @Mapping(target = "organisationId", source = "organisation.id")
- @Mapping(target = "echelonParentId", source = "echelonParent.id")
- EchelonOrganigrammeDTO toDto(EchelonOrganigramme entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "echelonParent", ignore = true)
- EchelonOrganigramme toEntity(EchelonOrganigrammeDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "echelonParent", ignore = true)
- void updateEntityFromDto(EchelonOrganigrammeDTO dto, @MappingTarget EchelonOrganigramme entity);
-}
+package dev.lions.unionflow.server.mapper.gouvernance;
+
+import dev.lions.unionflow.server.api.dto.gouvernance.EchelonOrganigrammeDTO;
+import dev.lions.unionflow.server.entity.gouvernance.EchelonOrganigramme;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface EchelonOrganigrammeMapper {
+
+ @Mapping(target = "organisationId", source = "organisation.id")
+ @Mapping(target = "echelonParentId", source = "echelonParent.id")
+ EchelonOrganigrammeDTO toDto(EchelonOrganigramme entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "echelonParent", ignore = true)
+ EchelonOrganigramme toEntity(EchelonOrganigrammeDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "echelonParent", ignore = true)
+ void updateEntityFromDto(EchelonOrganigrammeDTO dto, @MappingTarget EchelonOrganigramme entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/DemandeCreditMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/DemandeCreditMapper.java
index 2ac90df..169a13c 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/DemandeCreditMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/DemandeCreditMapper.java
@@ -1,65 +1,65 @@
-package dev.lions.unionflow.server.mapper.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditRequest;
-import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse;
-import dev.lions.unionflow.server.entity.mutuelle.credit.DemandeCredit;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "jakarta-cdi", uses = {
- EcheanceCreditMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface DemandeCreditMapper {
-
- @Mapping(target = "membreId", source = "membre.id")
- @Mapping(target = "compteLieId", source = "compteLie.id")
- DemandeCreditResponse toDto(DemandeCredit entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "compteLie", ignore = true)
- @Mapping(target = "echeancier", ignore = true)
- @Mapping(target = "garanties", ignore = true)
- @Mapping(target = "numeroDossier", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateSoumission", ignore = true)
- @Mapping(target = "dateValidation", ignore = true)
- @Mapping(target = "notesComite", ignore = true)
- @Mapping(target = "dureeMoisDemande", source = "dureeMois")
- @Mapping(target = "montantApprouve", ignore = true)
- @Mapping(target = "dureeMoisApprouvee", ignore = true)
- @Mapping(target = "tauxInteretAnnuel", ignore = true)
- @Mapping(target = "coutTotalCredit", ignore = true)
- @Mapping(target = "datePremierEcheance", ignore = true)
- DemandeCredit toEntity(DemandeCreditRequest request);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "compteLie", ignore = true)
- @Mapping(target = "echeancier", ignore = true)
- @Mapping(target = "garanties", ignore = true)
- @Mapping(target = "numeroDossier", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateSoumission", ignore = true)
- @Mapping(target = "dateValidation", ignore = true)
- @Mapping(target = "notesComite", ignore = true)
- @Mapping(target = "dureeMoisDemande", ignore = true)
- @Mapping(target = "montantApprouve", ignore = true)
- @Mapping(target = "dureeMoisApprouvee", ignore = true)
- @Mapping(target = "tauxInteretAnnuel", ignore = true)
- @Mapping(target = "coutTotalCredit", ignore = true)
- @Mapping(target = "datePremierEcheance", ignore = true)
- void updateEntityFromDto(DemandeCreditRequest request, @MappingTarget DemandeCredit entity);
-}
+package dev.lions.unionflow.server.mapper.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditRequest;
+import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse;
+import dev.lions.unionflow.server.entity.mutuelle.credit.DemandeCredit;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "jakarta-cdi", uses = {
+ EcheanceCreditMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface DemandeCreditMapper {
+
+ @Mapping(target = "membreId", source = "membre.id")
+ @Mapping(target = "compteLieId", source = "compteLie.id")
+ DemandeCreditResponse toDto(DemandeCredit entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "compteLie", ignore = true)
+ @Mapping(target = "echeancier", ignore = true)
+ @Mapping(target = "garanties", ignore = true)
+ @Mapping(target = "numeroDossier", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateSoumission", ignore = true)
+ @Mapping(target = "dateValidation", ignore = true)
+ @Mapping(target = "notesComite", ignore = true)
+ @Mapping(target = "dureeMoisDemande", source = "dureeMois")
+ @Mapping(target = "montantApprouve", ignore = true)
+ @Mapping(target = "dureeMoisApprouvee", ignore = true)
+ @Mapping(target = "tauxInteretAnnuel", ignore = true)
+ @Mapping(target = "coutTotalCredit", ignore = true)
+ @Mapping(target = "datePremierEcheance", ignore = true)
+ DemandeCredit toEntity(DemandeCreditRequest request);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "compteLie", ignore = true)
+ @Mapping(target = "echeancier", ignore = true)
+ @Mapping(target = "garanties", ignore = true)
+ @Mapping(target = "numeroDossier", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateSoumission", ignore = true)
+ @Mapping(target = "dateValidation", ignore = true)
+ @Mapping(target = "notesComite", ignore = true)
+ @Mapping(target = "dureeMoisDemande", ignore = true)
+ @Mapping(target = "montantApprouve", ignore = true)
+ @Mapping(target = "dureeMoisApprouvee", ignore = true)
+ @Mapping(target = "tauxInteretAnnuel", ignore = true)
+ @Mapping(target = "coutTotalCredit", ignore = true)
+ @Mapping(target = "datePremierEcheance", ignore = true)
+ void updateEntityFromDto(DemandeCreditRequest request, @MappingTarget DemandeCredit entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/EcheanceCreditMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/EcheanceCreditMapper.java
index d85cb7c..f8b8ab6 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/EcheanceCreditMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/EcheanceCreditMapper.java
@@ -1,34 +1,34 @@
-package dev.lions.unionflow.server.mapper.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.dto.mutuelle.credit.EcheanceCreditDTO;
-import dev.lions.unionflow.server.entity.mutuelle.credit.EcheanceCredit;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface EcheanceCreditMapper {
-
- @Mapping(target = "demandeCreditId", source = "demandeCredit.id")
- EcheanceCreditDTO toDto(EcheanceCredit entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "demandeCredit", ignore = true)
- EcheanceCredit toEntity(EcheanceCreditDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "demandeCredit", ignore = true)
- void updateEntityFromDto(EcheanceCreditDTO dto, @MappingTarget EcheanceCredit entity);
-}
+package dev.lions.unionflow.server.mapper.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.dto.mutuelle.credit.EcheanceCreditDTO;
+import dev.lions.unionflow.server.entity.mutuelle.credit.EcheanceCredit;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface EcheanceCreditMapper {
+
+ @Mapping(target = "demandeCreditId", source = "demandeCredit.id")
+ EcheanceCreditDTO toDto(EcheanceCredit entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "demandeCredit", ignore = true)
+ EcheanceCredit toEntity(EcheanceCreditDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "demandeCredit", ignore = true)
+ void updateEntityFromDto(EcheanceCreditDTO dto, @MappingTarget EcheanceCredit entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/GarantieDemandeMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/GarantieDemandeMapper.java
index 3b0575d..5b349bf 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/GarantieDemandeMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/credit/GarantieDemandeMapper.java
@@ -1,33 +1,33 @@
-package dev.lions.unionflow.server.mapper.mutuelle.credit;
-
-import dev.lions.unionflow.server.api.dto.mutuelle.credit.GarantieDemandeDTO;
-import dev.lions.unionflow.server.entity.mutuelle.credit.GarantieDemande;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface GarantieDemandeMapper {
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "demandeCredit", ignore = true)
- GarantieDemande toEntity(GarantieDemandeDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "demandeCredit", ignore = true)
- void updateEntityFromDto(GarantieDemandeDTO dto, @MappingTarget GarantieDemande entity);
-
- GarantieDemandeDTO toDto(GarantieDemande entity);
-}
+package dev.lions.unionflow.server.mapper.mutuelle.credit;
+
+import dev.lions.unionflow.server.api.dto.mutuelle.credit.GarantieDemandeDTO;
+import dev.lions.unionflow.server.entity.mutuelle.credit.GarantieDemande;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface GarantieDemandeMapper {
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "demandeCredit", ignore = true)
+ GarantieDemande toEntity(GarantieDemandeDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "demandeCredit", ignore = true)
+ void updateEntityFromDto(GarantieDemandeDTO dto, @MappingTarget GarantieDemande entity);
+
+ GarantieDemandeDTO toDto(GarantieDemande entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/CompteEpargneMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/CompteEpargneMapper.java
index c753c3c..767b102 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/CompteEpargneMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/CompteEpargneMapper.java
@@ -1,52 +1,52 @@
-package dev.lions.unionflow.server.mapper.mutuelle.epargne;
-
-import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneRequest;
-import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse;
-import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface CompteEpargneMapper {
-
- @Mapping(target = "membreId", source = "membre.id")
- @Mapping(target = "organisationId", source = "organisation.id")
- CompteEpargneResponse toDto(CompteEpargne entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "numeroCompte", ignore = true)
- @Mapping(target = "soldeActuel", ignore = true)
- @Mapping(target = "soldeBloque", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateOuverture", ignore = true)
- @Mapping(target = "dateDerniereTransaction", ignore = true)
- @Mapping(target = "description", source = "notesOuverture")
- CompteEpargne toEntity(CompteEpargneRequest request);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "numeroCompte", ignore = true)
- @Mapping(target = "soldeActuel", ignore = true)
- @Mapping(target = "soldeBloque", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateOuverture", ignore = true)
- @Mapping(target = "dateDerniereTransaction", ignore = true)
- @Mapping(target = "description", source = "notesOuverture")
- void updateEntityFromDto(CompteEpargneRequest request, @MappingTarget CompteEpargne entity);
-}
+package dev.lions.unionflow.server.mapper.mutuelle.epargne;
+
+import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneRequest;
+import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse;
+import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface CompteEpargneMapper {
+
+ @Mapping(target = "membreId", source = "membre.id")
+ @Mapping(target = "organisationId", source = "organisation.id")
+ CompteEpargneResponse toDto(CompteEpargne entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "numeroCompte", ignore = true)
+ @Mapping(target = "soldeActuel", ignore = true)
+ @Mapping(target = "soldeBloque", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateOuverture", ignore = true)
+ @Mapping(target = "dateDerniereTransaction", ignore = true)
+ @Mapping(target = "description", source = "notesOuverture")
+ CompteEpargne toEntity(CompteEpargneRequest request);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "numeroCompte", ignore = true)
+ @Mapping(target = "soldeActuel", ignore = true)
+ @Mapping(target = "soldeBloque", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateOuverture", ignore = true)
+ @Mapping(target = "dateDerniereTransaction", ignore = true)
+ @Mapping(target = "description", source = "notesOuverture")
+ void updateEntityFromDto(CompteEpargneRequest request, @MappingTarget CompteEpargne entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/TransactionEpargneMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/TransactionEpargneMapper.java
index 9372d6b..c080148 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/TransactionEpargneMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/mutuelle/epargne/TransactionEpargneMapper.java
@@ -1,54 +1,54 @@
-package dev.lions.unionflow.server.mapper.mutuelle.epargne;
-
-import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
-import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse;
-import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface TransactionEpargneMapper {
-
- @Mapping(target = "compteId", source = "compte.id")
- @Mapping(target = "pieceJustificativeId", expression = "java(entity.getPieceJustificativeId() != null ? entity.getPieceJustificativeId().toString() : null)")
- TransactionEpargneResponse toDto(TransactionEpargne entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "compte", ignore = true)
- @Mapping(target = "type", source = "typeTransaction")
- @Mapping(target = "soldeAvant", ignore = true)
- @Mapping(target = "soldeApres", ignore = true)
- @Mapping(target = "dateTransaction", ignore = true)
- @Mapping(target = "operateurId", ignore = true)
- @Mapping(target = "referenceExterne", ignore = true)
- @Mapping(target = "statutExecution", ignore = true)
- @Mapping(target = "origineFonds", source = "origineFonds")
- @Mapping(target = "pieceJustificativeId", ignore = true)
- TransactionEpargne toEntity(TransactionEpargneRequest request);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "compte", ignore = true)
- @Mapping(target = "type", source = "typeTransaction")
- @Mapping(target = "soldeAvant", ignore = true)
- @Mapping(target = "soldeApres", ignore = true)
- @Mapping(target = "dateTransaction", ignore = true)
- @Mapping(target = "operateurId", ignore = true)
- @Mapping(target = "referenceExterne", ignore = true)
- @Mapping(target = "statutExecution", ignore = true)
- @Mapping(target = "origineFonds", source = "origineFonds")
- @Mapping(target = "pieceJustificativeId", ignore = true)
- void updateEntityFromDto(TransactionEpargneRequest request, @MappingTarget TransactionEpargne entity);
-}
+package dev.lions.unionflow.server.mapper.mutuelle.epargne;
+
+import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
+import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse;
+import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface TransactionEpargneMapper {
+
+ @Mapping(target = "compteId", source = "compte.id")
+ @Mapping(target = "pieceJustificativeId", expression = "java(entity.getPieceJustificativeId() != null ? entity.getPieceJustificativeId().toString() : null)")
+ TransactionEpargneResponse toDto(TransactionEpargne entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "compte", ignore = true)
+ @Mapping(target = "type", source = "typeTransaction")
+ @Mapping(target = "soldeAvant", ignore = true)
+ @Mapping(target = "soldeApres", ignore = true)
+ @Mapping(target = "dateTransaction", ignore = true)
+ @Mapping(target = "operateurId", ignore = true)
+ @Mapping(target = "referenceExterne", ignore = true)
+ @Mapping(target = "statutExecution", ignore = true)
+ @Mapping(target = "origineFonds", source = "origineFonds")
+ @Mapping(target = "pieceJustificativeId", ignore = true)
+ TransactionEpargne toEntity(TransactionEpargneRequest request);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "compte", ignore = true)
+ @Mapping(target = "type", source = "typeTransaction")
+ @Mapping(target = "soldeAvant", ignore = true)
+ @Mapping(target = "soldeApres", ignore = true)
+ @Mapping(target = "dateTransaction", ignore = true)
+ @Mapping(target = "operateurId", ignore = true)
+ @Mapping(target = "referenceExterne", ignore = true)
+ @Mapping(target = "statutExecution", ignore = true)
+ @Mapping(target = "origineFonds", source = "origineFonds")
+ @Mapping(target = "pieceJustificativeId", ignore = true)
+ void updateEntityFromDto(TransactionEpargneRequest request, @MappingTarget TransactionEpargne entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/ong/ProjetOngMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/ong/ProjetOngMapper.java
index 6a0e884..3042448 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/ong/ProjetOngMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/ong/ProjetOngMapper.java
@@ -1,38 +1,38 @@
-package dev.lions.unionflow.server.mapper.ong;
-
-import dev.lions.unionflow.server.api.dto.ong.ProjetOngDTO;
-import dev.lions.unionflow.server.entity.ong.ProjetOng;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface ProjetOngMapper {
-
- @Mapping(target = "organisationId", source = "organisation.id")
- ProjetOngDTO toDto(ProjetOng entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "depensesReelles", ignore = true)
- @Mapping(target = "statut", ignore = true)
- ProjetOng toEntity(ProjetOngDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "depensesReelles", ignore = true)
- @Mapping(target = "statut", ignore = true)
- void updateEntityFromDto(ProjetOngDTO dto, @MappingTarget ProjetOng entity);
-}
+package dev.lions.unionflow.server.mapper.ong;
+
+import dev.lions.unionflow.server.api.dto.ong.ProjetOngDTO;
+import dev.lions.unionflow.server.entity.ong.ProjetOng;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface ProjetOngMapper {
+
+ @Mapping(target = "organisationId", source = "organisation.id")
+ ProjetOngDTO toDto(ProjetOng entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "depensesReelles", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ ProjetOng toEntity(ProjetOngDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "depensesReelles", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ void updateEntityFromDto(ProjetOngDTO dto, @MappingTarget ProjetOng entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/registre/AgrementProfessionnelMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/registre/AgrementProfessionnelMapper.java
index b9c451e..4aa2832 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/registre/AgrementProfessionnelMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/registre/AgrementProfessionnelMapper.java
@@ -1,37 +1,37 @@
-package dev.lions.unionflow.server.mapper.registre;
-
-import dev.lions.unionflow.server.api.dto.registre.AgrementProfessionnelDTO;
-import dev.lions.unionflow.server.entity.registre.AgrementProfessionnel;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface AgrementProfessionnelMapper {
-
- @Mapping(target = "membreId", source = "membre.id")
- @Mapping(target = "organisationId", source = "organisation.id")
- AgrementProfessionnelDTO toDto(AgrementProfessionnel entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- AgrementProfessionnel toEntity(AgrementProfessionnelDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "membre", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- void updateEntityFromDto(AgrementProfessionnelDTO dto, @MappingTarget AgrementProfessionnel entity);
-}
+package dev.lions.unionflow.server.mapper.registre;
+
+import dev.lions.unionflow.server.api.dto.registre.AgrementProfessionnelDTO;
+import dev.lions.unionflow.server.entity.registre.AgrementProfessionnel;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface AgrementProfessionnelMapper {
+
+ @Mapping(target = "membreId", source = "membre.id")
+ @Mapping(target = "organisationId", source = "organisation.id")
+ AgrementProfessionnelDTO toDto(AgrementProfessionnel entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ AgrementProfessionnel toEntity(AgrementProfessionnelDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "membre", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ void updateEntityFromDto(AgrementProfessionnelDTO dto, @MappingTarget AgrementProfessionnel entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/tontine/TontineMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/tontine/TontineMapper.java
index 5519035..6d5485b 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/tontine/TontineMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/tontine/TontineMapper.java
@@ -1,46 +1,46 @@
-package dev.lions.unionflow.server.mapper.tontine;
-
-import dev.lions.unionflow.server.api.dto.tontine.TontineRequest;
-import dev.lions.unionflow.server.api.dto.tontine.TontineResponse;
-import dev.lions.unionflow.server.entity.tontine.Tontine;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "jakarta-cdi", uses = {
- TourTontineMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface TontineMapper {
-
- @Mapping(target = "organisationId", source = "organisation.id")
- @Mapping(target = "nombreParticipantsActuels", ignore = true)
- @Mapping(target = "fondTotalCollecte", ignore = true)
- TontineResponse toDto(Tontine entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "calendrierTours", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateDebutEffective", ignore = true)
- @Mapping(target = "dateFinPrevue", ignore = true)
- Tontine toEntity(TontineRequest request);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "calendrierTours", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "dateDebutEffective", ignore = true)
- @Mapping(target = "dateFinPrevue", ignore = true)
- void updateEntityFromDto(TontineRequest request, @MappingTarget Tontine entity);
-}
+package dev.lions.unionflow.server.mapper.tontine;
+
+import dev.lions.unionflow.server.api.dto.tontine.TontineRequest;
+import dev.lions.unionflow.server.api.dto.tontine.TontineResponse;
+import dev.lions.unionflow.server.entity.tontine.Tontine;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "jakarta-cdi", uses = {
+ TourTontineMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface TontineMapper {
+
+ @Mapping(target = "organisationId", source = "organisation.id")
+ @Mapping(target = "nombreParticipantsActuels", ignore = true)
+ @Mapping(target = "fondTotalCollecte", ignore = true)
+ TontineResponse toDto(Tontine entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "calendrierTours", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateDebutEffective", ignore = true)
+ @Mapping(target = "dateFinPrevue", ignore = true)
+ Tontine toEntity(TontineRequest request);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "calendrierTours", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "dateDebutEffective", ignore = true)
+ @Mapping(target = "dateFinPrevue", ignore = true)
+ void updateEntityFromDto(TontineRequest request, @MappingTarget Tontine entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/tontine/TourTontineMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/tontine/TourTontineMapper.java
index b522eaf..57d2593 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/tontine/TourTontineMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/tontine/TourTontineMapper.java
@@ -1,37 +1,37 @@
-package dev.lions.unionflow.server.mapper.tontine;
-
-import dev.lions.unionflow.server.api.dto.tontine.TourTontineDTO;
-import dev.lions.unionflow.server.entity.tontine.TourTontine;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface TourTontineMapper {
-
- @Mapping(target = "tontineId", source = "tontine.id")
- @Mapping(target = "membreBeneficiaireId", source = "membreBeneficiaire.id")
- TourTontineDTO toDto(TourTontine entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "tontine", ignore = true)
- @Mapping(target = "membreBeneficiaire", ignore = true)
- TourTontine toEntity(TourTontineDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "tontine", ignore = true)
- @Mapping(target = "membreBeneficiaire", ignore = true)
- void updateEntityFromDto(TourTontineDTO dto, @MappingTarget TourTontine entity);
-}
+package dev.lions.unionflow.server.mapper.tontine;
+
+import dev.lions.unionflow.server.api.dto.tontine.TourTontineDTO;
+import dev.lions.unionflow.server.entity.tontine.TourTontine;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface TourTontineMapper {
+
+ @Mapping(target = "tontineId", source = "tontine.id")
+ @Mapping(target = "membreBeneficiaireId", source = "membreBeneficiaire.id")
+ TourTontineDTO toDto(TourTontine entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "tontine", ignore = true)
+ @Mapping(target = "membreBeneficiaire", ignore = true)
+ TourTontine toEntity(TourTontineDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "tontine", ignore = true)
+ @Mapping(target = "membreBeneficiaire", ignore = true)
+ void updateEntityFromDto(TourTontineDTO dto, @MappingTarget TourTontine entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/vote/CampagneVoteMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/vote/CampagneVoteMapper.java
index 0d42529..28d4af2 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/vote/CampagneVoteMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/vote/CampagneVoteMapper.java
@@ -1,48 +1,48 @@
-package dev.lions.unionflow.server.mapper.vote;
-
-import dev.lions.unionflow.server.api.dto.vote.CampagneVoteRequest;
-import dev.lions.unionflow.server.api.dto.vote.CampagneVoteResponse;
-import dev.lions.unionflow.server.entity.vote.CampagneVote;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "jakarta-cdi", uses = {
- CandidatMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface CampagneVoteMapper {
-
- @Mapping(target = "organisationId", source = "organisation.id")
- @Mapping(target = "candidatsExposes", source = "candidats")
- @Mapping(target = "tauxDeParticipation", ignore = true)
- CampagneVoteResponse toDto(CampagneVote entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "candidats", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "totalElecteursInscrits", ignore = true)
- @Mapping(target = "totalVotantsEffectifs", ignore = true)
- @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
- CampagneVote toEntity(CampagneVoteRequest request);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "organisation", ignore = true)
- @Mapping(target = "candidats", ignore = true)
- @Mapping(target = "statut", ignore = true)
- @Mapping(target = "totalElecteursInscrits", ignore = true)
- @Mapping(target = "totalVotantsEffectifs", ignore = true)
- @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
- void updateEntityFromDto(CampagneVoteRequest request, @MappingTarget CampagneVote entity);
-}
+package dev.lions.unionflow.server.mapper.vote;
+
+import dev.lions.unionflow.server.api.dto.vote.CampagneVoteRequest;
+import dev.lions.unionflow.server.api.dto.vote.CampagneVoteResponse;
+import dev.lions.unionflow.server.entity.vote.CampagneVote;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "jakarta-cdi", uses = {
+ CandidatMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface CampagneVoteMapper {
+
+ @Mapping(target = "organisationId", source = "organisation.id")
+ @Mapping(target = "candidatsExposes", source = "candidats")
+ @Mapping(target = "tauxDeParticipation", ignore = true)
+ CampagneVoteResponse toDto(CampagneVote entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "candidats", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "totalElecteursInscrits", ignore = true)
+ @Mapping(target = "totalVotantsEffectifs", ignore = true)
+ @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
+ CampagneVote toEntity(CampagneVoteRequest request);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "organisation", ignore = true)
+ @Mapping(target = "candidats", ignore = true)
+ @Mapping(target = "statut", ignore = true)
+ @Mapping(target = "totalElecteursInscrits", ignore = true)
+ @Mapping(target = "totalVotantsEffectifs", ignore = true)
+ @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
+ void updateEntityFromDto(CampagneVoteRequest request, @MappingTarget CampagneVote entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/mapper/vote/CandidatMapper.java b/src/main/java/dev/lions/unionflow/server/mapper/vote/CandidatMapper.java
index 63c6665..b7f416f 100644
--- a/src/main/java/dev/lions/unionflow/server/mapper/vote/CandidatMapper.java
+++ b/src/main/java/dev/lions/unionflow/server/mapper/vote/CandidatMapper.java
@@ -1,38 +1,38 @@
-package dev.lions.unionflow.server.mapper.vote;
-
-import dev.lions.unionflow.server.api.dto.vote.CandidatDTO;
-import dev.lions.unionflow.server.entity.vote.Candidat;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.MappingTarget;
-
-@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
-public interface CandidatMapper {
-
- @Mapping(target = "campagneVoteId", source = "campagneVote.id")
- CandidatDTO toDto(Candidat entity);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "campagneVote", ignore = true)
- @Mapping(target = "nombreDeVoix", ignore = true)
- @Mapping(target = "pourcentageObtenu", ignore = true)
- Candidat toEntity(CandidatDTO dto);
-
- @Mapping(target = "id", ignore = true)
- @Mapping(target = "dateCreation", ignore = true)
- @Mapping(target = "dateModification", ignore = true)
- @Mapping(target = "creePar", ignore = true)
- @Mapping(target = "modifiePar", ignore = true)
- @Mapping(target = "version", ignore = true)
- @Mapping(target = "actif", ignore = true)
- @Mapping(target = "campagneVote", ignore = true)
- @Mapping(target = "nombreDeVoix", ignore = true)
- @Mapping(target = "pourcentageObtenu", ignore = true)
- void updateEntityFromDto(CandidatDTO dto, @MappingTarget Candidat entity);
-}
+package dev.lions.unionflow.server.mapper.vote;
+
+import dev.lions.unionflow.server.api.dto.vote.CandidatDTO;
+import dev.lions.unionflow.server.entity.vote.Candidat;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
+public interface CandidatMapper {
+
+ @Mapping(target = "campagneVoteId", source = "campagneVote.id")
+ CandidatDTO toDto(Candidat entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "campagneVote", ignore = true)
+ @Mapping(target = "nombreDeVoix", ignore = true)
+ @Mapping(target = "pourcentageObtenu", ignore = true)
+ Candidat toEntity(CandidatDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "dateCreation", ignore = true)
+ @Mapping(target = "dateModification", ignore = true)
+ @Mapping(target = "creePar", ignore = true)
+ @Mapping(target = "modifiePar", ignore = true)
+ @Mapping(target = "version", ignore = true)
+ @Mapping(target = "actif", ignore = true)
+ @Mapping(target = "campagneVote", ignore = true)
+ @Mapping(target = "nombreDeVoix", ignore = true)
+ @Mapping(target = "pourcentageObtenu", ignore = true)
+ void updateEntityFromDto(CandidatDTO dto, @MappingTarget Candidat entity);
+}
diff --git a/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventConsumer.java b/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventConsumer.java
index bcaa4f5..93ca586 100644
--- a/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventConsumer.java
+++ b/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventConsumer.java
@@ -1,103 +1,103 @@
-package dev.lions.unionflow.server.messaging;
-
-import dev.lions.unionflow.server.service.WebSocketBroadcastService;
-import io.smallrye.reactive.messaging.kafka.Record;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import org.eclipse.microprofile.reactive.messaging.Incoming;
-import org.jboss.logging.Logger;
-
-/**
- * Consumer Kafka pour consommer les events et les broadcaster via WebSocket.
- *
- * Ce consumer écoute tous les topics Kafka et transmet les events
- * en temps réel aux clients mobiles/web connectés via WebSocket.
- */
-@ApplicationScoped
-public class KafkaEventConsumer {
-
- private static final Logger LOG = Logger.getLogger(KafkaEventConsumer.class);
-
- @Inject
- WebSocketBroadcastService webSocketBroadcastService;
-
- /**
- * Consomme les events d'approbations financières.
- */
- @Incoming("finance-approvals-in")
- public void consumeFinanceApprovals(Record record) {
- LOG.debugf("Received finance approval event: key=%s, value=%s", record.key(), record.value());
- try {
- // Broadcast aux clients WebSocket
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast finance approval event");
- }
- }
-
- /**
- * Consomme les mises à jour de stats dashboard.
- */
- @Incoming("dashboard-stats-in")
- public void consumeDashboardStats(Record record) {
- LOG.debugf("Received dashboard stats event: key=%s", record.key());
- try {
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast dashboard stats event");
- }
- }
-
- /**
- * Consomme les notifications.
- */
- @Incoming("notifications-in")
- public void consumeNotifications(Record record) {
- LOG.debugf("Received notification event: key=%s", record.key());
- try {
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast notification event");
- }
- }
-
- /**
- * Consomme les events membres.
- */
- @Incoming("members-events-in")
- public void consumeMembersEvents(Record record) {
- LOG.debugf("Received member event: key=%s", record.key());
- try {
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast member event");
- }
- }
-
- /**
- * Consomme les events cotisations.
- */
- @Incoming("contributions-events-in")
- public void consumeContributionsEvents(Record record) {
- LOG.debugf("Received contribution event: key=%s", record.key());
- try {
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast contribution event");
- }
- }
-
- /**
- * Consomme les messages de chat (nouveaux messages envoyés dans une conversation).
- * Broadcaste l'event en temps réel aux clients WebSocket pour mise à jour instantanée.
- */
- @Incoming("chat-messages-in")
- public void consumeChatMessages(Record record) {
- LOG.debugf("Received chat message event: key=%s", record.key());
- try {
- webSocketBroadcastService.broadcast(record.value());
- } catch (Exception e) {
- LOG.errorf(e, "Failed to broadcast chat message event");
- }
- }
-}
+package dev.lions.unionflow.server.messaging;
+
+import dev.lions.unionflow.server.service.WebSocketBroadcastService;
+import io.smallrye.reactive.messaging.kafka.Record;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.reactive.messaging.Incoming;
+import org.jboss.logging.Logger;
+
+/**
+ * Consumer Kafka pour consommer les events et les broadcaster via WebSocket.
+ *
+ * Ce consumer écoute tous les topics Kafka et transmet les events
+ * en temps réel aux clients mobiles/web connectés via WebSocket.
+ */
+@ApplicationScoped
+public class KafkaEventConsumer {
+
+ private static final Logger LOG = Logger.getLogger(KafkaEventConsumer.class);
+
+ @Inject
+ WebSocketBroadcastService webSocketBroadcastService;
+
+ /**
+ * Consomme les events d'approbations financières.
+ */
+ @Incoming("finance-approvals-in")
+ public void consumeFinanceApprovals(Record record) {
+ LOG.debugf("Received finance approval event: key=%s, value=%s", record.key(), record.value());
+ try {
+ // Broadcast aux clients WebSocket
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast finance approval event");
+ }
+ }
+
+ /**
+ * Consomme les mises à jour de stats dashboard.
+ */
+ @Incoming("dashboard-stats-in")
+ public void consumeDashboardStats(Record record) {
+ LOG.debugf("Received dashboard stats event: key=%s", record.key());
+ try {
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast dashboard stats event");
+ }
+ }
+
+ /**
+ * Consomme les notifications.
+ */
+ @Incoming("notifications-in")
+ public void consumeNotifications(Record record) {
+ LOG.debugf("Received notification event: key=%s", record.key());
+ try {
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast notification event");
+ }
+ }
+
+ /**
+ * Consomme les events membres.
+ */
+ @Incoming("members-events-in")
+ public void consumeMembersEvents(Record record) {
+ LOG.debugf("Received member event: key=%s", record.key());
+ try {
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast member event");
+ }
+ }
+
+ /**
+ * Consomme les events cotisations.
+ */
+ @Incoming("contributions-events-in")
+ public void consumeContributionsEvents(Record record) {
+ LOG.debugf("Received contribution event: key=%s", record.key());
+ try {
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast contribution event");
+ }
+ }
+
+ /**
+ * Consomme les messages de chat (nouveaux messages envoyés dans une conversation).
+ * Broadcaste l'event en temps réel aux clients WebSocket pour mise à jour instantanée.
+ */
+ @Incoming("chat-messages-in")
+ public void consumeChatMessages(Record record) {
+ LOG.debugf("Received chat message event: key=%s", record.key());
+ try {
+ webSocketBroadcastService.broadcast(record.value());
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to broadcast chat message event");
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventProducer.java b/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventProducer.java
index 9cdde7e..205f187 100644
--- a/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventProducer.java
+++ b/src/main/java/dev/lions/unionflow/server/messaging/KafkaEventProducer.java
@@ -1,189 +1,189 @@
-package dev.lions.unionflow.server.messaging;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.smallrye.reactive.messaging.kafka.Record;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import org.eclipse.microprofile.reactive.messaging.Channel;
-import org.eclipse.microprofile.reactive.messaging.Emitter;
-import org.jboss.logging.Logger;
-
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * Producer Kafka pour publier des events UnionFlow.
- *
- * Publie sur différents topics Kafka qui sont ensuite consommés
- * par le WebSocket server pour broadcast aux clients mobiles/web.
- */
-@ApplicationScoped
-public class KafkaEventProducer {
-
- private static final Logger LOG = Logger.getLogger(KafkaEventProducer.class);
-
- @Inject
- ObjectMapper objectMapper;
-
- @Channel("finance-approvals-out")
- Emitter> financeApprovalsEmitter;
-
- @Channel("dashboard-stats-out")
- Emitter> dashboardStatsEmitter;
-
- @Channel("notifications-out")
- Emitter> notificationsEmitter;
-
- @Channel("members-events-out")
- Emitter> membersEventsEmitter;
-
- @Channel("contributions-events-out")
- Emitter> contributionsEventsEmitter;
-
- @Channel("chat-messages-out")
- Emitter> chatMessagesEmitter;
-
- /**
- * Publie un event d'approbation en attente.
- */
- public void publishApprovalPending(UUID approvalId, String organizationId, Map approvalData) {
- var event = buildEvent("APPROVAL_PENDING", organizationId, approvalData);
- publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
- }
-
- /**
- * Publie un event d'approbation approuvée.
- */
- public void publishApprovalApproved(UUID approvalId, String organizationId, Map approvalData) {
- var event = buildEvent("APPROVAL_APPROVED", organizationId, approvalData);
- publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
- }
-
- /**
- * Publie un event d'approbation rejetée.
- */
- public void publishApprovalRejected(UUID approvalId, String organizationId, Map approvalData) {
- var event = buildEvent("APPROVAL_REJECTED", organizationId, approvalData);
- publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
- }
-
- /**
- * Publie une mise à jour des stats dashboard.
- */
- public void publishDashboardStatsUpdate(String organizationId, Map stats) {
- var event = buildEvent("DASHBOARD_STATS_UPDATED", organizationId, stats);
- publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
- }
-
- /**
- * Publie un KPI temps réel.
- */
- public void publishKpiUpdate(String organizationId, Map kpiData) {
- var event = buildEvent("KPI_UPDATED", organizationId, kpiData);
- publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
- }
-
- /**
- * Publie une notification utilisateur.
- */
- public void publishUserNotification(String userId, Map notificationData) {
- var event = buildEvent("USER_NOTIFICATION", null, notificationData);
- event.put("userId", userId);
- publishToChannel(notificationsEmitter, userId, event, "notifications");
- }
-
- /**
- * Publie une notification broadcast (toute une organisation).
- */
- public void publishBroadcastNotification(String organizationId, Map notificationData) {
- var event = buildEvent("BROADCAST_NOTIFICATION", organizationId, notificationData);
- publishToChannel(notificationsEmitter, organizationId, event, "notifications");
- }
-
- /**
- * Publie un event de création de membre.
- */
- public void publishMemberCreated(UUID memberId, String organizationId, Map memberData) {
- var event = buildEvent("MEMBER_CREATED", organizationId, memberData);
- publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
- }
-
- /**
- * Publie un event de modification de membre.
- */
- public void publishMemberUpdated(UUID memberId, String organizationId, Map memberData) {
- var event = buildEvent("MEMBER_UPDATED", organizationId, memberData);
- publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
- }
-
- /**
- * Publie un event de désactivation de membre (soft delete).
- * Les consommateurs peuvent réagir : bloquer comptes épargne, annuler inscriptions,
- * reassigner approvals pending, nettoyer notifications, etc.
- */
- public void publishMemberDeactivated(dev.lions.unionflow.server.entity.Membre membre) {
- if (membre == null || membre.getId() == null) return;
- Map data = new java.util.HashMap<>();
- data.put("membreId", membre.getId().toString());
- data.put("email", membre.getEmail());
- data.put("nomComplet", membre.getNomComplet());
- data.put("numeroMembre", membre.getNumeroMembre());
- // organisationId principal (si présent) pour routage par org
- String orgId = membre.getMembresOrganisations() != null
- && !membre.getMembresOrganisations().isEmpty()
- && membre.getMembresOrganisations().get(0).getOrganisation() != null
- ? membre.getMembresOrganisations().get(0).getOrganisation().getId().toString()
- : "";
- var event = buildEvent("MEMBER_DEACTIVATED", orgId, data);
- publishToChannel(membersEventsEmitter, membre.getId().toString(), event, "members-events");
- }
-
- /**
- * Publie un event de cotisation payée.
- */
- public void publishContributionPaid(UUID contributionId, String organizationId, Map contributionData) {
- var event = buildEvent("CONTRIBUTION_PAID", organizationId, contributionData);
- publishToChannel(contributionsEventsEmitter, contributionId.toString(), event, "contributions-events");
- }
-
- /**
- * Publie un event de nouveau message de chat.
- * Les clients WebSocket de l'organisation sont notifiés pour rafraîchir leurs messages.
- */
- public void publishNouveauMessage(UUID conversationId, String organizationId, Map messageData) {
- var event = buildEvent("NOUVEAU_MESSAGE", organizationId, messageData);
- publishToChannel(chatMessagesEmitter, conversationId.toString(), event, "chat-messages");
- }
-
- /**
- * Construit un event avec structure standardisée.
- */
- private Map buildEvent(String eventType, String organizationId, Map data) {
- var event = new HashMap();
- event.put("eventType", eventType);
- event.put("timestamp", Instant.now().toString());
- if (organizationId != null) {
- event.put("organizationId", organizationId);
- }
- event.put("data", data);
- return event;
- }
-
- /**
- * Publie un event sur un channel Kafka avec gestion d'erreur.
- */
- private void publishToChannel(Emitter> emitter, String key, Map event, String topicName) {
- try {
- String eventJson = objectMapper.writeValueAsString(event);
- emitter.send(Record.of(key, eventJson));
- LOG.debugf("Published event to %s: %s", topicName, eventJson);
- } catch (JsonProcessingException e) {
- LOG.errorf(e, "Failed to serialize event for topic %s", topicName);
- } catch (Exception e) {
- LOG.errorf(e, "Failed to publish event to topic %s", topicName);
- }
- }
-}
+package dev.lions.unionflow.server.messaging;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.smallrye.reactive.messaging.kafka.Record;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.reactive.messaging.Channel;
+import org.eclipse.microprofile.reactive.messaging.Emitter;
+import org.jboss.logging.Logger;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Producer Kafka pour publier des events UnionFlow.
+ *
+ * Publie sur différents topics Kafka qui sont ensuite consommés
+ * par le WebSocket server pour broadcast aux clients mobiles/web.
+ */
+@ApplicationScoped
+public class KafkaEventProducer {
+
+ private static final Logger LOG = Logger.getLogger(KafkaEventProducer.class);
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @Channel("finance-approvals-out")
+ Emitter> financeApprovalsEmitter;
+
+ @Channel("dashboard-stats-out")
+ Emitter> dashboardStatsEmitter;
+
+ @Channel("notifications-out")
+ Emitter> notificationsEmitter;
+
+ @Channel("members-events-out")
+ Emitter> membersEventsEmitter;
+
+ @Channel("contributions-events-out")
+ Emitter> contributionsEventsEmitter;
+
+ @Channel("chat-messages-out")
+ Emitter> chatMessagesEmitter;
+
+ /**
+ * Publie un event d'approbation en attente.
+ */
+ public void publishApprovalPending(UUID approvalId, String organizationId, Map approvalData) {
+ var event = buildEvent("APPROVAL_PENDING", organizationId, approvalData);
+ publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
+ }
+
+ /**
+ * Publie un event d'approbation approuvée.
+ */
+ public void publishApprovalApproved(UUID approvalId, String organizationId, Map approvalData) {
+ var event = buildEvent("APPROVAL_APPROVED", organizationId, approvalData);
+ publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
+ }
+
+ /**
+ * Publie un event d'approbation rejetée.
+ */
+ public void publishApprovalRejected(UUID approvalId, String organizationId, Map approvalData) {
+ var event = buildEvent("APPROVAL_REJECTED", organizationId, approvalData);
+ publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
+ }
+
+ /**
+ * Publie une mise à jour des stats dashboard.
+ */
+ public void publishDashboardStatsUpdate(String organizationId, Map stats) {
+ var event = buildEvent("DASHBOARD_STATS_UPDATED", organizationId, stats);
+ publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
+ }
+
+ /**
+ * Publie un KPI temps réel.
+ */
+ public void publishKpiUpdate(String organizationId, Map kpiData) {
+ var event = buildEvent("KPI_UPDATED", organizationId, kpiData);
+ publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
+ }
+
+ /**
+ * Publie une notification utilisateur.
+ */
+ public void publishUserNotification(String userId, Map notificationData) {
+ var event = buildEvent("USER_NOTIFICATION", null, notificationData);
+ event.put("userId", userId);
+ publishToChannel(notificationsEmitter, userId, event, "notifications");
+ }
+
+ /**
+ * Publie une notification broadcast (toute une organisation).
+ */
+ public void publishBroadcastNotification(String organizationId, Map notificationData) {
+ var event = buildEvent("BROADCAST_NOTIFICATION", organizationId, notificationData);
+ publishToChannel(notificationsEmitter, organizationId, event, "notifications");
+ }
+
+ /**
+ * Publie un event de création de membre.
+ */
+ public void publishMemberCreated(UUID memberId, String organizationId, Map memberData) {
+ var event = buildEvent("MEMBER_CREATED", organizationId, memberData);
+ publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
+ }
+
+ /**
+ * Publie un event de modification de membre.
+ */
+ public void publishMemberUpdated(UUID memberId, String organizationId, Map memberData) {
+ var event = buildEvent("MEMBER_UPDATED", organizationId, memberData);
+ publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
+ }
+
+ /**
+ * Publie un event de désactivation de membre (soft delete).
+ * Les consommateurs peuvent réagir : bloquer comptes épargne, annuler inscriptions,
+ * reassigner approvals pending, nettoyer notifications, etc.
+ */
+ public void publishMemberDeactivated(dev.lions.unionflow.server.entity.Membre membre) {
+ if (membre == null || membre.getId() == null) return;
+ Map data = new java.util.HashMap<>();
+ data.put("membreId", membre.getId().toString());
+ data.put("email", membre.getEmail());
+ data.put("nomComplet", membre.getNomComplet());
+ data.put("numeroMembre", membre.getNumeroMembre());
+ // organisationId principal (si présent) pour routage par org
+ String orgId = membre.getMembresOrganisations() != null
+ && !membre.getMembresOrganisations().isEmpty()
+ && membre.getMembresOrganisations().get(0).getOrganisation() != null
+ ? membre.getMembresOrganisations().get(0).getOrganisation().getId().toString()
+ : "";
+ var event = buildEvent("MEMBER_DEACTIVATED", orgId, data);
+ publishToChannel(membersEventsEmitter, membre.getId().toString(), event, "members-events");
+ }
+
+ /**
+ * Publie un event de cotisation payée.
+ */
+ public void publishContributionPaid(UUID contributionId, String organizationId, Map contributionData) {
+ var event = buildEvent("CONTRIBUTION_PAID", organizationId, contributionData);
+ publishToChannel(contributionsEventsEmitter, contributionId.toString(), event, "contributions-events");
+ }
+
+ /**
+ * Publie un event de nouveau message de chat.
+ * Les clients WebSocket de l'organisation sont notifiés pour rafraîchir leurs messages.
+ */
+ public void publishNouveauMessage(UUID conversationId, String organizationId, Map messageData) {
+ var event = buildEvent("NOUVEAU_MESSAGE", organizationId, messageData);
+ publishToChannel(chatMessagesEmitter, conversationId.toString(), event, "chat-messages");
+ }
+
+ /**
+ * Construit un event avec structure standardisée.
+ */
+ private Map buildEvent(String eventType, String organizationId, Map data) {
+ var event = new HashMap();
+ event.put("eventType", eventType);
+ event.put("timestamp", Instant.now().toString());
+ if (organizationId != null) {
+ event.put("organizationId", organizationId);
+ }
+ event.put("data", data);
+ return event;
+ }
+
+ /**
+ * Publie un event sur un channel Kafka avec gestion d'erreur.
+ */
+ private void publishToChannel(Emitter> emitter, String key, Map event, String topicName) {
+ try {
+ String eventJson = objectMapper.writeValueAsString(event);
+ emitter.send(Record.of(key, eventJson));
+ LOG.debugf("Published event to %s: %s", topicName, eventJson);
+ } catch (JsonProcessingException e) {
+ LOG.errorf(e, "Failed to serialize event for topic %s", topicName);
+ } catch (Exception e) {
+ LOG.errorf(e, "Failed to publish event to topic %s", topicName);
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java
index 7add5a2..363699b 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java
@@ -1,67 +1,67 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.DemandeAdhesion;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.persistence.TypedQuery;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * Repository pour l'entité DemandeAdhesion
- *
- * @author UnionFlow Team
- * @version 2.0
- * @since 2025-02-18
- */
-@ApplicationScoped
-public class AdhesionRepository extends BaseRepository {
-
- public AdhesionRepository() {
- super(DemandeAdhesion.class);
- }
-
- public Optional findByNumeroReference(String numeroReference) {
- TypedQuery query = entityManager.createQuery(
- "SELECT a FROM DemandeAdhesion a WHERE a.numeroReference = :numeroReference",
- DemandeAdhesion.class);
- query.setParameter("numeroReference", numeroReference);
- return query.getResultList().stream().findFirst();
- }
-
- public List findByMembreId(UUID membreId) {
- TypedQuery query = entityManager.createQuery(
- "SELECT a FROM DemandeAdhesion a WHERE a.utilisateur.id = :membreId",
- DemandeAdhesion.class);
- query.setParameter("membreId", membreId);
- return query.getResultList();
- }
-
- public List findByOrganisationId(UUID organisationId) {
- TypedQuery query = entityManager.createQuery(
- "SELECT a FROM DemandeAdhesion a WHERE a.organisation.id = :organisationId",
- DemandeAdhesion.class);
- query.setParameter("organisationId", organisationId);
- return query.getResultList();
- }
-
- public List findByStatut(String statut) {
- TypedQuery query = entityManager.createQuery(
- "SELECT a FROM DemandeAdhesion a WHERE a.statut = :statut", DemandeAdhesion.class);
- query.setParameter("statut", statut);
- return query.getResultList();
- }
-
- public List findEnAttente() {
- return findByStatut("EN_ATTENTE");
- }
-
- public List findApprouveesEnAttentePaiement() {
- TypedQuery query = entityManager.createQuery(
- "SELECT a FROM DemandeAdhesion a WHERE a.statut = :statut"
- + " AND (a.montantPaye IS NULL OR a.montantPaye < a.fraisAdhesion)",
- DemandeAdhesion.class);
- query.setParameter("statut", "APPROUVEE");
- return query.getResultList();
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.DemandeAdhesion;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Repository pour l'entité DemandeAdhesion
+ *
+ * @author UnionFlow Team
+ * @version 2.0
+ * @since 2025-02-18
+ */
+@ApplicationScoped
+public class AdhesionRepository extends BaseRepository {
+
+ public AdhesionRepository() {
+ super(DemandeAdhesion.class);
+ }
+
+ public Optional findByNumeroReference(String numeroReference) {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT a FROM DemandeAdhesion a WHERE a.numeroReference = :numeroReference",
+ DemandeAdhesion.class);
+ query.setParameter("numeroReference", numeroReference);
+ return query.getResultList().stream().findFirst();
+ }
+
+ public List findByMembreId(UUID membreId) {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT a FROM DemandeAdhesion a WHERE a.utilisateur.id = :membreId",
+ DemandeAdhesion.class);
+ query.setParameter("membreId", membreId);
+ return query.getResultList();
+ }
+
+ public List findByOrganisationId(UUID organisationId) {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT a FROM DemandeAdhesion a WHERE a.organisation.id = :organisationId",
+ DemandeAdhesion.class);
+ query.setParameter("organisationId", organisationId);
+ return query.getResultList();
+ }
+
+ public List findByStatut(String statut) {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT a FROM DemandeAdhesion a WHERE a.statut = :statut", DemandeAdhesion.class);
+ query.setParameter("statut", statut);
+ return query.getResultList();
+ }
+
+ public List findEnAttente() {
+ return findByStatut("EN_ATTENTE");
+ }
+
+ public List findApprouveesEnAttentePaiement() {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT a FROM DemandeAdhesion a WHERE a.statut = :statut"
+ + " AND (a.montantPaye IS NULL OR a.montantPaye < a.fraisAdhesion)",
+ DemandeAdhesion.class);
+ query.setParameter("statut", "APPROUVEE");
+ return query.getResultList();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java
index c549332..625978b 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java
@@ -1,109 +1,109 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.Adresse;
-import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
-import jakarta.enterprise.context.ApplicationScoped;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * Repository pour l'entité Adresse
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@ApplicationScoped
-public class AdresseRepository implements PanacheRepositoryBase {
-
- /**
- * Trouve une adresse par son UUID
- *
- * @param id UUID de l'adresse
- * @return Adresse ou Optional.empty()
- */
- public Optional findAdresseById(UUID id) {
- return find("id = ?1", id).firstResultOptional();
- }
-
- /**
- * Trouve toutes les adresses d'une organisation
- *
- * @param organisationId ID de l'organisation
- * @return Liste des adresses
- */
- public List findByOrganisationId(UUID organisationId) {
- return find("organisation.id", organisationId).list();
- }
-
- /**
- * Trouve l'adresse principale d'une organisation
- *
- * @param organisationId ID de l'organisation
- * @return Adresse principale ou Optional.empty()
- */
- public Optional findPrincipaleByOrganisationId(UUID organisationId) {
- return find("organisation.id = ?1 AND principale = true", organisationId).firstResultOptional();
- }
-
- /**
- * Trouve toutes les adresses d'un membre
- *
- * @param membreId ID du membre
- * @return Liste des adresses
- */
- public List findByMembreId(UUID membreId) {
- return find("membre.id", membreId).list();
- }
-
- /**
- * Trouve l'adresse principale d'un membre
- *
- * @param membreId ID du membre
- * @return Adresse principale ou Optional.empty()
- */
- public Optional findPrincipaleByMembreId(UUID membreId) {
- return find("membre.id = ?1 AND principale = true", membreId).firstResultOptional();
- }
-
- /**
- * Trouve l'adresse d'un événement
- *
- * @param evenementId ID de l'événement
- * @return Adresse ou Optional.empty()
- */
- public Optional findByEvenementId(UUID evenementId) {
- return find("evenement.id", evenementId).firstResultOptional();
- }
-
- /**
- * Trouve les adresses par type
- *
- * @param typeAdresse Type d'adresse (String code)
- * @return Liste des adresses
- */
- public List findByType(String typeAdresse) {
- return find("typeAdresse", typeAdresse).list();
- }
-
- /**
- * Trouve les adresses par ville
- *
- * @param ville Nom de la ville
- * @return Liste des adresses
- */
- public List findByVille(String ville) {
- return find("LOWER(ville) = LOWER(?1)", ville).list();
- }
-
- /**
- * Trouve les adresses par pays
- *
- * @param pays Nom du pays
- * @return Liste des adresses
- */
- public List findByPays(String pays) {
- return find("LOWER(pays) = LOWER(?1)", pays).list();
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.Adresse;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Repository pour l'entité Adresse
+ *
+ * @author UnionFlow Team
+ * @version 3.0
+ * @since 2025-01-29
+ */
+@ApplicationScoped
+public class AdresseRepository implements PanacheRepositoryBase {
+
+ /**
+ * Trouve une adresse par son UUID
+ *
+ * @param id UUID de l'adresse
+ * @return Adresse ou Optional.empty()
+ */
+ public Optional findAdresseById(UUID id) {
+ return find("id = ?1", id).firstResultOptional();
+ }
+
+ /**
+ * Trouve toutes les adresses d'une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @return Liste des adresses
+ */
+ public List findByOrganisationId(UUID organisationId) {
+ return find("organisation.id", organisationId).list();
+ }
+
+ /**
+ * Trouve l'adresse principale d'une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @return Adresse principale ou Optional.empty()
+ */
+ public Optional findPrincipaleByOrganisationId(UUID organisationId) {
+ return find("organisation.id = ?1 AND principale = true", organisationId).firstResultOptional();
+ }
+
+ /**
+ * Trouve toutes les adresses d'un membre
+ *
+ * @param membreId ID du membre
+ * @return Liste des adresses
+ */
+ public List findByMembreId(UUID membreId) {
+ return find("membre.id", membreId).list();
+ }
+
+ /**
+ * Trouve l'adresse principale d'un membre
+ *
+ * @param membreId ID du membre
+ * @return Adresse principale ou Optional.empty()
+ */
+ public Optional findPrincipaleByMembreId(UUID membreId) {
+ return find("membre.id = ?1 AND principale = true", membreId).firstResultOptional();
+ }
+
+ /**
+ * Trouve l'adresse d'un événement
+ *
+ * @param evenementId ID de l'événement
+ * @return Adresse ou Optional.empty()
+ */
+ public Optional findByEvenementId(UUID evenementId) {
+ return find("evenement.id", evenementId).firstResultOptional();
+ }
+
+ /**
+ * Trouve les adresses par type
+ *
+ * @param typeAdresse Type d'adresse (String code)
+ * @return Liste des adresses
+ */
+ public List findByType(String typeAdresse) {
+ return find("typeAdresse", typeAdresse).list();
+ }
+
+ /**
+ * Trouve les adresses par ville
+ *
+ * @param ville Nom de la ville
+ * @return Liste des adresses
+ */
+ public List findByVille(String ville) {
+ return find("LOWER(ville) = LOWER(?1)", ville).list();
+ }
+
+ /**
+ * Trouve les adresses par pays
+ *
+ * @param pays Nom du pays
+ * @return Liste des adresses
+ */
+ public List findByPays(String pays) {
+ return find("LOWER(pays) = LOWER(?1)", pays).list();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/AlertConfigurationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AlertConfigurationRepository.java
index 9dc9ef1..1e5693b 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/AlertConfigurationRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/AlertConfigurationRepository.java
@@ -1,101 +1,101 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.AlertConfiguration;
-import io.quarkus.arc.Unremovable;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.persistence.TypedQuery;
-
-import java.util.Optional;
-
-/**
- * Repository pour l'entité AlertConfiguration (singleton)
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@ApplicationScoped
-@Unremovable
-public class AlertConfigurationRepository extends BaseRepository {
-
- public AlertConfigurationRepository() {
- super(AlertConfiguration.class);
- }
-
- /**
- * Récupérer la configuration unique des alertes.
- * Crée une configuration par défaut si elle n'existe pas.
- */
- public AlertConfiguration getConfiguration() {
- TypedQuery query = entityManager.createQuery(
- "SELECT c FROM AlertConfiguration c",
- AlertConfiguration.class
- );
- query.setMaxResults(1);
-
- Optional config = query.getResultList().stream().findFirst();
-
- if (config.isPresent()) {
- return config.get();
- } else {
- // Créer une configuration par défaut
- AlertConfiguration defaultConfig = new AlertConfiguration();
- persist(defaultConfig);
- return defaultConfig;
- }
- }
-
- /**
- * Mettre à jour la configuration des alertes
- */
- public AlertConfiguration updateConfiguration(AlertConfiguration config) {
- AlertConfiguration existing = getConfiguration();
-
- // Mettre à jour tous les champs
- existing.setCpuHighAlertEnabled(config.getCpuHighAlertEnabled());
- existing.setCpuThresholdPercent(config.getCpuThresholdPercent());
- existing.setCpuDurationMinutes(config.getCpuDurationMinutes());
- existing.setMemoryLowAlertEnabled(config.getMemoryLowAlertEnabled());
- existing.setMemoryThresholdPercent(config.getMemoryThresholdPercent());
- existing.setCriticalErrorAlertEnabled(config.getCriticalErrorAlertEnabled());
- existing.setErrorAlertEnabled(config.getErrorAlertEnabled());
- existing.setConnectionFailureAlertEnabled(config.getConnectionFailureAlertEnabled());
- existing.setConnectionFailureThreshold(config.getConnectionFailureThreshold());
- existing.setConnectionFailureWindowMinutes(config.getConnectionFailureWindowMinutes());
- existing.setEmailNotificationsEnabled(config.getEmailNotificationsEnabled());
- existing.setPushNotificationsEnabled(config.getPushNotificationsEnabled());
- existing.setSmsNotificationsEnabled(config.getSmsNotificationsEnabled());
- existing.setAlertEmailRecipients(config.getAlertEmailRecipients());
-
- persist(existing);
- return existing;
- }
-
- /**
- * Vérifier si les alertes CPU sont activées
- */
- public boolean isCpuAlertEnabled() {
- return getConfiguration().getCpuHighAlertEnabled();
- }
-
- /**
- * Vérifier si les alertes mémoire sont activées
- */
- public boolean isMemoryAlertEnabled() {
- return getConfiguration().getMemoryLowAlertEnabled();
- }
-
- /**
- * Récupérer le seuil CPU
- */
- public int getCpuThreshold() {
- return getConfiguration().getCpuThresholdPercent();
- }
-
- /**
- * Récupérer le seuil mémoire
- */
- public int getMemoryThreshold() {
- return getConfiguration().getMemoryThresholdPercent();
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.AlertConfiguration;
+import io.quarkus.arc.Unremovable;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+
+import java.util.Optional;
+
+/**
+ * Repository pour l'entité AlertConfiguration (singleton)
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@ApplicationScoped
+@Unremovable
+public class AlertConfigurationRepository extends BaseRepository {
+
+ public AlertConfigurationRepository() {
+ super(AlertConfiguration.class);
+ }
+
+ /**
+ * Récupérer la configuration unique des alertes.
+ * Crée une configuration par défaut si elle n'existe pas.
+ */
+ public AlertConfiguration getConfiguration() {
+ TypedQuery query = entityManager.createQuery(
+ "SELECT c FROM AlertConfiguration c",
+ AlertConfiguration.class
+ );
+ query.setMaxResults(1);
+
+ Optional config = query.getResultList().stream().findFirst();
+
+ if (config.isPresent()) {
+ return config.get();
+ } else {
+ // Créer une configuration par défaut
+ AlertConfiguration defaultConfig = new AlertConfiguration();
+ persist(defaultConfig);
+ return defaultConfig;
+ }
+ }
+
+ /**
+ * Mettre à jour la configuration des alertes
+ */
+ public AlertConfiguration updateConfiguration(AlertConfiguration config) {
+ AlertConfiguration existing = getConfiguration();
+
+ // Mettre à jour tous les champs
+ existing.setCpuHighAlertEnabled(config.getCpuHighAlertEnabled());
+ existing.setCpuThresholdPercent(config.getCpuThresholdPercent());
+ existing.setCpuDurationMinutes(config.getCpuDurationMinutes());
+ existing.setMemoryLowAlertEnabled(config.getMemoryLowAlertEnabled());
+ existing.setMemoryThresholdPercent(config.getMemoryThresholdPercent());
+ existing.setCriticalErrorAlertEnabled(config.getCriticalErrorAlertEnabled());
+ existing.setErrorAlertEnabled(config.getErrorAlertEnabled());
+ existing.setConnectionFailureAlertEnabled(config.getConnectionFailureAlertEnabled());
+ existing.setConnectionFailureThreshold(config.getConnectionFailureThreshold());
+ existing.setConnectionFailureWindowMinutes(config.getConnectionFailureWindowMinutes());
+ existing.setEmailNotificationsEnabled(config.getEmailNotificationsEnabled());
+ existing.setPushNotificationsEnabled(config.getPushNotificationsEnabled());
+ existing.setSmsNotificationsEnabled(config.getSmsNotificationsEnabled());
+ existing.setAlertEmailRecipients(config.getAlertEmailRecipients());
+
+ persist(existing);
+ return existing;
+ }
+
+ /**
+ * Vérifier si les alertes CPU sont activées
+ */
+ public boolean isCpuAlertEnabled() {
+ return getConfiguration().getCpuHighAlertEnabled();
+ }
+
+ /**
+ * Vérifier si les alertes mémoire sont activées
+ */
+ public boolean isMemoryAlertEnabled() {
+ return getConfiguration().getMemoryLowAlertEnabled();
+ }
+
+ /**
+ * Récupérer le seuil CPU
+ */
+ public int getCpuThreshold() {
+ return getConfiguration().getCpuThresholdPercent();
+ }
+
+ /**
+ * Récupérer le seuil mémoire
+ */
+ public int getMemoryThreshold() {
+ return getConfiguration().getMemoryThresholdPercent();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/AlerteLcbFtRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AlerteLcbFtRepository.java
index 41d05a1..71819e2 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/AlerteLcbFtRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/AlerteLcbFtRepository.java
@@ -1,151 +1,151 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.AlerteLcbFt;
-import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
-import jakarta.enterprise.context.ApplicationScoped;
-
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * Repository pour la gestion des alertes LCB-FT.
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-15
- */
-@ApplicationScoped
-public class AlerteLcbFtRepository implements PanacheRepositoryBase {
-
- /**
- * Recherche les alertes avec filtres et pagination
- */
- public List search(
- UUID organisationId,
- String typeAlerte,
- Boolean traitee,
- LocalDateTime dateDebut,
- LocalDateTime dateFin,
- int pageIndex,
- int pageSize
- ) {
- StringBuilder jpql = new StringBuilder("SELECT a FROM AlerteLcbFt a WHERE a.actif = true");
-
- if (organisationId != null) {
- jpql.append(" AND a.organisation.id = :organisationId");
- }
-
- if (typeAlerte != null && !typeAlerte.isBlank()) {
- jpql.append(" AND a.typeAlerte = :typeAlerte");
- }
-
- if (traitee != null) {
- jpql.append(" AND a.traitee = :traitee");
- }
-
- if (dateDebut != null) {
- jpql.append(" AND a.dateAlerte >= :dateDebut");
- }
-
- if (dateFin != null) {
- jpql.append(" AND a.dateAlerte <= :dateFin");
- }
-
- jpql.append(" ORDER BY a.dateAlerte DESC");
-
- var query = getEntityManager().createQuery(jpql.toString(), AlerteLcbFt.class);
-
- if (organisationId != null) {
- query.setParameter("organisationId", organisationId);
- }
-
- if (typeAlerte != null && !typeAlerte.isBlank()) {
- query.setParameter("typeAlerte", typeAlerte);
- }
-
- if (traitee != null) {
- query.setParameter("traitee", traitee);
- }
-
- if (dateDebut != null) {
- query.setParameter("dateDebut", dateDebut);
- }
-
- if (dateFin != null) {
- query.setParameter("dateFin", dateFin);
- }
-
- query.setFirstResult(pageIndex * pageSize);
- query.setMaxResults(pageSize);
-
- return query.getResultList();
- }
-
- /**
- * Compte le nombre d'alertes avec filtres
- */
- public long count(
- UUID organisationId,
- String typeAlerte,
- Boolean traitee,
- LocalDateTime dateDebut,
- LocalDateTime dateFin
- ) {
- StringBuilder jpql = new StringBuilder("SELECT COUNT(a) FROM AlerteLcbFt a WHERE a.actif = true");
-
- if (organisationId != null) {
- jpql.append(" AND a.organisation.id = :organisationId");
- }
-
- if (typeAlerte != null && !typeAlerte.isBlank()) {
- jpql.append(" AND a.typeAlerte = :typeAlerte");
- }
-
- if (traitee != null) {
- jpql.append(" AND a.traitee = :traitee");
- }
-
- if (dateDebut != null) {
- jpql.append(" AND a.dateAlerte >= :dateDebut");
- }
-
- if (dateFin != null) {
- jpql.append(" AND a.dateAlerte <= :dateFin");
- }
-
- var query = getEntityManager().createQuery(jpql.toString(), Long.class);
-
- if (organisationId != null) {
- query.setParameter("organisationId", organisationId);
- }
-
- if (typeAlerte != null && !typeAlerte.isBlank()) {
- query.setParameter("typeAlerte", typeAlerte);
- }
-
- if (traitee != null) {
- query.setParameter("traitee", traitee);
- }
-
- if (dateDebut != null) {
- query.setParameter("dateDebut", dateDebut);
- }
-
- if (dateFin != null) {
- query.setParameter("dateFin", dateFin);
- }
-
- return query.getSingleResult();
- }
-
- /**
- * Compte les alertes non traitées pour une organisation
- */
- public long countNonTraitees(UUID organisationId) {
- if (organisationId == null) {
- return count("traitee = false and actif = true");
- }
- return count("organisation.id = ?1 and traitee = false and actif = true", organisationId);
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.AlerteLcbFt;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Repository pour la gestion des alertes LCB-FT.
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-15
+ */
+@ApplicationScoped
+public class AlerteLcbFtRepository implements PanacheRepositoryBase {
+
+ /**
+ * Recherche les alertes avec filtres et pagination
+ */
+ public List search(
+ UUID organisationId,
+ String typeAlerte,
+ Boolean traitee,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin,
+ int pageIndex,
+ int pageSize
+ ) {
+ StringBuilder jpql = new StringBuilder("SELECT a FROM AlerteLcbFt a WHERE a.actif = true");
+
+ if (organisationId != null) {
+ jpql.append(" AND a.organisation.id = :organisationId");
+ }
+
+ if (typeAlerte != null && !typeAlerte.isBlank()) {
+ jpql.append(" AND a.typeAlerte = :typeAlerte");
+ }
+
+ if (traitee != null) {
+ jpql.append(" AND a.traitee = :traitee");
+ }
+
+ if (dateDebut != null) {
+ jpql.append(" AND a.dateAlerte >= :dateDebut");
+ }
+
+ if (dateFin != null) {
+ jpql.append(" AND a.dateAlerte <= :dateFin");
+ }
+
+ jpql.append(" ORDER BY a.dateAlerte DESC");
+
+ var query = getEntityManager().createQuery(jpql.toString(), AlerteLcbFt.class);
+
+ if (organisationId != null) {
+ query.setParameter("organisationId", organisationId);
+ }
+
+ if (typeAlerte != null && !typeAlerte.isBlank()) {
+ query.setParameter("typeAlerte", typeAlerte);
+ }
+
+ if (traitee != null) {
+ query.setParameter("traitee", traitee);
+ }
+
+ if (dateDebut != null) {
+ query.setParameter("dateDebut", dateDebut);
+ }
+
+ if (dateFin != null) {
+ query.setParameter("dateFin", dateFin);
+ }
+
+ query.setFirstResult(pageIndex * pageSize);
+ query.setMaxResults(pageSize);
+
+ return query.getResultList();
+ }
+
+ /**
+ * Compte le nombre d'alertes avec filtres
+ */
+ public long count(
+ UUID organisationId,
+ String typeAlerte,
+ Boolean traitee,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin
+ ) {
+ StringBuilder jpql = new StringBuilder("SELECT COUNT(a) FROM AlerteLcbFt a WHERE a.actif = true");
+
+ if (organisationId != null) {
+ jpql.append(" AND a.organisation.id = :organisationId");
+ }
+
+ if (typeAlerte != null && !typeAlerte.isBlank()) {
+ jpql.append(" AND a.typeAlerte = :typeAlerte");
+ }
+
+ if (traitee != null) {
+ jpql.append(" AND a.traitee = :traitee");
+ }
+
+ if (dateDebut != null) {
+ jpql.append(" AND a.dateAlerte >= :dateDebut");
+ }
+
+ if (dateFin != null) {
+ jpql.append(" AND a.dateAlerte <= :dateFin");
+ }
+
+ var query = getEntityManager().createQuery(jpql.toString(), Long.class);
+
+ if (organisationId != null) {
+ query.setParameter("organisationId", organisationId);
+ }
+
+ if (typeAlerte != null && !typeAlerte.isBlank()) {
+ query.setParameter("typeAlerte", typeAlerte);
+ }
+
+ if (traitee != null) {
+ query.setParameter("traitee", traitee);
+ }
+
+ if (dateDebut != null) {
+ query.setParameter("dateDebut", dateDebut);
+ }
+
+ if (dateFin != null) {
+ query.setParameter("dateFin", dateFin);
+ }
+
+ return query.getSingleResult();
+ }
+
+ /**
+ * Compte les alertes non traitées pour une organisation
+ */
+ public long countNonTraitees(UUID organisationId) {
+ if (organisationId == null) {
+ return count("traitee = false and actif = true");
+ }
+ return count("organisation.id = ?1 and traitee = false and actif = true", organisationId);
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java
index 98cfc2c..e33b44a 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java
@@ -1,23 +1,23 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.AuditLog;
-import jakarta.enterprise.context.ApplicationScoped;
-
-/**
- * Repository pour les logs d'audit
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2025-01-17
- */
-@ApplicationScoped
-public class AuditLogRepository extends BaseRepository {
-
- public AuditLogRepository() {
- super(AuditLog.class);
- }
-
- // Les méthodes de recherche spécifiques peuvent être ajoutées ici si nécessaire
- // Pour l'instant, on utilise les méthodes de base et les requêtes dans le
- // service
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.AuditLog;
+import jakarta.enterprise.context.ApplicationScoped;
+
+/**
+ * Repository pour les logs d'audit
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2025-01-17
+ */
+@ApplicationScoped
+public class AuditLogRepository extends BaseRepository {
+
+ public AuditLogRepository() {
+ super(AuditLog.class);
+ }
+
+ // Les méthodes de recherche spécifiques peuvent être ajoutées ici si nécessaire
+ // Pour l'instant, on utilise les méthodes de base et les requêtes dans le
+ // service
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java b/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java
index 6dc25e9..bd2d780d 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java
@@ -1,140 +1,140 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.BaseEntity;
-import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
-import jakarta.persistence.EntityManager;
-import jakarta.inject.Inject;
-import jakarta.transaction.Transactional;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * Repository de base pour les entités utilisant UUID comme identifiant
- *
- *
- * Étend PanacheRepositoryBase pour utiliser les fonctionnalités officielles de
- * Quarkus Panache avec UUID.
- *
- * @param Le type d'entité qui étend BaseEntity
- * @author UnionFlow Team
- * @version 5.0
- */
-public abstract class BaseRepository implements PanacheRepositoryBase {
-
- @Inject
- protected EntityManager entityManager;
-
- protected final Class entityClass;
-
- protected BaseRepository(Class entityClass) {
- this.entityClass = entityClass;
- }
-
- /**
- * Trouve une entité par son UUID.
- */
- @Override
- public T findById(UUID id) {
- return entityManager.find(entityClass, id);
- }
-
- /**
- * Trouve une entité par son UUID (retourne Optional).
- */
- @Override
- public Optional findByIdOptional(UUID id) {
- return Optional.ofNullable(findById(id));
- }
-
- /**
- * Persiste ou met à jour une entité.
- * Utilise merge si l'entité possède déjà un ID.
- */
- @Override
- @Transactional
- public void persist(T entity) {
- if (entity.getId() == null) {
- entityManager.persist(entity);
- } else {
- entityManager.merge(entity);
- }
- }
-
- /**
- * Met à jour une entité (Compatibilité)
- */
- @Transactional
- public T update(T entity) {
- return entityManager.merge(entity);
- }
-
- /**
- * Supprime une entité.
- */
- @Override
- @Transactional
- public void delete(T entity) {
- if (entity != null) {
- entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity));
- }
- }
-
- /**
- * Supprime une entité par son UUID.
- */
- @Override
- @Transactional
- public boolean deleteById(UUID id) {
- T entity = findById(id);
- if (entity != null) {
- delete(entity);
- return true;
- }
- return false;
- }
-
- /**
- * Liste toutes les entités.
- */
- @Override
- public List listAll() {
- return findAll().list();
- }
-
- /**
- * Liste toutes les entités avec pagination et tri (Compatibilité)
- */
- public List findAll(io.quarkus.panache.common.Page page, io.quarkus.panache.common.Sort sort) {
- io.quarkus.hibernate.orm.panache.PanacheQuery query;
- if (sort == null) {
- query = findAll();
- } else {
- query = findAll(sort);
- }
- return query.page(page).list();
- }
-
- /**
- * Compte toutes les entités.
- */
- @Override
- public long count() {
- return findAll().count();
- }
-
- /**
- * Vérifie si une entité existe par son UUID.
- */
- public boolean existsById(UUID id) {
- return findById(id) != null;
- }
-
- /**
- * Obtient l'EntityManager.
- */
- @Override
- public EntityManager getEntityManager() {
- return entityManager;
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.BaseEntity;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.persistence.EntityManager;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Repository de base pour les entités utilisant UUID comme identifiant
+ *
+ *
+ * Étend PanacheRepositoryBase pour utiliser les fonctionnalités officielles de
+ * Quarkus Panache avec UUID.
+ *
+ * @param Le type d'entité qui étend BaseEntity
+ * @author UnionFlow Team
+ * @version 5.0
+ */
+public abstract class BaseRepository implements PanacheRepositoryBase {
+
+ @Inject
+ protected EntityManager entityManager;
+
+ protected final Class entityClass;
+
+ protected BaseRepository(Class entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ /**
+ * Trouve une entité par son UUID.
+ */
+ @Override
+ public T findById(UUID id) {
+ return entityManager.find(entityClass, id);
+ }
+
+ /**
+ * Trouve une entité par son UUID (retourne Optional).
+ */
+ @Override
+ public Optional findByIdOptional(UUID id) {
+ return Optional.ofNullable(findById(id));
+ }
+
+ /**
+ * Persiste ou met à jour une entité.
+ * Utilise merge si l'entité possède déjà un ID.
+ */
+ @Override
+ @Transactional
+ public void persist(T entity) {
+ if (entity.getId() == null) {
+ entityManager.persist(entity);
+ } else {
+ entityManager.merge(entity);
+ }
+ }
+
+ /**
+ * Met à jour une entité (Compatibilité)
+ */
+ @Transactional
+ public T update(T entity) {
+ return entityManager.merge(entity);
+ }
+
+ /**
+ * Supprime une entité.
+ */
+ @Override
+ @Transactional
+ public void delete(T entity) {
+ if (entity != null) {
+ entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity));
+ }
+ }
+
+ /**
+ * Supprime une entité par son UUID.
+ */
+ @Override
+ @Transactional
+ public boolean deleteById(UUID id) {
+ T entity = findById(id);
+ if (entity != null) {
+ delete(entity);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Liste toutes les entités.
+ */
+ @Override
+ public List listAll() {
+ return findAll().list();
+ }
+
+ /**
+ * Liste toutes les entités avec pagination et tri (Compatibilité)
+ */
+ public List findAll(io.quarkus.panache.common.Page page, io.quarkus.panache.common.Sort sort) {
+ io.quarkus.hibernate.orm.panache.PanacheQuery query;
+ if (sort == null) {
+ query = findAll();
+ } else {
+ query = findAll(sort);
+ }
+ return query.page(page).list();
+ }
+
+ /**
+ * Compte toutes les entités.
+ */
+ @Override
+ public long count() {
+ return findAll().count();
+ }
+
+ /**
+ * Vérifie si une entité existe par son UUID.
+ */
+ public boolean existsById(UUID id) {
+ return findById(id) != null;
+ }
+
+ /**
+ * Obtient l'EntityManager.
+ */
+ @Override
+ public EntityManager getEntityManager() {
+ return entityManager;
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/BudgetRepository.java b/src/main/java/dev/lions/unionflow/server/repository/BudgetRepository.java
index eb79ab7..b1749f7 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/BudgetRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/BudgetRepository.java
@@ -1,122 +1,122 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.entity.Budget;
-import io.quarkus.arc.Unremovable;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.persistence.TypedQuery;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * Repository pour la gestion des budgets
- *
- * @author UnionFlow Team
- * @version 1.0
- * @since 2026-03-13
- */
-@ApplicationScoped
-@Unremovable
-public class BudgetRepository extends BaseRepository {
-
- public BudgetRepository() {
- super(Budget.class);
- }
-
- /**
- * Trouve tous les budgets d'une organisation
- *
- * @param organisationId ID de l'organisation
- * @return Liste des budgets
- */
- public List findByOrganisation(UUID organisationId) {
- return entityManager.createQuery(
- "SELECT b FROM Budget b WHERE b.organisation.id = :orgId ORDER BY b.year DESC, b.month DESC",
- Budget.class)
- .setParameter("orgId", organisationId)
- .getResultList();
- }
-
- /**
- * Trouve les budgets d'une organisation avec filtres
- *
- * @param organisationId ID de l'organisation
- * @param status Statut (optionnel)
- * @param year Année (optionnel)
- * @return Liste des budgets
- */
- public List findByOrganisationWithFilters(
- UUID organisationId,
- String status,
- Integer year) {
-
- StringBuilder jpql = new StringBuilder(
- "SELECT b FROM Budget b WHERE b.organisation.id = :orgId");
-
- if (status != null && !status.isEmpty()) {
- jpql.append(" AND b.status = :status");
- }
- if (year != null) {
- jpql.append(" AND b.year = :year");
- }
-
- jpql.append(" ORDER BY b.year DESC, b.month DESC");
-
- TypedQuery query = entityManager.createQuery(jpql.toString(), Budget.class);
- query.setParameter("orgId", organisationId);
-
- if (status != null && !status.isEmpty()) {
- query.setParameter("status", status);
- }
- if (year != null) {
- query.setParameter("year", year);
- }
-
- return query.getResultList();
- }
-
- /**
- * Trouve le budget actif pour une organisation
- *
- * @param organisationId ID de l'organisation
- * @return Liste des budgets actifs
- */
- public List findActiveByOrganisation(UUID organisationId) {
- return entityManager.createQuery(
- "SELECT b FROM Budget b WHERE b.organisation.id = :orgId AND b.status = 'ACTIVE' " +
- "ORDER BY b.year DESC, b.month DESC",
- Budget.class)
- .setParameter("orgId", organisationId)
- .getResultList();
- }
-
- /**
- * Trouve les budgets d'une année pour une organisation
- *
- * @param organisationId ID de l'organisation
- * @param year Année
- * @return Liste des budgets
- */
- public List findByOrganisationAndYear(UUID organisationId, int year) {
- return entityManager.createQuery(
- "SELECT b FROM Budget b WHERE b.organisation.id = :orgId AND b.year = :year " +
- "ORDER BY b.month ASC",
- Budget.class)
- .setParameter("orgId", organisationId)
- .setParameter("year", year)
- .getResultList();
- }
-
- /**
- * Compte les budgets actifs pour une organisation
- *
- * @param organisationId ID de l'organisation
- * @return Nombre de budgets actifs
- */
- public long countActiveByOrganisation(UUID organisationId) {
- return entityManager.createQuery(
- "SELECT COUNT(b) FROM Budget b WHERE b.organisation.id = :orgId AND b.status = 'ACTIVE'",
- Long.class)
- .setParameter("orgId", organisationId)
- .getSingleResult();
- }
-}
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.Budget;
+import io.quarkus.arc.Unremovable;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Repository pour la gestion des budgets
+ *
+ * @author UnionFlow Team
+ * @version 1.0
+ * @since 2026-03-13
+ */
+@ApplicationScoped
+@Unremovable
+public class BudgetRepository extends BaseRepository {
+
+ public BudgetRepository() {
+ super(Budget.class);
+ }
+
+ /**
+ * Trouve tous les budgets d'une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @return Liste des budgets
+ */
+ public List findByOrganisation(UUID organisationId) {
+ return entityManager.createQuery(
+ "SELECT b FROM Budget b WHERE b.organisation.id = :orgId ORDER BY b.year DESC, b.month DESC",
+ Budget.class)
+ .setParameter("orgId", organisationId)
+ .getResultList();
+ }
+
+ /**
+ * Trouve les budgets d'une organisation avec filtres
+ *
+ * @param organisationId ID de l'organisation
+ * @param status Statut (optionnel)
+ * @param year Année (optionnel)
+ * @return Liste des budgets
+ */
+ public List findByOrganisationWithFilters(
+ UUID organisationId,
+ String status,
+ Integer year) {
+
+ StringBuilder jpql = new StringBuilder(
+ "SELECT b FROM Budget b WHERE b.organisation.id = :orgId");
+
+ if (status != null && !status.isEmpty()) {
+ jpql.append(" AND b.status = :status");
+ }
+ if (year != null) {
+ jpql.append(" AND b.year = :year");
+ }
+
+ jpql.append(" ORDER BY b.year DESC, b.month DESC");
+
+ TypedQuery query = entityManager.createQuery(jpql.toString(), Budget.class);
+ query.setParameter("orgId", organisationId);
+
+ if (status != null && !status.isEmpty()) {
+ query.setParameter("status", status);
+ }
+ if (year != null) {
+ query.setParameter("year", year);
+ }
+
+ return query.getResultList();
+ }
+
+ /**
+ * Trouve le budget actif pour une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @return Liste des budgets actifs
+ */
+ public List findActiveByOrganisation(UUID organisationId) {
+ return entityManager.createQuery(
+ "SELECT b FROM Budget b WHERE b.organisation.id = :orgId AND b.status = 'ACTIVE' " +
+ "ORDER BY b.year DESC, b.month DESC",
+ Budget.class)
+ .setParameter("orgId", organisationId)
+ .getResultList();
+ }
+
+ /**
+ * Trouve les budgets d'une année pour une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @param year Année
+ * @return Liste des budgets
+ */
+ public List findByOrganisationAndYear(UUID organisationId, int year) {
+ return entityManager.createQuery(
+ "SELECT b FROM Budget b WHERE b.organisation.id = :orgId AND b.year = :year " +
+ "ORDER BY b.month ASC",
+ Budget.class)
+ .setParameter("orgId", organisationId)
+ .setParameter("year", year)
+ .getResultList();
+ }
+
+ /**
+ * Compte les budgets actifs pour une organisation
+ *
+ * @param organisationId ID de l'organisation
+ * @return Nombre de budgets actifs
+ */
+ public long countActiveByOrganisation(UUID organisationId) {
+ return entityManager.createQuery(
+ "SELECT COUNT(b) FROM Budget b WHERE b.organisation.id = :orgId AND b.status = 'ACTIVE'",
+ Long.class)
+ .setParameter("orgId", organisationId)
+ .getSingleResult();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java
index 3d9fc99..3942940 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java
@@ -1,82 +1,106 @@
-package dev.lions.unionflow.server.repository;
-
-import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
-import dev.lions.unionflow.server.entity.CompteComptable;
-import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
-import jakarta.enterprise.context.ApplicationScoped;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * Repository pour l'entité CompteComptable
- *
- * @author UnionFlow Team
- * @version 3.0
- * @since 2025-01-29
- */
-@ApplicationScoped
-public class CompteComptableRepository implements PanacheRepositoryBase {
-
- /**
- * Trouve un compte comptable par son UUID
- *
- * @param id UUID du compte comptable
- * @return Compte comptable ou Optional.empty()
- */
- public Optional