fix(disaster-recovery 2/2): restaurer 242 fichiers Java modifiés par a72ab54
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m22s

Suite à la récupération précédente (044ca4b) qui n'avait restauré que les
fichiers SUPPRIMÉS, ce commit restaure les MODIFICATIONS d'entités/services
qui étaient nécessaires pour que les fichiers restaurés compilent.

Restaurés depuis a72ab54^ (= 31330d9 + corrections) :
- Entities : Organisation, FormuleAbonnement, AuditService, MembreOrganisation, SouscriptionOrganisation, etc.
- Services : MigrerOrganisationsVersKeycloakService, ComptabilitePdfService, KycAmlService, AuditService.logKycRisqueEleve, etc.
- Resources : PaiementUnifieResource, etc.

Backend compile désormais (BUILD SUCCESS).
This commit is contained in:
2026-04-25 01:05:08 +00:00
parent 044ca4bd7e
commit 6e9841b3bb
242 changed files with 38000 additions and 37312 deletions

View File

@@ -1,138 +1,138 @@
package de.lions.unionflow.server.auth; package de.lions.unionflow.server.auth;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application * Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application
* mobile. * mobile.
*/ */
@Path("/auth") @Path("/auth")
@PermitAll @PermitAll
public class AuthCallbackResource { public class AuthCallbackResource {
private static final Logger log = Logger.getLogger(AuthCallbackResource.class); private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
/** /**
* Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile * Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile
* avec les paramètres reçus. * avec les paramètres reçus.
*/ */
@GET @GET
@Path("/callback") @Path("/callback")
public Response handleCallback( public Response handleCallback(
@QueryParam("code") String code, @QueryParam("code") String code,
@QueryParam("state") String state, @QueryParam("state") String state,
@QueryParam("session_state") String sessionState, @QueryParam("session_state") String sessionState,
@QueryParam("error") String error, @QueryParam("error") String error,
@QueryParam("error_description") String errorDescription) { @QueryParam("error_description") String errorDescription) {
try { try {
// Log des paramètres reçus pour debug // Log des paramètres reçus pour debug
log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s", log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s",
code, state, sessionState, error, errorDescription); code, state, sessionState, error, errorDescription);
// URL de redirection simple vers l'application mobile // URL de redirection simple vers l'application mobile
String redirectUrl = "dev.lions.unionflow-mobile://callback"; String redirectUrl = "dev.lions.unionflow-mobile://callback";
// Si nous avons un code d'autorisation, c'est un succès // Si nous avons un code d'autorisation, c'est un succès
if (code != null && !code.isEmpty()) { if (code != null && !code.isEmpty()) {
redirectUrl += "?code=" + code; redirectUrl += "?code=" + code;
if (state != null && !state.isEmpty()) { if (state != null && !state.isEmpty()) {
redirectUrl += "&state=" + state; redirectUrl += "&state=" + state;
} }
} else if (error != null) { } else if (error != null) {
redirectUrl += "?error=" + error; redirectUrl += "?error=" + error;
if (errorDescription != null) { if (errorDescription != null) {
redirectUrl += "&error_description=" + errorDescription; redirectUrl += "&error_description=" + errorDescription;
} }
} }
// Page HTML simple qui redirige automatiquement vers l'app mobile // Page HTML simple qui redirige automatiquement vers l'app mobile
String html = String html =
""" """
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Redirection vers UnionFlow</title> <title>Redirection vers UnionFlow</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-align: center; text-align: center;
padding: 50px; padding: 50px;
background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%); background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%);
color: white; color: white;
} }
.container { .container {
max-width: 400px; max-width: 400px;
margin: 0 auto; margin: 0 auto;
background: rgba(255,255,255,0.1); background: rgba(255,255,255,0.1);
padding: 30px; padding: 30px;
border-radius: 10px; border-radius: 10px;
} }
.spinner { .spinner {
border: 4px solid rgba(255,255,255,0.3); border: 4px solid rgba(255,255,255,0.3);
border-top: 4px solid white; border-top: 4px solid white;
border-radius: 50%%; border-radius: 50%%;
width: 40px; width: 40px;
height: 40px; height: 40px;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
margin: 20px auto; margin: 20px auto;
} }
@keyframes spin { 0%% { transform: rotate(0deg); } 100%% { transform: rotate(360deg); } } @keyframes spin { 0%% { transform: rotate(0deg); } 100%% { transform: rotate(360deg); } }
a { color: #ffeb3b; text-decoration: none; } a { color: #ffeb3b; text-decoration: none; }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h2>🔐 Authentification réussie</h2> <h2>🔐 Authentification réussie</h2>
<div class="spinner"></div> <div class="spinner"></div>
<p>Redirection vers l'application UnionFlow...</p> <p>Redirection vers l'application UnionFlow...</p>
<p><small>Si la redirection ne fonctionne pas automatiquement, <p><small>Si la redirection ne fonctionne pas automatiquement,
<a href="%s">cliquez ici</a></small></p> <a href="%s">cliquez ici</a></small></p>
</div> </div>
<script> <script>
// Tentative de redirection automatique // Tentative de redirection automatique
setTimeout(function() { setTimeout(function() {
window.location.href = '%s'; window.location.href = '%s';
}, 2000); }, 2000);
// Fallback: ouvrir l'app mobile si possible // Fallback: ouvrir l'app mobile si possible
setTimeout(function() { setTimeout(function() {
try { try {
window.open('%s', '_self'); window.open('%s', '_self');
} catch(e) { } catch(e) {
console.log('Redirection manuelle nécessaire'); console.log('Redirection manuelle nécessaire');
} }
}, 3000); }, 3000);
</script> </script>
</body> </body>
</html> </html>
""" """
.formatted(redirectUrl, redirectUrl, redirectUrl); .formatted(redirectUrl, redirectUrl, redirectUrl);
return Response.ok(html).type("text/html").build(); return Response.ok(html).type("text/html").build();
} catch (Exception e) { } catch (Exception e) {
// En cas d'erreur, retourner une page d'erreur simple // En cas d'erreur, retourner une page d'erreur simple
String errorHtml = String errorHtml =
""" """
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head><title>Erreur d'authentification</title></head> <head><title>Erreur d'authentification</title></head>
<body style="font-family: Arial; text-align: center; padding: 50px;"> <body style="font-family: Arial; text-align: center; padding: 50px;">
<h2>❌ Erreur d'authentification</h2> <h2>❌ Erreur d'authentification</h2>
<p>Une erreur s'est produite lors de la redirection.</p> <p>Une erreur s'est produite lors de la redirection.</p>
<p>Veuillez fermer cette page et réessayer.</p> <p>Veuillez fermer cette page et réessayer.</p>
</body> </body>
</html> </html>
"""; """;
return Response.status(500).entity(errorHtml).type("text/html").build(); return Response.status(500).entity(errorHtml).type("text/html").build();
} }
} }
} }

View File

@@ -1,250 +1,250 @@
package dev.lions.unionflow.server; package dev.lions.unionflow.server;
import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain; import io.quarkus.runtime.annotations.QuarkusMain;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Point d'entrée principal du serveur UnionFlow. * Point d'entrée principal du serveur UnionFlow.
* *
* <p><b>UnionFlow</b> est une plateforme de gestion associative multi-tenant * <p><b>UnionFlow</b> est une plateforme de gestion associative multi-tenant
* destinée aux organisations de solidarité (associations, mutuelles, coopératives, * destinée aux organisations de solidarité (associations, mutuelles, coopératives,
* tontines, ONG) en Afrique de l'Ouest. * tontines, ONG) en Afrique de l'Ouest.
* *
* <h2>Architecture</h2> * <h2>Architecture</h2>
* <ul> * <ul>
* <li><b>Backend</b> : Quarkus 3.15.1, Java 17, Hibernate Panache</li> * <li><b>Backend</b> : Quarkus 3.15.1, Java 17, Hibernate Panache</li>
* <li><b>Base de données</b> : PostgreSQL 15 avec Flyway</li> * <li><b>Base de données</b> : PostgreSQL 15 avec Flyway</li>
* <li><b>Authentification</b> : Keycloak 23 (OIDC/OAuth2)</li> * <li><b>Authentification</b> : Keycloak 23 (OIDC/OAuth2)</li>
* <li><b>API</b> : REST (JAX-RS) + WebSocket (temps réel)</li> * <li><b>API</b> : REST (JAX-RS) + WebSocket (temps réel)</li>
* <li><b>Paiements</b> : Wave Money CI (Mobile Money)</li> * <li><b>Paiements</b> : Wave Money CI (Mobile Money)</li>
* </ul> * </ul>
* *
* <h2>Modules fonctionnels</h2> * <h2>Modules fonctionnels</h2>
* <ul> * <ul>
* <li><b>Organisations</b> — Hiérarchie multi-niveau, types paramétrables, * <li><b>Organisations</b> — Hiérarchie multi-niveau, types paramétrables,
* modules activables par organisation</li> * modules activables par organisation</li>
* <li><b>Membres</b> — Adhésion, profils, rôles/permissions RBAC, * <li><b>Membres</b> — Adhésion, profils, rôles/permissions RBAC,
* synchronisation bidirectionnelle avec Keycloak</li> * synchronisation bidirectionnelle avec Keycloak</li>
* <li><b>Cotisations &amp; Paiements</b> — Campagnes récurrentes, * <li><b>Cotisations &amp; Paiements</b> — Campagnes récurrentes,
* ventilation polymorphique, intégration Wave Money</li> * ventilation polymorphique, intégration Wave Money</li>
* <li><b>Événements</b> — Création, inscriptions, gestion des présences, * <li><b>Événements</b> — Création, inscriptions, gestion des présences,
* géolocalisation</li> * géolocalisation</li>
* <li><b>Solidarité</b> — Demandes d'aide, propositions, matching intelligent, * <li><b>Solidarité</b> — Demandes d'aide, propositions, matching intelligent,
* workflow de validation multi-étapes</li> * workflow de validation multi-étapes</li>
* <li><b>Mutuelles</b> — Épargne, crédit, tontines, suivi des tours</li> * <li><b>Mutuelles</b> — Épargne, crédit, tontines, suivi des tours</li>
* <li><b>Comptabilité</b> — Plan comptable SYSCOHADA, journaux, * <li><b>Comptabilité</b> — Plan comptable SYSCOHADA, journaux,
* écritures automatiques, balance, grand livre</li> * écritures automatiques, balance, grand livre</li>
* <li><b>Documents</b> — Gestion polymorphique de pièces jointes * <li><b>Documents</b> — Gestion polymorphique de pièces jointes
* (stockage local + métadonnées)</li> * (stockage local + métadonnées)</li>
* <li><b>Notifications</b> — Templates multicanaux (email, SMS, push), * <li><b>Notifications</b> — Templates multicanaux (email, SMS, push),
* préférences utilisateur, historique persistant</li> * préférences utilisateur, historique persistant</li>
* <li><b>Analytics &amp; Dashboard</b> — KPIs temps réel via WebSocket, * <li><b>Analytics &amp; Dashboard</b> — KPIs temps réel via WebSocket,
* métriques d'activité, tendances, rapports PDF</li> * métriques d'activité, tendances, rapports PDF</li>
* <li><b>Administration</b> — Audit trail complet, tickets support, * <li><b>Administration</b> — Audit trail complet, tickets support,
* suggestions utilisateurs, favoris</li> * suggestions utilisateurs, favoris</li>
* <li><b>SaaS Multi-tenant</b> — Formules d'abonnement flexibles, * <li><b>SaaS Multi-tenant</b> — Formules d'abonnement flexibles,
* souscriptions par organisation, facturation</li> * souscriptions par organisation, facturation</li>
* <li><b>Configuration dynamique</b> — Table {@code configurations}, * <li><b>Configuration dynamique</b> — Table {@code configurations},
* pas de hardcoding, paramétrage par organisation</li> * pas de hardcoding, paramétrage par organisation</li>
* <li><b>Données de référence</b> — Table {@code types_reference} * <li><b>Données de référence</b> — Table {@code types_reference}
* entièrement CRUD-able (évite les enums Java)</li> * entièrement CRUD-able (évite les enums Java)</li>
* </ul> * </ul>
* *
* <h2>Inventaire technique</h2> * <h2>Inventaire technique</h2>
* <ul> * <ul>
* <li><b>60 entités JPA</b> — {@code BaseEntity} + {@code AuditEntityListener} * <li><b>60 entités JPA</b> — {@code BaseEntity} + {@code AuditEntityListener}
* pour audit automatique</li> * pour audit automatique</li>
* <li><b>46 services CDI</b> — Logique métier transactionnelle</li> * <li><b>46 services CDI</b> — Logique métier transactionnelle</li>
* <li><b>37 endpoints REST</b> — API JAX-RS avec validation Bean Validation</li> * <li><b>37 endpoints REST</b> — API JAX-RS avec validation Bean Validation</li>
* <li><b>49 repositories</b> — Hibernate Panache pour accès données</li> * <li><b>49 repositories</b> — Hibernate Panache pour accès données</li>
* <li><b>Migrations Flyway</b> — V1.0 --> V3.0 (schéma complet 60 tables)</li> * <li><b>Migrations Flyway</b> — V1.0 --> V3.0 (schéma complet 60 tables)</li>
* <li><b>Tests</b> — 1127 tests unitaires et d'intégration Quarkus</li> * <li><b>Tests</b> — 1127 tests unitaires et d'intégration Quarkus</li>
* <li><b>Couverture</b> — JaCoCo 40% minimum (cible 60%)</li> * <li><b>Couverture</b> — JaCoCo 40% minimum (cible 60%)</li>
* </ul> * </ul>
* *
* <h2>Patterns et Best Practices</h2> * <h2>Patterns et Best Practices</h2>
* <ul> * <ul>
* <li><b>Clean Architecture</b> — Séparation API/Impl/Entity</li> * <li><b>Clean Architecture</b> — Séparation API/Impl/Entity</li>
* <li><b>DTO Pattern</b> — Request/Response distincts (142 DTOs dans server-api)</li> * <li><b>DTO Pattern</b> — Request/Response distincts (142 DTOs dans server-api)</li>
* <li><b>Repository Pattern</b> — Abstraction accès données</li> * <li><b>Repository Pattern</b> — Abstraction accès données</li>
* <li><b>Service Layer</b> — Transactionnel, validation métier</li> * <li><b>Service Layer</b> — Transactionnel, validation métier</li>
* <li><b>Audit automatique</b> — EntityListener JPA pour traçabilité complète</li> * <li><b>Audit automatique</b> — EntityListener JPA pour traçabilité complète</li>
* <li><b>Soft Delete</b> — Champ {@code actif} sur toutes les entités</li> * <li><b>Soft Delete</b> — Champ {@code actif} sur toutes les entités</li>
* <li><b>Optimistic Locking</b> — Champ {@code version} pour concurrence</li> * <li><b>Optimistic Locking</b> — Champ {@code version} pour concurrence</li>
* <li><b>Configuration externalisée</b> — MicroProfile Config, pas de hardcoding</li> * <li><b>Configuration externalisée</b> — MicroProfile Config, pas de hardcoding</li>
* </ul> * </ul>
* *
* <h2>Sécurité</h2> * <h2>Sécurité</h2>
* <ul> * <ul>
* <li>OIDC avec Keycloak (realm: unionflow)</li> * <li>OIDC avec Keycloak (realm: unionflow)</li>
* <li>JWT signature côté backend (HMAC-SHA256)</li> * <li>JWT signature côté backend (HMAC-SHA256)</li>
* <li>RBAC avec rôles: SUPER_ADMIN, ADMIN_ORGANISATION, MEMBRE</li> * <li>RBAC avec rôles: SUPER_ADMIN, ADMIN_ORGANISATION, MEMBRE</li>
* <li>Permissions granulaires par module</li> * <li>Permissions granulaires par module</li>
* <li>CORS configuré pour client web</li> * <li>CORS configuré pour client web</li>
* <li>HTTPS obligatoire en production</li> * <li>HTTPS obligatoire en production</li>
* </ul> * </ul>
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0.0 * @version 3.0.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@QuarkusMain @QuarkusMain
@ApplicationScoped @ApplicationScoped
public class UnionFlowServerApplication implements QuarkusApplication { public class UnionFlowServerApplication implements QuarkusApplication {
private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class); private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class);
/** Port HTTP configuré (défaut: 8080). */ /** Port HTTP configuré (défaut: 8080). */
@ConfigProperty(name = "quarkus.http.port", defaultValue = "8080") @ConfigProperty(name = "quarkus.http.port", defaultValue = "8080")
int httpPort; int httpPort;
/** Host HTTP configuré (défaut: 0.0.0.0). */ /** Host HTTP configuré (défaut: 0.0.0.0). */
@ConfigProperty(name = "quarkus.http.host", defaultValue = "0.0.0.0") @ConfigProperty(name = "quarkus.http.host", defaultValue = "0.0.0.0")
String httpHost; String httpHost;
/** Nom de l'application. */ /** Nom de l'application. */
@ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server") @ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server")
String applicationName; String applicationName;
/** Version de l'application. */ /** Version de l'application. */
@ConfigProperty(name = "quarkus.application.version", defaultValue = "3.0.0") @ConfigProperty(name = "quarkus.application.version", defaultValue = "3.0.0")
String applicationVersion; String applicationVersion;
/** Profil actif (dev, test, prod). */ /** Profil actif (dev, test, prod). */
@ConfigProperty(name = "quarkus.profile") @ConfigProperty(name = "quarkus.profile")
String activeProfile; String activeProfile;
/** Version de Quarkus. */ /** Version de Quarkus. */
@ConfigProperty(name = "quarkus.platform.version", defaultValue = "3.15.1") @ConfigProperty(name = "quarkus.platform.version", defaultValue = "3.15.1")
String quarkusVersion; String quarkusVersion;
/** /**
* Point d'entrée JVM. * Point d'entrée JVM.
* *
* <p>Lance l'application Quarkus en mode bloquant. * <p>Lance l'application Quarkus en mode bloquant.
* En mode natif, cette méthode démarre instantanément (&lt; 50ms). * En mode natif, cette méthode démarre instantanément (&lt; 50ms).
* *
* @param args Arguments de ligne de commande (non utilisés) * @param args Arguments de ligne de commande (non utilisés)
*/ */
public static void main(String... args) { public static void main(String... args) {
Quarkus.run(UnionFlowServerApplication.class, args); Quarkus.run(UnionFlowServerApplication.class, args);
} }
/** /**
* Méthode de démarrage de l'application. * Méthode de démarrage de l'application.
* *
* <p>Affiche les informations de démarrage (URLs, configuration) * <p>Affiche les informations de démarrage (URLs, configuration)
* puis attend le signal d'arrêt (SIGTERM, SIGINT). * puis attend le signal d'arrêt (SIGTERM, SIGINT).
* *
* @param args Arguments passés depuis main() * @param args Arguments passés depuis main()
* @return Code de sortie (0 = succès) * @return Code de sortie (0 = succès)
* @throws Exception Si erreur fatale au démarrage * @throws Exception Si erreur fatale au démarrage
*/ */
@Override @Override
public int run(String... args) throws Exception { public int run(String... args) throws Exception {
logStartupBanner(); logStartupBanner();
logConfiguration(); logConfiguration();
logEndpoints(); logEndpoints();
logArchitecture(); logArchitecture();
LOG.info("UnionFlow Server prêt à recevoir des requêtes"); LOG.info("UnionFlow Server prêt à recevoir des requêtes");
LOG.info("Appuyez sur Ctrl+C pour arrêter"); LOG.info("Appuyez sur Ctrl+C pour arrêter");
// Attend le signal d'arrêt (bloquant) // Attend le signal d'arrêt (bloquant)
Quarkus.waitForExit(); Quarkus.waitForExit();
LOG.info("UnionFlow Server arrêté proprement"); LOG.info("UnionFlow Server arrêté proprement");
return 0; return 0;
} }
/** /**
* Affiche la bannière ASCII de démarrage. * Affiche la bannière ASCII de démarrage.
*/ */
private void logStartupBanner() { private void logStartupBanner() {
LOG.info("----------------------------------------------------------"); LOG.info("----------------------------------------------------------");
LOG.info("- -"); LOG.info("- -");
LOG.info("- UNIONFLOW SERVER v" + applicationVersion + " "); LOG.info("- UNIONFLOW SERVER v" + applicationVersion + " ");
LOG.info("- Plateforme de Gestion Associative Multi-Tenant -"); LOG.info("- Plateforme de Gestion Associative Multi-Tenant -");
LOG.info("- -"); LOG.info("- -");
LOG.info("----------------------------------------------------------"); LOG.info("----------------------------------------------------------");
} }
/** /**
* Affiche la configuration active. * Affiche la configuration active.
*/ */
private void logConfiguration() { private void logConfiguration() {
LOG.infof("Profil : %s", activeProfile); LOG.infof("Profil : %s", activeProfile);
LOG.infof("Application : %s v%s", applicationName, applicationVersion); LOG.infof("Application : %s v%s", applicationName, applicationVersion);
LOG.infof("Java : %s", System.getProperty("java.version")); LOG.infof("Java : %s", System.getProperty("java.version"));
LOG.infof("Quarkus : %s", quarkusVersion); LOG.infof("Quarkus : %s", quarkusVersion);
} }
/** /**
* Affiche les URLs des endpoints principaux. * Affiche les URLs des endpoints principaux.
*/ */
private void logEndpoints() { private void logEndpoints() {
String baseUrl = buildBaseUrl(); String baseUrl = buildBaseUrl();
LOG.info("--------------------------------------------------------------"); LOG.info("--------------------------------------------------------------");
LOG.info("📡 Endpoints disponibles:"); LOG.info("📡 Endpoints disponibles:");
LOG.infof(" - API REST --> %s/api", baseUrl); LOG.infof(" - API REST --> %s/api", baseUrl);
LOG.infof(" - Swagger UI --> %s/q/swagger-ui", baseUrl); LOG.infof(" - Swagger UI --> %s/q/swagger-ui", baseUrl);
LOG.infof(" - Health Check --> %s/q/health", baseUrl); LOG.infof(" - Health Check --> %s/q/health", baseUrl);
LOG.infof(" - Metrics --> %s/q/metrics", baseUrl); LOG.infof(" - Metrics --> %s/q/metrics", baseUrl);
LOG.infof(" - OpenAPI --> %s/q/openapi", baseUrl); LOG.infof(" - OpenAPI --> %s/q/openapi", baseUrl);
if ("dev".equals(activeProfile)) { if ("dev".equals(activeProfile)) {
LOG.infof(" - Dev UI --> %s/q/dev", baseUrl); LOG.infof(" - Dev UI --> %s/q/dev", baseUrl);
LOG.infof(" - H2 Console --> %s/q/dev/io.quarkus.quarkus-datasource/datasources", baseUrl); LOG.infof(" - H2 Console --> %s/q/dev/io.quarkus.quarkus-datasource/datasources", baseUrl);
} }
LOG.info("--------------------------------------------------------------"); LOG.info("--------------------------------------------------------------");
} }
/** /**
* Affiche l'inventaire de l'architecture. * Affiche l'inventaire de l'architecture.
*/ */
private void logArchitecture() { private void logArchitecture() {
LOG.info(" Architecture:"); LOG.info(" Architecture:");
LOG.info(" - 60 Entités JPA"); LOG.info(" - 60 Entités JPA");
LOG.info(" - 46 Services CDI"); LOG.info(" - 46 Services CDI");
LOG.info(" - 37 Endpoints REST"); LOG.info(" - 37 Endpoints REST");
LOG.info(" - 49 Repositories Panache"); LOG.info(" - 49 Repositories Panache");
LOG.info(" - 142 DTOs (Request/Response)"); LOG.info(" - 142 DTOs (Request/Response)");
LOG.info(" - 1127 Tests automatisés"); LOG.info(" - 1127 Tests automatisés");
LOG.info("--------------------------------------------------------------"); LOG.info("--------------------------------------------------------------");
} }
/** /**
* Retourne la valeur de la variable d'environnement UNIONFLOW_DOMAIN. * Retourne la valeur de la variable d'environnement UNIONFLOW_DOMAIN.
* Méthode protégée pour permettre la substitution en tests. * Méthode protégée pour permettre la substitution en tests.
* *
* @return valeur de UNIONFLOW_DOMAIN, ou null si non définie * @return valeur de UNIONFLOW_DOMAIN, ou null si non définie
*/ */
protected String getUnionflowDomain() { protected String getUnionflowDomain() {
return System.getenv("UNIONFLOW_DOMAIN"); return System.getenv("UNIONFLOW_DOMAIN");
} }
/** /**
* Construit l'URL de base de l'application. * Construit l'URL de base de l'application.
* *
* @return URL complète (ex: http://localhost:8080) * @return URL complète (ex: http://localhost:8080)
*/ */
String buildBaseUrl() { String buildBaseUrl() {
// En production, utiliser le nom de domaine configuré // En production, utiliser le nom de domaine configuré
if ("prod".equals(activeProfile)) { if ("prod".equals(activeProfile)) {
String domain = getUnionflowDomain(); String domain = getUnionflowDomain();
if (domain != null && !domain.isEmpty()) { if (domain != null && !domain.isEmpty()) {
return "https://" + domain; return "https://" + domain;
} }
} }
// En dev/test, utiliser localhost // En dev/test, utiliser localhost
String host = "0.0.0.0".equals(httpHost) ? "localhost" : httpHost; String host = "0.0.0.0".equals(httpHost) ? "localhost" : httpHost;
return String.format("http://%s:%d", host, httpPort); return String.format("http://%s:%d", host, httpPort);
} }
} }

View File

@@ -1,48 +1,48 @@
package dev.lions.unionflow.server.client; package dev.lions.unionflow.server.client;
import io.quarkus.oidc.client.NamedOidcClient; import io.quarkus.oidc.client.NamedOidcClient;
import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.Tokens; import io.quarkus.oidc.client.Tokens;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Injecte le token du service account "admin-service" (client credentials grant) * Injecte le token du service account "admin-service" (client credentials grant)
* dans tous les appels faits via {@link AdminUserServiceClient} et {@link AdminRoleServiceClient}. * dans tous les appels faits via {@link AdminUserServiceClient} et {@link AdminRoleServiceClient}.
* *
* <p>Utilise directement l'API {@link OidcClient} pour récupérer/rafraîchir le token. * <p>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 * Cette approche explicite évite toute ambiguïté avec {@code @OidcClientFilter} quand
* plusieurs interfaces REST partagent le même configKey. * plusieurs interfaces REST partagent le même configKey.
*/ */
@ApplicationScoped @ApplicationScoped
public class AdminServiceTokenHeadersFactory implements ClientHeadersFactory { public class AdminServiceTokenHeadersFactory implements ClientHeadersFactory {
private static final Logger LOG = Logger.getLogger(AdminServiceTokenHeadersFactory.class); private static final Logger LOG = Logger.getLogger(AdminServiceTokenHeadersFactory.class);
@Inject @Inject
@NamedOidcClient("admin-service") @NamedOidcClient("admin-service")
OidcClient adminOidcClient; OidcClient adminOidcClient;
@Override @Override
public MultivaluedMap<String, String> update( public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders) { MultivaluedMap<String, String> clientOutgoingHeaders) {
MultivaluedMap<String, String> result = new MultivaluedHashMap<>(); MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
try { try {
Tokens tokens = adminOidcClient.getTokens().await().indefinitely(); Tokens tokens = adminOidcClient.getTokens().await().indefinitely();
result.add("Authorization", "Bearer " + tokens.getAccessToken()); result.add("Authorization", "Bearer " + tokens.getAccessToken());
LOG.debugf("Token service account injecté pour admin-service (longueur: %d)", LOG.debugf("Token service account injecté pour admin-service (longueur: %d)",
tokens.getAccessToken().length()); tokens.getAccessToken().length());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf("Impossible d'obtenir le token service account 'admin-service': %s", e.getMessage()); LOG.errorf("Impossible d'obtenir le token service account 'admin-service': %s", e.getMessage());
throw new jakarta.ws.rs.ServiceUnavailableException( throw new jakarta.ws.rs.ServiceUnavailableException(
"Service d'authentification interne indisponible: " + e.getMessage()); "Service d'authentification interne indisponible: " + e.getMessage());
} }
return result; return result;
} }
} }

View File

@@ -1,71 +1,71 @@
package dev.lions.unionflow.server.client; package dev.lions.unionflow.server.client;
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal; import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import java.io.IOException; import java.io.IOException;
/** /**
* Filtre REST Client qui propage le token JWT de la requête entrante. * Filtre REST Client qui propage le token JWT de la requête entrante.
* *
* <p>NE PAS annoter avec {@code @Provider} — cela l'enregistrerait GLOBALEMENT * <p>NE PAS annoter avec {@code @Provider} — cela l'enregistrerait GLOBALEMENT
* sur tous les REST clients, y compris AdminUserServiceClient/AdminRoleServiceClient * sur tous les REST clients, y compris AdminUserServiceClient/AdminRoleServiceClient
* qui utilisent AdminServiceTokenHeadersFactory (service account). Le filtre global * qui utilisent AdminServiceTokenHeadersFactory (service account). Le filtre global
* écraserait le token de service account avec le JWT utilisateur → 401 sur LUM. * écraserait le token de service account avec le JWT utilisateur → 401 sur LUM.
* *
* <p>La propagation JWT est assurée par {@link OidcTokenPropagationHeadersFactory} * <p>La propagation JWT est assurée par {@link OidcTokenPropagationHeadersFactory}
* sur les clients qui en ont besoin ({@code @RegisterClientHeaders}). * sur les clients qui en ont besoin ({@code @RegisterClientHeaders}).
*/ */
public class JwtPropagationFilter implements ClientRequestFilter { public class JwtPropagationFilter implements ClientRequestFilter {
private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class); private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class);
@Inject @Inject
SecurityIdentity securityIdentity; SecurityIdentity securityIdentity;
@Override @Override
public void filter(ClientRequestContext requestContext) throws IOException { public void filter(ClientRequestContext requestContext) throws IOException {
if (securityIdentity != null && !securityIdentity.isAnonymous()) { if (securityIdentity != null && !securityIdentity.isAnonymous()) {
// Récupérer le token JWT depuis le principal // Récupérer le token JWT depuis le principal
if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) { if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal(); OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal();
String token = principal.getRawToken(); String token = principal.getRawToken();
if (token != null && !token.isBlank()) { if (token != null && !token.isBlank()) {
requestContext.getHeaders().putSingle( requestContext.getHeaders().putSingle(
HttpHeaders.AUTHORIZATION, HttpHeaders.AUTHORIZATION,
"Bearer " + token "Bearer " + token
); );
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri()); LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
} else { } else {
LOG.warnf("Token JWT vide pour %s", requestContext.getUri()); LOG.warnf("Token JWT vide pour %s", requestContext.getUri());
} }
} else if (securityIdentity.getPrincipal() instanceof JsonWebToken) { } else if (securityIdentity.getPrincipal() instanceof JsonWebToken) {
JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal(); JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal();
String token = jwt.getRawToken(); String token = jwt.getRawToken();
if (token != null && !token.isBlank()) { if (token != null && !token.isBlank()) {
requestContext.getHeaders().putSingle( requestContext.getHeaders().putSingle(
HttpHeaders.AUTHORIZATION, HttpHeaders.AUTHORIZATION,
"Bearer " + token "Bearer " + token
); );
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri()); LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
} }
} else { } else {
LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)", LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)",
requestContext.getUri(), requestContext.getUri(),
securityIdentity.getPrincipal().getClass().getName()); securityIdentity.getPrincipal().getClass().getName());
} }
} else { } else {
LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s", LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s",
requestContext.getUri()); requestContext.getUri());
} }
} }
} }

View File

@@ -1,72 +1,72 @@
package dev.lions.unionflow.server.client; package dev.lions.unionflow.server.client;
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal; import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Factory pour propager automatiquement le token JWT OIDC * Factory pour propager automatiquement le token JWT OIDC
* vers les appels REST Client (compatible Quarkus REST). * vers les appels REST Client (compatible Quarkus REST).
* *
* Stratégie : copier le header Authorization de la requête entrante * Stratégie : copier le header Authorization de la requête entrante
* ou récupérer le token depuis SecurityIdentity si disponible. * ou récupérer le token depuis SecurityIdentity si disponible.
*/ */
@ApplicationScoped @ApplicationScoped
public class OidcTokenPropagationHeadersFactory implements ClientHeadersFactory { public class OidcTokenPropagationHeadersFactory implements ClientHeadersFactory {
private static final Logger LOG = Logger.getLogger(OidcTokenPropagationHeadersFactory.class); private static final Logger LOG = Logger.getLogger(OidcTokenPropagationHeadersFactory.class);
@Inject @Inject
Instance<SecurityIdentity> securityIdentity; Instance<SecurityIdentity> securityIdentity;
@Override @Override
public MultivaluedMap<String, String> update( public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders) { MultivaluedMap<String, String> clientOutgoingHeaders) {
MultivaluedMap<String, String> result = new MultivaluedHashMap<>(); MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
// STRATÉGIE 1 : Copier directement le header Authorization de la requête entrante // STRATÉGIE 1 : Copier directement le header Authorization de la requête entrante
if (incomingHeaders != null && incomingHeaders.containsKey("Authorization")) { if (incomingHeaders != null && incomingHeaders.containsKey("Authorization")) {
String authHeader = incomingHeaders.getFirst("Authorization"); String authHeader = incomingHeaders.getFirst("Authorization");
if (authHeader != null && !authHeader.isBlank()) { if (authHeader != null && !authHeader.isBlank()) {
result.add("Authorization", authHeader); result.add("Authorization", authHeader);
LOG.infof("✅ Token JWT propagé depuis incomingHeaders (longueur: %d)", authHeader.length()); LOG.infof("✅ Token JWT propagé depuis incomingHeaders (longueur: %d)", authHeader.length());
return result; return result;
} }
} }
// STRATÉGIE 2 : Récupérer depuis SecurityIdentity // STRATÉGIE 2 : Récupérer depuis SecurityIdentity
// En contexte CDI, securityIdentity.isResolvable() est toujours true. // En contexte CDI, securityIdentity.isResolvable() est toujours true.
SecurityIdentity identity = securityIdentity.get(); SecurityIdentity identity = securityIdentity.get();
if (identity != null && !identity.isAnonymous()) { if (identity != null && !identity.isAnonymous()) {
if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) { if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal(); OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
String token = principal.getRawToken(); String token = principal.getRawToken();
if (token != null && !token.isBlank()) { if (token != null && !token.isBlank()) {
result.add("Authorization", "Bearer " + token); result.add("Authorization", "Bearer " + token);
LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length()); LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length());
return result; return result;
} else { } else {
LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity"); LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity");
} }
} else { } else {
LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)", LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)",
identity.getPrincipal() != null ? identity.getPrincipal().getClass().getName() : "null"); identity.getPrincipal() != null ? identity.getPrincipal().getClass().getName() : "null");
} }
} else { } else {
LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme"); LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme");
} }
LOG.errorf("❌ Impossible de propager le token JWT - aucune stratégie n'a fonctionné"); LOG.errorf("❌ Impossible de propager le token JWT - aucune stratégie n'a fonctionné");
return result; return result;
} }
} }

View File

@@ -1,57 +1,57 @@
package dev.lions.unionflow.server.client; package dev.lions.unionflow.server.client;
import dev.lions.user.manager.dto.role.RoleDTO; import dev.lions.user.manager.dto.role.RoleDTO;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import java.util.List; import java.util.List;
/** /**
* REST Client pour l'API rôles de lions-user-manager (Keycloak). * REST Client pour l'API rôles de lions-user-manager (Keycloak).
* Même base URL que UserServiceClient (configKey = lions-user-manager-api). * Même base URL que UserServiceClient (configKey = lions-user-manager-api).
*/ */
@Path("/api/roles") @Path("/api/roles")
@RegisterRestClient(configKey = "lions-user-manager-api") @RegisterRestClient(configKey = "lions-user-manager-api")
@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class) @RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public interface RoleServiceClient { public interface RoleServiceClient {
@GET @GET
@Path("/realm") @Path("/realm")
List<RoleDTO> getRealmRoles(@QueryParam("realm") String realmName); List<RoleDTO> getRealmRoles(@QueryParam("realm") String realmName);
@GET @GET
@Path("/user/realm/{userId}") @Path("/user/realm/{userId}")
List<RoleDTO> getUserRealmRoles( List<RoleDTO> getUserRealmRoles(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName @QueryParam("realm") String realmName
); );
@POST @POST
@Path("/assign/realm/{userId}") @Path("/assign/realm/{userId}")
void assignRealmRoles( void assignRealmRoles(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName, @QueryParam("realm") String realmName,
RoleNamesRequest request RoleNamesRequest request
); );
@POST @POST
@Path("/revoke/realm/{userId}") @Path("/revoke/realm/{userId}")
void revokeRealmRoles( void revokeRealmRoles(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName, @QueryParam("realm") String realmName,
RoleNamesRequest request RoleNamesRequest request
); );
/** Corps de requête pour assign/revoke (compatible lions-user-manager). */ /** Corps de requête pour assign/revoke (compatible lions-user-manager). */
class RoleNamesRequest { class RoleNamesRequest {
public List<String> roleNames; public List<String> roleNames;
public RoleNamesRequest() {} public RoleNamesRequest() {}
public RoleNamesRequest(List<String> roleNames) { this.roleNames = roleNames; } public RoleNamesRequest(List<String> roleNames) { this.roleNames = roleNames; }
public List<String> getRoleNames() { return roleNames; } public List<String> getRoleNames() { return roleNames; }
public void setRoleNames(List<String> roleNames) { this.roleNames = roleNames; } public void setRoleNames(List<String> roleNames) { this.roleNames = roleNames; }
} }
} }

View File

@@ -1,77 +1,77 @@
package dev.lions.unionflow.server.client; package dev.lions.unionflow.server.client;
import dev.lions.user.manager.dto.user.UserDTO; import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO; import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO; import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
/** /**
* REST Client pour le service de gestion des utilisateurs Keycloak * REST Client pour le service de gestion des utilisateurs Keycloak
* via lions-user-manager API * via lions-user-manager API
* *
* Configuration dans application.properties: * Configuration dans application.properties:
* quarkus.rest-client.lions-user-manager-api.url=http://localhost:8081 * quarkus.rest-client.lions-user-manager-api.url=http://localhost:8081
*/ */
@Path("/api/users") @Path("/api/users")
@RegisterRestClient(configKey = "lions-user-manager-api") @RegisterRestClient(configKey = "lions-user-manager-api")
@RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class) @RegisterClientHeaders(OidcTokenPropagationHeadersFactory.class)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public interface UserServiceClient { public interface UserServiceClient {
/** /**
* Rechercher des utilisateurs selon des critères * Rechercher des utilisateurs selon des critères
*/ */
@POST @POST
@Path("/search") @Path("/search")
UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria); UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria);
/** /**
* Récupérer un utilisateur par ID * Récupérer un utilisateur par ID
*/ */
@GET @GET
@Path("/{userId}") @Path("/{userId}")
UserDTO getUserById( UserDTO getUserById(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName); @QueryParam("realm") String realmName);
/** /**
* Créer un nouvel utilisateur * Créer un nouvel utilisateur
*/ */
@POST @POST
UserDTO createUser( UserDTO createUser(
UserDTO user, UserDTO user,
@QueryParam("realm") String realmName); @QueryParam("realm") String realmName);
/** /**
* Mettre à jour un utilisateur * Mettre à jour un utilisateur
*/ */
@PUT @PUT
@Path("/{userId}") @Path("/{userId}")
UserDTO updateUser( UserDTO updateUser(
@PathParam("userId") String userId, @PathParam("userId") String userId,
UserDTO user, UserDTO user,
@QueryParam("realm") String realmName); @QueryParam("realm") String realmName);
/** /**
* Supprimer un utilisateur * Supprimer un utilisateur
*/ */
@DELETE @DELETE
@Path("/{userId}") @Path("/{userId}")
void deleteUser( void deleteUser(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName); @QueryParam("realm") String realmName);
/** /**
* Envoyer un email de vérification * Envoyer un email de vérification
*/ */
@POST @POST
@Path("/{userId}/send-verification-email") @Path("/{userId}/send-verification-email")
void sendVerificationEmail( void sendVerificationEmail(
@PathParam("userId") String userId, @PathParam("userId") String userId,
@QueryParam("realm") String realmName); @QueryParam("realm") String realmName);
} }

View File

@@ -1,143 +1,143 @@
package dev.lions.unionflow.server.dto; package dev.lions.unionflow.server.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import dev.lions.unionflow.server.entity.Evenement; import dev.lions.unionflow.server.entity.Evenement;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le * DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le
* format attendu par * format attendu par
* l'application mobile Flutter * l'application mobile Flutter
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 2.0 * @version 2.0
* @since 2025-01-16 * @since 2025-01-16
*/ */
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class EvenementMobileDTO { public class EvenementMobileDTO {
private UUID id; private UUID id;
private String titre; private String titre;
private String description; private String description;
private LocalDateTime dateDebut; private LocalDateTime dateDebut;
private LocalDateTime dateFin; private LocalDateTime dateFin;
private String lieu; private String lieu;
private String adresse; private String adresse;
private String ville; private String ville;
private String codePostal; private String codePostal;
// Mapping: typeEvenement -> type // Mapping: typeEvenement -> type
private String type; private String type;
// Mapping: statut -> statut (OK) // Mapping: statut -> statut (OK)
private String statut; private String statut;
// Mapping: capaciteMax -> maxParticipants // Mapping: capaciteMax -> maxParticipants
private Integer maxParticipants; private Integer maxParticipants;
// Nombre de participants actuels (calculé depuis les inscriptions) // Nombre de participants actuels (calculé depuis les inscriptions)
private Integer participantsActuels; private Integer participantsActuels;
// IDs et noms pour les relations // IDs et noms pour les relations
private UUID organisateurId; private UUID organisateurId;
private String organisateurNom; private String organisateurNom;
private UUID organisationId; private UUID organisationId;
private String organisationNom; private String organisationNom;
// Priorité (à ajouter dans l'entité si nécessaire) // Priorité (à ajouter dans l'entité si nécessaire)
private String priorite; private String priorite;
// Mapping: visiblePublic -> estPublic // Mapping: visiblePublic -> estPublic
private Boolean estPublic; private Boolean estPublic;
// Mapping: inscriptionRequise -> inscriptionRequise (OK) // Mapping: inscriptionRequise -> inscriptionRequise (OK)
private Boolean inscriptionRequise; private Boolean inscriptionRequise;
// Mapping: prix -> cout // Mapping: prix -> cout
private BigDecimal cout; private BigDecimal cout;
// Devise // Devise
private String devise; private String devise;
// Tags (à implémenter si nécessaire) // Tags (à implémenter si nécessaire)
private String[] tags; private String[] tags;
// URLs // URLs
private String imageUrl; private String imageUrl;
private String documentUrl; private String documentUrl;
// Notes // Notes
private String notes; private String notes;
// Dates de création/modification // Dates de création/modification
private LocalDateTime dateCreation; private LocalDateTime dateCreation;
private LocalDateTime dateModification; private LocalDateTime dateModification;
// Actif // Actif
private Boolean actif; private Boolean actif;
/** /**
* Convertit une entité Evenement en DTO mobile * Convertit une entité Evenement en DTO mobile
* *
* @param evenement L'entité à convertir * @param evenement L'entité à convertir
* @return Le DTO mobile * @return Le DTO mobile
*/ */
public static EvenementMobileDTO fromEntity(Evenement evenement) { public static EvenementMobileDTO fromEntity(Evenement evenement) {
if (evenement == null) { if (evenement == null) {
return null; return null;
} }
return EvenementMobileDTO.builder() return EvenementMobileDTO.builder()
.id(evenement.getId()) // Utilise getId() depuis BaseEntity .id(evenement.getId()) // Utilise getId() depuis BaseEntity
.titre(evenement.getTitre()) .titre(evenement.getTitre())
.description(evenement.getDescription()) .description(evenement.getDescription())
.dateDebut(evenement.getDateDebut()) .dateDebut(evenement.getDateDebut())
.dateFin(evenement.getDateFin()) .dateFin(evenement.getDateFin())
.lieu(evenement.getLieu()) .lieu(evenement.getLieu())
.adresse(evenement.getAdresse()) .adresse(evenement.getAdresse())
.ville(null) // Pas de champ ville dans l'entité .ville(null) // Pas de champ ville dans l'entité
.codePostal(null) // Pas de champ codePostal dans l'entité .codePostal(null) // Pas de champ codePostal dans l'entité
// Mapping des enums // Mapping des enums
.type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement() : null) .type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement() : null)
.statut(evenement.getStatut() != null ? evenement.getStatut() : "PLANIFIE") .statut(evenement.getStatut() != null ? evenement.getStatut() : "PLANIFIE")
// Mapping des champs renommés // Mapping des champs renommés
.maxParticipants(evenement.getCapaciteMax()) .maxParticipants(evenement.getCapaciteMax())
.participantsActuels(evenement.getNombreInscrits()) .participantsActuels(evenement.getNombreInscrits())
// Relations (gestion sécurisée des lazy loading) // Relations (gestion sécurisée des lazy loading)
.organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null) .organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null)
.organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null) .organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null)
.organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null) .organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null)
.organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null) .organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null)
// Priorité (valeur par défaut) // Priorité (valeur par défaut)
.priorite("MOYENNE") .priorite("MOYENNE")
// Mapping booléens // Mapping booléens
.estPublic(evenement.getVisiblePublic()) .estPublic(evenement.getVisiblePublic())
.inscriptionRequise(evenement.getInscriptionRequise()) .inscriptionRequise(evenement.getInscriptionRequise())
// Mapping prix -> cout // Mapping prix -> cout
.cout(evenement.getPrix()) .cout(evenement.getPrix())
.devise("XOF") .devise("XOF")
// Tags vides pour l'instant // Tags vides pour l'instant
.tags(new String[] {}) .tags(new String[] {})
// URLs (à implémenter si nécessaire) // URLs (à implémenter si nécessaire)
.imageUrl(null) .imageUrl(null)
.documentUrl(null) .documentUrl(null)
// Notes // Notes
.notes(evenement.getInstructionsParticulieres()) .notes(evenement.getInstructionsParticulieres())
// Dates // Dates
.dateCreation(evenement.getDateCreation()) .dateCreation(evenement.getDateCreation())
.dateModification(evenement.getDateModification()) .dateModification(evenement.getDateModification())
// Actif // Actif
.actif(evenement.getActif()) .actif(evenement.getActif())
.build(); .build();
} }
} }

View File

@@ -1,150 +1,150 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Adresse pour la gestion des adresses des organisations, membres et * Entité Adresse pour la gestion des adresses des organisations, membres et
* événements * événements
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table(name = "adresses", indexes = { @Table(name = "adresses", indexes = {
@Index(name = "idx_adresse_ville", columnList = "ville"), @Index(name = "idx_adresse_ville", columnList = "ville"),
@Index(name = "idx_adresse_pays", columnList = "pays"), @Index(name = "idx_adresse_pays", columnList = "pays"),
@Index(name = "idx_adresse_type", columnList = "type_adresse"), @Index(name = "idx_adresse_type", columnList = "type_adresse"),
@Index(name = "idx_adresse_organisation", columnList = "organisation_id"), @Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
@Index(name = "idx_adresse_membre", columnList = "membre_id"), @Index(name = "idx_adresse_membre", columnList = "membre_id"),
@Index(name = "idx_adresse_evenement", columnList = "evenement_id") @Index(name = "idx_adresse_evenement", columnList = "evenement_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Adresse extends BaseEntity { public class Adresse extends BaseEntity {
/** Type d'adresse (code depuis types_reference) */ /** Type d'adresse (code depuis types_reference) */
@Column(name = "type_adresse", nullable = false, length = 50) @Column(name = "type_adresse", nullable = false, length = 50)
private String typeAdresse; private String typeAdresse;
/** Adresse complète */ /** Adresse complète */
@Column(name = "adresse", length = 500) @Column(name = "adresse", length = 500)
private String adresse; private String adresse;
/** Complément d'adresse */ /** Complément d'adresse */
@Column(name = "complement_adresse", length = 200) @Column(name = "complement_adresse", length = 200)
private String complementAdresse; private String complementAdresse;
/** Code postal */ /** Code postal */
@Column(name = "code_postal", length = 20) @Column(name = "code_postal", length = 20)
private String codePostal; private String codePostal;
/** Ville */ /** Ville */
@Column(name = "ville", length = 100) @Column(name = "ville", length = 100)
private String ville; private String ville;
/** Région */ /** Région */
@Column(name = "region", length = 100) @Column(name = "region", length = 100)
private String region; private String region;
/** Pays */ /** Pays */
@Column(name = "pays", length = 100) @Column(name = "pays", length = 100)
private String pays; private String pays;
/** Coordonnées géographiques - Latitude */ /** Coordonnées géographiques - Latitude */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90") @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") @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6) @Digits(integer = 3, fraction = 6)
@Column(name = "latitude", precision = 9, scale = 6) @Column(name = "latitude", precision = 9, scale = 6)
private BigDecimal latitude; private BigDecimal latitude;
/** Coordonnées géographiques - Longitude */ /** Coordonnées géographiques - Longitude */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180") @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") @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6) @Digits(integer = 3, fraction = 6)
@Column(name = "longitude", precision = 9, scale = 6) @Column(name = "longitude", precision = 9, scale = 6)
private BigDecimal longitude; private BigDecimal longitude;
/** Adresse principale (une seule par entité) */ /** Adresse principale (une seule par entité) */
@Builder.Default @Builder.Default
@Column(name = "principale", nullable = false) @Column(name = "principale", nullable = false)
private Boolean principale = false; private Boolean principale = false;
/** Libellé personnalisé */ /** Libellé personnalisé */
@Column(name = "libelle", length = 100) @Column(name = "libelle", length = 100)
private String libelle; private String libelle;
/** Notes et commentaires */ /** Notes et commentaires */
@Column(name = "notes", length = 500) @Column(name = "notes", length = 500)
private String notes; private String notes;
// Relations // Relations
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id") @JoinColumn(name = "membre_id")
private Membre membre; private Membre membre;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id") @JoinColumn(name = "evenement_id")
private Evenement evenement; private Evenement evenement;
/** Méthode métier pour obtenir l'adresse complète formatée */ /** Méthode métier pour obtenir l'adresse complète formatée */
public String getAdresseComplete() { public String getAdresseComplete() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (adresse != null && !adresse.isEmpty()) { if (adresse != null && !adresse.isEmpty()) {
sb.append(adresse); sb.append(adresse);
} }
if (complementAdresse != null && !complementAdresse.isEmpty()) { if (complementAdresse != null && !complementAdresse.isEmpty()) {
if (sb.length() > 0) if (sb.length() > 0)
sb.append(", "); sb.append(", ");
sb.append(complementAdresse); sb.append(complementAdresse);
} }
if (codePostal != null && !codePostal.isEmpty()) { if (codePostal != null && !codePostal.isEmpty()) {
if (sb.length() > 0) if (sb.length() > 0)
sb.append(", "); sb.append(", ");
sb.append(codePostal); sb.append(codePostal);
} }
if (ville != null && !ville.isEmpty()) { if (ville != null && !ville.isEmpty()) {
if (sb.length() > 0) if (sb.length() > 0)
sb.append(" "); sb.append(" ");
sb.append(ville); sb.append(ville);
} }
if (region != null && !region.isEmpty()) { if (region != null && !region.isEmpty()) {
if (sb.length() > 0) if (sb.length() > 0)
sb.append(", "); sb.append(", ");
sb.append(region); sb.append(region);
} }
if (pays != null && !pays.isEmpty()) { if (pays != null && !pays.isEmpty()) {
if (sb.length() > 0) if (sb.length() > 0)
sb.append(", "); sb.append(", ");
sb.append(pays); sb.append(pays);
} }
return sb.toString(); return sb.toString();
} }
/** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */ /** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
public boolean hasCoordinates() { public boolean hasCoordinates() {
return latitude != null && longitude != null; return latitude != null && longitude != null;
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
protected void onCreate() { protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity super.onCreate(); // Appelle le onCreate de BaseEntity
if (principale == null) { if (principale == null) {
principale = false; principale = false;
} }
} }
} }

View File

@@ -1,113 +1,113 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/** /**
* Entité singleton pour la configuration des alertes système. * Entité singleton pour la configuration des alertes système.
* Une seule ligne en base de données. * Une seule ligne en base de données.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-15 * @since 2026-03-15
*/ */
@Entity @Entity
@Table(name = "alert_configuration") @Table(name = "alert_configuration")
@Getter @Getter
@Setter @Setter
public class AlertConfiguration extends BaseEntity { public class AlertConfiguration extends BaseEntity {
/** /**
* Alerte CPU activée * Alerte CPU activée
*/ */
@Column(name = "cpu_high_alert_enabled", nullable = false) @Column(name = "cpu_high_alert_enabled", nullable = false)
private Boolean cpuHighAlertEnabled = true; private Boolean cpuHighAlertEnabled = true;
/** /**
* Seuil CPU en pourcentage (0-100) * Seuil CPU en pourcentage (0-100)
*/ */
@Column(name = "cpu_threshold_percent", nullable = false) @Column(name = "cpu_threshold_percent", nullable = false)
private Integer cpuThresholdPercent = 80; private Integer cpuThresholdPercent = 80;
/** /**
* Durée en minutes avant déclenchement alerte CPU * Durée en minutes avant déclenchement alerte CPU
*/ */
@Column(name = "cpu_duration_minutes", nullable = false) @Column(name = "cpu_duration_minutes", nullable = false)
private Integer cpuDurationMinutes = 5; private Integer cpuDurationMinutes = 5;
/** /**
* Alerte mémoire faible activée * Alerte mémoire faible activée
*/ */
@Column(name = "memory_low_alert_enabled", nullable = false) @Column(name = "memory_low_alert_enabled", nullable = false)
private Boolean memoryLowAlertEnabled = true; private Boolean memoryLowAlertEnabled = true;
/** /**
* Seuil mémoire en pourcentage (0-100) * Seuil mémoire en pourcentage (0-100)
*/ */
@Column(name = "memory_threshold_percent", nullable = false) @Column(name = "memory_threshold_percent", nullable = false)
private Integer memoryThresholdPercent = 85; private Integer memoryThresholdPercent = 85;
/** /**
* Alerte erreur critique activée * Alerte erreur critique activée
*/ */
@Column(name = "critical_error_alert_enabled", nullable = false) @Column(name = "critical_error_alert_enabled", nullable = false)
private Boolean criticalErrorAlertEnabled = true; private Boolean criticalErrorAlertEnabled = true;
/** /**
* Alerte erreur activée * Alerte erreur activée
*/ */
@Column(name = "error_alert_enabled", nullable = false) @Column(name = "error_alert_enabled", nullable = false)
private Boolean errorAlertEnabled = true; private Boolean errorAlertEnabled = true;
/** /**
* Alerte échec de connexion activée * Alerte échec de connexion activée
*/ */
@Column(name = "connection_failure_alert_enabled", nullable = false) @Column(name = "connection_failure_alert_enabled", nullable = false)
private Boolean connectionFailureAlertEnabled = true; private Boolean connectionFailureAlertEnabled = true;
/** /**
* Seuil d'échecs de connexion * Seuil d'échecs de connexion
*/ */
@Column(name = "connection_failure_threshold", nullable = false) @Column(name = "connection_failure_threshold", nullable = false)
private Integer connectionFailureThreshold = 100; private Integer connectionFailureThreshold = 100;
/** /**
* Fenêtre temporelle en minutes pour les échecs de connexion * Fenêtre temporelle en minutes pour les échecs de connexion
*/ */
@Column(name = "connection_failure_window_minutes", nullable = false) @Column(name = "connection_failure_window_minutes", nullable = false)
private Integer connectionFailureWindowMinutes = 5; private Integer connectionFailureWindowMinutes = 5;
/** /**
* Notifications par email activées * Notifications par email activées
*/ */
@Column(name = "email_notifications_enabled", nullable = false) @Column(name = "email_notifications_enabled", nullable = false)
private Boolean emailNotificationsEnabled = true; private Boolean emailNotificationsEnabled = true;
/** /**
* Notifications push activées * Notifications push activées
*/ */
@Column(name = "push_notifications_enabled", nullable = false) @Column(name = "push_notifications_enabled", nullable = false)
private Boolean pushNotificationsEnabled = false; private Boolean pushNotificationsEnabled = false;
/** /**
* Notifications SMS activées * Notifications SMS activées
*/ */
@Column(name = "sms_notifications_enabled", nullable = false) @Column(name = "sms_notifications_enabled", nullable = false)
private Boolean smsNotificationsEnabled = false; private Boolean smsNotificationsEnabled = false;
/** /**
* Liste des emails destinataires des alertes (séparés par virgule) * Liste des emails destinataires des alertes (séparés par virgule)
*/ */
@Column(name = "alert_email_recipients", length = 1000) @Column(name = "alert_email_recipients", length = 1000)
private String alertEmailRecipients = "admin@unionflow.test"; private String alertEmailRecipients = "admin@unionflow.test";
/** /**
* S'assurer qu'il n'y a qu'une seule configuration * S'assurer qu'il n'y a qu'une seule configuration
*/ */
@PrePersist @PrePersist
@PreUpdate @PreUpdate
protected void ensureSingleton() { protected void ensureSingleton() {
// La logique singleton sera gérée par le repository // La logique singleton sera gérée par le repository
} }
} }

View File

@@ -1,124 +1,124 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
/** /**
* Entité représentant une alerte LCB-FT (Lutte Contre le Blanchiment et Financement du Terrorisme). * 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. * Les alertes sont générées automatiquement lors de transactions dépassant les seuils configurés.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-15 * @since 2026-03-15
*/ */
@Entity @Entity
@Table(name = "alertes_lcb_ft", indexes = { @Table(name = "alertes_lcb_ft", indexes = {
@Index(name = "idx_alerte_lcb_ft_organisation", columnList = "organisation_id"), @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_type", columnList = "type_alerte"),
@Index(name = "idx_alerte_lcb_ft_date", columnList = "date_alerte"), @Index(name = "idx_alerte_lcb_ft_date", columnList = "date_alerte"),
@Index(name = "idx_alerte_lcb_ft_traitee", columnList = "traitee") @Index(name = "idx_alerte_lcb_ft_traitee", columnList = "traitee")
}) })
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AlerteLcbFt extends BaseEntity { public class AlerteLcbFt extends BaseEntity {
/** /**
* Organisation concernée par l'alerte * Organisation concernée par l'alerte
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
/** /**
* Membre concerné par l'alerte * Membre concerné par l'alerte
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id") @JoinColumn(name = "membre_id")
private Membre membre; private Membre membre;
/** /**
* Type d'alerte : SEUIL_DEPASSE, JUSTIFICATION_MANQUANTE, etc. * Type d'alerte : SEUIL_DEPASSE, JUSTIFICATION_MANQUANTE, etc.
*/ */
@Column(name = "type_alerte", nullable = false, length = 50) @Column(name = "type_alerte", nullable = false, length = 50)
private String typeAlerte; private String typeAlerte;
/** /**
* Date et heure de génération de l'alerte * Date et heure de génération de l'alerte
*/ */
@Column(name = "date_alerte", nullable = false) @Column(name = "date_alerte", nullable = false)
private LocalDateTime dateAlerte; private LocalDateTime dateAlerte;
/** /**
* Description de l'alerte * Description de l'alerte
*/ */
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** /**
* Détails supplémentaires (JSON ou texte) * Détails supplémentaires (JSON ou texte)
*/ */
@Column(name = "details", columnDefinition = "TEXT") @Column(name = "details", columnDefinition = "TEXT")
private String details; private String details;
/** /**
* Montant de la transaction ayant généré l'alerte * Montant de la transaction ayant généré l'alerte
*/ */
@Column(name = "montant", precision = 15, scale = 2) @Column(name = "montant", precision = 15, scale = 2)
private BigDecimal montant; private BigDecimal montant;
/** /**
* Seuil qui a été dépassé * Seuil qui a été dépassé
*/ */
@Column(name = "seuil", precision = 15, scale = 2) @Column(name = "seuil", precision = 15, scale = 2)
private BigDecimal seuil; private BigDecimal seuil;
/** /**
* Type d'opération : DEPOT, RETRAIT, TRANSFERT, etc. * Type d'opération : DEPOT, RETRAIT, TRANSFERT, etc.
*/ */
@Column(name = "type_operation", length = 50) @Column(name = "type_operation", length = 50)
private String typeOperation; private String typeOperation;
/** /**
* Référence de la transaction concernée (UUID) * Référence de la transaction concernée (UUID)
*/ */
@Column(name = "transaction_ref", length = 100) @Column(name = "transaction_ref", length = 100)
private String transactionRef; private String transactionRef;
/** /**
* Niveau de gravité : INFO, WARNING, CRITICAL * Niveau de gravité : INFO, WARNING, CRITICAL
*/ */
@Column(name = "severite", nullable = false, length = 20) @Column(name = "severite", nullable = false, length = 20)
private String severite; private String severite;
/** /**
* Indique si l'alerte a été traitée * Indique si l'alerte a été traitée
*/ */
@Builder.Default @Builder.Default
@Column(name = "traitee", nullable = false) @Column(name = "traitee", nullable = false)
private Boolean traitee = false; private Boolean traitee = false;
/** /**
* Date de traitement de l'alerte * Date de traitement de l'alerte
*/ */
@Column(name = "date_traitement") @Column(name = "date_traitement")
private LocalDateTime dateTraitement; private LocalDateTime dateTraitement;
/** /**
* Utilisateur ayant traité l'alerte * Utilisateur ayant traité l'alerte
*/ */
@Column(name = "traite_par") @Column(name = "traite_par")
private UUID traitePar; private UUID traitePar;
/** /**
* Commentaire sur le traitement * Commentaire sur le traitement
*/ */
@Column(name = "commentaire_traitement", columnDefinition = "TEXT") @Column(name = "commentaire_traitement", columnDefinition = "TEXT")
private String commentaireTraitement; private String commentaireTraitement;
} }

View File

@@ -1,94 +1,94 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Action d'Approbateur * Entité Action d'Approbateur
* *
* Représente l'action (approve/reject) d'un approbateur sur une demande d'approbation. * Représente l'action (approve/reject) d'un approbateur sur une demande d'approbation.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-13 * @since 2026-03-13
*/ */
@Entity @Entity
@Table(name = "approver_actions", indexes = { @Table(name = "approver_actions", indexes = {
@Index(name = "idx_approver_action_approval", columnList = "approval_id"), @Index(name = "idx_approver_action_approval", columnList = "approval_id"),
@Index(name = "idx_approver_action_approver", columnList = "approver_id"), @Index(name = "idx_approver_action_approver", columnList = "approver_id"),
@Index(name = "idx_approver_action_decision", columnList = "decision"), @Index(name = "idx_approver_action_decision", columnList = "decision"),
@Index(name = "idx_approver_action_decided_at", columnList = "decided_at") @Index(name = "idx_approver_action_decided_at", columnList = "decided_at")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ApproverAction extends BaseEntity { public class ApproverAction extends BaseEntity {
/** Approbation parente */ /** Approbation parente */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "approval_id", nullable = false) @JoinColumn(name = "approval_id", nullable = false)
private TransactionApproval approval; private TransactionApproval approval;
/** ID de l'approbateur (membre) */ /** ID de l'approbateur (membre) */
@NotNull @NotNull
@Column(name = "approver_id", nullable = false) @Column(name = "approver_id", nullable = false)
private UUID approverId; private UUID approverId;
/** Nom complet de l'approbateur (cache) */ /** Nom complet de l'approbateur (cache) */
@NotBlank @NotBlank
@Column(name = "approver_name", nullable = false, length = 200) @Column(name = "approver_name", nullable = false, length = 200)
private String approverName; private String approverName;
/** Rôle de l'approbateur au moment de l'action */ /** Rôle de l'approbateur au moment de l'action */
@NotBlank @NotBlank
@Column(name = "approver_role", nullable = false, length = 50) @Column(name = "approver_role", nullable = false, length = 50)
private String approverRole; private String approverRole;
/** Décision (PENDING, APPROVED, REJECTED) */ /** Décision (PENDING, APPROVED, REJECTED) */
@NotBlank @NotBlank
@Pattern(regexp = "^(PENDING|APPROVED|REJECTED)$") @Pattern(regexp = "^(PENDING|APPROVED|REJECTED)$")
@Builder.Default @Builder.Default
@Column(name = "decision", nullable = false, length = 10) @Column(name = "decision", nullable = false, length = 10)
private String decision = "PENDING"; private String decision = "PENDING";
/** Commentaire optionnel */ /** Commentaire optionnel */
@Size(max = 1000) @Size(max = 1000)
@Column(name = "comment", length = 1000) @Column(name = "comment", length = 1000)
private String comment; private String comment;
/** Date de la décision */ /** Date de la décision */
@Column(name = "decided_at") @Column(name = "decided_at")
private LocalDateTime decidedAt; private LocalDateTime decidedAt;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (decision == null) { if (decision == null) {
decision = "PENDING"; decision = "PENDING";
} }
} }
/** Méthode métier pour approuver avec commentaire */ /** Méthode métier pour approuver avec commentaire */
public void approve(String comment) { public void approve(String comment) {
this.decision = "APPROVED"; this.decision = "APPROVED";
this.comment = comment; this.comment = comment;
this.decidedAt = LocalDateTime.now(); this.decidedAt = LocalDateTime.now();
} }
/** Méthode métier pour rejeter avec raison */ /** Méthode métier pour rejeter avec raison */
public void reject(String reason) { public void reject(String reason) {
this.decision = "REJECTED"; this.decision = "REJECTED";
this.comment = reason; this.comment = reason;
this.decidedAt = LocalDateTime.now(); this.decidedAt = LocalDateTime.now();
} }
} }

View File

@@ -1,99 +1,99 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.audit.PorteeAudit; import dev.lions.unionflow.server.api.enums.audit.PorteeAudit;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/** /**
* Entité pour les logs d'audit * Entité pour les logs d'audit
* Enregistre toutes les actions importantes du système * Enregistre toutes les actions importantes du système
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2025-01-17 * @since 2025-01-17
*/ */
@Entity @Entity
@Table(name = "audit_logs", indexes = { @Table(name = "audit_logs", indexes = {
@Index(name = "idx_audit_date_heure", columnList = "date_heure"), @Index(name = "idx_audit_date_heure", columnList = "date_heure"),
@Index(name = "idx_audit_utilisateur", columnList = "utilisateur"), @Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
@Index(name = "idx_audit_module", columnList = "module"), @Index(name = "idx_audit_module", columnList = "module"),
@Index(name = "idx_audit_type_action", columnList = "type_action"), @Index(name = "idx_audit_type_action", columnList = "type_action"),
@Index(name = "idx_audit_severite", columnList = "severite") @Index(name = "idx_audit_severite", columnList = "severite")
}) })
@Getter @Getter
@Setter @Setter
public class AuditLog extends BaseEntity { public class AuditLog extends BaseEntity {
@Column(name = "type_action", nullable = false, length = 50) @Column(name = "type_action", nullable = false, length = 50)
private String typeAction; private String typeAction;
@Column(name = "severite", nullable = false, length = 20) @Column(name = "severite", nullable = false, length = 20)
private String severite; private String severite;
@Column(name = "utilisateur", length = 255) @Column(name = "utilisateur", length = 255)
private String utilisateur; private String utilisateur;
@Column(name = "role", length = 50) @Column(name = "role", length = 50)
private String role; private String role;
@Column(name = "module", length = 50) @Column(name = "module", length = 50)
private String module; private String module;
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
@Column(name = "details", columnDefinition = "TEXT") @Column(name = "details", columnDefinition = "TEXT")
private String details; private String details;
@Column(name = "ip_address", length = 45) @Column(name = "ip_address", length = 45)
private String ipAddress; private String ipAddress;
@Column(name = "user_agent", length = 500) @Column(name = "user_agent", length = 500)
private String userAgent; private String userAgent;
@Column(name = "session_id", length = 255) @Column(name = "session_id", length = 255)
private String sessionId; private String sessionId;
@Column(name = "date_heure", nullable = false) @Column(name = "date_heure", nullable = false)
private LocalDateTime dateHeure; private LocalDateTime dateHeure;
@Column(name = "donnees_avant", columnDefinition = "TEXT") @Column(name = "donnees_avant", columnDefinition = "TEXT")
private String donneesAvant; private String donneesAvant;
@Column(name = "donnees_apres", columnDefinition = "TEXT") @Column(name = "donnees_apres", columnDefinition = "TEXT")
private String donneesApres; private String donneesApres;
@Column(name = "entite_id", length = 255) @Column(name = "entite_id", length = 255)
private String entiteId; private String entiteId;
@Column(name = "entite_type", length = 100) @Column(name = "entite_type", length = 100)
private String entiteType; private String entiteType;
/** /**
* Organisation concernée par cet événement d'audit. * Organisation concernée par cet événement d'audit.
* NULL pour les événements de portée PLATEFORME. * NULL pour les événements de portée PLATEFORME.
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
/** /**
* Portée de visibilité : * Portée de visibilité :
* ORGANISATION = visible par le manager de l'organisation * ORGANISATION = visible par le manager de l'organisation
* PLATEFORME = visible uniquement par le Super Admin UnionFlow * PLATEFORME = visible uniquement par le Super Admin UnionFlow
*/ */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "portee", nullable = false, length = 15) @Column(name = "portee", nullable = false, length = 15)
private PorteeAudit portee = PorteeAudit.PLATEFORME; private PorteeAudit portee = PorteeAudit.PLATEFORME;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (dateHeure == null) { if (dateHeure == null) {
dateHeure = LocalDateTime.now(); dateHeure = LocalDateTime.now();
} }
} }
} }

View File

@@ -1,95 +1,95 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente; import dev.lions.unionflow.server.api.enums.ayantdroit.LienParente;
import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit; import dev.lions.unionflow.server.api.enums.ayantdroit.StatutAyantDroit;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.*; import lombok.*;
/** /**
* Ayant droit d'un membre dans une mutuelle de santé. * Ayant droit d'un membre dans une mutuelle de santé.
* *
* <p> * <p>
* Permet la gestion des bénéficiaires (conjoint, enfants, parents) pour * Permet la gestion des bénéficiaires (conjoint, enfants, parents) pour
* les conventions avec les centres de santé partenaires et les plafonds * les conventions avec les centres de santé partenaires et les plafonds
* annuels. * annuels.
* *
* <p> * <p>
* Table : {@code ayants_droit} * Table : {@code ayants_droit}
*/ */
@Entity @Entity
@Table(name = "ayants_droit", indexes = { @Table(name = "ayants_droit", indexes = {
@Index(name = "idx_ad_membre_org", columnList = "membre_organisation_id"), @Index(name = "idx_ad_membre_org", columnList = "membre_organisation_id"),
@Index(name = "idx_ad_couverture", columnList = "date_debut_couverture, date_fin_couverture") @Index(name = "idx_ad_couverture", columnList = "date_debut_couverture, date_fin_couverture")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class AyantDroit extends BaseEntity { public class AyantDroit extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_organisation_id", nullable = false) @JoinColumn(name = "membre_organisation_id", nullable = false)
private MembreOrganisation membreOrganisation; private MembreOrganisation membreOrganisation;
@NotBlank @NotBlank
@Column(name = "prenom", nullable = false, length = 100) @Column(name = "prenom", nullable = false, length = 100)
private String prenom; private String prenom;
@NotBlank @NotBlank
@Column(name = "nom", nullable = false, length = 100) @Column(name = "nom", nullable = false, length = 100)
private String nom; private String nom;
@Column(name = "date_naissance") @Column(name = "date_naissance")
private LocalDate dateNaissance; private LocalDate dateNaissance;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@NotNull @NotNull
@Column(name = "lien_parente", nullable = false, length = 20) @Column(name = "lien_parente", nullable = false, length = 20)
private LienParente lienParente; private LienParente lienParente;
/** Numéro attribué pour les conventions santé avec les centres partenaires */ /** Numéro attribué pour les conventions santé avec les centres partenaires */
@Column(name = "numero_beneficiaire", length = 50) @Column(name = "numero_beneficiaire", length = 50)
private String numeroBeneficiaire; private String numeroBeneficiaire;
@Column(name = "date_debut_couverture") @Column(name = "date_debut_couverture")
private LocalDate dateDebutCouverture; private LocalDate dateDebutCouverture;
/** NULL = couverture ouverte */ /** NULL = couverture ouverte */
@Column(name = "date_fin_couverture") @Column(name = "date_fin_couverture")
private LocalDate dateFinCouverture; private LocalDate dateFinCouverture;
@Column(name = "sexe", length = 20) @Column(name = "sexe", length = 20)
private String sexe; private String sexe;
@Column(name = "piece_identite", length = 100) @Column(name = "piece_identite", length = 100)
private String pieceIdentite; private String pieceIdentite;
@Column(name = "pourcentage_couverture", precision = 5, scale = 2) @Column(name = "pourcentage_couverture", precision = 5, scale = 2)
private BigDecimal pourcentageCouvertureSante; private BigDecimal pourcentageCouvertureSante;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutAyantDroit statut = StatutAyantDroit.EN_ATTENTE; private StatutAyantDroit statut = StatutAyantDroit.EN_ATTENTE;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean isCouvertAujourdhui() { public boolean isCouvertAujourdhui() {
LocalDate today = LocalDate.now(); LocalDate today = LocalDate.now();
if (dateDebutCouverture != null && today.isBefore(dateDebutCouverture)) if (dateDebutCouverture != null && today.isBefore(dateDebutCouverture))
return false; return false;
if (dateFinCouverture != null && today.isAfter(dateFinCouverture)) if (dateFinCouverture != null && today.isAfter(dateFinCouverture))
return false; return false;
return Boolean.TRUE.equals(getActif()); return Boolean.TRUE.equals(getActif());
} }
public String getNomComplet() { public String getNomComplet() {
return prenom + " " + nom; return prenom + " " + nom;
} }
} }

View File

@@ -1,101 +1,101 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.entity.listener.AuditEntityListener; import dev.lions.unionflow.server.entity.listener.AuditEntityListener;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/** /**
* Classe de base pour toutes les entités UnionFlow. * Classe de base pour toutes les entités UnionFlow.
* *
* <p> * <p>
* Étend PanacheEntityBase pour bénéficier du pattern Active Record et résoudre * Étend PanacheEntityBase pour bénéficier du pattern Active Record et résoudre
* les warnings Hibernate. * les warnings Hibernate.
* Fournit les champs communs d'audit et le versioning optimistic. * Fournit les champs communs d'audit et le versioning optimistic.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 4.0 * @version 4.0
*/ */
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditEntityListener.class) @EntityListeners(AuditEntityListener.class)
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public abstract class BaseEntity extends PanacheEntityBase { public abstract class BaseEntity extends PanacheEntityBase {
/** Identifiant unique auto-généré. */ /** Identifiant unique auto-généré. */
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id", updatable = false, nullable = false) @Column(name = "id", updatable = false, nullable = false)
private UUID id; private UUID id;
/** /**
* Date de création. * Date de création.
*/ */
@Column(name = "date_creation", nullable = false, updatable = false) @Column(name = "date_creation", nullable = false, updatable = false)
private LocalDateTime dateCreation; private LocalDateTime dateCreation;
/** /**
* Date de dernière modification. * Date de dernière modification.
*/ */
@Column(name = "date_modification") @Column(name = "date_modification")
private LocalDateTime dateModification; private LocalDateTime dateModification;
/** /**
* Email de l'utilisateur ayant créé l'entité. * Email de l'utilisateur ayant créé l'entité.
*/ */
@Column(name = "cree_par", length = 255) @Column(name = "cree_par", length = 255)
private String creePar; private String creePar;
/** /**
* Email du dernier utilisateur ayant modifié l'entité. * Email du dernier utilisateur ayant modifié l'entité.
*/ */
@Column(name = "modifie_par", length = 255) @Column(name = "modifie_par", length = 255)
private String modifiePar; private String modifiePar;
/** Version pour l'optimistic locking JPA. */ /** Version pour l'optimistic locking JPA. */
@Version @Version
@Column(name = "version") @Column(name = "version")
private Long version; private Long version;
/** /**
* État actif/inactif pour le soft-delete. * État actif/inactif pour le soft-delete.
*/ */
@Column(name = "actif", nullable = false) @Column(name = "actif", nullable = false)
private Boolean actif; private Boolean actif;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
if (this.dateCreation == null) { if (this.dateCreation == null) {
this.dateCreation = LocalDateTime.now(); this.dateCreation = LocalDateTime.now();
} }
if (this.actif == null) { if (this.actif == null) {
this.actif = true; this.actif = true;
} }
} }
@PreUpdate @PreUpdate
protected void onUpdate() { protected void onUpdate() {
this.dateModification = LocalDateTime.now(); this.dateModification = LocalDateTime.now();
} }
/** /**
* Marque l'entité comme modifiée par un utilisateur donné. * Marque l'entité comme modifiée par un utilisateur donné.
* *
* @param utilisateur email de l'utilisateur * @param utilisateur email de l'utilisateur
*/ */
public void marquerCommeModifie(String utilisateur) { public void marquerCommeModifie(String utilisateur) {
this.dateModification = LocalDateTime.now(); this.dateModification = LocalDateTime.now();
this.modifiePar = utilisateur; this.modifiePar = utilisateur;
} }
} }

View File

@@ -1,218 +1,218 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Budget * Entité Budget
* *
* Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation. * Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-13 * @since 2026-03-13
*/ */
@Entity @Entity
@Table(name = "budgets", indexes = { @Table(name = "budgets", indexes = {
@Index(name = "idx_budget_organisation", columnList = "organisation_id"), @Index(name = "idx_budget_organisation", columnList = "organisation_id"),
@Index(name = "idx_budget_status", columnList = "status"), @Index(name = "idx_budget_status", columnList = "status"),
@Index(name = "idx_budget_period", columnList = "period"), @Index(name = "idx_budget_period", columnList = "period"),
@Index(name = "idx_budget_year_month", columnList = "year, month"), @Index(name = "idx_budget_year_month", columnList = "year, month"),
@Index(name = "idx_budget_created_by", columnList = "created_by_id") @Index(name = "idx_budget_created_by", columnList = "created_by_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Budget extends BaseEntity { public class Budget extends BaseEntity {
/** Nom du budget */ /** Nom du budget */
@NotBlank @NotBlank
@Size(max = 200) @Size(max = 200)
@Column(name = "name", nullable = false, length = 200) @Column(name = "name", nullable = false, length = 200)
private String name; private String name;
/** Description optionnelle */ /** Description optionnelle */
@Size(max = 1000) @Size(max = 1000)
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
/** Organisation concernée */ /** Organisation concernée */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
/** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */ /** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */
@NotBlank @NotBlank
@Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$") @Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$")
@Column(name = "period", nullable = false, length = 20) @Column(name = "period", nullable = false, length = 20)
private String period; private String period;
/** Année du budget */ /** Année du budget */
@NotNull @NotNull
@Min(value = 2020, message = "L'année doit être >= 2020") @Min(value = 2020, message = "L'année doit être >= 2020")
@Max(value = 2100, message = "L'année doit être <= 2100") @Max(value = 2100, message = "L'année doit être <= 2100")
@Column(name = "year", nullable = false) @Column(name = "year", nullable = false)
private Integer year; private Integer year;
/** Mois (1-12) pour budget mensuel, null sinon */ /** Mois (1-12) pour budget mensuel, null sinon */
@Min(value = 1) @Min(value = 1)
@Max(value = 12) @Max(value = 12)
@Column(name = "month") @Column(name = "month")
private Integer month; private Integer month;
/** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */ /** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */
@NotBlank @NotBlank
@Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$") @Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$")
@Builder.Default @Builder.Default
@Column(name = "status", nullable = false, length = 20) @Column(name = "status", nullable = false, length = 20)
private String status = "DRAFT"; private String status = "DRAFT";
/** Lignes budgétaires */ /** Lignes budgétaires */
@OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<BudgetLine> lines = new ArrayList<>(); private List<BudgetLine> lines = new ArrayList<>();
/** Total prévu (somme des montants prévus des lignes) */ /** Total prévu (somme des montants prévus des lignes) */
@NotNull @NotNull
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 14, fraction = 2) @Digits(integer = 14, fraction = 2)
@Builder.Default @Builder.Default
@Column(name = "total_planned", nullable = false, precision = 16, scale = 2) @Column(name = "total_planned", nullable = false, precision = 16, scale = 2)
private BigDecimal totalPlanned = BigDecimal.ZERO; private BigDecimal totalPlanned = BigDecimal.ZERO;
/** Total réalisé (somme des montants réalisés des lignes) */ /** Total réalisé (somme des montants réalisés des lignes) */
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 14, fraction = 2) @Digits(integer = 14, fraction = 2)
@Builder.Default @Builder.Default
@Column(name = "total_realized", nullable = false, precision = 16, scale = 2) @Column(name = "total_realized", nullable = false, precision = 16, scale = 2)
private BigDecimal totalRealized = BigDecimal.ZERO; private BigDecimal totalRealized = BigDecimal.ZERO;
/** Code devise ISO 3 lettres */ /** Code devise ISO 3 lettres */
@NotBlank @NotBlank
@Pattern(regexp = "^[A-Z]{3}$") @Pattern(regexp = "^[A-Z]{3}$")
@Builder.Default @Builder.Default
@Column(name = "currency", nullable = false, length = 3) @Column(name = "currency", nullable = false, length = 3)
private String currency = "XOF"; private String currency = "XOF";
/** ID du créateur du budget */ /** ID du créateur du budget */
@NotNull @NotNull
@Column(name = "created_by_id", nullable = false) @Column(name = "created_by_id", nullable = false)
private UUID createdById; private UUID createdById;
/** Date de création */ /** Date de création */
@NotNull @NotNull
@Column(name = "created_at_budget", nullable = false) @Column(name = "created_at_budget", nullable = false)
private LocalDateTime createdAtBudget; private LocalDateTime createdAtBudget;
/** Date d'approbation */ /** Date d'approbation */
@Column(name = "approved_at") @Column(name = "approved_at")
private LocalDateTime approvedAt; private LocalDateTime approvedAt;
/** ID de l'approbateur */ /** ID de l'approbateur */
@Column(name = "approved_by_id") @Column(name = "approved_by_id")
private UUID approvedById; private UUID approvedById;
/** Date de début de la période budgétaire */ /** Date de début de la période budgétaire */
@NotNull @NotNull
@Column(name = "start_date", nullable = false) @Column(name = "start_date", nullable = false)
private LocalDate startDate; private LocalDate startDate;
/** Date de fin de la période budgétaire */ /** Date de fin de la période budgétaire */
@NotNull @NotNull
@Column(name = "end_date", nullable = false) @Column(name = "end_date", nullable = false)
private LocalDate endDate; private LocalDate endDate;
/** Métadonnées additionnelles (JSON) */ /** Métadonnées additionnelles (JSON) */
@Column(name = "metadata", columnDefinition = "TEXT") @Column(name = "metadata", columnDefinition = "TEXT")
private String metadata; private String metadata;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (createdAtBudget == null) { if (createdAtBudget == null) {
createdAtBudget = LocalDateTime.now(); createdAtBudget = LocalDateTime.now();
} }
if (currency == null) { if (currency == null) {
currency = "XOF"; currency = "XOF";
} }
if (status == null) { if (status == null) {
status = "DRAFT"; status = "DRAFT";
} }
if (totalPlanned == null) { if (totalPlanned == null) {
totalPlanned = BigDecimal.ZERO; totalPlanned = BigDecimal.ZERO;
} }
if (totalRealized == null) { if (totalRealized == null) {
totalRealized = BigDecimal.ZERO; totalRealized = BigDecimal.ZERO;
} }
} }
/** Méthode métier pour ajouter une ligne budgétaire */ /** Méthode métier pour ajouter une ligne budgétaire */
public void addLine(BudgetLine line) { public void addLine(BudgetLine line) {
lines.add(line); lines.add(line);
line.setBudget(this); line.setBudget(this);
recalculateTotals(); recalculateTotals();
} }
/** Méthode métier pour supprimer une ligne budgétaire */ /** Méthode métier pour supprimer une ligne budgétaire */
public void removeLine(BudgetLine line) { public void removeLine(BudgetLine line) {
lines.remove(line); lines.remove(line);
line.setBudget(null); line.setBudget(null);
recalculateTotals(); recalculateTotals();
} }
/** Méthode métier pour recalculer les totaux */ /** Méthode métier pour recalculer les totaux */
public void recalculateTotals() { public void recalculateTotals() {
this.totalPlanned = lines.stream() this.totalPlanned = lines.stream()
.map(BudgetLine::getAmountPlanned) .map(BudgetLine::getAmountPlanned)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
this.totalRealized = lines.stream() this.totalRealized = lines.stream()
.map(BudgetLine::getAmountRealized) .map(BudgetLine::getAmountRealized)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
} }
/** Méthode métier pour calculer le taux de réalisation (%) */ /** Méthode métier pour calculer le taux de réalisation (%) */
public double getRealizationRate() { public double getRealizationRate() {
if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) { if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) {
return 0.0; return 0.0;
} }
return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP) return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP)
.multiply(new BigDecimal("100")) .multiply(new BigDecimal("100"))
.doubleValue(); .doubleValue();
} }
/** Méthode métier pour calculer l'écart (réalisé - prévu) */ /** Méthode métier pour calculer l'écart (réalisé - prévu) */
public BigDecimal getVariance() { public BigDecimal getVariance() {
return totalRealized.subtract(totalPlanned); return totalRealized.subtract(totalPlanned);
} }
/** Méthode métier pour vérifier si le budget est dépassé */ /** Méthode métier pour vérifier si le budget est dépassé */
public boolean isOverBudget() { public boolean isOverBudget() {
return totalRealized.compareTo(totalPlanned) > 0; return totalRealized.compareTo(totalPlanned) > 0;
} }
/** Méthode métier pour vérifier si le budget est actif */ /** Méthode métier pour vérifier si le budget est actif */
public boolean isActive() { public boolean isActive() {
return "ACTIVE".equals(status); return "ACTIVE".equals(status);
} }
/** Méthode métier pour vérifier si la période est en cours */ /** Méthode métier pour vérifier si la période est en cours */
public boolean isCurrentPeriod() { public boolean isCurrentPeriod() {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
return !now.isBefore(startDate) && !now.isAfter(endDate); return !now.isBefore(startDate) && !now.isAfter(endDate);
} }
} }

View File

@@ -1,102 +1,102 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Ligne Budgétaire * Entité Ligne Budgétaire
* *
* Représente une ligne dans un budget (catégorie de dépense/recette). * Représente une ligne dans un budget (catégorie de dépense/recette).
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-13 * @since 2026-03-13
*/ */
@Entity @Entity
@Table(name = "budget_lines", indexes = { @Table(name = "budget_lines", indexes = {
@Index(name = "idx_budget_line_budget", columnList = "budget_id"), @Index(name = "idx_budget_line_budget", columnList = "budget_id"),
@Index(name = "idx_budget_line_category", columnList = "category") @Index(name = "idx_budget_line_category", columnList = "category")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class BudgetLine extends BaseEntity { public class BudgetLine extends BaseEntity {
/** Budget parent */ /** Budget parent */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "budget_id", nullable = false) @JoinColumn(name = "budget_id", nullable = false)
private Budget budget; private Budget budget;
/** Catégorie (CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER) */ /** Catégorie (CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER) */
@NotBlank @NotBlank
@Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$") @Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$")
@Column(name = "category", nullable = false, length = 20) @Column(name = "category", nullable = false, length = 20)
private String category; private String category;
/** Nom de la ligne */ /** Nom de la ligne */
@NotBlank @NotBlank
@Size(max = 200) @Size(max = 200)
@Column(name = "name", nullable = false, length = 200) @Column(name = "name", nullable = false, length = 200)
private String name; private String name;
/** Description optionnelle */ /** Description optionnelle */
@Size(max = 500) @Size(max = 500)
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** Montant prévu */ /** Montant prévu */
@NotNull @NotNull
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 14, fraction = 2) @Digits(integer = 14, fraction = 2)
@Column(name = "amount_planned", nullable = false, precision = 16, scale = 2) @Column(name = "amount_planned", nullable = false, precision = 16, scale = 2)
private BigDecimal amountPlanned; private BigDecimal amountPlanned;
/** Montant réalisé */ /** Montant réalisé */
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 14, fraction = 2) @Digits(integer = 14, fraction = 2)
@Builder.Default @Builder.Default
@Column(name = "amount_realized", nullable = false, precision = 16, scale = 2) @Column(name = "amount_realized", nullable = false, precision = 16, scale = 2)
private BigDecimal amountRealized = BigDecimal.ZERO; private BigDecimal amountRealized = BigDecimal.ZERO;
/** Notes additionnelles */ /** Notes additionnelles */
@Size(max = 1000) @Size(max = 1000)
@Column(name = "notes", length = 1000) @Column(name = "notes", length = 1000)
private String notes; private String notes;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (amountRealized == null) { if (amountRealized == null) {
amountRealized = BigDecimal.ZERO; amountRealized = BigDecimal.ZERO;
} }
} }
/** Méthode métier pour calculer le taux de réalisation (%) */ /** Méthode métier pour calculer le taux de réalisation (%) */
public double getRealizationRate() { public double getRealizationRate() {
if (amountPlanned.compareTo(BigDecimal.ZERO) == 0) { if (amountPlanned.compareTo(BigDecimal.ZERO) == 0) {
return 0.0; return 0.0;
} }
return amountRealized.divide(amountPlanned, 4, java.math.RoundingMode.HALF_UP) return amountRealized.divide(amountPlanned, 4, java.math.RoundingMode.HALF_UP)
.multiply(new BigDecimal("100")) .multiply(new BigDecimal("100"))
.doubleValue(); .doubleValue();
} }
/** Méthode métier pour calculer l'écart */ /** Méthode métier pour calculer l'écart */
public BigDecimal getVariance() { public BigDecimal getVariance() {
return amountRealized.subtract(amountPlanned); return amountRealized.subtract(amountPlanned);
} }
/** Méthode métier pour vérifier si la ligne est dépassée */ /** Méthode métier pour vérifier si la ligne est dépassée */
public boolean isOverBudget() { public boolean isOverBudget() {
return amountRealized.compareTo(amountPlanned) > 0; return amountRealized.compareTo(amountPlanned) > 0;
} }
} }

View File

@@ -1,122 +1,127 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité CompteComptable pour le plan comptable * Entité CompteComptable pour le plan comptable
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "comptes_comptables", name = "comptes_comptables",
indexes = { indexes = {
@Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true), @Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true),
@Index(name = "idx_compte_type", columnList = "type_compte"), @Index(name = "idx_compte_type", columnList = "type_compte"),
@Index(name = "idx_compte_classe", columnList = "classe_comptable") @Index(name = "idx_compte_classe", columnList = "classe_comptable")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CompteComptable extends BaseEntity { public class CompteComptable extends BaseEntity {
/** Numéro de compte unique (ex: 411000, 512000) */ /** Numéro de compte unique (ex: 411000, 512000) */
@NotBlank @NotBlank
@Column(name = "numero_compte", unique = true, nullable = false, length = 10) @Column(name = "numero_compte", unique = true, nullable = false, length = 10)
private String numeroCompte; private String numeroCompte;
/** Libellé du compte */ /** Libellé du compte */
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 200) @Column(name = "libelle", nullable = false, length = 200)
private String libelle; private String libelle;
/** Type de compte */ /** Type de compte */
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_compte", nullable = false, length = 30) @Column(name = "type_compte", nullable = false, length = 30)
private TypeCompteComptable typeCompte; private TypeCompteComptable typeCompte;
/** Classe comptable (1-7) */ /** Classe comptable (1-7) */
@NotNull @NotNull
@Min(value = 1, message = "La classe comptable doit être entre 1 et 7") @Min(value = 1, message = "La classe comptable doit être entre 1 et 9")
@Max(value = 7, message = "La classe comptable doit être entre 1 et 7") @Max(value = 9, message = "La classe comptable doit être entre 1 et 9")
@Column(name = "classe_comptable", nullable = false) @Column(name = "classe_comptable", nullable = false)
private Integer classeComptable; private Integer classeComptable;
/** Solde initial */ /** Solde initial */
@Builder.Default @Builder.Default
@DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul") @DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "solde_initial", precision = 14, scale = 2) @Column(name = "solde_initial", precision = 14, scale = 2)
private BigDecimal soldeInitial = BigDecimal.ZERO; private BigDecimal soldeInitial = BigDecimal.ZERO;
/** Solde actuel (calculé) */ /** Solde actuel (calculé) */
@Builder.Default @Builder.Default
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "solde_actuel", precision = 14, scale = 2) @Column(name = "solde_actuel", precision = 14, scale = 2)
private BigDecimal soldeActuel = BigDecimal.ZERO; private BigDecimal soldeActuel = BigDecimal.ZERO;
/** Compte collectif (regroupe plusieurs sous-comptes) */ /** Compte collectif (regroupe plusieurs sous-comptes) */
@Builder.Default @Builder.Default
@Column(name = "compte_collectif", nullable = false) @Column(name = "compte_collectif", nullable = false)
private Boolean compteCollectif = false; private Boolean compteCollectif = false;
/** Compte analytique */ /** Compte analytique */
@Builder.Default @Builder.Default
@Column(name = "compte_analytique", nullable = false) @Column(name = "compte_analytique", nullable = false)
private Boolean compteAnalytique = false; private Boolean compteAnalytique = false;
/** Description du compte */ /** Description du compte */
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** Lignes d'écriture associées */ /** Organisation propriétaire (null = compte standard global) */
@JsonIgnore @ManyToOne(fetch = FetchType.LAZY)
@OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "organisation_id")
@Builder.Default private Organisation organisation;
private List<LigneEcriture> lignesEcriture = new ArrayList<>();
/** Lignes d'écriture associées */
/** Méthode métier pour obtenir le numéro formaté */ @JsonIgnore
public String getNumeroFormate() { @OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
return String.format("%-10s", numeroCompte); @Builder.Default
} private List<LigneEcriture> lignesEcriture = new ArrayList<>();
/** Méthode métier pour vérifier si c'est un compte de trésorerie */ /** Méthode métier pour obtenir le numéro formaté */
public boolean isTresorerie() { public String getNumeroFormate() {
return TypeCompteComptable.TRESORERIE.equals(typeCompte); return String.format("%-10s", numeroCompte);
} }
/** Callback JPA avant la persistance */ /** Méthode métier pour vérifier si c'est un compte de trésorerie */
@PrePersist public boolean isTresorerie() {
protected void onCreate() { return TypeCompteComptable.TRESORERIE.equals(typeCompte);
super.onCreate(); }
if (soldeInitial == null) {
soldeInitial = BigDecimal.ZERO; /** Callback JPA avant la persistance */
} @PrePersist
if (soldeActuel == null) { protected void onCreate() {
soldeActuel = soldeInitial; super.onCreate();
} if (soldeInitial == null) {
if (compteCollectif == null) { soldeInitial = BigDecimal.ZERO;
compteCollectif = false; }
} if (soldeActuel == null) {
if (compteAnalytique == null) { soldeActuel = soldeInitial;
compteAnalytique = false; }
} if (compteCollectif == null) {
} compteCollectif = false;
} }
if (compteAnalytique == null) {
compteAnalytique = false;
}
}
}

View File

@@ -1,105 +1,105 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave; import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité CompteWave pour la gestion des comptes Wave Mobile Money * Entité CompteWave pour la gestion des comptes Wave Mobile Money
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table(name = "comptes_wave", indexes = { @Table(name = "comptes_wave", indexes = {
@Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true), @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_statut", columnList = "statut_compte"),
@Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"), @Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"),
@Index(name = "idx_compte_wave_membre", columnList = "membre_id") @Index(name = "idx_compte_wave_membre", columnList = "membre_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CompteWave extends BaseEntity { public class CompteWave extends BaseEntity {
/** Numéro de téléphone Wave (format +225XXXXXXXX) */ /** Numéro de téléphone Wave (format +225XXXXXXXX) */
@NotBlank @NotBlank
@Pattern(regexp = "^\\+225[0-9]{8}$", message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX") @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) @Column(name = "numero_telephone", unique = true, nullable = false, length = 13)
private String numeroTelephone; private String numeroTelephone;
/** Statut du compte */ /** Statut du compte */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut_compte", nullable = false, length = 30) @Column(name = "statut_compte", nullable = false, length = 30)
private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE; private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE;
/** Identifiant Wave API (encrypté) */ /** Identifiant Wave API (encrypté) */
@Column(name = "wave_account_id", length = 255) @Column(name = "wave_account_id", length = 255)
private String waveAccountId; private String waveAccountId;
/** Clé API Wave (encryptée) */ /** Clé API Wave (encryptée) */
@Column(name = "wave_api_key", length = 500) @Column(name = "wave_api_key", length = 500)
private String waveApiKey; private String waveApiKey;
/** Environnement (SANDBOX ou PRODUCTION) */ /** Environnement (SANDBOX ou PRODUCTION) */
@Column(name = "environnement", length = 20) @Column(name = "environnement", length = 20)
private String environnement; private String environnement;
/** Date de dernière vérification */ /** Date de dernière vérification */
@Column(name = "date_derniere_verification") @Column(name = "date_derniere_verification")
private java.time.LocalDateTime dateDerniereVerification; private java.time.LocalDateTime dateDerniereVerification;
/** Commentaires */ /** Commentaires */
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
// Relations // Relations
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id") @JoinColumn(name = "membre_id")
private Membre membre; private Membre membre;
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<TransactionWave> transactions = new ArrayList<>(); private List<TransactionWave> transactions = new ArrayList<>();
/** Méthode métier pour vérifier si le compte est vérifié */ /** Méthode métier pour vérifier si le compte est vérifié */
public boolean isVerifie() { public boolean isVerifie() {
return StatutCompteWave.VERIFIE.equals(statutCompte); return StatutCompteWave.VERIFIE.equals(statutCompte);
} }
/** Méthode métier pour vérifier si le compte peut être utilisé */ /** Méthode métier pour vérifier si le compte peut être utilisé */
public boolean peutEtreUtilise() { public boolean peutEtreUtilise() {
return StatutCompteWave.VERIFIE.equals(statutCompte); return StatutCompteWave.VERIFIE.equals(statutCompte);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statutCompte == null) { if (statutCompte == null) {
statutCompte = StatutCompteWave.NON_VERIFIE; statutCompte = StatutCompteWave.NON_VERIFIE;
} }
if (environnement == null || environnement.isEmpty()) { if (environnement == null || environnement.isEmpty()) {
environnement = "SANDBOX"; environnement = "SANDBOX";
} }
} }
} }

View File

@@ -1,53 +1,53 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Configuration pour la gestion de la configuration système * Entité Configuration pour la gestion de la configuration système
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
*/ */
@Entity @Entity
@Table(name = "configurations") @Table(name = "configurations")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Configuration extends BaseEntity { public class Configuration extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "cle", nullable = false, unique = true, length = 255) @Column(name = "cle", nullable = false, unique = true, length = 255)
private String cle; private String cle;
@Column(name = "valeur", columnDefinition = "TEXT") @Column(name = "valeur", columnDefinition = "TEXT")
private String valeur; private String valeur;
@Column(name = "type", length = 50) @Column(name = "type", length = 50)
private String type; // STRING, NUMBER, BOOLEAN, JSON, DATE private String type; // STRING, NUMBER, BOOLEAN, JSON, DATE
@Column(name = "categorie", length = 50) @Column(name = "categorie", length = 50)
private String categorie; // SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE private String categorie; // SYSTEME, SECURITE, NOTIFICATION, INTEGRATION, APPEARANCE
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
@Column(name = "modifiable") @Column(name = "modifiable")
@Builder.Default @Builder.Default
private Boolean modifiable = true; private Boolean modifiable = true;
@Column(name = "visible") @Column(name = "visible")
@Builder.Default @Builder.Default
private Boolean visible = true; private Boolean visible = true;
@Column(name = "metadonnees", columnDefinition = "TEXT") @Column(name = "metadonnees", columnDefinition = "TEXT")
private String metadonnees; // JSON string pour stocker les métadonnées private String metadonnees; // JSON string pour stocker les métadonnées
} }

View File

@@ -1,69 +1,69 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité ConfigurationWave pour la configuration de l'intégration Wave * Entité ConfigurationWave pour la configuration de l'intégration Wave
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "configurations_wave", name = "configurations_wave",
indexes = { indexes = {
@Index(name = "idx_config_wave_cle", columnList = "cle", unique = true) @Index(name = "idx_config_wave_cle", columnList = "cle", unique = true)
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ConfigurationWave extends BaseEntity { public class ConfigurationWave extends BaseEntity {
/** Clé de configuration */ /** Clé de configuration */
@NotBlank @NotBlank
@Column(name = "cle", unique = true, nullable = false, length = 100) @Column(name = "cle", unique = true, nullable = false, length = 100)
private String cle; private String cle;
/** Valeur de configuration (peut être encryptée) */ /** Valeur de configuration (peut être encryptée) */
@Column(name = "valeur", columnDefinition = "TEXT") @Column(name = "valeur", columnDefinition = "TEXT")
private String valeur; private String valeur;
/** Description de la configuration */ /** Description de la configuration */
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */ /** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */
@Column(name = "type_valeur", length = 20) @Column(name = "type_valeur", length = 20)
private String typeValeur; private String typeValeur;
/** Environnement (SANDBOX, PRODUCTION, COMMON) */ /** Environnement (SANDBOX, PRODUCTION, COMMON) */
@Column(name = "environnement", length = 20) @Column(name = "environnement", length = 20)
private String environnement; private String environnement;
/** Méthode métier pour vérifier si la valeur est encryptée */ /** Méthode métier pour vérifier si la valeur est encryptée */
public boolean isEncryptee() { public boolean isEncryptee() {
return "ENCRYPTED".equals(typeValeur); return "ENCRYPTED".equals(typeValeur);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (typeValeur == null || typeValeur.isEmpty()) { if (typeValeur == null || typeValeur.isEmpty()) {
typeValeur = "STRING"; typeValeur = "STRING";
} }
if (environnement == null || environnement.isEmpty()) { if (environnement == null || environnement.isEmpty()) {
environnement = "COMMON"; environnement = "COMMON";
} }
} }
} }

View File

@@ -1,194 +1,194 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Cotisation avec UUID Représente une cotisation d'un membre à son * Entité Cotisation avec UUID Représente une cotisation d'un membre à son
* organisation * organisation
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 2.0 * @version 2.0
* @since 2025-01-16 * @since 2025-01-16
*/ */
@Entity @Entity
@Table(name = "cotisations", indexes = { @Table(name = "cotisations", indexes = {
@Index(name = "idx_cotisation_membre", columnList = "membre_id"), @Index(name = "idx_cotisation_membre", columnList = "membre_id"),
@Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true), @Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
@Index(name = "idx_cotisation_statut", columnList = "statut"), @Index(name = "idx_cotisation_statut", columnList = "statut"),
@Index(name = "idx_cotisation_echeance", columnList = "date_echeance"), @Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
@Index(name = "idx_cotisation_type", columnList = "type_cotisation"), @Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
@Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois") @Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Cotisation extends BaseEntity { public class Cotisation extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "numero_reference", unique = true, nullable = false, length = 50) @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
private String numeroReference; private String numeroReference;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
/** Organisation pour laquelle la cotisation est due */ /** Organisation pour laquelle la cotisation est due */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
/** Intention de paiement Wave associée (null si cotisation en attente) */ /** Intention de paiement Wave associée (null si cotisation en attente) */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "intention_paiement_id") @JoinColumn(name = "intention_paiement_id")
private IntentionPaiement intentionPaiement; private IntentionPaiement intentionPaiement;
@NotBlank @NotBlank
@Column(name = "type_cotisation", nullable = false, length = 50) @Column(name = "type_cotisation", nullable = false, length = 50)
private String typeCotisation; private String typeCotisation;
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 100) @Column(name = "libelle", nullable = false, length = 100)
private String libelle; private String libelle;
@NotNull @NotNull
@DecimalMin(value = "0.0", message = "Le montant dû doit être positif") @DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "montant_du", nullable = false, precision = 12, scale = 2) @Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
private BigDecimal montantDu; private BigDecimal montantDu;
@Builder.Default @Builder.Default
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif") @DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2) @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
private BigDecimal montantPaye = BigDecimal.ZERO; private BigDecimal montantPaye = BigDecimal.ZERO;
@NotBlank @NotBlank
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
@Column(name = "code_devise", nullable = false, length = 3) @Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise; private String codeDevise;
@NotBlank @NotBlank
@Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$") @Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
@Column(name = "statut", nullable = false, length = 30) @Column(name = "statut", nullable = false, length = 30)
private String statut; private String statut;
@NotNull @NotNull
@Column(name = "date_echeance", nullable = false) @Column(name = "date_echeance", nullable = false)
private LocalDate dateEcheance; private LocalDate dateEcheance;
@Column(name = "date_paiement") @Column(name = "date_paiement")
private LocalDateTime datePaiement; private LocalDateTime datePaiement;
@Size(max = 500) @Size(max = 500)
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
@Size(max = 20) @Size(max = 20)
@Column(name = "periode", length = 20) @Column(name = "periode", length = 20)
private String periode; private String periode;
@NotNull @NotNull
@Min(value = 2020, message = "L'année doit être supérieure à 2020") @Min(value = 2020, message = "L'année doit être supérieure à 2020")
@Max(value = 2100, message = "L'année doit être inférieure à 2100") @Max(value = 2100, message = "L'année doit être inférieure à 2100")
@Column(name = "annee", nullable = false) @Column(name = "annee", nullable = false)
private Integer annee; private Integer annee;
@Min(value = 1, message = "Le mois doit être entre 1 et 12") @Min(value = 1, message = "Le mois doit être entre 1 et 12")
@Max(value = 12, message = "Le mois doit être entre 1 et 12") @Max(value = 12, message = "Le mois doit être entre 1 et 12")
@Column(name = "mois") @Column(name = "mois")
private Integer mois; private Integer mois;
@Size(max = 1000) @Size(max = 1000)
@Column(name = "observations", length = 1000) @Column(name = "observations", length = 1000)
private String observations; private String observations;
@Builder.Default @Builder.Default
@Column(name = "recurrente", nullable = false) @Column(name = "recurrente", nullable = false)
private Boolean recurrente = false; private Boolean recurrente = false;
@Builder.Default @Builder.Default
@Min(value = 0, message = "Le nombre de rappels doit être positif") @Min(value = 0, message = "Le nombre de rappels doit être positif")
@Column(name = "nombre_rappels", nullable = false) @Column(name = "nombre_rappels", nullable = false)
private Integer nombreRappels = 0; private Integer nombreRappels = 0;
@Column(name = "date_dernier_rappel") @Column(name = "date_dernier_rappel")
private LocalDateTime dateDernierRappel; private LocalDateTime dateDernierRappel;
@Column(name = "valide_par_id") @Column(name = "valide_par_id")
private UUID valideParId; private UUID valideParId;
@Size(max = 100) @Size(max = 100)
@Column(name = "nom_validateur", length = 100) @Column(name = "nom_validateur", length = 100)
private String nomValidateur; private String nomValidateur;
@Column(name = "date_validation") @Column(name = "date_validation")
private LocalDateTime dateValidation; private LocalDateTime dateValidation;
/** Méthode métier pour calculer le montant restant à payer */ /** Méthode métier pour calculer le montant restant à payer */
public BigDecimal getMontantRestant() { public BigDecimal getMontantRestant() {
if (montantDu == null || montantPaye == null) { if (montantDu == null || montantPaye == null) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
return montantDu.subtract(montantPaye); return montantDu.subtract(montantPaye);
} }
/** Méthode métier pour vérifier si la cotisation est entièrement payée */ /** Méthode métier pour vérifier si la cotisation est entièrement payée */
public boolean isEntierementPayee() { public boolean isEntierementPayee() {
return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0; return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
} }
/** Méthode métier pour vérifier si la cotisation est en retard */ /** Méthode métier pour vérifier si la cotisation est en retard */
public boolean isEnRetard() { public boolean isEnRetard() {
return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee(); return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
} }
private static final AtomicLong REFERENCE_COUNTER = private static final AtomicLong REFERENCE_COUNTER =
new AtomicLong(System.currentTimeMillis() % 100000000L); new AtomicLong(System.currentTimeMillis() % 100000000L);
/** Méthode métier pour générer un numéro de référence unique */ /** Méthode métier pour générer un numéro de référence unique */
public static String genererNumeroReference() { public static String genererNumeroReference() {
return "COT-" return "COT-"
+ LocalDate.now().getYear() + LocalDate.now().getYear()
+ "-" + "-"
+ String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L); + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity super.onCreate(); // Appelle le onCreate de BaseEntity
if (numeroReference == null || numeroReference.isEmpty()) { if (numeroReference == null || numeroReference.isEmpty()) {
numeroReference = genererNumeroReference(); numeroReference = genererNumeroReference();
} }
if (codeDevise == null) { if (codeDevise == null) {
codeDevise = "XOF"; codeDevise = "XOF";
} }
if (statut == null) { if (statut == null) {
statut = "EN_ATTENTE"; statut = "EN_ATTENTE";
} }
if (montantPaye == null) { if (montantPaye == null) {
montantPaye = BigDecimal.ZERO; montantPaye = BigDecimal.ZERO;
} }
if (nombreRappels == null) { if (nombreRappels == null) {
nombreRappels = 0; nombreRappels = 0;
} }
if (recurrente == null) { if (recurrente == null) {
recurrente = false; recurrente = false;
} }
} }
} }

View File

@@ -1,132 +1,132 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import lombok.*; import lombok.*;
/** /**
* Demande d'adhésion d'un utilisateur à une organisation. * Demande d'adhésion d'un utilisateur à une organisation.
* *
* <p>Flux : * <p>Flux :
* <ol> * <ol>
* <li>L'utilisateur crée son compte et choisit une organisation</li> * <li>L'utilisateur crée son compte et choisit une organisation</li>
* <li>Une {@code DemandeAdhesion} est créée (statut EN_ATTENTE)</li> * <li>Une {@code DemandeAdhesion} est créée (statut EN_ATTENTE)</li>
* <li>Si frais d'adhésion : une {@link IntentionPaiement} est créée et liée</li> * <li>Si frais d'adhésion : une {@link IntentionPaiement} est créée et liée</li>
* <li>Le manager valide → {@link MembreOrganisation} créé, quota souscription décrémenté</li> * <li>Le manager valide → {@link MembreOrganisation} créé, quota souscription décrémenté</li>
* </ol> * </ol>
* *
* <p>Remplace l'ancienne entité {@code Adhesion}. * <p>Remplace l'ancienne entité {@code Adhesion}.
* Table : {@code demandes_adhesion} * Table : {@code demandes_adhesion}
*/ */
@Entity @Entity
@Table( @Table(
name = "demandes_adhesion", name = "demandes_adhesion",
indexes = { indexes = {
@Index(name = "idx_da_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_da_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_da_organisation", columnList = "organisation_id"), @Index(name = "idx_da_organisation", columnList = "organisation_id"),
@Index(name = "idx_da_statut", columnList = "statut"), @Index(name = "idx_da_statut", columnList = "statut"),
@Index(name = "idx_da_date", columnList = "date_demande") @Index(name = "idx_da_date", columnList = "date_demande")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class DemandeAdhesion extends BaseEntity { public class DemandeAdhesion extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "numero_reference", unique = true, nullable = false, length = 50) @Column(name = "numero_reference", unique = true, nullable = false, length = 50)
private String numeroReference; private String numeroReference;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "utilisateur_id", nullable = false) @JoinColumn(name = "utilisateur_id", nullable = false)
private Membre utilisateur; private Membre utilisateur;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Pattern(regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE)$") @Pattern(regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE)$")
@Builder.Default @Builder.Default
@Column(name = "statut", nullable = false, length = 20) @Column(name = "statut", nullable = false, length = 20)
private String statut = "EN_ATTENTE"; private String statut = "EN_ATTENTE";
@Builder.Default @Builder.Default
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2) @Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
private BigDecimal fraisAdhesion = BigDecimal.ZERO; private BigDecimal fraisAdhesion = BigDecimal.ZERO;
@Builder.Default @Builder.Default
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2) @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
private BigDecimal montantPaye = BigDecimal.ZERO; private BigDecimal montantPaye = BigDecimal.ZERO;
@Builder.Default @Builder.Default
@Pattern(regexp = "^[A-Z]{3}$") @Pattern(regexp = "^[A-Z]{3}$")
@Column(name = "code_devise", nullable = false, length = 3) @Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise = "XOF"; private String codeDevise = "XOF";
/** Intention de paiement Wave liée aux frais d'adhésion */ /** Intention de paiement Wave liée aux frais d'adhésion */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "intention_paiement_id") @JoinColumn(name = "intention_paiement_id")
private IntentionPaiement intentionPaiement; private IntentionPaiement intentionPaiement;
@Builder.Default @Builder.Default
@Column(name = "date_demande", nullable = false) @Column(name = "date_demande", nullable = false)
private LocalDateTime dateDemande = LocalDateTime.now(); private LocalDateTime dateDemande = LocalDateTime.now();
@Column(name = "date_traitement") @Column(name = "date_traitement")
private LocalDateTime dateTraitement; private LocalDateTime dateTraitement;
/** Manager/Admin qui a approuvé ou rejeté */ /** Manager/Admin qui a approuvé ou rejeté */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "traite_par_id") @JoinColumn(name = "traite_par_id")
private Membre traitePar; private Membre traitePar;
@Column(name = "motif_rejet", length = 1000) @Column(name = "motif_rejet", length = 1000)
private String motifRejet; private String motifRejet;
@Column(name = "observations", length = 1000) @Column(name = "observations", length = 1000)
private String observations; private String observations;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean isEnAttente() { return "EN_ATTENTE".equals(statut); } public boolean isEnAttente() { return "EN_ATTENTE".equals(statut); }
public boolean isApprouvee() { return "APPROUVEE".equals(statut); } public boolean isApprouvee() { return "APPROUVEE".equals(statut); }
public boolean isRejetee() { return "REJETEE".equals(statut); } public boolean isRejetee() { return "REJETEE".equals(statut); }
public boolean isPayeeIntegralement() { public boolean isPayeeIntegralement() {
return fraisAdhesion != null return fraisAdhesion != null
&& montantPaye != null && montantPaye != null
&& montantPaye.compareTo(fraisAdhesion) >= 0; && montantPaye.compareTo(fraisAdhesion) >= 0;
} }
private static final AtomicLong REFERENCE_COUNTER = private static final AtomicLong REFERENCE_COUNTER =
new AtomicLong(System.currentTimeMillis() % 100000000L); new AtomicLong(System.currentTimeMillis() % 100000000L);
public static String genererNumeroReference() { public static String genererNumeroReference() {
return "ADH-" + java.time.LocalDate.now().getYear() return "ADH-" + java.time.LocalDate.now().getYear()
+ "-" + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L); + "-" + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
} }
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (dateDemande == null) dateDemande = LocalDateTime.now(); if (dateDemande == null) dateDemande = LocalDateTime.now();
if (statut == null) statut = "EN_ATTENTE"; if (statut == null) statut = "EN_ATTENTE";
if (codeDevise == null) codeDevise = "XOF"; if (codeDevise == null) codeDevise = "XOF";
if (fraisAdhesion == null) fraisAdhesion = BigDecimal.ZERO; if (fraisAdhesion == null) fraisAdhesion = BigDecimal.ZERO;
if (montantPaye == null) montantPaye = BigDecimal.ZERO; if (montantPaye == null) montantPaye = BigDecimal.ZERO;
if (numeroReference == null || numeroReference.isEmpty()) { if (numeroReference == null || numeroReference.isEmpty()) {
numeroReference = genererNumeroReference(); numeroReference = genererNumeroReference();
} }
} }
} }

View File

@@ -1,130 +1,130 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** Entité représentant une demande d'aide dans le système de solidarité */ /** Entité représentant une demande d'aide dans le système de solidarité */
@Entity @Entity
@Table(name = "demandes_aide") @Table(name = "demandes_aide")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class DemandeAide extends BaseEntity { public class DemandeAide extends BaseEntity {
@Column(name = "titre", nullable = false, length = 200) @Column(name = "titre", nullable = false, length = 200)
private String titre; private String titre;
@Column(name = "description", nullable = false, columnDefinition = "TEXT") @Column(name = "description", nullable = false, columnDefinition = "TEXT")
private String description; private String description;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_aide", nullable = false) @Column(name = "type_aide", nullable = false)
private TypeAide typeAide; private TypeAide typeAide;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false) @Column(name = "statut", nullable = false)
private StatutAide statut; private StatutAide statut;
@Column(name = "montant_demande", precision = 10, scale = 2) @Column(name = "montant_demande", precision = 10, scale = 2)
private BigDecimal montantDemande; private BigDecimal montantDemande;
@Column(name = "montant_approuve", precision = 10, scale = 2) @Column(name = "montant_approuve", precision = 10, scale = 2)
private BigDecimal montantApprouve; private BigDecimal montantApprouve;
@Column(name = "date_demande", nullable = false) @Column(name = "date_demande", nullable = false)
private LocalDateTime dateDemande; private LocalDateTime dateDemande;
@Column(name = "date_evaluation") @Column(name = "date_evaluation")
private LocalDateTime dateEvaluation; private LocalDateTime dateEvaluation;
@Column(name = "date_versement") @Column(name = "date_versement")
private LocalDateTime dateVersement; private LocalDateTime dateVersement;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demandeur_id", nullable = false) @JoinColumn(name = "demandeur_id", nullable = false)
private Membre demandeur; private Membre demandeur;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evaluateur_id") @JoinColumn(name = "evaluateur_id")
private Membre evaluateur; private Membre evaluateur;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@Column(name = "justification", columnDefinition = "TEXT") @Column(name = "justification", columnDefinition = "TEXT")
private String justification; private String justification;
@Column(name = "commentaire_evaluation", columnDefinition = "TEXT") @Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
private String commentaireEvaluation; private String commentaireEvaluation;
@Column(name = "urgence", nullable = false) @Column(name = "urgence", nullable = false)
@Builder.Default @Builder.Default
private Boolean urgence = false; private Boolean urgence = false;
@Column(name = "documents_fournis") @Column(name = "documents_fournis")
private String documentsFournis; private String documentsFournis;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity super.onCreate(); // Appelle le onCreate de BaseEntity
if (dateDemande == null) { if (dateDemande == null) {
dateDemande = LocalDateTime.now(); dateDemande = LocalDateTime.now();
} }
if (statut == null) { if (statut == null) {
statut = StatutAide.EN_ATTENTE; statut = StatutAide.EN_ATTENTE;
} }
if (urgence == null) { if (urgence == null) {
urgence = false; urgence = false;
} }
} }
@PreUpdate @PreUpdate
protected void onUpdate() { protected void onUpdate() {
// Méthode appelée avant mise à jour // Méthode appelée avant mise à jour
} }
/** Vérifie si la demande est en attente */ /** Vérifie si la demande est en attente */
public boolean isEnAttente() { public boolean isEnAttente() {
return StatutAide.EN_ATTENTE.equals(statut); return StatutAide.EN_ATTENTE.equals(statut);
} }
/** Vérifie si la demande est approuvée */ /** Vérifie si la demande est approuvée */
public boolean isApprouvee() { public boolean isApprouvee() {
return StatutAide.APPROUVEE.equals(statut); return StatutAide.APPROUVEE.equals(statut);
} }
/** Vérifie si la demande est rejetée */ /** Vérifie si la demande est rejetée */
public boolean isRejetee() { public boolean isRejetee() {
return StatutAide.REJETEE.equals(statut); return StatutAide.REJETEE.equals(statut);
} }
/** Vérifie si la demande est urgente */ /** Vérifie si la demande est urgente */
public boolean isUrgente() { public boolean isUrgente() {
return Boolean.TRUE.equals(urgence); return Boolean.TRUE.equals(urgence);
} }
/** Calcule le pourcentage d'approbation par rapport au montant demandé */ /** Calcule le pourcentage d'approbation par rapport au montant demandé */
public BigDecimal getPourcentageApprobation() { public BigDecimal getPourcentageApprobation() {
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) { if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
if (montantApprouve == null) { if (montantApprouve == null) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
return montantApprouve return montantApprouve
.divide(montantDemande, 4, RoundingMode.HALF_UP) .divide(montantDemande, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100)); .multiply(BigDecimal.valueOf(100));
} }
} }

View File

@@ -1,130 +1,130 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.document.TypeDocument; import dev.lions.unionflow.server.api.enums.document.TypeDocument;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Document pour la gestion documentaire sécurisée * Entité Document pour la gestion documentaire sécurisée
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "documents", name = "documents",
indexes = { indexes = {
@Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"), @Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"),
@Index(name = "idx_document_type", columnList = "type_document"), @Index(name = "idx_document_type", columnList = "type_document"),
@Index(name = "idx_document_hash_md5", columnList = "hash_md5"), @Index(name = "idx_document_hash_md5", columnList = "hash_md5"),
@Index(name = "idx_document_hash_sha256", columnList = "hash_sha256") @Index(name = "idx_document_hash_sha256", columnList = "hash_sha256")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Document extends BaseEntity { public class Document extends BaseEntity {
/** Nom du fichier original */ /** Nom du fichier original */
@NotBlank @NotBlank
@Column(name = "nom_fichier", nullable = false, length = 255) @Column(name = "nom_fichier", nullable = false, length = 255)
private String nomFichier; private String nomFichier;
/** Nom original du fichier (tel que téléchargé) */ /** Nom original du fichier (tel que téléchargé) */
@Column(name = "nom_original", length = 255) @Column(name = "nom_original", length = 255)
private String nomOriginal; private String nomOriginal;
/** Chemin de stockage */ /** Chemin de stockage */
@NotBlank @NotBlank
@Column(name = "chemin_stockage", nullable = false, length = 1000) @Column(name = "chemin_stockage", nullable = false, length = 1000)
private String cheminStockage; private String cheminStockage;
/** Type MIME */ /** Type MIME */
@Column(name = "type_mime", length = 100) @Column(name = "type_mime", length = 100)
private String typeMime; private String typeMime;
/** Taille du fichier en octets */ /** Taille du fichier en octets */
@NotNull @NotNull
@Min(value = 0, message = "La taille doit être positive") @Min(value = 0, message = "La taille doit être positive")
@Column(name = "taille_octets", nullable = false) @Column(name = "taille_octets", nullable = false)
private Long tailleOctets; private Long tailleOctets;
/** Type de document */ /** Type de document */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_document", length = 50) @Column(name = "type_document", length = 50)
private TypeDocument typeDocument; private TypeDocument typeDocument;
/** Hash MD5 pour vérification d'intégrité */ /** Hash MD5 pour vérification d'intégrité */
@Column(name = "hash_md5", length = 32) @Column(name = "hash_md5", length = 32)
private String hashMd5; private String hashMd5;
/** Hash SHA256 pour vérification d'intégrité */ /** Hash SHA256 pour vérification d'intégrité */
@Column(name = "hash_sha256", length = 64) @Column(name = "hash_sha256", length = 64)
private String hashSha256; private String hashSha256;
/** Description du document */ /** Description du document */
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
/** Nombre de téléchargements */ /** Nombre de téléchargements */
@Builder.Default @Builder.Default
@Column(name = "nombre_telechargements", nullable = false) @Column(name = "nombre_telechargements", nullable = false)
private Integer nombreTelechargements = 0; private Integer nombreTelechargements = 0;
/** Date de dernier téléchargement */ /** Date de dernier téléchargement */
@Column(name = "date_dernier_telechargement") @Column(name = "date_dernier_telechargement")
private java.time.LocalDateTime dateDernierTelechargement; private java.time.LocalDateTime dateDernierTelechargement;
/** Pièces jointes associées */ /** Pièces jointes associées */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<PieceJointe> piecesJointes = new ArrayList<>(); private List<PieceJointe> piecesJointes = new ArrayList<>();
/** Méthode métier pour vérifier l'intégrité avec MD5 */ /** Méthode métier pour vérifier l'intégrité avec MD5 */
public boolean verifierIntegriteMd5(String hashAttendu) { public boolean verifierIntegriteMd5(String hashAttendu) {
return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu); return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu);
} }
/** Méthode métier pour vérifier l'intégrité avec SHA256 */ /** Méthode métier pour vérifier l'intégrité avec SHA256 */
public boolean verifierIntegriteSha256(String hashAttendu) { public boolean verifierIntegriteSha256(String hashAttendu) {
return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu); return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu);
} }
/** Méthode métier pour obtenir la taille formatée */ /** Méthode métier pour obtenir la taille formatée */
public String getTailleFormatee() { public String getTailleFormatee() {
if (tailleOctets == null) { if (tailleOctets == null) {
return "0 B"; return "0 B";
} }
if (tailleOctets < 1024) { if (tailleOctets < 1024) {
return tailleOctets + " B"; return tailleOctets + " B";
} else if (tailleOctets < 1024 * 1024) { } else if (tailleOctets < 1024 * 1024) {
return String.format("%.2f KB", tailleOctets / 1024.0); return String.format("%.2f KB", tailleOctets / 1024.0);
} else { } else {
return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0)); return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0));
} }
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (nombreTelechargements == null) { if (nombreTelechargements == null) {
nombreTelechargements = 0; nombreTelechargements = 0;
} }
if (typeDocument == null) { if (typeDocument == null) {
typeDocument = TypeDocument.AUTRE; typeDocument = TypeDocument.AUTRE;
} }
} }
} }

View File

@@ -1,174 +1,174 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité EcritureComptable pour les écritures comptables * Entité EcritureComptable pour les écritures comptables
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "ecritures_comptables", name = "ecritures_comptables",
indexes = { indexes = {
@Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true), @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
@Index(name = "idx_ecriture_date", columnList = "date_ecriture"), @Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
@Index(name = "idx_ecriture_journal", columnList = "journal_id"), @Index(name = "idx_ecriture_journal", columnList = "journal_id"),
@Index(name = "idx_ecriture_organisation", columnList = "organisation_id"), @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
@Index(name = "idx_ecriture_paiement", columnList = "paiement_id") @Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class EcritureComptable extends BaseEntity { public class EcritureComptable extends BaseEntity {
/** Numéro de pièce unique */ /** Numéro de pièce unique */
@NotBlank @NotBlank
@Column(name = "numero_piece", unique = true, nullable = false, length = 50) @Column(name = "numero_piece", unique = true, nullable = false, length = 50)
private String numeroPiece; private String numeroPiece;
/** Date de l'écriture */ /** Date de l'écriture */
@NotNull @NotNull
@Column(name = "date_ecriture", nullable = false) @Column(name = "date_ecriture", nullable = false)
private LocalDate dateEcriture; private LocalDate dateEcriture;
/** Libellé de l'écriture */ /** Libellé de l'écriture */
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 500) @Column(name = "libelle", nullable = false, length = 500)
private String libelle; private String libelle;
/** Référence externe */ /** Référence externe */
@Column(name = "reference", length = 100) @Column(name = "reference", length = 100)
private String reference; private String reference;
/** Lettrage (pour rapprochement) */ /** Lettrage (pour rapprochement) */
@Column(name = "lettrage", length = 20) @Column(name = "lettrage", length = 20)
private String lettrage; private String lettrage;
/** Pointage (pour rapprochement bancaire) */ /** Pointage (pour rapprochement bancaire) */
@Builder.Default @Builder.Default
@Column(name = "pointe", nullable = false) @Column(name = "pointe", nullable = false)
private Boolean pointe = false; private Boolean pointe = false;
/** Montant total débit (somme des lignes) */ /** Montant total débit (somme des lignes) */
@Builder.Default @Builder.Default
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_debit", precision = 14, scale = 2) @Column(name = "montant_debit", precision = 14, scale = 2)
private BigDecimal montantDebit = BigDecimal.ZERO; private BigDecimal montantDebit = BigDecimal.ZERO;
/** Montant total crédit (somme des lignes) */ /** Montant total crédit (somme des lignes) */
@Builder.Default @Builder.Default
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_credit", precision = 14, scale = 2) @Column(name = "montant_credit", precision = 14, scale = 2)
private BigDecimal montantCredit = BigDecimal.ZERO; private BigDecimal montantCredit = BigDecimal.ZERO;
/** Commentaires */ /** Commentaires */
@Column(name = "commentaire", length = 1000) @Column(name = "commentaire", length = 1000)
private String commentaire; private String commentaire;
// Relations // Relations
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "journal_id", nullable = false) @JoinColumn(name = "journal_id", nullable = false)
private JournalComptable journal; private JournalComptable journal;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id") @JoinColumn(name = "paiement_id")
private Paiement paiement; private Paiement paiement;
/** Lignes d'écriture */ /** Lignes d'écriture */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<LigneEcriture> lignes = new ArrayList<>(); private List<LigneEcriture> lignes = new ArrayList<>();
/** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */ /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
public boolean isEquilibree() { public boolean isEquilibree() {
if (montantDebit == null || montantCredit == null) { if (montantDebit == null || montantCredit == null) {
return false; return false;
} }
return montantDebit.compareTo(montantCredit) == 0; return montantDebit.compareTo(montantCredit) == 0;
} }
/** Méthode métier pour calculer les totaux à partir des lignes */ /** Méthode métier pour calculer les totaux à partir des lignes */
public void calculerTotaux() { public void calculerTotaux() {
if (lignes == null || lignes.isEmpty()) { if (lignes == null || lignes.isEmpty()) {
montantDebit = BigDecimal.ZERO; montantDebit = BigDecimal.ZERO;
montantCredit = BigDecimal.ZERO; montantCredit = BigDecimal.ZERO;
return; return;
} }
montantDebit = montantDebit =
lignes.stream() lignes.stream()
.map(LigneEcriture::getMontantDebit) .map(LigneEcriture::getMontantDebit)
.filter(amount -> amount != null) .filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
montantCredit = montantCredit =
lignes.stream() lignes.stream()
.map(LigneEcriture::getMontantCredit) .map(LigneEcriture::getMontantCredit)
.filter(amount -> amount != null) .filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
} }
/** Méthode métier pour générer un numéro de pièce unique */ /** Méthode métier pour générer un numéro de pièce unique */
public static String genererNumeroPiece(String prefixe, LocalDate date) { public static String genererNumeroPiece(String prefixe, LocalDate date) {
return String.format( return String.format(
"%s-%04d%02d%02d-%012d", "%s-%04d%02d%02d-%012d",
prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(), prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
System.currentTimeMillis() % 1000000000000L); System.currentTimeMillis() % 1000000000000L);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (numeroPiece == null || numeroPiece.isEmpty()) { if (numeroPiece == null || numeroPiece.isEmpty()) {
numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now()); numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
} }
if (dateEcriture == null) { if (dateEcriture == null) {
dateEcriture = LocalDate.now(); dateEcriture = LocalDate.now();
} }
if (montantDebit == null) { if (montantDebit == null) {
montantDebit = BigDecimal.ZERO; montantDebit = BigDecimal.ZERO;
} }
if (montantCredit == null) { if (montantCredit == null) {
montantCredit = BigDecimal.ZERO; montantCredit = BigDecimal.ZERO;
} }
if (pointe == null) { if (pointe == null) {
pointe = false; pointe = false;
} }
// Calculer les totaux si les lignes sont déjà présentes // Calculer les totaux si les lignes sont déjà présentes
if (lignes != null && !lignes.isEmpty()) { if (lignes != null && !lignes.isEmpty()) {
calculerTotaux(); calculerTotaux();
} }
} }
/** Callback JPA avant la mise à jour */ /** Callback JPA avant la mise à jour */
@PreUpdate @PreUpdate
protected void onUpdate() { protected void onUpdate() {
calculerTotaux(); calculerTotaux();
} }
} }

View File

@@ -1,259 +1,259 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.*; import lombok.*;
/** /**
* Entité Événement pour la gestion des événements de l'union * Entité Événement pour la gestion des événements de l'union
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 2.0 * @version 2.0
* @since 2025-01-16 * @since 2025-01-16
*/ */
@Entity @Entity
@Table(name = "evenements", indexes = { @Table(name = "evenements", indexes = {
@Index(name = "idx_evenement_date_debut", columnList = "date_debut"), @Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
@Index(name = "idx_evenement_statut", columnList = "statut"), @Index(name = "idx_evenement_statut", columnList = "statut"),
@Index(name = "idx_evenement_type", columnList = "type_evenement"), @Index(name = "idx_evenement_type", columnList = "type_evenement"),
@Index(name = "idx_evenement_organisation", columnList = "organisation_id") @Index(name = "idx_evenement_organisation", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Evenement extends BaseEntity { public class Evenement extends BaseEntity {
@NotBlank @NotBlank
@Size(min = 3, max = 200) @Size(min = 3, max = 200)
@Column(name = "titre", nullable = false, length = 200) @Column(name = "titre", nullable = false, length = 200)
private String titre; private String titre;
@Size(max = 2000) @Size(max = 2000)
@Column(name = "description", length = 2000) @Column(name = "description", length = 2000)
private String description; private String description;
@NotNull @NotNull
@Column(name = "date_debut", nullable = false) @Column(name = "date_debut", nullable = false)
private LocalDateTime dateDebut; private LocalDateTime dateDebut;
@Column(name = "date_fin") @Column(name = "date_fin")
private LocalDateTime dateFin; private LocalDateTime dateFin;
@Size(max = 500) @Size(max = 500)
@Column(name = "lieu", length = 500) @Column(name = "lieu", length = 500)
private String lieu; private String lieu;
@Size(max = 1000) @Size(max = 1000)
@Column(name = "adresse", length = 1000) @Column(name = "adresse", length = 1000)
private String adresse; private String adresse;
@Column(name = "type_evenement", length = 50) @Column(name = "type_evenement", length = 50)
private String typeEvenement; private String typeEvenement;
@Builder.Default @Builder.Default
@Column(name = "statut", nullable = false, length = 30) @Column(name = "statut", nullable = false, length = 30)
private String statut = "PLANIFIE"; private String statut = "PLANIFIE";
@Min(0) @Min(0)
@Column(name = "capacite_max") @Column(name = "capacite_max")
private Integer capaciteMax; private Integer capaciteMax;
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 8, fraction = 2) @Digits(integer = 8, fraction = 2)
@Column(name = "prix", precision = 10, scale = 2) @Column(name = "prix", precision = 10, scale = 2)
private BigDecimal prix; private BigDecimal prix;
@Builder.Default @Builder.Default
@Column(name = "inscription_requise", nullable = false) @Column(name = "inscription_requise", nullable = false)
private Boolean inscriptionRequise = false; private Boolean inscriptionRequise = false;
@Column(name = "date_limite_inscription") @Column(name = "date_limite_inscription")
private LocalDateTime dateLimiteInscription; private LocalDateTime dateLimiteInscription;
@Size(max = 1000) @Size(max = 1000)
@Column(name = "instructions_particulieres", length = 1000) @Column(name = "instructions_particulieres", length = 1000)
private String instructionsParticulieres; private String instructionsParticulieres;
@Size(max = 500) @Size(max = 500)
@Column(name = "contact_organisateur", length = 500) @Column(name = "contact_organisateur", length = 500)
private String contactOrganisateur; private String contactOrganisateur;
@Size(max = 2000) @Size(max = 2000)
@Column(name = "materiel_requis", length = 2000) @Column(name = "materiel_requis", length = 2000)
private String materielRequis; private String materielRequis;
@Builder.Default @Builder.Default
@Column(name = "visible_public", nullable = false) @Column(name = "visible_public", nullable = false)
private Boolean visiblePublic = true; private Boolean visiblePublic = true;
// Relations // Relations
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisateur_id") @JoinColumn(name = "organisateur_id")
private Membre organisateur; private Membre organisateur;
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<InscriptionEvenement> inscriptions = new ArrayList<>(); private List<InscriptionEvenement> inscriptions = new ArrayList<>();
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<Adresse> adresses = new ArrayList<>(); private List<Adresse> adresses = new ArrayList<>();
/** Types d'événements */ /** Types d'événements */
public enum TypeEvenement { public enum TypeEvenement {
ASSEMBLEE_GENERALE("Assemblée Générale"), ASSEMBLEE_GENERALE("Assemblée Générale"),
REUNION("Réunion"), REUNION("Réunion"),
FORMATION("Formation"), FORMATION("Formation"),
CONFERENCE("Conférence"), CONFERENCE("Conférence"),
ATELIER("Atelier"), ATELIER("Atelier"),
SEMINAIRE("Séminaire"), SEMINAIRE("Séminaire"),
EVENEMENT_SOCIAL("Événement Social"), EVENEMENT_SOCIAL("Événement Social"),
MANIFESTATION("Manifestation"), MANIFESTATION("Manifestation"),
CELEBRATION("Célébration"), CELEBRATION("Célébration"),
AUTRE("Autre"); AUTRE("Autre");
private final String libelle; private final String libelle;
TypeEvenement(String libelle) { TypeEvenement(String libelle) {
this.libelle = libelle; this.libelle = libelle;
} }
public String getLibelle() { public String getLibelle() {
return libelle; return libelle;
} }
} }
/** Statuts d'événement */ /** Statuts d'événement */
public enum StatutEvenement { public enum StatutEvenement {
PLANIFIE("Planifié"), PLANIFIE("Planifié"),
CONFIRME("Confirmé"), CONFIRME("Confirmé"),
EN_COURS("En cours"), EN_COURS("En cours"),
TERMINE("Terminé"), TERMINE("Terminé"),
ANNULE("Annulé"), ANNULE("Annulé"),
REPORTE("Reporté"); REPORTE("Reporté");
private final String libelle; private final String libelle;
StatutEvenement(String libelle) { StatutEvenement(String libelle) {
this.libelle = libelle; this.libelle = libelle;
} }
public String getLibelle() { public String getLibelle() {
return libelle; return libelle;
} }
} }
// Méthodes métier // Méthodes métier
/** Vérifie si l'événement est ouvert aux inscriptions */ /** Vérifie si l'événement est ouvert aux inscriptions */
@JsonIgnore @JsonIgnore
public boolean isOuvertAuxInscriptions() { public boolean isOuvertAuxInscriptions() {
if (!inscriptionRequise || !getActif()) { if (!inscriptionRequise || !getActif()) {
return false; return false;
} }
LocalDateTime maintenant = LocalDateTime.now(); LocalDateTime maintenant = LocalDateTime.now();
// Vérifier si la date limite d'inscription n'est pas dépassée // Vérifier si la date limite d'inscription n'est pas dépassée
if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) { if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
return false; return false;
} }
// Vérifier si l'événement n'a pas déjà commencé // Vérifier si l'événement n'a pas déjà commencé
if (maintenant.isAfter(dateDebut)) { if (maintenant.isAfter(dateDebut)) {
return false; return false;
} }
// Vérifier la capacité // Vérifier la capacité
if (capaciteMax != null && getNombreInscrits() >= capaciteMax) { if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
return false; return false;
} }
return "PLANIFIE".equals(statut) || "CONFIRME".equals(statut); return "PLANIFIE".equals(statut) || "CONFIRME".equals(statut);
} }
/** Obtient le nombre d'inscrits à l'événement */ /** Obtient le nombre d'inscrits à l'événement */
@JsonIgnore @JsonIgnore
public int getNombreInscrits() { public int getNombreInscrits() {
return inscriptions != null return inscriptions != null
? (int) inscriptions.stream() ? (int) inscriptions.stream()
.filter( .filter(
inscription -> InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut())) inscription -> InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()))
.count() .count()
: 0; : 0;
} }
/** Vérifie si l'événement est complet */ /** Vérifie si l'événement est complet */
@JsonIgnore @JsonIgnore
public boolean isComplet() { public boolean isComplet() {
return capaciteMax != null && getNombreInscrits() >= capaciteMax; return capaciteMax != null && getNombreInscrits() >= capaciteMax;
} }
/** Vérifie si l'événement est en cours */ /** Vérifie si l'événement est en cours */
public boolean isEnCours() { public boolean isEnCours() {
LocalDateTime maintenant = LocalDateTime.now(); LocalDateTime maintenant = LocalDateTime.now();
return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin)); return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
} }
/** Vérifie si l'événement est terminé */ /** Vérifie si l'événement est terminé */
public boolean isTermine() { public boolean isTermine() {
if ("TERMINE".equals(statut)) { if ("TERMINE".equals(statut)) {
return true; return true;
} }
LocalDateTime maintenant = LocalDateTime.now(); LocalDateTime maintenant = LocalDateTime.now();
return dateFin != null && maintenant.isAfter(dateFin); return dateFin != null && maintenant.isAfter(dateFin);
} }
/** Calcule la durée de l'événement en heures */ /** Calcule la durée de l'événement en heures */
public Long getDureeEnHeures() { public Long getDureeEnHeures() {
if (dateFin == null) { if (dateFin == null) {
return null; return null;
} }
return java.time.Duration.between(dateDebut, dateFin).toHours(); return java.time.Duration.between(dateDebut, dateFin).toHours();
} }
/** Obtient le nombre de places restantes */ /** Obtient le nombre de places restantes */
@JsonIgnore @JsonIgnore
public Integer getPlacesRestantes() { public Integer getPlacesRestantes() {
if (capaciteMax == null) { if (capaciteMax == null) {
return null; // Capacité illimitée return null; // Capacité illimitée
} }
return Math.max(0, capaciteMax - getNombreInscrits()); return Math.max(0, capaciteMax - getNombreInscrits());
} }
/** Vérifie si un membre est inscrit à l'événement */ /** Vérifie si un membre est inscrit à l'événement */
public boolean isMemberInscrit(UUID membreId) { public boolean isMemberInscrit(UUID membreId) {
return inscriptions != null return inscriptions != null
&& inscriptions.stream() && inscriptions.stream()
.anyMatch( .anyMatch(
inscription -> inscription.getMembre().getId().equals(membreId) inscription -> inscription.getMembre().getId().equals(membreId)
&& InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut())); && InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()));
} }
/** Obtient le taux de remplissage en pourcentage */ /** Obtient le taux de remplissage en pourcentage */
@JsonIgnore @JsonIgnore
public Double getTauxRemplissage() { public Double getTauxRemplissage() {
if (capaciteMax == null || capaciteMax == 0) { if (capaciteMax == null || capaciteMax == 0) {
return null; return null;
} }
return (double) getNombreInscrits() / capaciteMax * 100; return (double) getNombreInscrits() / capaciteMax * 100;
} }
} }

View File

@@ -1,79 +1,79 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
/** /**
* Entité Favori pour la gestion des favoris utilisateur * Entité Favori pour la gestion des favoris utilisateur
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
*/ */
@Entity @Entity
@Table( @Table(
name = "favoris", name = "favoris",
indexes = { indexes = {
@Index(name = "idx_favori_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_favori_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_favori_type", columnList = "type_favori"), @Index(name = "idx_favori_type", columnList = "type_favori"),
@Index(name = "idx_favori_categorie", columnList = "categorie") @Index(name = "idx_favori_categorie", columnList = "categorie")
} }
) )
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Favori extends BaseEntity { public class Favori extends BaseEntity {
@NotNull @NotNull
@Column(name = "utilisateur_id", nullable = false) @Column(name = "utilisateur_id", nullable = false)
private UUID utilisateurId; private UUID utilisateurId;
@NotBlank @NotBlank
@Column(name = "type_favori", nullable = false, length = 50) @Column(name = "type_favori", nullable = false, length = 50)
private String typeFavori; // PAGE, DOCUMENT, CONTACT, RACCOURCI private String typeFavori; // PAGE, DOCUMENT, CONTACT, RACCOURCI
@NotBlank @NotBlank
@Column(name = "titre", nullable = false, length = 255) @Column(name = "titre", nullable = false, length = 255)
private String titre; private String titre;
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
@Column(name = "url", length = 1000) @Column(name = "url", length = 1000)
private String url; private String url;
@Column(name = "icon", length = 100) @Column(name = "icon", length = 100)
private String icon; private String icon;
@Column(name = "couleur", length = 50) @Column(name = "couleur", length = 50)
private String couleur; private String couleur;
@Column(name = "categorie", length = 100) @Column(name = "categorie", length = 100)
private String categorie; private String categorie;
@Column(name = "ordre") @Column(name = "ordre")
@Builder.Default @Builder.Default
private Integer ordre = 0; private Integer ordre = 0;
@Column(name = "nb_visites") @Column(name = "nb_visites")
@Builder.Default @Builder.Default
private Integer nbVisites = 0; private Integer nbVisites = 0;
@Column(name = "derniere_visite") @Column(name = "derniere_visite")
private LocalDateTime derniereVisite; private LocalDateTime derniereVisite;
@Column(name = "est_plus_utilise") @Column(name = "est_plus_utilise")
@Builder.Default @Builder.Default
private Boolean estPlusUtilise = false; private Boolean estPlusUtilise = false;
} }

View File

@@ -1,117 +1,117 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.*; import lombok.*;
/** /**
* Entité FeedbackEvenement représentant l'évaluation d'un membre sur un événement * Entité FeedbackEvenement représentant l'évaluation d'un membre sur un événement
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-16 * @since 2026-03-16
*/ */
@Entity @Entity
@Table( @Table(
name = "feedbacks_evenement", name = "feedbacks_evenement",
indexes = { indexes = {
@Index(name = "idx_feedback_membre", columnList = "membre_id"), @Index(name = "idx_feedback_membre", columnList = "membre_id"),
@Index(name = "idx_feedback_evenement", columnList = "evenement_id"), @Index(name = "idx_feedback_evenement", columnList = "evenement_id"),
@Index(name = "idx_feedback_date", columnList = "date_feedback") @Index(name = "idx_feedback_date", columnList = "date_feedback")
}, },
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_feedback_membre_evenement", name = "uk_feedback_membre_evenement",
columnNames = {"membre_id", "evenement_id"}) columnNames = {"membre_id", "evenement_id"})
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class FeedbackEvenement extends BaseEntity { public class FeedbackEvenement extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id", nullable = false) @JoinColumn(name = "evenement_id", nullable = false)
private Evenement evenement; private Evenement evenement;
@NotNull @NotNull
@Min(1) @Min(1)
@Max(5) @Max(5)
@Column(name = "note", nullable = false) @Column(name = "note", nullable = false)
private Integer note; private Integer note;
@Column(name = "commentaire", length = 1000) @Column(name = "commentaire", length = 1000)
private String commentaire; private String commentaire;
@Builder.Default @Builder.Default
@Column(name = "date_feedback", nullable = false) @Column(name = "date_feedback", nullable = false)
private LocalDateTime dateFeedback = LocalDateTime.now(); private LocalDateTime dateFeedback = LocalDateTime.now();
@Column(name = "moderation_statut", length = 20) @Column(name = "moderation_statut", length = 20)
@Builder.Default @Builder.Default
private String moderationStatut = ModerationStatut.PUBLIE.name(); private String moderationStatut = ModerationStatut.PUBLIE.name();
@Column(name = "raison_moderation", length = 500) @Column(name = "raison_moderation", length = 500)
private String raisonModeration; private String raisonModeration;
/** Énumération des statuts de modération */ /** Énumération des statuts de modération */
public enum ModerationStatut { public enum ModerationStatut {
PUBLIE, // Visible publiquement PUBLIE, // Visible publiquement
EN_ATTENTE, // En attente de modération EN_ATTENTE, // En attente de modération
REJETE // Rejeté par modération REJETE // Rejeté par modération
} }
// Méthodes utilitaires // Méthodes utilitaires
/** Vérifie si le feedback est publié */ /** Vérifie si le feedback est publié */
public boolean isPublie() { public boolean isPublie() {
return ModerationStatut.PUBLIE.name().equals(this.moderationStatut); return ModerationStatut.PUBLIE.name().equals(this.moderationStatut);
} }
/** Marque le feedback comme en attente de modération */ /** Marque le feedback comme en attente de modération */
public void mettreEnAttente(String raison) { public void mettreEnAttente(String raison) {
this.moderationStatut = ModerationStatut.EN_ATTENTE.name(); this.moderationStatut = ModerationStatut.EN_ATTENTE.name();
this.raisonModeration = raison; this.raisonModeration = raison;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
/** Publie le feedback */ /** Publie le feedback */
public void publier() { public void publier() {
this.moderationStatut = ModerationStatut.PUBLIE.name(); this.moderationStatut = ModerationStatut.PUBLIE.name();
this.raisonModeration = null; this.raisonModeration = null;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
/** Rejette le feedback */ /** Rejette le feedback */
public void rejeter(String raison) { public void rejeter(String raison) {
this.moderationStatut = ModerationStatut.REJETE.name(); this.moderationStatut = ModerationStatut.REJETE.name();
this.raisonModeration = raison; this.raisonModeration = raison;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
super.onUpdate(); super.onUpdate();
} }
@Override @Override
public String toString() { public String toString() {
return String.format( return String.format(
"FeedbackEvenement{id=%s, membre=%s, evenement=%s, note=%d, dateFeedback=%s}", "FeedbackEvenement{id=%s, membre=%s, evenement=%s, note=%d, dateFeedback=%s}",
getId(), getId(),
membre != null ? membre.getEmail() : "null", membre != null ? membre.getEmail() : "null",
evenement != null ? evenement.getTitre() : "null", evenement != null ? evenement.getTitre() : "null",
note, note,
dateFeedback); dateFeedback);
} }
} }

View File

@@ -1,120 +1,124 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres; import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule; import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.*; import lombok.*;
/** /**
* Catalogue des forfaits SaaS UnionFlow. * Catalogue des forfaits SaaS UnionFlow.
* *
* <p>Starter (≤50) → Standard (≤200) → Premium (≤500) → Crystal (illimité) * <p>Starter (≤50) → Standard (≤200) → Premium (≤500) → Crystal (illimité)
* Fourchette tarifaire : 5 000 à 10 000 XOF/mois. Stockage max : 1 Go. * Fourchette tarifaire : 5 000 à 10 000 XOF/mois. Stockage max : 1 Go.
* *
* <p>Table : {@code formules_abonnement} * <p>Table : {@code formules_abonnement}
*/ */
@Entity @Entity
@Table( @Table(
name = "formules_abonnement", name = "formules_abonnement",
indexes = { indexes = {
@Index(name = "idx_formule_code_plage", columnList = "code, plage", unique = true), @Index(name = "idx_formule_code_plage", columnList = "code, plage", unique = true),
@Index(name = "idx_formule_code", columnList = "code"), @Index(name = "idx_formule_code", columnList = "code"),
@Index(name = "idx_formule_plage", columnList = "plage"), @Index(name = "idx_formule_plage", columnList = "plage"),
@Index(name = "idx_formule_actif", columnList = "actif") @Index(name = "idx_formule_actif", columnList = "actif")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class FormuleAbonnement extends BaseEntity { public class FormuleAbonnement extends BaseEntity {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@NotNull @NotNull
@Column(name = "code", nullable = false, length = 20) @Column(name = "code", nullable = false, length = 20)
private TypeFormule code; private TypeFormule code;
/** /**
* Plage de taille d'organisation à laquelle cette formule s'applique. * Plage de taille d'organisation à laquelle cette formule s'applique.
* Combinée avec le code de formule, forme une clé unique dans le catalogue. * Combinée avec le code de formule, forme une clé unique dans le catalogue.
*/ */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@NotNull @NotNull
@Column(name = "plage", nullable = false, length = 20) @Column(name = "plage", nullable = false, length = 20)
private PlageMembres plage; private PlageMembres plage;
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 100) @Column(name = "libelle", nullable = false, length = 100)
private String libelle; private String libelle;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String description; private String description;
/** Nombre maximum de membres. NULL = illimité (Crystal) */ /** Nombre maximum de membres. NULL = illimité (Crystal) */
@Column(name = "max_membres") @Column(name = "max_membres")
private Integer maxMembres; private Integer maxMembres;
/** Stockage maximum en Mo — 1 024 Mo (1 Go) par défaut */ /** Stockage maximum en Mo — 1 024 Mo (1 Go) par défaut */
@Builder.Default @Builder.Default
@Column(name = "max_stockage_mo", nullable = false) @Column(name = "max_stockage_mo", nullable = false)
private Integer maxStockageMo = 1024; private Integer maxStockageMo = 1024;
@NotNull @NotNull
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 8, fraction = 2) @Digits(integer = 8, fraction = 2)
@Column(name = "prix_mensuel", nullable = false, precision = 10, scale = 2) @Column(name = "prix_mensuel", nullable = false, precision = 10, scale = 2)
private BigDecimal prixMensuel; private BigDecimal prixMensuel;
@NotNull @NotNull
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 8, fraction = 2) @Digits(integer = 8, fraction = 2)
@Column(name = "prix_annuel", nullable = false, precision = 10, scale = 2) @Column(name = "prix_annuel", nullable = false, precision = 10, scale = 2)
private BigDecimal prixAnnuel; private BigDecimal prixAnnuel;
@Builder.Default @Builder.Default
@Column(name = "ordre_affichage", nullable = false) @Column(name = "ordre_affichage", nullable = false)
private Integer ordreAffichage = 0; private Integer ordreAffichage = 0;
// ── Champs Option C (ajoutés en V19) ────────────────────────────────────── // ── Champs Option C (ajoutés en V19) ──────────────────────────────────────
/** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */ /** Nom commercial du plan (MICRO, DECOUVERTE, ESSENTIEL, AVANCE, PROFESSIONNEL, ENTERPRISE) */
@Column(name = "plan_commercial", length = 30) @Column(name = "plan_commercial", length = 30)
private String planCommercial; private String planCommercial;
/** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */ /** Niveau de reporting disponible (BASIQUE, STANDARD, AVANCE) */
@Column(name = "niveau_reporting", length = 20) @Column(name = "niveau_reporting", length = 20)
private String niveauReporting; private String niveauReporting;
/** Accès à l'API REST (false pour les plans de base) */ /** Accès à l'API REST (false pour les plans de base) */
@Builder.Default @Builder.Default
@Column(name = "api_access", nullable = false) @Column(name = "api_access", nullable = false)
private Boolean apiAccess = false; private Boolean apiAccess = false;
/** Accès au module de fédération multi-org (ENTERPRISE uniquement) */ /** Accès au module de fédération multi-org (ENTERPRISE uniquement) */
@Builder.Default @Builder.Default
@Column(name = "federation_access", nullable = false) @Column(name = "federation_access", nullable = false)
private Boolean federationAccess = false; private Boolean federationAccess = false;
/** Support prioritaire inclus */ /** Support prioritaire inclus */
@Builder.Default @Builder.Default
@Column(name = "support_prioritaire", nullable = false) @Column(name = "support_prioritaire", nullable = false)
private Boolean supportPrioritaire = false; private Boolean supportPrioritaire = false;
/** SLA garanti (ex: "99.0%", "99.9%") */ /** SLA garanti (ex: "99.0%", "99.9%") */
@Column(name = "sla_garanti", length = 10) @Column(name = "sla_garanti", length = 10)
private String slaGaranti; private String slaGaranti;
/** Nombre maximum d'administrateurs. NULL = illimité */ /** Nombre maximum d'administrateurs. NULL = illimité */
@Column(name = "max_admins") @Column(name = "max_admins")
private Integer maxAdmins; private Integer maxAdmins;
public boolean isIllimitee() { /** Code du provider de paiement par défaut (WAVE, ORANGE_MONEY, MTN_MOMO, PISPI). NULL = global. */
return maxMembres == null; @Column(name = "provider_defaut", length = 20)
} private String providerDefaut;
public boolean accepteNouveauMembre(int quotaActuel) { public boolean isIllimitee() {
return isIllimitee() || quotaActuel < maxMembres; return maxMembres == null;
} }
}
public boolean accepteNouveauMembre(int quotaActuel) {
return isIllimitee() || quotaActuel < maxMembres;
}
}

View File

@@ -1,143 +1,143 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.*; import lombok.*;
/** /**
* Entité InscriptionEvenement représentant l'inscription d'un membre à un * Entité InscriptionEvenement représentant l'inscription d'un membre à un
* événement * événement
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 2.0 * @version 2.0
* @since 2025-01-16 * @since 2025-01-16
*/ */
@Entity @Entity
@Table(name = "inscriptions_evenement", indexes = { @Table(name = "inscriptions_evenement", indexes = {
@Index(name = "idx_inscription_membre", columnList = "membre_id"), @Index(name = "idx_inscription_membre", columnList = "membre_id"),
@Index(name = "idx_inscription_evenement", columnList = "evenement_id"), @Index(name = "idx_inscription_evenement", columnList = "evenement_id"),
@Index(name = "idx_inscription_date", columnList = "date_inscription") @Index(name = "idx_inscription_date", columnList = "date_inscription")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class InscriptionEvenement extends BaseEntity { public class InscriptionEvenement extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id", nullable = false) @JoinColumn(name = "evenement_id", nullable = false)
private Evenement evenement; private Evenement evenement;
@Builder.Default @Builder.Default
@Column(name = "date_inscription", nullable = false) @Column(name = "date_inscription", nullable = false)
private LocalDateTime dateInscription = LocalDateTime.now(); private LocalDateTime dateInscription = LocalDateTime.now();
@Column(name = "statut", length = 20) @Column(name = "statut", length = 20)
@Builder.Default @Builder.Default
private String statut = StatutInscription.CONFIRMEE.name(); private String statut = StatutInscription.CONFIRMEE.name();
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
/** Énumération des statuts d'inscription (pour constantes) */ /** Énumération des statuts d'inscription (pour constantes) */
public enum StatutInscription { public enum StatutInscription {
CONFIRMEE, CONFIRMEE,
EN_ATTENTE, EN_ATTENTE,
ANNULEE, ANNULEE,
REFUSEE; REFUSEE;
} }
// Méthodes utilitaires // Méthodes utilitaires
/** /**
* Vérifie si l'inscription est confirmée * Vérifie si l'inscription est confirmée
* *
* @return true si l'inscription est confirmée * @return true si l'inscription est confirmée
*/ */
public boolean isConfirmee() { public boolean isConfirmee() {
return StatutInscription.CONFIRMEE.name().equals(this.statut); return StatutInscription.CONFIRMEE.name().equals(this.statut);
} }
/** /**
* Vérifie si l'inscription est en attente * Vérifie si l'inscription est en attente
* *
* @return true si l'inscription est en attente * @return true si l'inscription est en attente
*/ */
public boolean isEnAttente() { public boolean isEnAttente() {
return StatutInscription.EN_ATTENTE.name().equals(this.statut); return StatutInscription.EN_ATTENTE.name().equals(this.statut);
} }
/** /**
* Vérifie si l'inscription est annulée * Vérifie si l'inscription est annulée
* *
* @return true si l'inscription est annulée * @return true si l'inscription est annulée
*/ */
public boolean isAnnulee() { public boolean isAnnulee() {
return StatutInscription.ANNULEE.name().equals(this.statut); return StatutInscription.ANNULEE.name().equals(this.statut);
} }
/** Confirme l'inscription */ /** Confirme l'inscription */
public void confirmer() { public void confirmer() {
this.statut = StatutInscription.CONFIRMEE.name(); this.statut = StatutInscription.CONFIRMEE.name();
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
/** /**
* Annule l'inscription * Annule l'inscription
* *
* @param commentaire le commentaire d'annulation * @param commentaire le commentaire d'annulation
*/ */
public void annuler(String commentaire) { public void annuler(String commentaire) {
this.statut = StatutInscription.ANNULEE.name(); this.statut = StatutInscription.ANNULEE.name();
this.commentaire = commentaire; this.commentaire = commentaire;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
/** /**
* Met l'inscription en attente * Met l'inscription en attente
* *
* @param commentaire le commentaire de mise en attente * @param commentaire le commentaire de mise en attente
*/ */
public void mettreEnAttente(String commentaire) { public void mettreEnAttente(String commentaire) {
this.statut = StatutInscription.EN_ATTENTE.name(); this.statut = StatutInscription.EN_ATTENTE.name();
this.commentaire = commentaire; this.commentaire = commentaire;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
/** /**
* Refuser l'inscription * Refuser l'inscription
* *
* @param commentaire le commentaire de refus * @param commentaire le commentaire de refus
*/ */
public void refuser(String commentaire) { public void refuser(String commentaire) {
this.statut = StatutInscription.REFUSEE.name(); this.statut = StatutInscription.REFUSEE.name();
this.commentaire = commentaire; this.commentaire = commentaire;
setDateModification(LocalDateTime.now()); setDateModification(LocalDateTime.now());
} }
// Callbacks JPA // Callbacks JPA
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
super.onUpdate(); super.onUpdate();
} }
@Override @Override
public String toString() { public String toString() {
return String.format( return String.format(
"InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}", "InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}",
getId(), getId(),
membre != null ? membre.getEmail() : "null", membre != null ? membre.getEmail() : "null",
evenement != null ? evenement.getTitre() : "null", evenement != null ? evenement.getTitre() : "null",
statut, statut,
dateInscription); dateInscription);
} }
} }

View File

@@ -1,122 +1,122 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement; import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement; import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.*; import lombok.*;
/** /**
* Hub centralisé pour tout paiement Wave initié depuis UnionFlow. * Hub centralisé pour tout paiement Wave initié depuis UnionFlow.
* *
* <p>Flux : * <p>Flux :
* <ol> * <ol>
* <li>UnionFlow crée une {@code IntentionPaiement} avec les objets cibles (cotisations, etc.)</li> * <li>UnionFlow crée une {@code IntentionPaiement} avec les objets cibles (cotisations, etc.)</li>
* <li>UnionFlow appelle l'API Wave → récupère {@code waveCheckoutSessionId}</li> * <li>UnionFlow appelle l'API Wave → récupère {@code waveCheckoutSessionId}</li>
* <li>Le membre confirme dans l'app Wave</li> * <li>Le membre confirme dans l'app Wave</li>
* <li>Wave envoie un webhook → UnionFlow réconcilie via {@code waveCheckoutSessionId}</li> * <li>Wave envoie un webhook → UnionFlow réconcilie via {@code waveCheckoutSessionId}</li>
* <li>UnionFlow valide automatiquement les objets listés dans {@code objetsCibles}</li> * <li>UnionFlow valide automatiquement les objets listés dans {@code objetsCibles}</li>
* </ol> * </ol>
* *
* <p>Table : {@code intentions_paiement} * <p>Table : {@code intentions_paiement}
*/ */
@Entity @Entity
@Table( @Table(
name = "intentions_paiement", name = "intentions_paiement",
indexes = { indexes = {
@Index(name = "idx_intention_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_intention_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_intention_statut", columnList = "statut"), @Index(name = "idx_intention_statut", columnList = "statut"),
@Index(name = "idx_intention_wave_session", columnList = "wave_checkout_session_id", unique = true), @Index(name = "idx_intention_wave_session", columnList = "wave_checkout_session_id", unique = true),
@Index(name = "idx_intention_expiration", columnList = "date_expiration") @Index(name = "idx_intention_expiration", columnList = "date_expiration")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class IntentionPaiement extends BaseEntity { public class IntentionPaiement extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "utilisateur_id", nullable = false) @JoinColumn(name = "utilisateur_id", nullable = false)
private Membre utilisateur; private Membre utilisateur;
/** NULL pour les abonnements UnionFlow SA (payés par l'organisation directement) */ /** NULL pour les abonnements UnionFlow SA (payés par l'organisation directement) */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@NotNull @NotNull
@DecimalMin("0.01") @DecimalMin("0.01")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_total", nullable = false, precision = 14, scale = 2) @Column(name = "montant_total", nullable = false, precision = 14, scale = 2)
private BigDecimal montantTotal; private BigDecimal montantTotal;
@NotBlank @NotBlank
@Pattern(regexp = "^[A-Z]{3}$") @Pattern(regexp = "^[A-Z]{3}$")
@Builder.Default @Builder.Default
@Column(name = "code_devise", nullable = false, length = 3) @Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise = "XOF"; private String codeDevise = "XOF";
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@NotNull @NotNull
@Column(name = "type_objet", nullable = false, length = 30) @Column(name = "type_objet", nullable = false, length = 30)
private TypeObjetIntentionPaiement typeObjet; private TypeObjetIntentionPaiement typeObjet;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut", nullable = false, length = 20) @Column(name = "statut", nullable = false, length = 20)
private StatutIntentionPaiement statut = StatutIntentionPaiement.INITIEE; private StatutIntentionPaiement statut = StatutIntentionPaiement.INITIEE;
/** ID de session Wave — clé de réconciliation sur webhook */ /** ID de session Wave — clé de réconciliation sur webhook */
@Column(name = "wave_checkout_session_id", unique = true, length = 255) @Column(name = "wave_checkout_session_id", unique = true, length = 255)
private String waveCheckoutSessionId; private String waveCheckoutSessionId;
/** URL de paiement Wave à rediriger l'utilisateur */ /** URL de paiement Wave à rediriger l'utilisateur */
@Column(name = "wave_launch_url", length = 1000) @Column(name = "wave_launch_url", length = 1000)
private String waveLaunchUrl; private String waveLaunchUrl;
/** ID transaction Wave reçu via webhook */ /** ID transaction Wave reçu via webhook */
@Column(name = "wave_transaction_id", length = 100) @Column(name = "wave_transaction_id", length = 100)
private String waveTransactionId; private String waveTransactionId;
/** /**
* JSON : liste des objets couverts par ce paiement. * JSON : liste des objets couverts par ce paiement.
* Exemple : [{\"type\":\"COTISATION\",\"id\":\"uuid\",\"montant\":5000}, ...] * Exemple : [{\"type\":\"COTISATION\",\"id\":\"uuid\",\"montant\":5000}, ...]
*/ */
@Column(name = "objets_cibles", columnDefinition = "TEXT") @Column(name = "objets_cibles", columnDefinition = "TEXT")
private String objetsCibles; private String objetsCibles;
@Column(name = "date_expiration") @Column(name = "date_expiration")
private LocalDateTime dateExpiration; private LocalDateTime dateExpiration;
@Column(name = "date_completion") @Column(name = "date_completion")
private LocalDateTime dateCompletion; private LocalDateTime dateCompletion;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean isActive() { public boolean isActive() {
return StatutIntentionPaiement.INITIEE.equals(statut) return StatutIntentionPaiement.INITIEE.equals(statut)
|| StatutIntentionPaiement.EN_COURS.equals(statut); || StatutIntentionPaiement.EN_COURS.equals(statut);
} }
public boolean isExpiree() { public boolean isExpiree() {
return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration); return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration);
} }
public boolean isCompletee() { public boolean isCompletee() {
return StatutIntentionPaiement.COMPLETEE.equals(statut); return StatutIntentionPaiement.COMPLETEE.equals(statut);
} }
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statut == null) statut = StatutIntentionPaiement.INITIEE; if (statut == null) statut = StatutIntentionPaiement.INITIEE;
if (codeDevise == null) codeDevise = "XOF"; if (codeDevise == null) codeDevise = "XOF";
if (dateExpiration == null) { if (dateExpiration == null) {
dateExpiration = LocalDateTime.now().plusMinutes(30); dateExpiration = LocalDateTime.now().plusMinutes(30);
} }
} }
} }

View File

@@ -1,100 +1,108 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable; import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité JournalComptable pour la gestion des journaux * Entité JournalComptable pour la gestion des journaux
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "journaux_comptables", name = "journaux_comptables",
indexes = { uniqueConstraints = {
@Index(name = "idx_journal_code", columnList = "code", unique = true), @UniqueConstraint(name = "uk_journaux_org_code", columnNames = {"organisation_id", "code"})
@Index(name = "idx_journal_type", columnList = "type_journal"), },
@Index(name = "idx_journal_periode", columnList = "date_debut, date_fin") indexes = {
}) @Index(name = "idx_journal_code", columnList = "code"),
@Data @Index(name = "idx_journal_type", columnList = "type_journal"),
@NoArgsConstructor @Index(name = "idx_journal_periode", columnList = "date_debut, date_fin")
@AllArgsConstructor })
@Builder @Data
@EqualsAndHashCode(callSuper = true) @NoArgsConstructor
public class JournalComptable extends BaseEntity { @AllArgsConstructor
@Builder
/** Code unique du journal */ @EqualsAndHashCode(callSuper = true)
@NotBlank public class JournalComptable extends BaseEntity {
@Column(name = "code", unique = true, nullable = false, length = 10)
private String code; /** Code du journal (unique par organisation). */
@NotBlank
/** Libellé du journal */ @Column(name = "code", nullable = false, length = 10)
@NotBlank private String code;
@Column(name = "libelle", nullable = false, length = 100)
private String libelle; /** Libellé du journal */
@NotBlank
/** Type de journal */ @Column(name = "libelle", nullable = false, length = 100)
@NotNull private String libelle;
@Enumerated(EnumType.STRING)
@Column(name = "type_journal", nullable = false, length = 30) /** Type de journal */
private TypeJournalComptable typeJournal; @NotNull
@Enumerated(EnumType.STRING)
/** Date de début de la période */ @Column(name = "type_journal", nullable = false, length = 30)
@Column(name = "date_debut") private TypeJournalComptable typeJournal;
private LocalDate dateDebut;
/** Date de début de la période */
/** Date de fin de la période */ @Column(name = "date_debut")
@Column(name = "date_fin") private LocalDate dateDebut;
private LocalDate dateFin;
/** Date de fin de la période */
/** Statut du journal (OUVERT, FERME, ARCHIVE) */ @Column(name = "date_fin")
@Builder.Default private LocalDate dateFin;
@Column(name = "statut", length = 20)
private String statut = "OUVERT"; /** Statut du journal (OUVERT, FERME, ARCHIVE) */
@Builder.Default
/** Description */ @Column(name = "statut", length = 20)
@Column(name = "description", length = 500) private String statut = "OUVERT";
private String description;
/** Description */
/** Écritures comptables associées */ @Column(name = "description", length = 500)
@JsonIgnore private String description;
@OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default /** Organisation propriétaire */
private List<EcritureComptable> ecritures = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
/** Méthode métier pour vérifier si le journal est ouvert */ private Organisation organisation;
public boolean isOuvert() {
return "OUVERT".equals(statut); /** Écritures comptables associées */
} @JsonIgnore
@OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
/** Méthode métier pour vérifier si une date est dans la période */ @Builder.Default
public boolean estDansPeriode(LocalDate date) { private List<EcritureComptable> ecritures = new ArrayList<>();
if (dateDebut == null || dateFin == null) {
return true; // Période illimitée /** Méthode métier pour vérifier si le journal est ouvert */
} public boolean isOuvert() {
return !date.isBefore(dateDebut) && !date.isAfter(dateFin); return "OUVERT".equals(statut);
} }
/** Callback JPA avant la persistance */ /** Méthode métier pour vérifier si une date est dans la période */
@PrePersist public boolean estDansPeriode(LocalDate date) {
protected void onCreate() { if (dateDebut == null || dateFin == null) {
super.onCreate(); return true; // Période illimitée
if (statut == null || statut.isEmpty()) { }
statut = "OUVERT"; 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";
}
}
}

View File

@@ -1,100 +1,100 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité LigneEcriture pour les lignes d'une écriture comptable * Entité LigneEcriture pour les lignes d'une écriture comptable
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "lignes_ecriture", name = "lignes_ecriture",
indexes = { indexes = {
@Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"), @Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"),
@Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id") @Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class LigneEcriture extends BaseEntity { public class LigneEcriture extends BaseEntity {
/** Numéro de ligne */ /** Numéro de ligne */
@NotNull @NotNull
@Min(value = 1, message = "Le numéro de ligne doit être positif") @Min(value = 1, message = "Le numéro de ligne doit être positif")
@Column(name = "numero_ligne", nullable = false) @Column(name = "numero_ligne", nullable = false)
private Integer numeroLigne; private Integer numeroLigne;
/** Montant débit */ /** Montant débit */
@DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul") @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_debit", precision = 14, scale = 2) @Column(name = "montant_debit", precision = 14, scale = 2)
private BigDecimal montantDebit; private BigDecimal montantDebit;
/** Montant crédit */ /** Montant crédit */
@DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul") @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_credit", precision = 14, scale = 2) @Column(name = "montant_credit", precision = 14, scale = 2)
private BigDecimal montantCredit; private BigDecimal montantCredit;
/** Libellé de la ligne */ /** Libellé de la ligne */
@Column(name = "libelle", length = 500) @Column(name = "libelle", length = 500)
private String libelle; private String libelle;
/** Référence */ /** Référence */
@Column(name = "reference", length = 100) @Column(name = "reference", length = 100)
private String reference; private String reference;
// Relations // Relations
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ecriture_id", nullable = false) @JoinColumn(name = "ecriture_id", nullable = false)
private EcritureComptable ecriture; private EcritureComptable ecriture;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_comptable_id", nullable = false) @JoinColumn(name = "compte_comptable_id", nullable = false)
private CompteComptable compteComptable; 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) */ /** 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() { public boolean isValide() {
boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0; boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0;
boolean aCredit = montantCredit != null && montantCredit.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 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) */ /** Méthode métier pour obtenir le montant (débit ou crédit) */
public BigDecimal getMontant() { public BigDecimal getMontant() {
if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) { if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) {
return montantDebit; return montantDebit;
} }
if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) { if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) {
return montantCredit; return montantCredit;
} }
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (montantDebit == null) { if (montantDebit == null) {
montantDebit = BigDecimal.ZERO; montantDebit = BigDecimal.ZERO;
} }
if (montantCredit == null) { if (montantCredit == null) {
montantCredit = BigDecimal.ZERO; montantCredit = BigDecimal.ZERO;
} }
} }
} }

View File

@@ -1,169 +1,173 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.*; import lombok.*;
/** /**
* Identité globale unique d'un utilisateur UnionFlow. * Identité globale unique d'un utilisateur UnionFlow.
* *
* <p> * <p>
* Un utilisateur possède un seul compte sur toute la plateforme. * Un utilisateur possède un seul compte sur toute la plateforme.
* Ses adhésions aux organisations sont gérées dans {@link MembreOrganisation}. * Ses adhésions aux organisations sont gérées dans {@link MembreOrganisation}.
* *
* <p> * <p>
* Table : {@code utilisateurs} * Table : {@code utilisateurs}
*/ */
@Entity @Entity
@Table(name = "utilisateurs", indexes = { @Table(name = "utilisateurs", indexes = {
@Index(name = "idx_utilisateur_email", columnList = "email", unique = true), @Index(name = "idx_utilisateur_email", columnList = "email", unique = true),
@Index(name = "idx_utilisateur_numero", columnList = "numero_membre", 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_keycloak", columnList = "keycloak_id", unique = true),
@Index(name = "idx_utilisateur_actif", columnList = "actif"), @Index(name = "idx_utilisateur_actif", columnList = "actif"),
@Index(name = "idx_utilisateur_statut", columnList = "statut_compte") @Index(name = "idx_utilisateur_statut", columnList = "statut_compte")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Membre extends BaseEntity { public class Membre extends BaseEntity {
/** Identifiant Keycloak (UUID du compte OIDC) */ /** Identifiant Keycloak (UUID du compte OIDC) */
@Column(name = "keycloak_id", unique = true) @Column(name = "keycloak_id", unique = true)
private UUID keycloakId; private UUID keycloakId;
/** Numéro de membre — unique globalement sur toute la plateforme */ /** Numéro de membre — unique globalement sur toute la plateforme */
@NotBlank @NotBlank
@Column(name = "numero_membre", unique = true, nullable = false, length = 20) @Column(name = "numero_membre", unique = true, nullable = false, length = 20)
private String numeroMembre; private String numeroMembre;
@NotBlank @NotBlank
@Column(name = "prenom", nullable = false, length = 100) @Column(name = "prenom", nullable = false, length = 100)
private String prenom; private String prenom;
@NotBlank @NotBlank
@Column(name = "nom", nullable = false, length = 100) @Column(name = "nom", nullable = false, length = 100)
private String nom; private String nom;
@Email @Email
@NotBlank @NotBlank
@Column(name = "email", unique = true, nullable = false, length = 255) @Column(name = "email", unique = true, nullable = false, length = 255)
private String email; private String email;
@Column(name = "telephone", length = 20) @Column(name = "telephone", length = 20)
private String telephone; 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)") /** 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 = "telephone_wave", length = 20) @Column(name = "fcm_token", length = 500)
private String telephoneWave; private String fcmToken;
@NotNull @Pattern(regexp = "^\\+[1-9][0-9]{6,14}$", message = "Le numéro Wave doit être au format international E.164 (ex: +22507XXXXXXXX)")
@Column(name = "date_naissance", nullable = false) @Column(name = "telephone_wave", length = 20)
private LocalDate dateNaissance; private String telephoneWave;
@Column(name = "profession", length = 100) @NotNull
private String profession; @Column(name = "date_naissance", nullable = false)
private LocalDate dateNaissance;
@Column(name = "photo_url", length = 500)
private String photoUrl; @Column(name = "profession", length = 100)
private String profession;
@Builder.Default
@Column(name = "statut_compte", nullable = false, length = 30) @Column(name = "photo_url", length = 500)
private String statutCompte = "EN_ATTENTE_VALIDATION"; private String photoUrl;
/** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */ @Builder.Default
@Builder.Default @Column(name = "statut_compte", nullable = false, length = 30)
@Column(name = "premiere_connexion", nullable = false) private String statutCompte = "EN_ATTENTE_VALIDATION";
private Boolean premiereConnexion = true;
/** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
/** @Builder.Default
* Statut matrimonial (domaine @Column(name = "premiere_connexion", nullable = false)
* {@code STATUT_MATRIMONIAL} dans private Boolean premiereConnexion = true;
* {@code types_reference}).
*/ /**
@Column(name = "statut_matrimonial", length = 50) * Statut matrimonial (domaine
private String statutMatrimonial; * {@code STATUT_MATRIMONIAL} dans
* {@code types_reference}).
/** Nationalité. */ */
@Column(name = "nationalite", length = 100) @Column(name = "statut_matrimonial", length = 50)
private String nationalite; private String statutMatrimonial;
/** /** Nationalité. */
* Type de pièce d'identité (domaine @Column(name = "nationalite", length = 100)
* {@code TYPE_IDENTITE} dans private String nationalite;
* {@code types_reference}).
*/ /**
@Column(name = "type_identite", length = 50) * Type de pièce d'identité (domaine
private String typeIdentite; * {@code TYPE_IDENTITE} dans
* {@code types_reference}).
/** Numéro de la pièce d'identité. */ */
@Column(name = "numero_identite", length = 100) @Column(name = "type_identite", length = 50)
private String numeroIdentite; private String typeIdentite;
/** Notes / biographie libre du membre. */ /** Numéro de la pièce d'identité. */
@Column(name = "notes", length = 1000) @Column(name = "numero_identite", length = 100)
private String notes; private String numeroIdentite;
/** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */ /** Notes / biographie libre du membre. */
@Column(name = "niveau_vigilance_kyc", length = 20) @Column(name = "notes", length = 1000)
private String niveauVigilanceKyc; private String notes;
/** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */ /** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
@Column(name = "statut_kyc", length = 20) @Column(name = "niveau_vigilance_kyc", length = 20)
private String statutKyc; private String niveauVigilanceKyc;
/** Date de dernière vérification d'identité. */ /** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */
@Column(name = "date_verification_identite") @Column(name = "statut_kyc", length = 20)
private LocalDate dateVerificationIdentite; private String statutKyc;
// ── Relations ──────────────────────────────────────────────────────────── /** Date de dernière vérification d'identité. */
@Column(name = "date_verification_identite")
/** Adhésions à des organisations */ private LocalDate dateVerificationIdentite;
@JsonIgnore
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) // ── Relations ────────────────────────────────────────────────────────────
@Builder.Default
private List<MembreOrganisation> membresOrganisations = new ArrayList<>(); /** Adhésions à des organisations */
@JsonIgnore
@JsonIgnore @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @Builder.Default
@Builder.Default private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
private List<Adresse> adresses = new ArrayList<>();
@JsonIgnore
@JsonIgnore @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @Builder.Default
@Builder.Default private List<Adresse> adresses = new ArrayList<>();
private List<CompteWave> comptesWave = new ArrayList<>();
@JsonIgnore
@JsonIgnore @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @Builder.Default
@Builder.Default private List<CompteWave> comptesWave = new ArrayList<>();
private List<Paiement> paiements = new ArrayList<>();
@JsonIgnore
// ── Méthodes métier ─────────────────────────────────────────────────────── @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
public String getNomComplet() { private List<Paiement> paiements = new ArrayList<>();
return prenom + " " + nom;
} // ── Méthodes métier ───────────────────────────────────────────────────────
public boolean isMajeur() { public String getNomComplet() {
return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18)); return prenom + " " + nom;
} }
public int getAge() { public boolean isMajeur() {
return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0; return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18));
} }
@PrePersist public int getAge() {
protected void onCreate() { return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0;
super.onCreate(); }
if (statutCompte == null) {
statutCompte = "EN_ATTENTE_VALIDATION"; @PrePersist
} protected void onCreate() {
} super.onCreate();
} if (statutCompte == null) {
statutCompte = "EN_ATTENTE_VALIDATION";
}
}
}

View File

@@ -1,141 +1,141 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre; import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.*; import lombok.*;
/** /**
* Lien entre un utilisateur et une organisation. * Lien entre un utilisateur et une organisation.
* *
* <p>Un utilisateur peut adhérer à plusieurs organisations simultanément. * <p>Un utilisateur peut adhérer à plusieurs organisations simultanément.
* Chaque adhésion a son propre statut, date et unité d'affectation. * Chaque adhésion a son propre statut, date et unité d'affectation.
* *
* <p>Table : {@code membres_organisations} * <p>Table : {@code membres_organisations}
*/ */
@Entity @Entity
@Table( @Table(
name = "membres_organisations", name = "membres_organisations",
indexes = { indexes = {
@Index(name = "idx_mo_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_mo_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_mo_organisation", columnList = "organisation_id"), @Index(name = "idx_mo_organisation", columnList = "organisation_id"),
@Index(name = "idx_mo_statut", columnList = "statut_membre"), @Index(name = "idx_mo_statut", columnList = "statut_membre"),
@Index(name = "idx_mo_unite", columnList = "unite_id") @Index(name = "idx_mo_unite", columnList = "unite_id")
}, },
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_mo_utilisateur_organisation", name = "uk_mo_utilisateur_organisation",
columnNames = {"utilisateur_id", "organisation_id"}) columnNames = {"utilisateur_id", "organisation_id"})
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class MembreOrganisation extends BaseEntity { public class MembreOrganisation extends BaseEntity {
/** L'utilisateur (identité globale) */ /** L'utilisateur (identité globale) */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "utilisateur_id", nullable = false) @JoinColumn(name = "utilisateur_id", nullable = false)
private Membre membre; private Membre membre;
/** L'organisation racine à laquelle appartient ce membre */ /** L'organisation racine à laquelle appartient ce membre */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
/** /**
* Unité d'affectation (agence/bureau). * Unité d'affectation (agence/bureau).
* NULL = affecté au siège. * NULL = affecté au siège.
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "unite_id") @JoinColumn(name = "unite_id")
private Organisation unite; private Organisation unite;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut_membre", nullable = false, length = 30) @Column(name = "statut_membre", nullable = false, length = 30)
private StatutMembre statutMembre = StatutMembre.EN_ATTENTE_VALIDATION; private StatutMembre statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
@Column(name = "date_adhesion") @Column(name = "date_adhesion")
private LocalDate dateAdhesion; private LocalDate dateAdhesion;
@Column(name = "date_changement_statut") @Column(name = "date_changement_statut")
private LocalDate dateChangementStatut; private LocalDate dateChangementStatut;
@Column(name = "motif_statut", length = 500) @Column(name = "motif_statut", length = 500)
private String motifStatut; private String motifStatut;
/** Utilisateur qui a approuvé ou traité ce changement de statut */ /** Utilisateur qui a approuvé ou traité ce changement de statut */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "approuve_par_id") @JoinColumn(name = "approuve_par_id")
private Membre approuvePar; private Membre approuvePar;
// ── Champs d'invitation (StatutMembre.INVITE) ────────────────────────────── // ── Champs d'invitation (StatutMembre.INVITE) ──────────────────────────────
/** Date à laquelle l'invitation a été envoyée. */ /** Date à laquelle l'invitation a été envoyée. */
@Column(name = "date_invitation") @Column(name = "date_invitation")
private LocalDateTime dateInvitation; private LocalDateTime dateInvitation;
/** Date d'expiration de l'invitation (null = pas d'expiration). */ /** Date d'expiration de l'invitation (null = pas d'expiration). */
@Column(name = "date_expiration_invitation") @Column(name = "date_expiration_invitation")
private LocalDateTime dateExpirationInvitation; private LocalDateTime dateExpirationInvitation;
/** Token opaque utilisé dans le lien d'invitation envoyé par email. */ /** Token opaque utilisé dans le lien d'invitation envoyé par email. */
@Column(name = "token_invitation", length = 64) @Column(name = "token_invitation", length = 64)
private String tokenInvitation; private String tokenInvitation;
/** ID de l'administrateur qui a envoyé l'invitation. */ /** ID de l'administrateur qui a envoyé l'invitation. */
@Column(name = "invite_par") @Column(name = "invite_par")
private UUID invitePar; private UUID invitePar;
/** Motif d'archivage (pour StatutMembre.ARCHIVE). */ /** Motif d'archivage (pour StatutMembre.ARCHIVE). */
@Column(name = "motif_archivage", length = 500) @Column(name = "motif_archivage", length = 500)
private String motifArchivage; private String motifArchivage;
// ── Rôle fonctionnel dans l'organisation ───────────────────────────────── // ── Rôle fonctionnel dans l'organisation ─────────────────────────────────
/** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */ /** Rôle de ce membre dans l'organisation (ex: PRESIDENT, TRESORIER...). */
@Column(name = "role_org", length = 50) @Column(name = "role_org", length = 50)
private String roleOrg; private String roleOrg;
// ── Relations ───────────────────────────────────────────────────────────── // ── Relations ─────────────────────────────────────────────────────────────
/** Rôles de ce membre dans cette organisation */ /** Rôles de ce membre dans cette organisation */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<MembreRole> roles = new ArrayList<>(); private List<MembreRole> roles = new ArrayList<>();
/** Ayants droit (mutuelles de santé uniquement) */ /** Ayants droit (mutuelles de santé uniquement) */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "membreOrganisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<AyantDroit> ayantsDroit = new ArrayList<>(); private List<AyantDroit> ayantsDroit = new ArrayList<>();
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean isActif() { public boolean isActif() {
return StatutMembre.ACTIF.equals(statutMembre) && Boolean.TRUE.equals(getActif()); return StatutMembre.ACTIF.equals(statutMembre) && Boolean.TRUE.equals(getActif());
} }
public boolean peutDemanderAide() { public boolean peutDemanderAide() {
return StatutMembre.ACTIF.equals(statutMembre); return StatutMembre.ACTIF.equals(statutMembre);
} }
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statutMembre == null) { if (statutMembre == null) {
statutMembre = StatutMembre.EN_ATTENTE_VALIDATION; statutMembre = StatutMembre.EN_ATTENTE_VALIDATION;
} }
} }
} }

View File

@@ -1,94 +1,94 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Table de liaison entre Membre et Role * Table de liaison entre Membre et Role
* Permet à un membre d'avoir plusieurs rôles avec dates de début/fin * Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "membres_roles", name = "membres_roles",
indexes = { indexes = {
@Index(name = "idx_mr_membre_org", columnList = "membre_organisation_id"), @Index(name = "idx_mr_membre_org", columnList = "membre_organisation_id"),
@Index(name = "idx_mr_organisation", columnList = "organisation_id"), @Index(name = "idx_mr_organisation", columnList = "organisation_id"),
@Index(name = "idx_mr_role", columnList = "role_id"), @Index(name = "idx_mr_role", columnList = "role_id"),
@Index(name = "idx_mr_actif", columnList = "actif") @Index(name = "idx_mr_actif", columnList = "actif")
}, },
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_mr_membre_org_role", name = "uk_mr_membre_org_role",
columnNames = {"membre_organisation_id", "role_id"}) columnNames = {"membre_organisation_id", "role_id"})
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class MembreRole extends BaseEntity { public class MembreRole extends BaseEntity {
/** Lien membership (utilisateur dans le contexte de son organisation) */ /** Lien membership (utilisateur dans le contexte de son organisation) */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_organisation_id", nullable = false) @JoinColumn(name = "membre_organisation_id", nullable = false)
private MembreOrganisation membreOrganisation; private MembreOrganisation membreOrganisation;
/** Organisation dans laquelle ce rôle est actif (dénormalisé pour les requêtes) */ /** Organisation dans laquelle ce rôle est actif (dénormalisé pour les requêtes) */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
/** Rôle */ /** Rôle */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false) @JoinColumn(name = "role_id", nullable = false)
private Role role; private Role role;
/** Date de début d'attribution */ /** Date de début d'attribution */
@Column(name = "date_debut") @Column(name = "date_debut")
private LocalDate dateDebut; private LocalDate dateDebut;
/** Date de fin d'attribution (null = permanent) */ /** Date de fin d'attribution (null = permanent) */
@Column(name = "date_fin") @Column(name = "date_fin")
private LocalDate dateFin; private LocalDate dateFin;
/** Commentaire sur l'attribution */ /** Commentaire sur l'attribution */
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
/** Méthode métier pour vérifier si l'attribution est active */ /** Méthode métier pour vérifier si l'attribution est active */
public boolean isActif() { public boolean isActif() {
if (!Boolean.TRUE.equals(getActif())) { if (!Boolean.TRUE.equals(getActif())) {
return false; return false;
} }
LocalDate aujourdhui = LocalDate.now(); LocalDate aujourdhui = LocalDate.now();
if (dateDebut != null && aujourdhui.isBefore(dateDebut)) { if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
return false; return false;
} }
if (dateFin != null && aujourdhui.isAfter(dateFin)) { if (dateFin != null && aujourdhui.isAfter(dateFin)) {
return false; return false;
} }
return true; return true;
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (dateDebut == null) { if (dateDebut == null) {
dateDebut = LocalDate.now(); dateDebut = LocalDate.now();
} }
} }
} }

View File

@@ -1,38 +1,38 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.util.UUID; import java.util.UUID;
/** /**
* Lien « suivi » entre deux membres : le membre connecté (follower) suit un autre membre (suivi). * Lien « suivi » entre deux membres : le membre connecté (follower) suit un autre membre (suivi).
* Utilisé pour la fonctionnalité Réseau / Suivre dans lapp mobile. * Utilisé pour la fonctionnalité Réseau / Suivre dans lapp mobile.
*/ */
@Entity @Entity
@Table( @Table(
name = "membre_suivi", name = "membre_suivi",
uniqueConstraints = @UniqueConstraint(columnNames = { "follower_utilisateur_id", "suivi_utilisateur_id" }), uniqueConstraints = @UniqueConstraint(columnNames = { "follower_utilisateur_id", "suivi_utilisateur_id" }),
indexes = { indexes = {
@Index(name = "idx_membre_suivi_follower", columnList = "follower_utilisateur_id"), @Index(name = "idx_membre_suivi_follower", columnList = "follower_utilisateur_id"),
@Index(name = "idx_membre_suivi_suivi", columnList = "suivi_utilisateur_id") @Index(name = "idx_membre_suivi_suivi", columnList = "suivi_utilisateur_id")
} }
) )
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class MembreSuivi extends BaseEntity { public class MembreSuivi extends BaseEntity {
/** Utilisateur qui suit (membre connecté). */ /** Utilisateur qui suit (membre connecté). */
@NotNull @NotNull
@Column(name = "follower_utilisateur_id", nullable = false) @Column(name = "follower_utilisateur_id", nullable = false)
private UUID followerUtilisateurId; private UUID followerUtilisateurId;
/** Utilisateur suivi (membre cible). */ /** Utilisateur suivi (membre cible). */
@NotNull @NotNull
@Column(name = "suivi_utilisateur_id", nullable = false) @Column(name = "suivi_utilisateur_id", nullable = false)
private UUID suiviUtilisateurId; private UUID suiviUtilisateurId;
} }

View File

@@ -1,56 +1,56 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.*; import lombok.*;
/** /**
* Catalogue des modules métier activables par type d'organisation. * Catalogue des modules métier activables par type d'organisation.
* *
* <p>Géré uniquement par le Super Admin UnionFlow. * <p>Géré uniquement par le Super Admin UnionFlow.
* Les organisations ne peuvent pas créer de nouveaux modules. * Les organisations ne peuvent pas créer de nouveaux modules.
* *
* <p>Table : {@code modules_disponibles} * <p>Table : {@code modules_disponibles}
*/ */
@Entity @Entity
@Table( @Table(
name = "modules_disponibles", name = "modules_disponibles",
indexes = { indexes = {
@Index(name = "idx_module_code", columnList = "code", unique = true), @Index(name = "idx_module_code", columnList = "code", unique = true),
@Index(name = "idx_module_actif", columnList = "actif") @Index(name = "idx_module_actif", columnList = "actif")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ModuleDisponible extends BaseEntity { public class ModuleDisponible extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "code", unique = true, nullable = false, length = 50) @Column(name = "code", unique = true, nullable = false, length = 50)
private String code; private String code;
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 150) @Column(name = "libelle", nullable = false, length = 150)
private String libelle; private String libelle;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String description; private String description;
/** /**
* JSON array des types d'organisations compatibles. * JSON array des types d'organisations compatibles.
* Exemple : ["MUTUELLE_SANTE","ONG"] ou ["ALL"] pour tous. * Exemple : ["MUTUELLE_SANTE","ONG"] ou ["ALL"] pour tous.
*/ */
@Column(name = "types_org_compatibles", columnDefinition = "TEXT") @Column(name = "types_org_compatibles", columnDefinition = "TEXT")
private String typesOrgCompatibles; private String typesOrgCompatibles;
@Builder.Default @Builder.Default
@Column(name = "ordre_affichage", nullable = false) @Column(name = "ordre_affichage", nullable = false)
private Integer ordreAffichage = 0; private Integer ordreAffichage = 0;
public boolean estCompatibleAvec(String typeOrganisation) { public boolean estCompatibleAvec(String typeOrganisation) {
if (typesOrgCompatibles == null) return false; if (typesOrgCompatibles == null) return false;
return typesOrgCompatibles.contains("ALL") return typesOrgCompatibles.contains("ALL")
|| typesOrgCompatibles.contains(typeOrganisation); || typesOrgCompatibles.contains(typeOrganisation);
} }
} }

View File

@@ -1,64 +1,64 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.*; import lombok.*;
/** /**
* Module activé pour une organisation donnée. * Module activé pour une organisation donnée.
* *
* <p> * <p>
* Les modules sont activés automatiquement selon le type d'organisation * 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. * lors de la première souscription, et peuvent être désactivés par le manager.
* *
* <p> * <p>
* Table : {@code modules_organisation_actifs} * Table : {@code modules_organisation_actifs}
*/ */
@Entity @Entity
@Table(name = "modules_organisation_actifs", indexes = { @Table(name = "modules_organisation_actifs", indexes = {
@Index(name = "idx_moa_organisation", columnList = "organisation_id"), @Index(name = "idx_moa_organisation", columnList = "organisation_id"),
@Index(name = "idx_moa_module", columnList = "module_code") @Index(name = "idx_moa_module", columnList = "module_code")
}, uniqueConstraints = { }, uniqueConstraints = {
@UniqueConstraint(name = "uk_moa_org_module", columnNames = { "organisation_id", "module_code" }) @UniqueConstraint(name = "uk_moa_org_module", columnNames = { "organisation_id", "module_code" })
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ModuleOrganisationActif extends BaseEntity { public class ModuleOrganisationActif extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Column(name = "module_code", nullable = false, length = 50) @Column(name = "module_code", nullable = false, length = 50)
private String moduleCode; private String moduleCode;
/** /**
* Référence vers le catalogue des modules. * Référence vers le catalogue des modules.
* Assure l'intégrité référentielle avec * Assure l'intégrité référentielle avec
* {@code modules_disponibles}. * {@code modules_disponibles}.
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "module_disponible_id") @JoinColumn(name = "module_disponible_id")
private ModuleDisponible moduleDisponible; private ModuleDisponible moduleDisponible;
@Builder.Default @Builder.Default
@Column(name = "date_activation", nullable = false) @Column(name = "date_activation", nullable = false)
private LocalDateTime dateActivation = LocalDateTime.now(); private LocalDateTime dateActivation = LocalDateTime.now();
/** /**
* Configuration JSON spécifique au module pour cette organisation. * Configuration JSON spécifique au module pour cette organisation.
* Exemple pour CREDIT_EPARGNE : {"taux_interet_max": 18, "duree_max_mois": 24} * Exemple pour CREDIT_EPARGNE : {"taux_interet_max": 18, "duree_max_mois": 24}
*/ */
@Column(name = "parametres", columnDefinition = "TEXT") @Column(name = "parametres", columnDefinition = "TEXT")
private String parametres; private String parametres;
public boolean isActif() { public boolean isActif() {
return Boolean.TRUE.equals(getActif()); return Boolean.TRUE.equals(getActif());
} }
} }

View File

@@ -1,123 +1,123 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Notification pour la gestion des notifications * Entité Notification pour la gestion des notifications
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table(name = "notifications", indexes = { @Table(name = "notifications", indexes = {
@Index(name = "idx_notification_type", columnList = "type_notification"), @Index(name = "idx_notification_type", columnList = "type_notification"),
@Index(name = "idx_notification_statut", columnList = "statut"), @Index(name = "idx_notification_statut", columnList = "statut"),
@Index(name = "idx_notification_priorite", columnList = "priorite"), @Index(name = "idx_notification_priorite", columnList = "priorite"),
@Index(name = "idx_notification_membre", columnList = "membre_id"), @Index(name = "idx_notification_membre", columnList = "membre_id"),
@Index(name = "idx_notification_organisation", columnList = "organisation_id"), @Index(name = "idx_notification_organisation", columnList = "organisation_id"),
@Index(name = "idx_notification_date_envoi", columnList = "date_envoi") @Index(name = "idx_notification_date_envoi", columnList = "date_envoi")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Notification extends BaseEntity { public class Notification extends BaseEntity {
/** Type de notification */ /** Type de notification */
@NotNull @NotNull
@Column(name = "type_notification", nullable = false, length = 30) @Column(name = "type_notification", nullable = false, length = 30)
private String typeNotification; private String typeNotification;
/** Priorité */ /** Priorité */
@Builder.Default @Builder.Default
@Column(name = "priorite", length = 20) @Column(name = "priorite", length = 20)
private String priorite = "NORMALE"; private String priorite = "NORMALE";
/** Statut */ /** Statut */
@Builder.Default @Builder.Default
@Column(name = "statut", length = 30) @Column(name = "statut", length = 30)
private String statut = "EN_ATTENTE"; private String statut = "EN_ATTENTE";
/** Sujet */ /** Sujet */
@Column(name = "sujet", length = 500) @Column(name = "sujet", length = 500)
private String sujet; private String sujet;
/** Corps du message */ /** Corps du message */
@Column(name = "corps", columnDefinition = "TEXT") @Column(name = "corps", columnDefinition = "TEXT")
private String corps; private String corps;
/** Date d'envoi prévue */ /** Date d'envoi prévue */
@Column(name = "date_envoi_prevue") @Column(name = "date_envoi_prevue")
private LocalDateTime dateEnvoiPrevue; private LocalDateTime dateEnvoiPrevue;
/** Date d'envoi réelle */ /** Date d'envoi réelle */
@Column(name = "date_envoi") @Column(name = "date_envoi")
private LocalDateTime dateEnvoi; private LocalDateTime dateEnvoi;
/** Date de lecture */ /** Date de lecture */
@Column(name = "date_lecture") @Column(name = "date_lecture")
private LocalDateTime dateLecture; private LocalDateTime dateLecture;
/** Nombre de tentatives d'envoi */ /** Nombre de tentatives d'envoi */
@Builder.Default @Builder.Default
@Column(name = "nombre_tentatives", nullable = false) @Column(name = "nombre_tentatives", nullable = false)
private Integer nombreTentatives = 0; private Integer nombreTentatives = 0;
/** Message d'erreur (si échec) */ /** Message d'erreur (si échec) */
@Column(name = "message_erreur", length = 1000) @Column(name = "message_erreur", length = 1000)
private String messageErreur; private String messageErreur;
/** Données additionnelles (JSON) */ /** Données additionnelles (JSON) */
@Column(name = "donnees_additionnelles", columnDefinition = "TEXT") @Column(name = "donnees_additionnelles", columnDefinition = "TEXT")
private String donneesAdditionnelles; private String donneesAdditionnelles;
// Relations // Relations
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id") @JoinColumn(name = "membre_id")
private Membre membre; private Membre membre;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "template_id") @JoinColumn(name = "template_id")
private TemplateNotification template; private TemplateNotification template;
/** Méthode métier pour vérifier si la notification est envoyée */ /** Méthode métier pour vérifier si la notification est envoyée */
public boolean isEnvoyee() { public boolean isEnvoyee() {
return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.name().equals(statut); 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 */ /** Méthode métier pour vérifier si la notification est lue */
public boolean isLue() { public boolean isLue() {
return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.name().equals(statut); return statut != null && dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.name().equals(statut);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (priorite == null) { if (priorite == null) {
priorite = "NORMALE"; priorite = "NORMALE";
} }
if (statut == null) { if (statut == null) {
statut = "EN_ATTENTE"; statut = "EN_ATTENTE";
} }
if (nombreTentatives == null) { if (nombreTentatives == null) {
nombreTentatives = 0; nombreTentatives = 0;
} }
if (dateEnvoiPrevue == null) { if (dateEnvoiPrevue == null) {
dateEnvoiPrevue = LocalDateTime.now(); dateEnvoiPrevue = LocalDateTime.now();
} }
} }
} }

View File

@@ -1,326 +1,331 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Period; import java.time.Period;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import java.util.UUID;
import lombok.Builder; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Organisation avec UUID Représente une organisation (Lions Club, /**
* Association, * Entité Organisation avec UUID Représente une organisation (Lions Club,
* Coopérative, etc.) * Association,
* * Coopérative, etc.)
* @author UnionFlow Team *
* @version 2.0 * @author UnionFlow Team
* @since 2025-01-16 * @version 2.0
*/ * @since 2025-01-16
@Entity */
@Table(name = "organisations", indexes = { @Entity
@Index(name = "idx_organisation_nom", columnList = "nom"), @Table(name = "organisations", indexes = {
@Index(name = "idx_organisation_email", columnList = "email", unique = true), @Index(name = "idx_organisation_nom", columnList = "nom"),
@Index(name = "idx_organisation_statut", columnList = "statut"), @Index(name = "idx_organisation_email", columnList = "email", unique = true),
@Index(name = "idx_organisation_type", columnList = "type_organisation"), @Index(name = "idx_organisation_statut", columnList = "statut"),
@Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"), @Index(name = "idx_organisation_type", columnList = "type_organisation"),
@Index(name = "idx_organisation_numero_enregistrement", columnList = "numero_enregistrement", unique = true) @Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"),
}) @Index(name = "idx_organisation_numero_enregistrement", columnList = "numero_enregistrement", unique = true)
@Data })
@NoArgsConstructor @Data
@AllArgsConstructor @NoArgsConstructor
@Builder @AllArgsConstructor
@EqualsAndHashCode(callSuper = true) @Builder
public class Organisation extends BaseEntity { @EqualsAndHashCode(callSuper = true)
public class Organisation extends BaseEntity {
@NotBlank
@Column(name = "nom", nullable = false, length = 200) @NotBlank
private String nom; @Column(name = "nom", nullable = false, length = 200)
private String nom;
@Column(name = "nom_court", length = 50)
private String nomCourt; @Column(name = "nom_court", length = 50)
private String nomCourt;
@NotBlank
@Column(name = "type_organisation", nullable = false, length = 50) @NotBlank
private String typeOrganisation; @Column(name = "type_organisation", nullable = false, length = 50)
private String typeOrganisation;
@NotBlank
@Column(name = "statut", nullable = false, length = 50) @NotBlank
private String statut; @Column(name = "statut", nullable = false, length = 50)
private String statut;
@Column(name = "description", length = 2000)
private String description; @Column(name = "description", length = 2000)
private String description;
@Column(name = "date_fondation")
private LocalDate dateFondation; @Column(name = "date_fondation")
private LocalDate dateFondation;
@Column(name = "numero_enregistrement", unique = true, length = 100)
private String numeroEnregistrement; @Column(name = "numero_enregistrement", unique = true, length = 100)
private String numeroEnregistrement;
// Informations de contact
@Email // Informations de contact
@NotBlank @Email
@Column(name = "email", unique = true, nullable = false, length = 255) @NotBlank
private String email; @Column(name = "email", unique = true, nullable = false, length = 255)
private String email;
@Column(name = "telephone", length = 20)
private String telephone; @Column(name = "telephone", length = 20)
private String telephone;
@Column(name = "telephone_secondaire", length = 20)
private String telephoneSecondaire; @Column(name = "telephone_secondaire", length = 20)
private String telephoneSecondaire;
@Email
@Column(name = "email_secondaire", length = 255) @Email
private String emailSecondaire; @Column(name = "email_secondaire", length = 255)
private String emailSecondaire;
// Adresse principale (champs dénormalisés pour performance)
@Column(name = "adresse", length = 500) // Adresse principale (champs dénormalisés pour performance)
private String adresse; @Column(name = "adresse", length = 500)
private String adresse;
@Column(name = "ville", length = 100)
private String ville; @Column(name = "ville", length = 100)
private String ville;
@Column(name = "region", length = 100)
private String region; @Column(name = "region", length = 100)
private String region;
@Column(name = "pays", length = 100)
private String pays; @Column(name = "pays", length = 100)
private String pays;
@Column(name = "code_postal", length = 20)
private String codePostal; @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") // Coordonnées géographiques
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90") @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6) @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Column(name = "latitude", precision = 9, scale = 6) @Digits(integer = 3, fraction = 6)
private BigDecimal latitude; @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") @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6) @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Column(name = "longitude", precision = 9, scale = 6) @Digits(integer = 3, fraction = 6)
private BigDecimal longitude; @Column(name = "longitude", precision = 9, scale = 6)
private BigDecimal longitude;
// Web et réseaux sociaux
@Column(name = "site_web", length = 500) // Web et réseaux sociaux
private String siteWeb; @Column(name = "site_web", length = 500)
private String siteWeb;
@Column(name = "logo", length = 500)
private String logo; @Column(name = "logo", length = 500)
private String logo;
@Column(name = "reseaux_sociaux", length = 1000)
private String reseauxSociaux; @Column(name = "reseaux_sociaux", length = 1000)
private String reseauxSociaux;
// ── Hiérarchie ──────────────────────────────────────────────────────────────
// ── Hiérarchie ──────────────────────────────────────────────────────────────
/** Organisation parente — FK propre (null = organisation racine) */
@ManyToOne(fetch = FetchType.LAZY) /** Organisation parente — FK propre (null = organisation racine) */
@JoinColumn(name = "organisation_parente_id") @ManyToOne(fetch = FetchType.LAZY)
private Organisation organisationParente; @JoinColumn(name = "organisation_parente_id")
private Organisation organisationParente;
@Builder.Default
@Column(name = "niveau_hierarchique", nullable = false) @Builder.Default
private Integer niveauHierarchique = 0; @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. * 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) @Builder.Default
private Boolean estOrganisationRacine = true; @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. * 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; @Column(name = "chemin_hierarchique", length = 2000)
private String cheminHierarchique;
// Statistiques
@Builder.Default // Statistiques
@Column(name = "nombre_membres", nullable = false) @Builder.Default
private Integer nombreMembres = 0; @Column(name = "nombre_membres", nullable = false)
private Integer nombreMembres = 0;
@Builder.Default
@Column(name = "nombre_administrateurs", nullable = false) @Builder.Default
private Integer nombreAdministrateurs = 0; @Column(name = "nombre_administrateurs", nullable = false)
private Integer nombreAdministrateurs = 0;
// Finances
@DecimalMin(value = "0.0", message = "Le budget annuel doit être positif") // Finances
@Digits(integer = 12, fraction = 2) @DecimalMin(value = "0.0", message = "Le budget annuel doit être positif")
@Column(name = "budget_annuel", precision = 14, scale = 2) @Digits(integer = 12, fraction = 2)
private BigDecimal budgetAnnuel; @Column(name = "budget_annuel", precision = 14, scale = 2)
private BigDecimal budgetAnnuel;
@Builder.Default
@Column(name = "devise", length = 3) @Builder.Default
private String devise = "XOF"; @Column(name = "devise", length = 3)
private String devise = "XOF";
@Builder.Default
@Column(name = "cotisation_obligatoire", nullable = false) @Builder.Default
private Boolean cotisationObligatoire = false; @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) @DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
@Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2) @Digits(integer = 10, fraction = 2)
private BigDecimal montantCotisationAnnuelle; @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
private BigDecimal montantCotisationAnnuelle;
// Informations complémentaires
@Column(name = "objectifs", length = 2000) // Informations complémentaires
private String objectifs; @Column(name = "objectifs", length = 2000)
private String objectifs;
@Column(name = "activites_principales", length = 2000)
private String activitesPrincipales; @Column(name = "activites_principales", length = 2000)
private String activitesPrincipales;
@Column(name = "certifications", length = 500)
private String certifications; @Column(name = "certifications", length = 500)
private String certifications;
@Column(name = "partenaires", length = 1000)
private String partenaires; @Column(name = "partenaires", length = 1000)
private String partenaires;
@Column(name = "notes", length = 1000)
private String notes; @Column(name = "notes", length = 1000)
private String notes;
// Paramètres
@Builder.Default // Paramètres
@Column(name = "organisation_publique", nullable = false) @Builder.Default
private Boolean organisationPublique = true; @Column(name = "organisation_publique", nullable = false)
private Boolean organisationPublique = true;
@Builder.Default
@Column(name = "accepte_nouveaux_membres", nullable = false) @Builder.Default
private Boolean accepteNouveauxMembres = true; @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) /** Catégorie du type d'organisation (ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX, PROFESSIONNEL, RESEAU_FEDERATION) */
private String categorieType; @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) /** ID de l'Organization Keycloak 26 correspondante — null si pas encore migrée. */
private String modulesActifs; @Column(name = "keycloak_org_id")
private UUID keycloakOrgId;
// Relations
/** Modules activés pour cette organisation (liste CSV, ex: "MEMBRES,COTISATIONS,TONTINE") */
/** Adhésions des membres à cette organisation */ @Column(name = "modules_actifs", length = 1000)
@JsonIgnore private String modulesActifs;
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default // Relations
private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
/** Adhésions des membres à cette organisation */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<Adresse> adresses = new ArrayList<>(); private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<CompteWave> comptesWave = new ArrayList<>(); private List<Adresse> adresses = new ArrayList<>();
/** Méthode métier pour obtenir le nom complet avec sigle */ @JsonIgnore
public String getNomComplet() { @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
if (nomCourt != null && !nomCourt.isEmpty()) { @Builder.Default
return nom + " (" + nomCourt + ")"; private List<CompteWave> comptesWave = new ArrayList<>();
}
return nom; /** Méthode métier pour obtenir le nom complet avec sigle */
} public String getNomComplet() {
if (nomCourt != null && !nomCourt.isEmpty()) {
/** Méthode métier pour calculer l'ancienneté en années */ return nom + " (" + nomCourt + ")";
public int getAncienneteAnnees() { }
if (dateFondation == null) { return nom;
return 0; }
}
return Period.between(dateFondation, LocalDate.now()).getYears(); /** Méthode métier pour calculer l'ancienneté en années */
} public int getAncienneteAnnees() {
if (dateFondation == null) {
/** return 0;
* Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans) }
*/ return Period.between(dateFondation, LocalDate.now()).getYears();
public boolean isRecente() { }
return getAncienneteAnnees() < 2;
} /**
* Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans)
/** Méthode métier pour vérifier si l'organisation est active */ */
public boolean isActive() { public boolean isRecente() {
return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif()); return getAncienneteAnnees() < 2;
} }
/** Méthode métier pour ajouter un membre */ /** Méthode métier pour vérifier si l'organisation est active */
public void ajouterMembre() { public boolean isActive() {
if (nombreMembres == null) { return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
nombreMembres = 0; }
}
nombreMembres++; /** Méthode métier pour ajouter un membre */
} public void ajouterMembre() {
if (nombreMembres == null) {
/** Méthode métier pour retirer un membre */ nombreMembres = 0;
public void retirerMembre() { }
if (nombreMembres != null && nombreMembres > 0) { nombreMembres++;
nombreMembres--; }
}
} /** Méthode métier pour retirer un membre */
public void retirerMembre() {
/** Méthode métier pour activer l'organisation */ if (nombreMembres != null && nombreMembres > 0) {
public void activer(String utilisateur) { nombreMembres--;
this.statut = "ACTIVE"; }
this.setActif(true); }
marquerCommeModifie(utilisateur);
} /** Méthode métier pour activer l'organisation */
public void activer(String utilisateur) {
/** Méthode métier pour suspendre l'organisation */ this.statut = "ACTIVE";
public void suspendre(String utilisateur) { this.setActif(true);
this.statut = "SUSPENDUE"; marquerCommeModifie(utilisateur);
this.accepteNouveauxMembres = false; }
marquerCommeModifie(utilisateur);
} /** Méthode métier pour suspendre l'organisation */
public void suspendre(String utilisateur) {
/** Méthode métier pour dissoudre l'organisation */ this.statut = "SUSPENDUE";
public void dissoudre(String utilisateur) { this.accepteNouveauxMembres = false;
this.statut = "DISSOUTE"; marquerCommeModifie(utilisateur);
this.setActif(false); }
this.accepteNouveauxMembres = false;
marquerCommeModifie(utilisateur); /** Méthode métier pour dissoudre l'organisation */
} public void dissoudre(String utilisateur) {
this.statut = "DISSOUTE";
/** Callback JPA avant la persistance */ this.setActif(false);
@PrePersist this.accepteNouveauxMembres = false;
protected void onCreate() { marquerCommeModifie(utilisateur);
super.onCreate(); // Appelle le onCreate de BaseEntity }
if (statut == null) {
statut = "ACTIVE"; /** Callback JPA avant la persistance */
} @PrePersist
if (typeOrganisation == null) { protected void onCreate() {
typeOrganisation = "ASSOCIATION"; super.onCreate(); // Appelle le onCreate de BaseEntity
} if (statut == null) {
if (devise == null) { statut = "ACTIVE";
devise = "XOF"; }
} if (typeOrganisation == null) {
if (niveauHierarchique == null) { typeOrganisation = "ASSOCIATION";
niveauHierarchique = 0; }
} if (devise == null) {
if (estOrganisationRacine == null) { devise = "XOF";
estOrganisationRacine = (organisationParente == null); }
} if (niveauHierarchique == null) {
if (nombreMembres == null) { niveauHierarchique = 0;
nombreMembres = 0; }
} if (estOrganisationRacine == null) {
if (nombreAdministrateurs == null) { estOrganisationRacine = (organisationParente == null);
nombreAdministrateurs = 0; }
} if (nombreMembres == null) {
if (organisationPublique == null) { nombreMembres = 0;
organisationPublique = true; }
} if (nombreAdministrateurs == null) {
if (accepteNouveauxMembres == null) { nombreAdministrateurs = 0;
accepteNouveauxMembres = true; }
} if (organisationPublique == null) {
if (cotisationObligatoire == null) { organisationPublique = true;
cotisationObligatoire = false; }
} if (accepteNouveauxMembres == null) {
} accepteNouveauxMembres = true;
} }
if (cotisationObligatoire == null) {
cotisationObligatoire = false;
}
}
}

View File

@@ -1,94 +1,94 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.*; import lombok.*;
/** /**
* Paramètres de cotisation configurés par le manager de chaque organisation. * Paramètres de cotisation configurés par le manager de chaque organisation.
* *
* <p> * <p>
* Le manager peut définir : * Le manager peut définir :
* <ul> * <ul>
* <li>Le montant mensuel et annuel fixé pour tous les membres</li> * <li>Le montant mensuel et annuel fixé pour tous les membres</li>
* <li>La date de départ du calcul des impayés (configurable)</li> * <li>La date de départ du calcul des impayés (configurable)</li>
* <li>Le délai en jours avant passage automatique en statut INACTIF</li> * <li>Le délai en jours avant passage automatique en statut INACTIF</li>
* </ul> * </ul>
* *
* <p> * <p>
* Table : {@code parametres_cotisation_organisation} * Table : {@code parametres_cotisation_organisation}
*/ */
@Entity @Entity
@Table(name = "parametres_cotisation_organisation", indexes = { @Table(name = "parametres_cotisation_organisation", indexes = {
@Index(name = "idx_param_cot_org", columnList = "organisation_id", unique = true) @Index(name = "idx_param_cot_org", columnList = "organisation_id", unique = true)
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ParametresCotisationOrganisation extends BaseEntity { public class ParametresCotisationOrganisation extends BaseEntity {
@NotNull @NotNull
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false, unique = true) @JoinColumn(name = "organisation_id", nullable = false, unique = true)
private Organisation organisation; private Organisation organisation;
@Builder.Default @Builder.Default
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "montant_cotisation_mensuelle", precision = 12, scale = 2) @Column(name = "montant_cotisation_mensuelle", precision = 12, scale = 2)
private BigDecimal montantCotisationMensuelle = BigDecimal.ZERO; private BigDecimal montantCotisationMensuelle = BigDecimal.ZERO;
@Builder.Default @Builder.Default
@DecimalMin("0.00") @DecimalMin("0.00")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2) @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
private BigDecimal montantCotisationAnnuelle = BigDecimal.ZERO; private BigDecimal montantCotisationAnnuelle = BigDecimal.ZERO;
@Column(name = "devise", nullable = false, length = 3) @Column(name = "devise", nullable = false, length = 3)
private String devise; private String devise;
/** /**
* Date de référence pour le calcul des membres «à jour». * Date de référence pour le calcul des membres «à jour».
* Toutes les échéances depuis cette date doivent être payées. * Toutes les échéances depuis cette date doivent être payées.
* Configurable par le manager. * Configurable par le manager.
*/ */
@Column(name = "date_debut_calcul_ajour") @Column(name = "date_debut_calcul_ajour")
private LocalDate dateDebutCalculAjour; private LocalDate dateDebutCalculAjour;
/** /**
* Nombre de jours de retard avant passage automatique du statut membre → * Nombre de jours de retard avant passage automatique du statut membre →
* INACTIF. * INACTIF.
* Défaut : 30 jours. * Défaut : 30 jours.
*/ */
@Builder.Default @Builder.Default
@Min(1) @Min(1)
@Column(name = "delai_retard_avant_inactif_jours", nullable = false) @Column(name = "delai_retard_avant_inactif_jours", nullable = false)
private Integer delaiRetardAvantInactifJours = 30; private Integer delaiRetardAvantInactifJours = 30;
@Builder.Default @Builder.Default
@Column(name = "cotisation_obligatoire", nullable = false) @Column(name = "cotisation_obligatoire", nullable = false)
private Boolean cotisationObligatoire = true; private Boolean cotisationObligatoire = true;
/** /**
* Active la génération automatique mensuelle des cotisations pour cette organisation. * 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 * 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. * le 1er de chaque mois, en utilisant les barèmes par rôle ou le montant par défaut.
*/ */
@Builder.Default @Builder.Default
@Column(name = "generation_automatique_activee", nullable = false) @Column(name = "generation_automatique_activee", nullable = false)
private Boolean generationAutomatiqueActivee = false; private Boolean generationAutomatiqueActivee = false;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
/** /**
* Vérifie si la date de référence pour les impayés est définie. * 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. * Sans cette date, aucun calcul d'ancienneté des impayés n'est possible.
*/ */
public boolean isCalculAjourActive() { public boolean isCalculAjourActive() {
return dateDebutCalculAjour != null; return dateDebutCalculAjour != null;
} }
} }

View File

@@ -1,36 +1,36 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.UUID; import java.util.UUID;
/** /**
* Paramètres LCB-FT par organisation ou globaux (organisationId null). * Paramètres LCB-FT par organisation ou globaux (organisationId null).
* Seuils au-dessus desquels l'origine des fonds est obligatoire / validation manuelle. * Seuils au-dessus desquels l'origine des fonds est obligatoire / validation manuelle.
*/ */
@Entity @Entity
@Table(name = "parametres_lcb_ft", indexes = { @Table(name = "parametres_lcb_ft", indexes = {
@Index(name = "idx_param_lcb_ft_org", columnList = "organisation_id") @Index(name = "idx_param_lcb_ft_org", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ParametresLcbFt extends BaseEntity { public class ParametresLcbFt extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
@Column(name = "code_devise", nullable = false, length = 3) @Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise; private String codeDevise;
@Column(name = "montant_seuil_justification", nullable = false, precision = 18, scale = 4) @Column(name = "montant_seuil_justification", nullable = false, precision = 18, scale = 4)
private BigDecimal montantSeuilJustification; private BigDecimal montantSeuilJustification;
@Column(name = "montant_seuil_validation_manuelle", precision = 18, scale = 4) @Column(name = "montant_seuil_validation_manuelle", precision = 18, scale = 4)
private BigDecimal montantSeuilValidationManuelle; private BigDecimal montantSeuilValidationManuelle;
} }

View File

@@ -1,92 +1,92 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Permission pour la gestion des permissions granulaires * Entité Permission pour la gestion des permissions granulaires
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "permissions", name = "permissions",
indexes = { indexes = {
@Index(name = "idx_permission_code", columnList = "code", unique = true), @Index(name = "idx_permission_code", columnList = "code", unique = true),
@Index(name = "idx_permission_module", columnList = "module"), @Index(name = "idx_permission_module", columnList = "module"),
@Index(name = "idx_permission_ressource", columnList = "ressource") @Index(name = "idx_permission_ressource", columnList = "ressource")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Permission extends BaseEntity { public class Permission extends BaseEntity {
/** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */ /** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
@NotBlank @NotBlank
@Column(name = "code", unique = true, nullable = false, length = 100) @Column(name = "code", unique = true, nullable = false, length = 100)
private String code; private String code;
/** Module (ex: ORGANISATION, MEMBRE, COTISATION) */ /** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
@NotBlank @NotBlank
@Column(name = "module", nullable = false, length = 50) @Column(name = "module", nullable = false, length = 50)
private String module; private String module;
/** Ressource (ex: MEMBRE, COTISATION, ADHESION) */ /** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
@NotBlank @NotBlank
@Column(name = "ressource", nullable = false, length = 50) @Column(name = "ressource", nullable = false, length = 50)
private String ressource; private String ressource;
/** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */ /** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
@NotBlank @NotBlank
@Column(name = "action", nullable = false, length = 50) @Column(name = "action", nullable = false, length = 50)
private String action; private String action;
/** Libellé de la permission */ /** Libellé de la permission */
@Column(name = "libelle", length = 200) @Column(name = "libelle", length = 200)
private String libelle; private String libelle;
/** Description de la permission */ /** Description de la permission */
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** Rôles associés */ /** Rôles associés */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<RolePermission> roles = new ArrayList<>(); private List<RolePermission> roles = new ArrayList<>();
/** Méthode métier pour générer le code à partir des composants */ /** Méthode métier pour générer le code à partir des composants */
public static String genererCode(String module, String ressource, String action) { public static String genererCode(String module, String ressource, String action) {
return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase()); return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
} }
/** Méthode métier pour vérifier si le code est valide */ /** Méthode métier pour vérifier si le code est valide */
public boolean isCodeValide() { public boolean isCodeValide() {
return code != null && code.contains(" > ") && code.split(" > ").length == 3; return code != null && code.contains(" > ") && code.split(" > ").length == 3;
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
// Générer le code si non fourni // Générer le code si non fourni
if (code == null || code.isEmpty()) { if (code == null || code.isEmpty()) {
if (module != null && ressource != null && action != null) { if (module != null && ressource != null && action != null) {
code = genererCode(module, ressource, action); code = genererCode(module, ressource, action);
} }
} }
} }
} }

View File

@@ -1,122 +1,122 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.Index; import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Association polymorphique entre un document et * Association polymorphique entre un document et
* une entité métier quelconque. * une entité métier quelconque.
* *
* <p> * <p>
* Remplace les 6 FK nullables mutuellement * Remplace les 6 FK nullables mutuellement
* exclusives (membre, organisation, cotisation, * exclusives (membre, organisation, cotisation,
* adhesion, demandeAide, transactionWave) par un * adhesion, demandeAide, transactionWave) par un
* couple {@code (type_entite_rattachee, * couple {@code (type_entite_rattachee,
* entite_rattachee_id)}. * entite_rattachee_id)}.
* *
* <p> * <p>
* Les types autorisés sont définis dans le * Les types autorisés sont définis dans le
* domaine {@code ENTITE_RATTACHEE} de la table * domaine {@code ENTITE_RATTACHEE} de la table
* {@code types_reference} (ex: MEMBRE, * {@code types_reference} (ex: MEMBRE,
* ORGANISATION, COTISATION, ADHESION, AIDE, * ORGANISATION, COTISATION, ADHESION, AIDE,
* TRANSACTION_WAVE). * TRANSACTION_WAVE).
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2026-02-21 * @since 2026-02-21
*/ */
@Entity @Entity
@Table(name = "pieces_jointes", indexes = { @Table(name = "pieces_jointes", indexes = {
@Index(name = "idx_pj_document", columnList = "document_id"), @Index(name = "idx_pj_document", columnList = "document_id"),
@Index(name = "idx_pj_entite", columnList = "type_entite_rattachee," @Index(name = "idx_pj_entite", columnList = "type_entite_rattachee,"
+ " entite_rattachee_id"), + " entite_rattachee_id"),
@Index(name = "idx_pj_type_entite", columnList = "type_entite_rattachee") @Index(name = "idx_pj_type_entite", columnList = "type_entite_rattachee")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class PieceJointe extends BaseEntity { public class PieceJointe extends BaseEntity {
/** Ordre d'affichage. */ /** Ordre d'affichage. */
@NotNull @NotNull
@Min(value = 1, message = "L'ordre doit être positif") @Min(value = 1, message = "L'ordre doit être positif")
@Column(name = "ordre", nullable = false) @Column(name = "ordre", nullable = false)
private Integer ordre; private Integer ordre;
/** Libellé de la pièce jointe. */ /** Libellé de la pièce jointe. */
@Size(max = 200) @Size(max = 200)
@Column(name = "libelle", length = 200) @Column(name = "libelle", length = 200)
private String libelle; private String libelle;
/** Commentaire. */ /** Commentaire. */
@Size(max = 500) @Size(max = 500)
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
/** Document associé (obligatoire). */ /** Document associé (obligatoire). */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "document_id", nullable = false) @JoinColumn(name = "document_id", nullable = false)
private Document document; private Document document;
/** /**
* Type de l'entité rattachée (code du domaine * Type de l'entité rattachée (code du domaine
* {@code ENTITE_RATTACHEE} dans * {@code ENTITE_RATTACHEE} dans
* {@code types_reference}). * {@code types_reference}).
* *
* <p> * <p>
* Valeurs attendues : {@code MEMBRE}, * Valeurs attendues : {@code MEMBRE},
* {@code ORGANISATION}, {@code COTISATION}, * {@code ORGANISATION}, {@code COTISATION},
* {@code ADHESION}, {@code AIDE}, * {@code ADHESION}, {@code AIDE},
* {@code TRANSACTION_WAVE}. * {@code TRANSACTION_WAVE}.
*/ */
@NotBlank @NotBlank
@Size(max = 50) @Size(max = 50)
@Column(name = "type_entite_rattachee", nullable = false, length = 50) @Column(name = "type_entite_rattachee", nullable = false, length = 50)
private String typeEntiteRattachee; private String typeEntiteRattachee;
/** /**
* UUID de l'entité rattachée (membre, * UUID de l'entité rattachée (membre,
* organisation, cotisation, etc.). * organisation, cotisation, etc.).
*/ */
@NotNull @NotNull
@Column(name = "entite_rattachee_id", nullable = false) @Column(name = "entite_rattachee_id", nullable = false)
private UUID entiteRattacheeId; private UUID entiteRattacheeId;
/** /**
* Callback JPA avant la persistance. * Callback JPA avant la persistance.
* *
* <p> * <p>
* Initialise {@code ordre} à 1 si non * Initialise {@code ordre} à 1 si non
* renseigné. Normalise le type en majuscules. * renseigné. Normalise le type en majuscules.
*/ */
@Override @Override
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (ordre == null) { if (ordre == null) {
ordre = 1; ordre = 1;
} }
if (typeEntiteRattachee != null) { if (typeEntiteRattachee != null) {
typeEntiteRattachee = typeEntiteRattachee.toUpperCase(); typeEntiteRattachee = typeEntiteRattachee.toUpperCase();
} }
} }
} }

View File

@@ -1,98 +1,98 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Role pour la gestion des rôles dans le système * Entité Role pour la gestion des rôles dans le système
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.1 * @version 3.1
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table(name = "roles", indexes = { @Table(name = "roles", indexes = {
@Index(name = "idx_role_code", columnList = "code", unique = true), @Index(name = "idx_role_code", columnList = "code", unique = true),
@Index(name = "idx_role_actif", columnList = "actif"), @Index(name = "idx_role_actif", columnList = "actif"),
@Index(name = "idx_role_niveau", columnList = "niveau_hierarchique") @Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity { public class Role extends BaseEntity {
/** Code unique du rôle */ /** Code unique du rôle */
@NotBlank @NotBlank
@Column(name = "code", unique = true, nullable = false, length = 50) @Column(name = "code", unique = true, nullable = false, length = 50)
private String code; private String code;
/** Libellé du rôle */ /** Libellé du rôle */
@NotBlank @NotBlank
@Column(name = "libelle", nullable = false, length = 100) @Column(name = "libelle", nullable = false, length = 100)
private String libelle; private String libelle;
/** Description du rôle */ /** Description du rôle */
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
/** Niveau hiérarchique (plus bas = plus prioritaire) */ /** Niveau hiérarchique (plus bas = plus prioritaire) */
@NotNull @NotNull
@Builder.Default @Builder.Default
@Column(name = "niveau_hierarchique", nullable = false) @Column(name = "niveau_hierarchique", nullable = false)
private Integer niveauHierarchique = 100; private Integer niveauHierarchique = 100;
/** Type de rôle (SYSTEME, ORGANISATION, PERSONNALISE) */ /** Type de rôle (SYSTEME, ORGANISATION, PERSONNALISE) */
@Column(name = "type_role", nullable = false, length = 50) @Column(name = "type_role", nullable = false, length = 50)
private String typeRole; private String typeRole;
/** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */ /** Catégorie du rôle (PLATEFORME, FONCTIONNEL, METIER) */
@Column(name = "categorie", length = 30) @Column(name = "categorie", length = 30)
@Builder.Default @Builder.Default
private String categorie = "FONCTIONNEL"; private String categorie = "FONCTIONNEL";
/** Organisation propriétaire (null pour rôles système) */ /** Organisation propriétaire (null pour rôles système) */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
/** Permissions associées */ /** Permissions associées */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<RolePermission> permissions = new ArrayList<>(); private List<RolePermission> permissions = new ArrayList<>();
/** Énumération des constantes de types de rôle */ /** Énumération des constantes de types de rôle */
public enum TypeRole { public enum TypeRole {
SYSTEME, SYSTEME,
ORGANISATION, ORGANISATION,
PERSONNALISE; PERSONNALISE;
} }
/** Méthode métier pour vérifier si c'est un rôle système */ /** Méthode métier pour vérifier si c'est un rôle système */
public boolean isRoleSysteme() { public boolean isRoleSysteme() {
return TypeRole.SYSTEME.name().equals(typeRole); return TypeRole.SYSTEME.name().equals(typeRole);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (typeRole == null) { if (typeRole == null) {
typeRole = TypeRole.PERSONNALISE.name(); typeRole = TypeRole.PERSONNALISE.name();
} }
if (niveauHierarchique == null) { if (niveauHierarchique == null) {
niveauHierarchique = 100; niveauHierarchique = 100;
} }
} }
} }

View File

@@ -1,54 +1,54 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Table de liaison entre Role et Permission * Table de liaison entre Role et Permission
* Permet à un rôle d'avoir plusieurs permissions * Permet à un rôle d'avoir plusieurs permissions
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "roles_permissions", name = "roles_permissions",
indexes = { indexes = {
@Index(name = "idx_role_permission_role", columnList = "role_id"), @Index(name = "idx_role_permission_role", columnList = "role_id"),
@Index(name = "idx_role_permission_permission", columnList = "permission_id") @Index(name = "idx_role_permission_permission", columnList = "permission_id")
}, },
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_role_permission", name = "uk_role_permission",
columnNames = {"role_id", "permission_id"}) columnNames = {"role_id", "permission_id"})
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RolePermission extends BaseEntity { public class RolePermission extends BaseEntity {
/** Rôle */ /** Rôle */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false) @JoinColumn(name = "role_id", nullable = false)
private Role role; private Role role;
/** Permission */ /** Permission */
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "permission_id", nullable = false) @JoinColumn(name = "permission_id", nullable = false)
private Permission permission; private Permission permission;
/** Commentaire sur l'association */ /** Commentaire sur l'association */
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
} }

View File

@@ -1,171 +1,171 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres; 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.StatutSouscription;
import dev.lions.unionflow.server.api.enums.abonnement.StatutValidationSouscription; 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.TypePeriodeAbonnement;
import dev.lions.unionflow.server.api.enums.abonnement.TypeOrganisationFacturation; import dev.lions.unionflow.server.api.enums.abonnement.TypeOrganisationFacturation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import lombok.*; import lombok.*;
/** /**
* Abonnement actif d'une organisation racine à un forfait UnionFlow. * Abonnement actif d'une organisation racine à un forfait UnionFlow.
* *
* <p>Règle clé : quand {@code quotaUtilise >= quotaMax}, toute nouvelle * <p>Règle clé : quand {@code quotaUtilise >= quotaMax}, toute nouvelle
* validation d'adhésion est bloquée avec un message explicite. * validation d'adhésion est bloquée avec un message explicite.
* Le manager peut upgrader son forfait à tout moment. * Le manager peut upgrader son forfait à tout moment.
* *
* <p>Table : {@code souscriptions_organisation} * <p>Table : {@code souscriptions_organisation}
*/ */
@Entity @Entity
@Table( @Table(
name = "souscriptions_organisation", name = "souscriptions_organisation",
indexes = { indexes = {
@Index(name = "idx_souscription_org", columnList = "organisation_id", unique = true), @Index(name = "idx_souscription_org", columnList = "organisation_id", unique = true),
@Index(name = "idx_souscription_statut", columnList = "statut"), @Index(name = "idx_souscription_statut", columnList = "statut"),
@Index(name = "idx_souscription_fin", columnList = "date_fin") @Index(name = "idx_souscription_fin", columnList = "date_fin")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SouscriptionOrganisation extends BaseEntity { public class SouscriptionOrganisation extends BaseEntity {
/** Organisation racine abonnée (une seule souscription active par org) */ /** Organisation racine abonnée (une seule souscription active par org) */
@NotNull @NotNull
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false, unique = true) @JoinColumn(name = "organisation_id", nullable = false, unique = true)
private Organisation organisation; private Organisation organisation;
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "formule_id", nullable = false) @JoinColumn(name = "formule_id", nullable = false)
private FormuleAbonnement formule; private FormuleAbonnement formule;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "type_periode", nullable = false, length = 10) @Column(name = "type_periode", nullable = false, length = 10)
private TypePeriodeAbonnement typePeriode = TypePeriodeAbonnement.MENSUEL; private TypePeriodeAbonnement typePeriode = TypePeriodeAbonnement.MENSUEL;
@NotNull @NotNull
@Column(name = "date_debut", nullable = false) @Column(name = "date_debut", nullable = false)
private LocalDate dateDebut; private LocalDate dateDebut;
@NotNull @NotNull
@Column(name = "date_fin", nullable = false) @Column(name = "date_fin", nullable = false)
private LocalDate dateFin; private LocalDate dateFin;
/** Snapshot du quota max au moment de la souscription */ /** Snapshot du quota max au moment de la souscription */
@Column(name = "quota_max") @Column(name = "quota_max")
private Integer quotaMax; private Integer quotaMax;
/** Compteur incrémenté à chaque adhésion validée */ /** Compteur incrémenté à chaque adhésion validée */
@Builder.Default @Builder.Default
@Min(0) @Min(0)
@Column(name = "quota_utilise", nullable = false) @Column(name = "quota_utilise", nullable = false)
private Integer quotaUtilise = 0; private Integer quotaUtilise = 0;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut", nullable = false, length = 30) @Column(name = "statut", nullable = false, length = 30)
private StatutSouscription statut = StatutSouscription.ACTIVE; private StatutSouscription statut = StatutSouscription.ACTIVE;
@Column(name = "reference_paiement_wave", length = 100) @Column(name = "reference_paiement_wave", length = 100)
private String referencePaiementWave; private String referencePaiementWave;
@Column(name = "wave_session_id", length = 255) @Column(name = "wave_session_id", length = 255)
private String waveSessionId; private String waveSessionId;
@Column(name = "wave_checkout_url", length = 1024) @Column(name = "wave_checkout_url", length = 1024)
private String waveCheckoutUrl; private String waveCheckoutUrl;
@Column(name = "date_dernier_paiement") @Column(name = "date_dernier_paiement")
private LocalDate dateDernierPaiement; private LocalDate dateDernierPaiement;
@Column(name = "date_prochain_paiement") @Column(name = "date_prochain_paiement")
private LocalDate dateProchainePaiement; private LocalDate dateProchainePaiement;
// ── Champs workflow de validation (onboarding) ──────────────────────────── // ── Champs workflow de validation (onboarding) ────────────────────────────
/** Plage de membres choisie lors de la souscription. */ /** Plage de membres choisie lors de la souscription. */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "plage", length = 20) @Column(name = "plage", length = 20)
private PlageMembres plage; private PlageMembres plage;
/** Type d'organisation déclaré, utilisé pour le coefficient tarifaire. */ /** Type d'organisation déclaré, utilisé pour le coefficient tarifaire. */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_organisation", length = 30) @Column(name = "type_organisation", length = 30)
private TypeOrganisationFacturation typeOrganisationSouscription; private TypeOrganisationFacturation typeOrganisationSouscription;
/** Coefficient multiplicateur effectivement appliqué (org × période). */ /** Coefficient multiplicateur effectivement appliqué (org × période). */
@Column(name = "coefficient_applique", precision = 4, scale = 2) @Column(name = "coefficient_applique", precision = 4, scale = 2)
private BigDecimal coefficientApplique; private BigDecimal coefficientApplique;
/** État du workflow de validation SuperAdmin. */ /** État du workflow de validation SuperAdmin. */
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut_validation", nullable = false, length = 40) @Column(name = "statut_validation", nullable = false, length = 40)
private StatutValidationSouscription statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT; private StatutValidationSouscription statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
/** Montant total facturé pour la période choisie (en XOF). */ /** Montant total facturé pour la période choisie (en XOF). */
@Column(name = "montant_total", precision = 12, scale = 2) @Column(name = "montant_total", precision = 12, scale = 2)
private BigDecimal montantTotal; private BigDecimal montantTotal;
/** Date à laquelle le SuperAdmin a approuvé ou rejeté la souscription. */ /** Date à laquelle le SuperAdmin a approuvé ou rejeté la souscription. */
@Column(name = "date_validation") @Column(name = "date_validation")
private LocalDate dateValidation; private LocalDate dateValidation;
/** UUID du SuperAdmin ayant validé ou rejeté. */ /** UUID du SuperAdmin ayant validé ou rejeté. */
@Column(name = "validated_by_id") @Column(name = "validated_by_id")
private UUID validatedById; private UUID validatedById;
/** Motif de rejet renseigné par le SuperAdmin. */ /** Motif de rejet renseigné par le SuperAdmin. */
@Column(name = "commentaire_rejet", length = 500) @Column(name = "commentaire_rejet", length = 500)
private String commentaireRejet; private String commentaireRejet;
/** Mot de passe temporaire généré à l'activation du compte. */ /** Mot de passe temporaire généré à l'activation du compte. */
@Column(name = "mot_de_passe_temporaire", length = 100) @Column(name = "mot_de_passe_temporaire", length = 100)
private String motDePasseTemporaire; private String motDePasseTemporaire;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean isActive() { public boolean isActive() {
return StatutSouscription.ACTIVE.equals(statut) return StatutSouscription.ACTIVE.equals(statut)
&& LocalDate.now().isBefore(dateFin.plusDays(1)); && LocalDate.now().isBefore(dateFin.plusDays(1));
} }
public boolean isQuotaDepasse() { public boolean isQuotaDepasse() {
return quotaMax != null && quotaUtilise >= quotaMax; return quotaMax != null && quotaUtilise >= quotaMax;
} }
public int getPlacesRestantes() { public int getPlacesRestantes() {
if (quotaMax == null) return Integer.MAX_VALUE; if (quotaMax == null) return Integer.MAX_VALUE;
return Math.max(0, quotaMax - quotaUtilise); return Math.max(0, quotaMax - quotaUtilise);
} }
/** Incrémente le quota lors de la validation d'une adhésion */ /** Incrémente le quota lors de la validation d'une adhésion */
public void incrementerQuota() { public void incrementerQuota() {
if (quotaUtilise == null) quotaUtilise = 0; if (quotaUtilise == null) quotaUtilise = 0;
quotaUtilise++; quotaUtilise++;
} }
/** Décrémente le quota lors de la radiation d'un membre */ /** Décrémente le quota lors de la radiation d'un membre */
public void decrementerQuota() { public void decrementerQuota() {
if (quotaUtilise != null && quotaUtilise > 0) quotaUtilise--; if (quotaUtilise != null && quotaUtilise > 0) quotaUtilise--;
} }
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statut == null) statut = StatutSouscription.ACTIVE; if (statut == null) statut = StatutSouscription.ACTIVE;
if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL; if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL;
if (quotaUtilise == null) quotaUtilise = 0; if (quotaUtilise == null) quotaUtilise = 0;
if (statutValidation == null) statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT; if (statutValidation == null) statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
if (formule != null && quotaMax == null) quotaMax = formule.getMaxMembres(); if (formule != null && quotaMax == null) quotaMax = formule.getMaxMembres();
} }
} }

View File

@@ -1,91 +1,91 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
/** /**
* Entité Suggestion pour la gestion des suggestions utilisateur * Entité Suggestion pour la gestion des suggestions utilisateur
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
*/ */
@Entity @Entity
@Table( @Table(
name = "suggestions", name = "suggestions",
indexes = { indexes = {
@Index(name = "idx_suggestion_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_suggestion_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_suggestion_statut", columnList = "statut"), @Index(name = "idx_suggestion_statut", columnList = "statut"),
@Index(name = "idx_suggestion_categorie", columnList = "categorie") @Index(name = "idx_suggestion_categorie", columnList = "categorie")
} }
) )
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Suggestion extends BaseEntity { public class Suggestion extends BaseEntity {
@NotNull @NotNull
@Column(name = "utilisateur_id", nullable = false) @Column(name = "utilisateur_id", nullable = false)
private UUID utilisateurId; private UUID utilisateurId;
@Column(name = "utilisateur_nom", length = 255) @Column(name = "utilisateur_nom", length = 255)
private String utilisateurNom; private String utilisateurNom;
@NotBlank @NotBlank
@Column(name = "titre", nullable = false, length = 255) @Column(name = "titre", nullable = false, length = 255)
private String titre; private String titre;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String description; private String description;
@Column(name = "justification", columnDefinition = "TEXT") @Column(name = "justification", columnDefinition = "TEXT")
private String justification; private String justification;
@Column(name = "categorie", length = 50) @Column(name = "categorie", length = 50)
private String categorie; // UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING private String categorie; // UI, FEATURE, PERFORMANCE, SECURITE, INTEGRATION, MOBILE, REPORTING
@Column(name = "priorite_estimee", length = 50) @Column(name = "priorite_estimee", length = 50)
private String prioriteEstimee; // BASSE, MOYENNE, HAUTE, CRITIQUE private String prioriteEstimee; // BASSE, MOYENNE, HAUTE, CRITIQUE
@Column(name = "statut", length = 50) @Column(name = "statut", length = 50)
@Builder.Default @Builder.Default
private String statut = "NOUVELLE"; // NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE private String statut = "NOUVELLE"; // NOUVELLE, EVALUATION, APPROUVEE, DEVELOPPEMENT, IMPLEMENTEE, REJETEE
@Column(name = "nb_votes") @Column(name = "nb_votes")
@Builder.Default @Builder.Default
private Integer nbVotes = 0; private Integer nbVotes = 0;
@Column(name = "nb_commentaires") @Column(name = "nb_commentaires")
@Builder.Default @Builder.Default
private Integer nbCommentaires = 0; private Integer nbCommentaires = 0;
@Column(name = "nb_vues") @Column(name = "nb_vues")
@Builder.Default @Builder.Default
private Integer nbVues = 0; private Integer nbVues = 0;
@Column(name = "date_soumission") @Column(name = "date_soumission")
private LocalDateTime dateSoumission; private LocalDateTime dateSoumission;
@Column(name = "date_evaluation") @Column(name = "date_evaluation")
private LocalDateTime dateEvaluation; private LocalDateTime dateEvaluation;
@Column(name = "date_implementation") @Column(name = "date_implementation")
private LocalDateTime dateImplementation; private LocalDateTime dateImplementation;
@Column(name = "version_ciblee", length = 50) @Column(name = "version_ciblee", length = 50)
private String versionCiblee; private String versionCiblee;
@Column(name = "mise_a_jour", columnDefinition = "TEXT") @Column(name = "mise_a_jour", columnDefinition = "TEXT")
private String miseAJour; private String miseAJour;
} }

View File

@@ -1,66 +1,66 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
/** /**
* Entité SuggestionVote pour gérer les votes sur les suggestions * Entité SuggestionVote pour gérer les votes sur les suggestions
* *
* <p>Permet d'éviter qu'un utilisateur vote plusieurs fois pour la même suggestion. * <p>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. * La contrainte d'unicité (suggestion_id, utilisateur_id) est gérée au niveau de la base de données.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
*/ */
@Entity @Entity
@Table( @Table(
name = "suggestion_votes", name = "suggestion_votes",
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_suggestion_vote", name = "uk_suggestion_vote",
columnNames = {"suggestion_id", "utilisateur_id"} columnNames = {"suggestion_id", "utilisateur_id"}
) )
}, },
indexes = { indexes = {
@Index(name = "idx_vote_suggestion", columnList = "suggestion_id"), @Index(name = "idx_vote_suggestion", columnList = "suggestion_id"),
@Index(name = "idx_vote_utilisateur", columnList = "utilisateur_id") @Index(name = "idx_vote_utilisateur", columnList = "utilisateur_id")
} }
) )
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SuggestionVote extends BaseEntity { public class SuggestionVote extends BaseEntity {
@NotNull @NotNull
@Column(name = "suggestion_id", nullable = false) @Column(name = "suggestion_id", nullable = false)
private UUID suggestionId; private UUID suggestionId;
@NotNull @NotNull
@Column(name = "utilisateur_id", nullable = false) @Column(name = "utilisateur_id", nullable = false)
private UUID utilisateurId; private UUID utilisateurId;
@Column(name = "date_vote", nullable = false) @Column(name = "date_vote", nullable = false)
@Builder.Default @Builder.Default
private LocalDateTime dateVote = LocalDateTime.now(); private LocalDateTime dateVote = LocalDateTime.now();
@PrePersist @PrePersist
protected void onPrePersist() { protected void onPrePersist() {
if (dateVote == null) { if (dateVote == null) {
dateVote = LocalDateTime.now(); dateVote = LocalDateTime.now();
} }
if (getDateCreation() == null) { if (getDateCreation() == null) {
setDateCreation(LocalDateTime.now()); setDateCreation(LocalDateTime.now());
} }
} }
} }

View File

@@ -1,119 +1,119 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* Entité pour les alertes système. * Entité pour les alertes système.
* Enregistre les alertes de seuils dépassés, erreurs critiques, etc. * Enregistre les alertes de seuils dépassés, erreurs critiques, etc.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-15 * @since 2026-03-15
*/ */
@Entity @Entity
@Table(name = "system_alerts", indexes = { @Table(name = "system_alerts", indexes = {
@Index(name = "idx_system_alert_timestamp", columnList = "timestamp"), @Index(name = "idx_system_alert_timestamp", columnList = "timestamp"),
@Index(name = "idx_system_alert_level", columnList = "level"), @Index(name = "idx_system_alert_level", columnList = "level"),
@Index(name = "idx_system_alert_acknowledged", columnList = "acknowledged"), @Index(name = "idx_system_alert_acknowledged", columnList = "acknowledged"),
@Index(name = "idx_system_alert_source", columnList = "source") @Index(name = "idx_system_alert_source", columnList = "source")
}) })
@Getter @Getter
@Setter @Setter
public class SystemAlert extends BaseEntity { public class SystemAlert extends BaseEntity {
/** /**
* Niveau de l'alerte (CRITICAL, ERROR, WARNING, INFO) * Niveau de l'alerte (CRITICAL, ERROR, WARNING, INFO)
*/ */
@Column(name = "level", nullable = false, length = 20) @Column(name = "level", nullable = false, length = 20)
private String level; private String level;
/** /**
* Titre court de l'alerte * Titre court de l'alerte
*/ */
@Column(name = "title", nullable = false, length = 255) @Column(name = "title", nullable = false, length = 255)
private String title; private String title;
/** /**
* Message détaillé de l'alerte * Message détaillé de l'alerte
*/ */
@Column(name = "message", nullable = false, length = 1000) @Column(name = "message", nullable = false, length = 1000)
private String message; private String message;
/** /**
* Date/heure de création de l'alerte * Date/heure de création de l'alerte
*/ */
@Column(name = "timestamp", nullable = false) @Column(name = "timestamp", nullable = false)
private LocalDateTime timestamp; private LocalDateTime timestamp;
/** /**
* Alerte acquittée ou non * Alerte acquittée ou non
*/ */
@Column(name = "acknowledged", nullable = false) @Column(name = "acknowledged", nullable = false)
private Boolean acknowledged = false; private Boolean acknowledged = false;
/** /**
* Email de l'utilisateur ayant acquitté l'alerte * Email de l'utilisateur ayant acquitté l'alerte
*/ */
@Column(name = "acknowledged_by", length = 255) @Column(name = "acknowledged_by", length = 255)
private String acknowledgedBy; private String acknowledgedBy;
/** /**
* Date/heure d'acquittement * Date/heure d'acquittement
*/ */
@Column(name = "acknowledged_at") @Column(name = "acknowledged_at")
private LocalDateTime acknowledgedAt; private LocalDateTime acknowledgedAt;
/** /**
* Source de l'alerte (CPU, MEMORY, DISK, DATABASE, etc.) * Source de l'alerte (CPU, MEMORY, DISK, DATABASE, etc.)
*/ */
@Column(name = "source", length = 100) @Column(name = "source", length = 100)
private String source; private String source;
/** /**
* Type d'alerte (THRESHOLD, INFO, ERROR, etc.) * Type d'alerte (THRESHOLD, INFO, ERROR, etc.)
*/ */
@Column(name = "alert_type", length = 50) @Column(name = "alert_type", length = 50)
private String alertType; private String alertType;
/** /**
* Valeur actuelle ayant déclenché l'alerte * Valeur actuelle ayant déclenché l'alerte
*/ */
@Column(name = "current_value") @Column(name = "current_value")
private Double currentValue; private Double currentValue;
/** /**
* Valeur seuil dépassée * Valeur seuil dépassée
*/ */
@Column(name = "threshold_value") @Column(name = "threshold_value")
private Double thresholdValue; private Double thresholdValue;
/** /**
* Unité de mesure (%, MB, GB, ms, etc.) * Unité de mesure (%, MB, GB, ms, etc.)
*/ */
@Column(name = "unit", length = 20) @Column(name = "unit", length = 20)
private String unit; private String unit;
/** /**
* Actions recommandées pour résoudre l'alerte * Actions recommandées pour résoudre l'alerte
*/ */
@Column(name = "recommended_actions", columnDefinition = "TEXT") @Column(name = "recommended_actions", columnDefinition = "TEXT")
private String recommendedActions; private String recommendedActions;
/** /**
* Initialisation automatique du timestamp * Initialisation automatique du timestamp
*/ */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (timestamp == null) { if (timestamp == null) {
timestamp = LocalDateTime.now(); timestamp = LocalDateTime.now();
} }
if (acknowledged == null) { if (acknowledged == null) {
acknowledged = false; acknowledged = false;
} }
} }
} }

View File

@@ -1,98 +1,98 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* Entité pour les logs techniques du système. * Entité pour les logs techniques du système.
* Enregistre les erreurs, warnings, et événements système. * Enregistre les erreurs, warnings, et événements système.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-15 * @since 2026-03-15
*/ */
@Entity @Entity
@Table(name = "system_logs", indexes = { @Table(name = "system_logs", indexes = {
@Index(name = "idx_system_log_timestamp", columnList = "timestamp"), @Index(name = "idx_system_log_timestamp", columnList = "timestamp"),
@Index(name = "idx_system_log_level", columnList = "level"), @Index(name = "idx_system_log_level", columnList = "level"),
@Index(name = "idx_system_log_source", columnList = "source"), @Index(name = "idx_system_log_source", columnList = "source"),
@Index(name = "idx_system_log_user_id", columnList = "user_id") @Index(name = "idx_system_log_user_id", columnList = "user_id")
}) })
@Getter @Getter
@Setter @Setter
public class SystemLog extends BaseEntity { public class SystemLog extends BaseEntity {
/** /**
* Niveau du log (CRITICAL, ERROR, WARNING, INFO, DEBUG) * Niveau du log (CRITICAL, ERROR, WARNING, INFO, DEBUG)
*/ */
@Column(name = "level", nullable = false, length = 20) @Column(name = "level", nullable = false, length = 20)
private String level; private String level;
/** /**
* Source du log (Database, API, Auth, System, Cache, etc.) * Source du log (Database, API, Auth, System, Cache, etc.)
*/ */
@Column(name = "source", nullable = false, length = 100) @Column(name = "source", nullable = false, length = 100)
private String source; private String source;
/** /**
* Message principal du log * Message principal du log
*/ */
@Column(name = "message", nullable = false, length = 1000) @Column(name = "message", nullable = false, length = 1000)
private String message; private String message;
/** /**
* Détails supplémentaires (stacktrace, contexte, etc.) * Détails supplémentaires (stacktrace, contexte, etc.)
*/ */
@Column(name = "details", columnDefinition = "TEXT") @Column(name = "details", columnDefinition = "TEXT")
private String details; private String details;
/** /**
* Date/heure du log * Date/heure du log
*/ */
@Column(name = "timestamp", nullable = false) @Column(name = "timestamp", nullable = false)
private LocalDateTime timestamp; private LocalDateTime timestamp;
/** /**
* Identifiant de l'utilisateur concerné (optionnel) * Identifiant de l'utilisateur concerné (optionnel)
*/ */
@Column(name = "user_id", length = 255) @Column(name = "user_id", length = 255)
private String userId; private String userId;
/** /**
* Adresse IP de la requête (optionnel) * Adresse IP de la requête (optionnel)
*/ */
@Column(name = "ip_address", length = 45) @Column(name = "ip_address", length = 45)
private String ipAddress; private String ipAddress;
/** /**
* Identifiant de session (optionnel) * Identifiant de session (optionnel)
*/ */
@Column(name = "session_id", length = 255) @Column(name = "session_id", length = 255)
private String sessionId; private String sessionId;
/** /**
* Endpoint HTTP concerné (optionnel) * Endpoint HTTP concerné (optionnel)
*/ */
@Column(name = "endpoint", length = 500) @Column(name = "endpoint", length = 500)
private String endpoint; private String endpoint;
/** /**
* Code de statut HTTP (optionnel) * Code de statut HTTP (optionnel)
*/ */
@Column(name = "http_status_code") @Column(name = "http_status_code")
private Integer httpStatusCode; private Integer httpStatusCode;
/** /**
* Initialisation automatique du timestamp * Initialisation automatique du timestamp
*/ */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); // Appel du @PrePersist de BaseEntity (dateCreation, actif) super.onCreate(); // Appel du @PrePersist de BaseEntity (dateCreation, actif)
if (timestamp == null) { if (timestamp == null) {
timestamp = LocalDateTime.now(); timestamp = LocalDateTime.now();
} }
} }
} }

View File

@@ -1,83 +1,83 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité TemplateNotification pour les templates de notifications réutilisables * Entité TemplateNotification pour les templates de notifications réutilisables
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "templates_notifications", name = "templates_notifications",
indexes = { indexes = {
@Index(name = "idx_template_code", columnList = "code", unique = true), @Index(name = "idx_template_code", columnList = "code", unique = true),
@Index(name = "idx_template_actif", columnList = "actif") @Index(name = "idx_template_actif", columnList = "actif")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TemplateNotification extends BaseEntity { public class TemplateNotification extends BaseEntity {
/** Code unique du template */ /** Code unique du template */
@NotBlank @NotBlank
@Column(name = "code", unique = true, nullable = false, length = 100) @Column(name = "code", unique = true, nullable = false, length = 100)
private String code; private String code;
/** Sujet du template */ /** Sujet du template */
@Column(name = "sujet", length = 500) @Column(name = "sujet", length = 500)
private String sujet; private String sujet;
/** Corps du template (texte) */ /** Corps du template (texte) */
@Column(name = "corps_texte", columnDefinition = "TEXT") @Column(name = "corps_texte", columnDefinition = "TEXT")
private String corpsTexte; private String corpsTexte;
/** Corps du template (HTML) */ /** Corps du template (HTML) */
@Column(name = "corps_html", columnDefinition = "TEXT") @Column(name = "corps_html", columnDefinition = "TEXT")
private String corpsHtml; private String corpsHtml;
/** Variables disponibles (JSON) */ /** Variables disponibles (JSON) */
@Column(name = "variables_disponibles", columnDefinition = "TEXT") @Column(name = "variables_disponibles", columnDefinition = "TEXT")
private String variablesDisponibles; private String variablesDisponibles;
/** Canaux supportés (JSON array) */ /** Canaux supportés (JSON array) */
@Column(name = "canaux_supportes", length = 500) @Column(name = "canaux_supportes", length = 500)
private String canauxSupportes; private String canauxSupportes;
/** Langue du template */ /** Langue du template */
@Column(name = "langue", length = 10) @Column(name = "langue", length = 10)
private String langue; private String langue;
/** Description */ /** Description */
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
/** Notifications utilisant ce template */ /** Notifications utilisant ce template */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<Notification> notifications = new ArrayList<>(); private List<Notification> notifications = new ArrayList<>();
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (langue == null || langue.isEmpty()) { if (langue == null || langue.isEmpty()) {
langue = "fr"; langue = "fr";
} }
} }
} }

View File

@@ -1,92 +1,92 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
/** /**
* Entité Ticket pour la gestion des tickets support * Entité Ticket pour la gestion des tickets support
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
*/ */
@Entity @Entity
@Table( @Table(
name = "tickets", name = "tickets",
indexes = { indexes = {
@Index(name = "idx_ticket_utilisateur", columnList = "utilisateur_id"), @Index(name = "idx_ticket_utilisateur", columnList = "utilisateur_id"),
@Index(name = "idx_ticket_statut", columnList = "statut"), @Index(name = "idx_ticket_statut", columnList = "statut"),
@Index(name = "idx_ticket_categorie", columnList = "categorie"), @Index(name = "idx_ticket_categorie", columnList = "categorie"),
@Index(name = "idx_ticket_numero", columnList = "numero_ticket", unique = true) @Index(name = "idx_ticket_numero", columnList = "numero_ticket", unique = true)
} }
) )
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Ticket extends BaseEntity { public class Ticket extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "numero_ticket", nullable = false, unique = true, length = 50) @Column(name = "numero_ticket", nullable = false, unique = true, length = 50)
private String numeroTicket; private String numeroTicket;
@NotNull @NotNull
@Column(name = "utilisateur_id", nullable = false) @Column(name = "utilisateur_id", nullable = false)
private UUID utilisateurId; private UUID utilisateurId;
@NotBlank @NotBlank
@Column(name = "sujet", nullable = false, length = 255) @Column(name = "sujet", nullable = false, length = 255)
private String sujet; private String sujet;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String description; private String description;
@Column(name = "categorie", length = 50) @Column(name = "categorie", length = 50)
private String categorie; // TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE private String categorie; // TECHNIQUE, FONCTIONNALITE, UTILISATION, COMPTE, AUTRE
@Column(name = "priorite", length = 50) @Column(name = "priorite", length = 50)
private String priorite; // BASSE, NORMALE, HAUTE, URGENTE private String priorite; // BASSE, NORMALE, HAUTE, URGENTE
@Column(name = "statut", length = 50) @Column(name = "statut", length = 50)
@Builder.Default @Builder.Default
private String statut = "OUVERT"; // OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME private String statut = "OUVERT"; // OUVERT, EN_COURS, EN_ATTENTE, RESOLU, FERME
@Column(name = "agent_id") @Column(name = "agent_id")
private UUID agentId; private UUID agentId;
@Column(name = "agent_nom", length = 255) @Column(name = "agent_nom", length = 255)
private String agentNom; private String agentNom;
@Column(name = "date_derniere_reponse") @Column(name = "date_derniere_reponse")
private LocalDateTime dateDerniereReponse; private LocalDateTime dateDerniereReponse;
@Column(name = "date_resolution") @Column(name = "date_resolution")
private LocalDateTime dateResolution; private LocalDateTime dateResolution;
@Column(name = "date_fermeture") @Column(name = "date_fermeture")
private LocalDateTime dateFermeture; private LocalDateTime dateFermeture;
@Column(name = "nb_messages") @Column(name = "nb_messages")
@Builder.Default @Builder.Default
private Integer nbMessages = 0; private Integer nbMessages = 0;
@Column(name = "nb_fichiers") @Column(name = "nb_fichiers")
@Builder.Default @Builder.Default
private Integer nbFichiers = 0; private Integer nbFichiers = 0;
@Column(name = "note_satisfaction") @Column(name = "note_satisfaction")
private Integer noteSatisfaction; private Integer noteSatisfaction;
@Column(name = "resolution", columnDefinition = "TEXT") @Column(name = "resolution", columnDefinition = "TEXT")
private String resolution; private String resolution;
} }

View File

@@ -1,183 +1,183 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité Approbation de Transaction * Entité Approbation de Transaction
* *
* Représente une approbation dans le workflow financier multi-niveaux. * 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. * Chaque transaction financière au-dessus d'un certain seuil nécessite une ou plusieurs approbations.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-13 * @since 2026-03-13
*/ */
@Entity @Entity
@Table(name = "transaction_approvals", indexes = { @Table(name = "transaction_approvals", indexes = {
@Index(name = "idx_approval_transaction", columnList = "transaction_id"), @Index(name = "idx_approval_transaction", columnList = "transaction_id"),
@Index(name = "idx_approval_status", columnList = "status"), @Index(name = "idx_approval_status", columnList = "status"),
@Index(name = "idx_approval_requester", columnList = "requester_id"), @Index(name = "idx_approval_requester", columnList = "requester_id"),
@Index(name = "idx_approval_organisation", columnList = "organisation_id"), @Index(name = "idx_approval_organisation", columnList = "organisation_id"),
@Index(name = "idx_approval_created", columnList = "created_at"), @Index(name = "idx_approval_created", columnList = "created_at"),
@Index(name = "idx_approval_level", columnList = "required_level") @Index(name = "idx_approval_level", columnList = "required_level")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TransactionApproval extends BaseEntity { public class TransactionApproval extends BaseEntity {
/** ID de la transaction financière à approuver */ /** ID de la transaction financière à approuver */
@NotNull @NotNull
@Column(name = "transaction_id", nullable = false) @Column(name = "transaction_id", nullable = false)
private UUID transactionId; private UUID transactionId;
/** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */ /** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */
@NotBlank @NotBlank
@Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$") @Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$")
@Column(name = "transaction_type", nullable = false, length = 20) @Column(name = "transaction_type", nullable = false, length = 20)
private String transactionType; private String transactionType;
/** Montant de la transaction */ /** Montant de la transaction */
@NotNull @NotNull
@DecimalMin(value = "0.0", message = "Le montant doit être positif") @DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "amount", nullable = false, precision = 14, scale = 2) @Column(name = "amount", nullable = false, precision = 14, scale = 2)
private BigDecimal amount; private BigDecimal amount;
/** Code devise ISO 3 lettres */ /** Code devise ISO 3 lettres */
@NotBlank @NotBlank
@Pattern(regexp = "^[A-Z]{3}$") @Pattern(regexp = "^[A-Z]{3}$")
@Builder.Default @Builder.Default
@Column(name = "currency", nullable = false, length = 3) @Column(name = "currency", nullable = false, length = 3)
private String currency = "XOF"; private String currency = "XOF";
/** ID du membre demandeur */ /** ID du membre demandeur */
@NotNull @NotNull
@Column(name = "requester_id", nullable = false) @Column(name = "requester_id", nullable = false)
private UUID requesterId; private UUID requesterId;
/** Nom complet du demandeur (cache pour performance) */ /** Nom complet du demandeur (cache pour performance) */
@NotBlank @NotBlank
@Column(name = "requester_name", nullable = false, length = 200) @Column(name = "requester_name", nullable = false, length = 200)
private String requesterName; private String requesterName;
/** Organisation concernée (peut être null pour transactions globales) */ /** Organisation concernée (peut être null pour transactions globales) */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
/** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */ /** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */
@NotBlank @NotBlank
@Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$") @Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$")
@Column(name = "required_level", nullable = false, length = 10) @Column(name = "required_level", nullable = false, length = 10)
private String requiredLevel; private String requiredLevel;
/** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */ /** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */
@NotBlank @NotBlank
@Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$") @Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$")
@Builder.Default @Builder.Default
@Column(name = "status", nullable = false, length = 20) @Column(name = "status", nullable = false, length = 20)
private String status = "PENDING"; private String status = "PENDING";
/** Liste des actions d'approbateurs */ /** Liste des actions d'approbateurs */
@OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<ApproverAction> approvers = new ArrayList<>(); private List<ApproverAction> approvers = new ArrayList<>();
/** Raison du rejet (si status = REJECTED) */ /** Raison du rejet (si status = REJECTED) */
@Size(max = 1000) @Size(max = 1000)
@Column(name = "rejection_reason", length = 1000) @Column(name = "rejection_reason", length = 1000)
private String rejectionReason; private String rejectionReason;
/** Date de création de la demande d'approbation */ /** Date de création de la demande d'approbation */
@NotNull @NotNull
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** Date d'expiration (timeout) */ /** Date d'expiration (timeout) */
@Column(name = "expires_at") @Column(name = "expires_at")
private LocalDateTime expiresAt; private LocalDateTime expiresAt;
/** Date de completion (approbation finale ou rejet) */ /** Date de completion (approbation finale ou rejet) */
@Column(name = "completed_at") @Column(name = "completed_at")
private LocalDateTime completedAt; private LocalDateTime completedAt;
/** Métadonnées additionnelles (JSON) */ /** Métadonnées additionnelles (JSON) */
@Column(name = "metadata", columnDefinition = "TEXT") @Column(name = "metadata", columnDefinition = "TEXT")
private String metadata; private String metadata;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (createdAt == null) { if (createdAt == null) {
createdAt = LocalDateTime.now(); createdAt = LocalDateTime.now();
} }
if (currency == null) { if (currency == null) {
currency = "XOF"; currency = "XOF";
} }
if (status == null) { if (status == null) {
status = "PENDING"; status = "PENDING";
} }
// Expiration par défaut: 7 jours // Expiration par défaut: 7 jours
if (expiresAt == null) { if (expiresAt == null) {
expiresAt = createdAt.plusDays(7); expiresAt = createdAt.plusDays(7);
} }
} }
/** Méthode métier pour ajouter une action d'approbateur */ /** Méthode métier pour ajouter une action d'approbateur */
public void addApproverAction(ApproverAction action) { public void addApproverAction(ApproverAction action) {
approvers.add(action); approvers.add(action);
action.setApproval(this); action.setApproval(this);
} }
/** Méthode métier pour compter les approbations */ /** Méthode métier pour compter les approbations */
public long countApprovals() { public long countApprovals() {
return approvers.stream() return approvers.stream()
.filter(a -> "APPROVED".equals(a.getDecision())) .filter(a -> "APPROVED".equals(a.getDecision()))
.count(); .count();
} }
/** Méthode métier pour obtenir le nombre d'approbations requises */ /** Méthode métier pour obtenir le nombre d'approbations requises */
public int getRequiredApprovals() { public int getRequiredApprovals() {
return switch (requiredLevel) { return switch (requiredLevel) {
case "NONE" -> 0; case "NONE" -> 0;
case "LEVEL1" -> 1; case "LEVEL1" -> 1;
case "LEVEL2" -> 2; case "LEVEL2" -> 2;
case "LEVEL3" -> 3; case "LEVEL3" -> 3;
default -> 0; default -> 0;
}; };
} }
/** Méthode métier pour vérifier si toutes les approbations sont reçues */ /** Méthode métier pour vérifier si toutes les approbations sont reçues */
public boolean hasAllApprovals() { public boolean hasAllApprovals() {
return countApprovals() >= getRequiredApprovals(); return countApprovals() >= getRequiredApprovals();
} }
/** Méthode métier pour vérifier si l'approbation est expirée */ /** Méthode métier pour vérifier si l'approbation est expirée */
public boolean isExpired() { public boolean isExpired() {
return expiresAt != null && LocalDateTime.now().isAfter(expiresAt); return expiresAt != null && LocalDateTime.now().isAfter(expiresAt);
} }
/** Méthode métier pour vérifier si l'approbation est en attente */ /** Méthode métier pour vérifier si l'approbation est en attente */
public boolean isPending() { public boolean isPending() {
return "PENDING".equals(status); return "PENDING".equals(status);
} }
/** Méthode métier pour vérifier si l'approbation est complétée */ /** Méthode métier pour vérifier si l'approbation est complétée */
public boolean isCompleted() { public boolean isCompleted() {
return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status); return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status);
} }
} }

View File

@@ -1,164 +1,164 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave; import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité TransactionWave pour le suivi des transactions Wave Mobile Money * Entité TransactionWave pour le suivi des transactions Wave Mobile Money
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table( @Table(
name = "transactions_wave", name = "transactions_wave",
indexes = { indexes = {
@Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true), @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_request_id", columnList = "wave_request_id"),
@Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"), @Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"),
@Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"), @Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"),
@Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id") @Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TransactionWave extends BaseEntity { public class TransactionWave extends BaseEntity {
/** Identifiant Wave de la transaction (unique) */ /** Identifiant Wave de la transaction (unique) */
@NotBlank @NotBlank
@Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100) @Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100)
private String waveTransactionId; private String waveTransactionId;
/** Identifiant de requête Wave */ /** Identifiant de requête Wave */
@Column(name = "wave_request_id", length = 100) @Column(name = "wave_request_id", length = 100)
private String waveRequestId; private String waveRequestId;
/** Référence Wave */ /** Référence Wave */
@Column(name = "wave_reference", length = 100) @Column(name = "wave_reference", length = 100)
private String waveReference; private String waveReference;
/** Type de transaction */ /** Type de transaction */
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_transaction", nullable = false, length = 50) @Column(name = "type_transaction", nullable = false, length = 50)
private TypeTransactionWave typeTransaction; private TypeTransactionWave typeTransaction;
/** Statut de la transaction */ /** Statut de la transaction */
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut_transaction", nullable = false, length = 30) @Column(name = "statut_transaction", nullable = false, length = 30)
private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE; private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE;
/** Montant de la transaction */ /** Montant de la transaction */
@NotNull @NotNull
@DecimalMin(value = "0.0", message = "Le montant doit être positif") @DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant", nullable = false, precision = 14, scale = 2) @Column(name = "montant", nullable = false, precision = 14, scale = 2)
private BigDecimal montant; private BigDecimal montant;
/** Frais de transaction */ /** Frais de transaction */
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 10, fraction = 2) @Digits(integer = 10, fraction = 2)
@Column(name = "frais", precision = 12, scale = 2) @Column(name = "frais", precision = 12, scale = 2)
private BigDecimal frais; private BigDecimal frais;
/** Montant net (montant - frais) */ /** Montant net (montant - frais) */
@DecimalMin(value = "0.0") @DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2) @Digits(integer = 12, fraction = 2)
@Column(name = "montant_net", precision = 14, scale = 2) @Column(name = "montant_net", precision = 14, scale = 2)
private BigDecimal montantNet; private BigDecimal montantNet;
/** Code devise */ /** Code devise */
@NotBlank @NotBlank
@Pattern(regexp = "^[A-Z]{3}$") @Pattern(regexp = "^[A-Z]{3}$")
@Column(name = "code_devise", nullable = false, length = 3) @Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise; private String codeDevise;
/** Numéro téléphone payeur */ /** Numéro téléphone payeur */
@Column(name = "telephone_payeur", length = 13) @Column(name = "telephone_payeur", length = 13)
private String telephonePayeur; private String telephonePayeur;
/** Numéro téléphone bénéficiaire */ /** Numéro téléphone bénéficiaire */
@Column(name = "telephone_beneficiaire", length = 13) @Column(name = "telephone_beneficiaire", length = 13)
private String telephoneBeneficiaire; private String telephoneBeneficiaire;
/** Métadonnées JSON (réponse complète de Wave API) */ /** Métadonnées JSON (réponse complète de Wave API) */
@Column(name = "metadonnees", columnDefinition = "TEXT") @Column(name = "metadonnees", columnDefinition = "TEXT")
private String metadonnees; private String metadonnees;
/** Réponse complète de Wave API (JSON) */ /** Réponse complète de Wave API (JSON) */
@Column(name = "reponse_wave_api", columnDefinition = "TEXT") @Column(name = "reponse_wave_api", columnDefinition = "TEXT")
private String reponseWaveApi; private String reponseWaveApi;
/** Nombre de tentatives */ /** Nombre de tentatives */
@Builder.Default @Builder.Default
@Column(name = "nombre_tentatives", nullable = false) @Column(name = "nombre_tentatives", nullable = false)
private Integer nombreTentatives = 0; private Integer nombreTentatives = 0;
/** Date de dernière tentative */ /** Date de dernière tentative */
@Column(name = "date_derniere_tentative") @Column(name = "date_derniere_tentative")
private LocalDateTime dateDerniereTentative; private LocalDateTime dateDerniereTentative;
/** Message d'erreur (si échec) */ /** Message d'erreur (si échec) */
@Column(name = "message_erreur", length = 1000) @Column(name = "message_erreur", length = 1000)
private String messageErreur; private String messageErreur;
// Relations // Relations
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_wave_id", nullable = false) @JoinColumn(name = "compte_wave_id", nullable = false)
private CompteWave compteWave; private CompteWave compteWave;
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default @Builder.Default
private List<WebhookWave> webhooks = new ArrayList<>(); private List<WebhookWave> webhooks = new ArrayList<>();
/** Méthode métier pour vérifier si la transaction est réussie */ /** Méthode métier pour vérifier si la transaction est réussie */
public boolean isReussie() { public boolean isReussie() {
return StatutTransactionWave.REUSSIE.equals(statutTransaction); return StatutTransactionWave.REUSSIE.equals(statutTransaction);
} }
/** Méthode métier pour vérifier si la transaction peut être retentée */ /** Méthode métier pour vérifier si la transaction peut être retentée */
public boolean peutEtreRetentee() { public boolean peutEtreRetentee() {
return (statutTransaction == StatutTransactionWave.ECHOUE return (statutTransaction == StatutTransactionWave.ECHOUE
|| statutTransaction == StatutTransactionWave.EXPIRED) || statutTransaction == StatutTransactionWave.EXPIRED)
&& (nombreTentatives == null || nombreTentatives < 5); && (nombreTentatives == null || nombreTentatives < 5);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statutTransaction == null) { if (statutTransaction == null) {
statutTransaction = StatutTransactionWave.INITIALISE; statutTransaction = StatutTransactionWave.INITIALISE;
} }
if (codeDevise == null || codeDevise.isEmpty()) { if (codeDevise == null || codeDevise.isEmpty()) {
codeDevise = "XOF"; codeDevise = "XOF";
} }
if (nombreTentatives == null) { if (nombreTentatives == null) {
nombreTentatives = 0; nombreTentatives = 0;
} }
if (montantNet == null && montant != null && frais != null) { if (montantNet == null && montant != null && frais != null) {
montantNet = montant.subtract(frais); montantNet = montant.subtract(frais);
} }
} }
} }

View File

@@ -1,206 +1,206 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.Index; import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint; import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Donnée de référence paramétrable via le client. * Donnée de référence paramétrable via le client.
* *
* <p> * <p>
* Remplace toutes les enums Java et valeurs hardcodées * Remplace toutes les enums Java et valeurs hardcodées
* par une table unique CRUD-able depuis l'interface * par une table unique CRUD-able depuis l'interface
* d'administration. Chaque ligne appartient à un * d'administration. Chaque ligne appartient à un
* {@code domaine} (ex: STATUT_ORGANISATION, DEVISE) * {@code domaine} (ex: STATUT_ORGANISATION, DEVISE)
* et porte un {@code code} unique dans ce domaine. * et porte un {@code code} unique dans ce domaine.
* *
* <p> * <p>
* Le champ {@code organisation} permet une * Le champ {@code organisation} permet une
* personnalisation par organisation. Lorsqu'il est * personnalisation par organisation. Lorsqu'il est
* {@code null}, la valeur est globale à la plateforme. * {@code null}, la valeur est globale à la plateforme.
* *
* <p> * <p>
* Table : {@code types_reference} * Table : {@code types_reference}
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2026-02-21 * @since 2026-02-21
*/ */
@Entity @Entity
@Table(name = "types_reference", indexes = { @Table(name = "types_reference", indexes = {
@Index(name = "idx_typeref_domaine", columnList = "domaine"), @Index(name = "idx_typeref_domaine", columnList = "domaine"),
@Index(name = "idx_typeref_domaine_actif", columnList = "domaine, actif, ordre_affichage"), @Index(name = "idx_typeref_domaine_actif", columnList = "domaine, actif, ordre_affichage"),
@Index(name = "idx_typeref_org", columnList = "organisation_id") @Index(name = "idx_typeref_org", columnList = "organisation_id")
}, uniqueConstraints = { }, uniqueConstraints = {
@UniqueConstraint(name = "uk_typeref_domaine_code_org", columnNames = { @UniqueConstraint(name = "uk_typeref_domaine_code_org", columnNames = {
"domaine", "code", "organisation_id" "domaine", "code", "organisation_id"
}) })
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TypeReference extends BaseEntity { public class TypeReference extends BaseEntity {
/** /**
* Domaine fonctionnel de cette valeur de référence. * Domaine fonctionnel de cette valeur de référence.
* *
* <p> * <p>
* Exemples : {@code STATUT_ORGANISATION}, * Exemples : {@code STATUT_ORGANISATION},
* {@code TYPE_ORGANISATION}, {@code DEVISE}. * {@code TYPE_ORGANISATION}, {@code DEVISE}.
*/ */
@NotBlank @NotBlank
@Size(max = 50) @Size(max = 50)
@Column(name = "domaine", nullable = false, length = 50) @Column(name = "domaine", nullable = false, length = 50)
private String domaine; private String domaine;
/** /**
* Code technique unique au sein du domaine. * Code technique unique au sein du domaine.
* *
* <p> * <p>
* Exemples : {@code ACTIVE}, {@code XOF}, * Exemples : {@code ACTIVE}, {@code XOF},
* {@code ASSOCIATION}. * {@code ASSOCIATION}.
*/ */
@NotBlank @NotBlank
@Size(max = 50) @Size(max = 50)
@Column(name = "code", nullable = false, length = 50) @Column(name = "code", nullable = false, length = 50)
private String code; private String code;
/** /**
* Libellé affiché dans l'interface utilisateur. * Libellé affiché dans l'interface utilisateur.
* *
* <p> * <p>
* Exemple : {@code "Franc CFA (UEMOA)"}. * Exemple : {@code "Franc CFA (UEMOA)"}.
*/ */
@NotBlank @NotBlank
@Size(max = 200) @Size(max = 200)
@Column(name = "libelle", nullable = false, length = 200) @Column(name = "libelle", nullable = false, length = 200)
private String libelle; private String libelle;
/** Description longue optionnelle. */ /** Description longue optionnelle. */
@Size(max = 1000) @Size(max = 1000)
@Column(name = "description", length = 1000) @Column(name = "description", length = 1000)
private String description; private String description;
/** /**
* Classe d'icône pour le rendu UI. * Classe d'icône pour le rendu UI.
* *
* <p> * <p>
* Exemple : {@code "pi-check-circle"}. * Exemple : {@code "pi-check-circle"}.
*/ */
@Size(max = 100) @Size(max = 100)
@Column(name = "icone", length = 100) @Column(name = "icone", length = 100)
private String icone; private String icone;
/** /**
* Code couleur hexadécimal pour le rendu UI. * Code couleur hexadécimal pour le rendu UI.
* *
* <p> * <p>
* Exemple : {@code "#22C55E"}. * Exemple : {@code "#22C55E"}.
*/ */
@Size(max = 50) @Size(max = 50)
@Column(name = "couleur", length = 50) @Column(name = "couleur", length = 50)
private String couleur; private String couleur;
/** /**
* Niveau de sévérité pour les badges PrimeFaces. * Niveau de sévérité pour les badges PrimeFaces.
* *
* <p> * <p>
* Valeurs typiques : {@code success}, * Valeurs typiques : {@code success},
* {@code warning}, {@code danger}, {@code info}. * {@code warning}, {@code danger}, {@code info}.
*/ */
@Size(max = 20) @Size(max = 20)
@Column(name = "severity", length = 20) @Column(name = "severity", length = 20)
private String severity; private String severity;
/** /**
* Ordre d'affichage dans les listes déroulantes. * Ordre d'affichage dans les listes déroulantes.
* *
* <p> * <p>
* Les valeurs avec un ordre inférieur * Les valeurs avec un ordre inférieur
* apparaissent en premier. * apparaissent en premier.
*/ */
@Builder.Default @Builder.Default
@Column(name = "ordre_affichage", nullable = false) @Column(name = "ordre_affichage", nullable = false)
private Integer ordreAffichage = 0; private Integer ordreAffichage = 0;
/** /**
* Indique si cette valeur est la valeur par défaut * Indique si cette valeur est la valeur par défaut
* pour son domaine. Une seule valeur par défaut * pour son domaine. Une seule valeur par défaut
* est autorisée par domaine et organisation. * est autorisée par domaine et organisation.
*/ */
@Builder.Default @Builder.Default
@Column(name = "est_defaut", nullable = false) @Column(name = "est_defaut", nullable = false)
private Boolean estDefaut = false; private Boolean estDefaut = false;
/** /**
* Indique si cette valeur est protégée par le * Indique si cette valeur est protégée par le
* système. Les valeurs système ne peuvent être * système. Les valeurs système ne peuvent être
* ni supprimées ni désactivées par un * ni supprimées ni désactivées par un
* administrateur. * administrateur.
*/ */
@Builder.Default @Builder.Default
@Column(name = "est_systeme", nullable = false) @Column(name = "est_systeme", nullable = false)
private Boolean estSysteme = false; private Boolean estSysteme = false;
/** /**
* Catégorie fonctionnelle (ex: ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX…). * Catégorie fonctionnelle (ex: ASSOCIATIF, FINANCIER_SOLIDAIRE, RELIGIEUX…).
* Utilisée pour les types d'organisation (domaine TYPE_ORGANISATION). * Utilisée pour les types d'organisation (domaine TYPE_ORGANISATION).
*/ */
@Size(max = 50) @Size(max = 50)
@Column(name = "categorie", length = 50) @Column(name = "categorie", length = 50)
private String categorie; private String categorie;
/** /**
* Liste CSV des modules activés pour ce type d'organisation. * Liste CSV des modules activés pour ce type d'organisation.
* Exemple : "MEMBRES,COTISATIONS,TONTINE,FINANCE" * Exemple : "MEMBRES,COTISATIONS,TONTINE,FINANCE"
* Utilisée pour initialiser {@code Organisation.modulesActifs} à la création. * Utilisée pour initialiser {@code Organisation.modulesActifs} à la création.
*/ */
@Column(name = "modules_requis", columnDefinition = "TEXT") @Column(name = "modules_requis", columnDefinition = "TEXT")
private String modulesRequis; private String modulesRequis;
/** /**
* Organisation propriétaire de cette valeur. * Organisation propriétaire de cette valeur.
* *
* <p> * <p>
* Lorsque {@code null}, la valeur est globale * Lorsque {@code null}, la valeur est globale
* à la plateforme et visible par toutes les * à la plateforme et visible par toutes les
* organisations. * organisations.
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id") @JoinColumn(name = "organisation_id")
private Organisation organisation; private Organisation organisation;
/** /**
* Callback JPA exécuté avant la persistance. * Callback JPA exécuté avant la persistance.
* *
* <p> * <p>
* Normalise le code et le domaine en * Normalise le code et le domaine en
* majuscules pour garantir la cohérence. * majuscules pour garantir la cohérence.
*/ */
@Override @Override
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (domaine != null) { if (domaine != null) {
domaine = domaine.toUpperCase(); domaine = domaine.toUpperCase();
} }
if (code != null) { if (code != null) {
code = code.toUpperCase(); code = code.toUpperCase();
} }
} }
} }

View File

@@ -1,91 +1,91 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape; import dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.*; import lombok.*;
/** /**
* Historique des validations pour une demande d'aide. * Historique des validations pour une demande d'aide.
* *
* <p>Chaque ligne représente l'état d'une étape du workflow pour une demande. * <p>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. * La délégation de véto (valideur absent) est tracée avec motif — conformité BCEAO/OHADA.
* *
* <p>Table : {@code validation_etapes_demande} * <p>Table : {@code validation_etapes_demande}
*/ */
@Entity @Entity
@Table( @Table(
name = "validation_etapes_demande", name = "validation_etapes_demande",
indexes = { indexes = {
@Index(name = "idx_ved_demande", columnList = "demande_aide_id"), @Index(name = "idx_ved_demande", columnList = "demande_aide_id"),
@Index(name = "idx_ved_valideur", columnList = "valideur_id"), @Index(name = "idx_ved_valideur", columnList = "valideur_id"),
@Index(name = "idx_ved_statut", columnList = "statut") @Index(name = "idx_ved_statut", columnList = "statut")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ValidationEtapeDemande extends BaseEntity { public class ValidationEtapeDemande extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demande_aide_id", nullable = false) @JoinColumn(name = "demande_aide_id", nullable = false)
private DemandeAide demandeAide; private DemandeAide demandeAide;
@NotNull @NotNull
@Min(1) @Max(3) @Min(1) @Max(3)
@Column(name = "etape_numero", nullable = false) @Column(name = "etape_numero", nullable = false)
private Integer etapeNumero; private Integer etapeNumero;
/** Valideur assigné à cette étape */ /** Valideur assigné à cette étape */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "valideur_id") @JoinColumn(name = "valideur_id")
private Membre valideur; private Membre valideur;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Builder.Default @Builder.Default
@Column(name = "statut", nullable = false, length = 20) @Column(name = "statut", nullable = false, length = 20)
private StatutValidationEtape statut = StatutValidationEtape.EN_ATTENTE; private StatutValidationEtape statut = StatutValidationEtape.EN_ATTENTE;
@Column(name = "date_validation") @Column(name = "date_validation")
private LocalDateTime dateValidation; private LocalDateTime dateValidation;
@Column(name = "commentaire", length = 1000) @Column(name = "commentaire", length = 1000)
private String commentaire; private String commentaire;
/** /**
* Valideur supérieur qui a désactivé le véto de {@code valideur}. * Valideur supérieur qui a désactivé le véto de {@code valideur}.
* Renseigné uniquement en cas de délégation. * Renseigné uniquement en cas de délégation.
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "delegue_par_id") @JoinColumn(name = "delegue_par_id")
private Membre deleguePar; private Membre deleguePar;
/** /**
* Motif et trace de la délégation — obligatoire si {@code deleguePar} est renseigné. * Motif et trace de la délégation — obligatoire si {@code deleguePar} est renseigné.
* Conservé 10 ans — exigence BCEAO/OHADA/Fiscalité ivoirienne. * Conservé 10 ans — exigence BCEAO/OHADA/Fiscalité ivoirienne.
*/ */
@Column(name = "trace_delegation", columnDefinition = "TEXT") @Column(name = "trace_delegation", columnDefinition = "TEXT")
private String traceDelegation; private String traceDelegation;
// ── Méthodes métier ──────────────────────────────────────────────────────── // ── Méthodes métier ────────────────────────────────────────────────────────
public boolean estEnAttente() { public boolean estEnAttente() {
return StatutValidationEtape.EN_ATTENTE.equals(statut); return StatutValidationEtape.EN_ATTENTE.equals(statut);
} }
public boolean estFinalisee() { public boolean estFinalisee() {
return StatutValidationEtape.APPROUVEE.equals(statut) return StatutValidationEtape.APPROUVEE.equals(statut)
|| StatutValidationEtape.REJETEE.equals(statut) || StatutValidationEtape.REJETEE.equals(statut)
|| StatutValidationEtape.DELEGUEE.equals(statut) || StatutValidationEtape.DELEGUEE.equals(statut)
|| StatutValidationEtape.EXPIREE.equals(statut); || StatutValidationEtape.EXPIREE.equals(statut);
} }
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statut == null) statut = StatutValidationEtape.EN_ATTENTE; if (statut == null) statut = StatutValidationEtape.EN_ATTENTE;
} }
} }

View File

@@ -1,114 +1,114 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook; import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook; import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* Entité WebhookWave pour le traitement des événements Wave * Entité WebhookWave pour le traitement des événements Wave
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2025-01-29 * @since 2025-01-29
*/ */
@Entity @Entity
@Table(name = "webhooks_wave", indexes = { @Table(name = "webhooks_wave", indexes = {
@Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true), @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_statut", columnList = "statut_traitement"),
@Index(name = "idx_webhook_wave_type", columnList = "type_evenement"), @Index(name = "idx_webhook_wave_type", columnList = "type_evenement"),
@Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"), @Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"),
@Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id") @Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class WebhookWave extends BaseEntity { public class WebhookWave extends BaseEntity {
/** Identifiant unique de l'événement Wave */ /** Identifiant unique de l'événement Wave */
@NotBlank @NotBlank
@Column(name = "wave_event_id", unique = true, nullable = false, length = 100) @Column(name = "wave_event_id", unique = true, nullable = false, length = 100)
private String waveEventId; private String waveEventId;
/** Type d'événement */ /** Type d'événement */
@Column(name = "type_evenement", length = 50) @Column(name = "type_evenement", length = 50)
private String typeEvenement; private String typeEvenement;
/** Statut de traitement */ /** Statut de traitement */
@Builder.Default @Builder.Default
@Column(name = "statut_traitement", nullable = false, length = 30) @Column(name = "statut_traitement", nullable = false, length = 30)
private String statutTraitement = StatutWebhook.EN_ATTENTE.name(); private String statutTraitement = StatutWebhook.EN_ATTENTE.name();
/** Payload JSON reçu */ /** Payload JSON reçu */
@Column(name = "payload", columnDefinition = "TEXT") @Column(name = "payload", columnDefinition = "TEXT")
private String payload; private String payload;
/** Signature de validation */ /** Signature de validation */
@Column(name = "signature", length = 500) @Column(name = "signature", length = 500)
private String signature; private String signature;
/** Date de réception */ /** Date de réception */
@Column(name = "date_reception") @Column(name = "date_reception")
private LocalDateTime dateReception; private LocalDateTime dateReception;
/** Date de traitement */ /** Date de traitement */
@Column(name = "date_traitement") @Column(name = "date_traitement")
private LocalDateTime dateTraitement; private LocalDateTime dateTraitement;
/** Nombre de tentatives de traitement */ /** Nombre de tentatives de traitement */
@Builder.Default @Builder.Default
@Column(name = "nombre_tentatives", nullable = false) @Column(name = "nombre_tentatives", nullable = false)
private Integer nombreTentatives = 0; private Integer nombreTentatives = 0;
/** Message d'erreur (si échec) */ /** Message d'erreur (si échec) */
@Column(name = "message_erreur", length = 1000) @Column(name = "message_erreur", length = 1000)
private String messageErreur; private String messageErreur;
/** Commentaires */ /** Commentaires */
@Column(name = "commentaire", length = 500) @Column(name = "commentaire", length = 500)
private String commentaire; private String commentaire;
// Relations // Relations
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transaction_wave_id") @JoinColumn(name = "transaction_wave_id")
private TransactionWave transactionWave; private TransactionWave transactionWave;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id") @JoinColumn(name = "paiement_id")
private Versement versement; private Versement versement;
/** Méthode métier pour vérifier si le webhook est traité */ /** Méthode métier pour vérifier si le webhook est traité */
public boolean isTraite() { public boolean isTraite() {
return StatutWebhook.TRAITE.name().equals(statutTraitement); return StatutWebhook.TRAITE.name().equals(statutTraitement);
} }
/** Méthode métier pour vérifier si le webhook peut être retenté */ /** Méthode métier pour vérifier si le webhook peut être retenté */
public boolean peutEtreRetente() { public boolean peutEtreRetente() {
return (StatutWebhook.ECHOUE.name().equals(statutTraitement) return (StatutWebhook.ECHOUE.name().equals(statutTraitement)
|| StatutWebhook.EN_ATTENTE.name().equals(statutTraitement)) || StatutWebhook.EN_ATTENTE.name().equals(statutTraitement))
&& (nombreTentatives == null || nombreTentatives < 5); && (nombreTentatives == null || nombreTentatives < 5);
} }
/** Callback JPA avant la persistance */ /** Callback JPA avant la persistance */
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
super.onCreate(); super.onCreate();
if (statutTraitement == null) { if (statutTraitement == null) {
statutTraitement = StatutWebhook.EN_ATTENTE.name(); statutTraitement = StatutWebhook.EN_ATTENTE.name();
} }
if (dateReception == null) { if (dateReception == null) {
dateReception = LocalDateTime.now(); dateReception = LocalDateTime.now();
} }
if (nombreTentatives == null) { if (nombreTentatives == null) {
nombreTentatives = 0; nombreTentatives = 0;
} }
} }
} }

View File

@@ -1,66 +1,66 @@
package dev.lions.unionflow.server.entity; package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.solidarite.TypeWorkflow; import dev.lions.unionflow.server.api.enums.solidarite.TypeWorkflow;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.*; import lombok.*;
/** /**
* Configuration du workflow de validation pour une organisation. * Configuration du workflow de validation pour une organisation.
* *
* <p>Maximum 3 étapes ordonnées. Chaque étape requiert un rôle spécifique. * <p>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). * Exemple Mutuelle Y : Secrétaire (étape 1) → Trésorier (étape 2) → Président (étape 3).
* *
* <p>Table : {@code workflow_validation_config} * <p>Table : {@code workflow_validation_config}
*/ */
@Entity @Entity
@Table( @Table(
name = "workflow_validation_config", name = "workflow_validation_config",
indexes = { indexes = {
@Index(name = "idx_wf_organisation", columnList = "organisation_id"), @Index(name = "idx_wf_organisation", columnList = "organisation_id"),
@Index(name = "idx_wf_type", columnList = "type_workflow") @Index(name = "idx_wf_type", columnList = "type_workflow")
}, },
uniqueConstraints = { uniqueConstraints = {
@UniqueConstraint( @UniqueConstraint(
name = "uk_wf_org_type_etape", name = "uk_wf_org_type_etape",
columnNames = {"organisation_id", "type_workflow", "etape_numero"}) columnNames = {"organisation_id", "type_workflow", "etape_numero"})
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class WorkflowValidationConfig extends BaseEntity { public class WorkflowValidationConfig extends BaseEntity {
@NotNull @NotNull
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@NotNull @NotNull
@Builder.Default @Builder.Default
@Column(name = "type_workflow", nullable = false, length = 30) @Column(name = "type_workflow", nullable = false, length = 30)
private TypeWorkflow typeWorkflow = TypeWorkflow.DEMANDE_AIDE; private TypeWorkflow typeWorkflow = TypeWorkflow.DEMANDE_AIDE;
/** Numéro d'ordre de l'étape (1, 2 ou 3) */ /** Numéro d'ordre de l'étape (1, 2 ou 3) */
@NotNull @NotNull
@Min(1) @Max(3) @Min(1) @Max(3)
@Column(name = "etape_numero", nullable = false) @Column(name = "etape_numero", nullable = false)
private Integer etapeNumero; private Integer etapeNumero;
/** Rôle nécessaire pour valider cette étape */ /** Rôle nécessaire pour valider cette étape */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_requis_id") @JoinColumn(name = "role_requis_id")
private Role roleRequis; private Role roleRequis;
@NotBlank @NotBlank
@Column(name = "libelle_etape", nullable = false, length = 200) @Column(name = "libelle_etape", nullable = false, length = 200)
private String libelleEtape; private String libelleEtape;
/** Délai maximum en heures avant expiration automatique (SLA) */ /** Délai maximum en heures avant expiration automatique (SLA) */
@Builder.Default @Builder.Default
@Min(1) @Min(1)
@Column(name = "delai_max_heures", nullable = false) @Column(name = "delai_max_heures", nullable = false)
private Integer delaiMaxHeures = 72; private Integer delaiMaxHeures = 72;
} }

View File

@@ -1,50 +1,50 @@
package dev.lions.unionflow.server.entity.agricole; package dev.lions.unionflow.server.entity.agricole;
import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole; import dev.lions.unionflow.server.api.enums.agricole.StatutCampagneAgricole;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@Entity @Entity
@Table(name = "campagnes_agricoles", indexes = { @Table(name = "campagnes_agricoles", indexes = {
@Index(name = "idx_agricole_organisation", columnList = "organisation_id") @Index(name = "idx_agricole_organisation", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CampagneAgricole extends BaseEntity { public class CampagneAgricole extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Column(name = "designation", nullable = false, length = 200) @Column(name = "designation", nullable = false, length = 200)
private String designation; private String designation;
@Column(name = "type_culture", length = 100) @Column(name = "type_culture", length = 100)
private String typeCulturePrincipale; private String typeCulturePrincipale;
@Column(name = "surface_estimee_ha", precision = 19, scale = 4) @Column(name = "surface_estimee_ha", precision = 19, scale = 4)
private BigDecimal surfaceTotaleEstimeeHectares; private BigDecimal surfaceTotaleEstimeeHectares;
@Column(name = "volume_prev_tonnes", precision = 19, scale = 4) @Column(name = "volume_prev_tonnes", precision = 19, scale = 4)
private BigDecimal volumePrevisionnelTonnes; private BigDecimal volumePrevisionnelTonnes;
@Column(name = "volume_reel_tonnes", precision = 19, scale = 4) @Column(name = "volume_reel_tonnes", precision = 19, scale = 4)
private BigDecimal volumeReelTonnes; private BigDecimal volumeReelTonnes;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutCampagneAgricole statut = StatutCampagneAgricole.PREPARATION; private StatutCampagneAgricole statut = StatutCampagneAgricole.PREPARATION;
} }

View File

@@ -1,71 +1,71 @@
package dev.lions.unionflow.server.entity.collectefonds; package dev.lions.unionflow.server.entity.collectefonds;
import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte; import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "campagnes_collecte", indexes = { @Table(name = "campagnes_collecte", indexes = {
@Index(name = "idx_collecte_organisation", columnList = "organisation_id") @Index(name = "idx_collecte_organisation", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CampagneCollecte extends BaseEntity { public class CampagneCollecte extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Column(name = "titre", nullable = false, length = 200) @Column(name = "titre", nullable = false, length = 200)
private String titre; private String titre;
@Column(name = "courte_description", length = 500) @Column(name = "courte_description", length = 500)
private String courteDescription; private String courteDescription;
@Column(name = "html_description_complete", columnDefinition = "TEXT") @Column(name = "html_description_complete", columnDefinition = "TEXT")
private String htmlDescriptionComplete; private String htmlDescriptionComplete;
@Column(name = "image_banniere_url", length = 500) @Column(name = "image_banniere_url", length = 500)
private String imageBanniereUrl; private String imageBanniereUrl;
@Column(name = "objectif_financier", precision = 19, scale = 4) @Column(name = "objectif_financier", precision = 19, scale = 4)
private BigDecimal objectifFinancier; private BigDecimal objectifFinancier;
@Column(name = "montant_collecte_actuel", precision = 19, scale = 4) @Column(name = "montant_collecte_actuel", precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal montantCollecteActuel = BigDecimal.ZERO; private BigDecimal montantCollecteActuel = BigDecimal.ZERO;
@Column(name = "nombre_donateurs") @Column(name = "nombre_donateurs")
@Builder.Default @Builder.Default
private Integer nombreDonateurs = 0; private Integer nombreDonateurs = 0;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutCampagneCollecte statut = StatutCampagneCollecte.BROUILLON; private StatutCampagneCollecte statut = StatutCampagneCollecte.BROUILLON;
@NotNull @NotNull
@Column(name = "date_ouverture", nullable = false) @Column(name = "date_ouverture", nullable = false)
@Builder.Default @Builder.Default
private LocalDateTime dateOuverture = LocalDateTime.now(); private LocalDateTime dateOuverture = LocalDateTime.now();
@Column(name = "date_cloture_prevue") @Column(name = "date_cloture_prevue")
private LocalDateTime dateCloturePrevue; private LocalDateTime dateCloturePrevue;
@Column(name = "est_publique", nullable = false) @Column(name = "est_publique", nullable = false)
@Builder.Default @Builder.Default
private Boolean estPublique = true; private Boolean estPublique = true;
} }

View File

@@ -1,59 +1,59 @@
package dev.lions.unionflow.server.entity.collectefonds; package dev.lions.unionflow.server.entity.collectefonds;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "contributions_collecte", indexes = { @Table(name = "contributions_collecte", indexes = {
@Index(name = "idx_contribution_campagne", columnList = "campagne_id"), @Index(name = "idx_contribution_campagne", columnList = "campagne_id"),
@Index(name = "idx_contribution_membre", columnList = "membre_donateur_id") @Index(name = "idx_contribution_membre", columnList = "membre_donateur_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ContributionCollecte extends BaseEntity { public class ContributionCollecte extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "campagne_id", nullable = false) @JoinColumn(name = "campagne_id", nullable = false)
private CampagneCollecte campagne; private CampagneCollecte campagne;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_donateur_id") @JoinColumn(name = "membre_donateur_id")
private Membre membreDonateur; private Membre membreDonateur;
@Column(name = "alias_donateur", length = 150) @Column(name = "alias_donateur", length = 150)
private String aliasDonateur; private String aliasDonateur;
@Column(name = "est_anonyme", nullable = false) @Column(name = "est_anonyme", nullable = false)
@Builder.Default @Builder.Default
private Boolean estAnonyme = false; private Boolean estAnonyme = false;
@NotNull @NotNull
@Column(name = "montant_soutien", nullable = false, precision = 19, scale = 4) @Column(name = "montant_soutien", nullable = false, precision = 19, scale = 4)
private BigDecimal montantSoutien; private BigDecimal montantSoutien;
@Column(name = "message_soutien", length = 500) @Column(name = "message_soutien", length = 500)
private String messageSoutien; private String messageSoutien;
@NotNull @NotNull
@Column(name = "date_contribution", nullable = false) @Column(name = "date_contribution", nullable = false)
@Builder.Default @Builder.Default
private LocalDateTime dateContribution = LocalDateTime.now(); private LocalDateTime dateContribution = LocalDateTime.now();
@Column(name = "transaction_paiement_id", length = 100) @Column(name = "transaction_paiement_id", length = 100)
private String transactionPaiementId; private String transactionPaiementId;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut_paiement", length = 50) @Column(name = "statut_paiement", length = 50)
private StatutTransactionWave statutPaiement; private StatutTransactionWave statutPaiement;
} }

View File

@@ -1,51 +1,51 @@
package dev.lions.unionflow.server.entity.culte; package dev.lions.unionflow.server.entity.culte;
import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux; import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "dons_religieux", indexes = { @Table(name = "dons_religieux", indexes = {
@Index(name = "idx_don_c_organisation", columnList = "institution_id"), @Index(name = "idx_don_c_organisation", columnList = "institution_id"),
@Index(name = "idx_don_c_fidele", columnList = "fidele_id") @Index(name = "idx_don_c_fidele", columnList = "fidele_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class DonReligieux extends BaseEntity { public class DonReligieux extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "institution_id", nullable = false) @JoinColumn(name = "institution_id", nullable = false)
private Organisation institution; private Organisation institution;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fidele_id") @JoinColumn(name = "fidele_id")
private Membre fidele; private Membre fidele;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_don", nullable = false, length = 50) @Column(name = "type_don", nullable = false, length = 50)
private TypeDonReligieux typeDon; private TypeDonReligieux typeDon;
@NotNull @NotNull
@Column(name = "montant", nullable = false, precision = 19, scale = 4) @Column(name = "montant", nullable = false, precision = 19, scale = 4)
private BigDecimal montant; private BigDecimal montant;
@NotNull @NotNull
@Column(name = "date_encaissement", nullable = false) @Column(name = "date_encaissement", nullable = false)
@Builder.Default @Builder.Default
private LocalDateTime dateEncaissement = LocalDateTime.now(); private LocalDateTime dateEncaissement = LocalDateTime.now();
@Column(name = "periode_nature", length = 150) @Column(name = "periode_nature", length = 150)
private String periodeOuNatureAssociee; private String periodeOuNatureAssociee;
} }

View File

@@ -1,43 +1,43 @@
package dev.lions.unionflow.server.entity.gouvernance; package dev.lions.unionflow.server.entity.gouvernance;
import dev.lions.unionflow.server.api.enums.gouvernance.NiveauEchelon; import dev.lions.unionflow.server.api.enums.gouvernance.NiveauEchelon;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
@Entity @Entity
@Table(name = "echelons_organigramme", indexes = { @Table(name = "echelons_organigramme", indexes = {
@Index(name = "idx_echelon_org", columnList = "organisation_id"), @Index(name = "idx_echelon_org", columnList = "organisation_id"),
@Index(name = "idx_echelon_parent", columnList = "echelon_parent_id") @Index(name = "idx_echelon_parent", columnList = "echelon_parent_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class EchelonOrganigramme extends BaseEntity { public class EchelonOrganigramme extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "echelon_parent_id") @JoinColumn(name = "echelon_parent_id")
private Organisation echelonParent; private Organisation echelonParent;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "niveau_echelon", nullable = false, length = 50) @Column(name = "niveau_echelon", nullable = false, length = 50)
private NiveauEchelon niveau; private NiveauEchelon niveau;
@NotBlank @NotBlank
@Column(name = "designation", nullable = false, length = 200) @Column(name = "designation", nullable = false, length = 200)
private String designation; private String designation;
@Column(name = "zone_delegation", length = 200) @Column(name = "zone_delegation", length = 200)
private String zoneGeographiqueOuDelegation; private String zoneGeographiqueOuDelegation;
} }

View File

@@ -1,106 +1,106 @@
package dev.lions.unionflow.server.entity.listener; package dev.lions.unionflow.server.entity.listener;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.service.KeycloakService; import dev.lions.unionflow.server.service.KeycloakService;
import io.quarkus.arc.Arc; import io.quarkus.arc.Arc;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Listener JPA pour l'alimentation automatique * Listener JPA pour l'alimentation automatique
* des champs d'audit. * des champs d'audit.
* *
* <p> * <p>
* Renseigne automatiquement {@code creePar} lors * Renseigne automatiquement {@code creePar} lors
* de la création et {@code modifiePar} lors de la * de la création et {@code modifiePar} lors de la
* mise à jour, en récupérant l'email de * mise à jour, en récupérant l'email de
* l'utilisateur authentifié via * l'utilisateur authentifié via
* {@link KeycloakService}. * {@link KeycloakService}.
* *
* <p> * <p>
* Ce listener est référencé via * Ce listener est référencé via
* {@code @EntityListeners} sur {@link BaseEntity}, * {@code @EntityListeners} sur {@link BaseEntity},
* garantissant que <strong>toutes</strong> les * garantissant que <strong>toutes</strong> les
* entités héritent automatiquement de ce * entités héritent automatiquement de ce
* comportement (WOU strict). * comportement (WOU strict).
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 3.0 * @version 3.0
* @since 2026-02-21 * @since 2026-02-21
*/ */
public class AuditEntityListener { public class AuditEntityListener {
/** /**
* Utilisateur par défaut pour les opérations * Utilisateur par défaut pour les opérations
* système sans contexte de sécurité. * système sans contexte de sécurité.
*/ */
private static final String UTILISATEUR_SYSTEME = "system"; private static final String UTILISATEUR_SYSTEME = "system";
private static final Logger LOG = Logger.getLogger(AuditEntityListener.class); private static final Logger LOG = Logger.getLogger(AuditEntityListener.class);
/** /**
* Callback exécuté avant la persistance. * Callback exécuté avant la persistance.
* *
* <p> * <p>
* Renseigne {@code creePar} avec l'email * Renseigne {@code creePar} avec l'email
* de l'utilisateur authentifié, ou * de l'utilisateur authentifié, ou
* {@code "system"} si aucun contexte de * {@code "system"} si aucun contexte de
* sécurité n'est disponible. * sécurité n'est disponible.
* *
* @param entity l'entité en cours de création * @param entity l'entité en cours de création
*/ */
@PrePersist @PrePersist
public void avantCreation(BaseEntity entity) { public void avantCreation(BaseEntity entity) {
if (entity.getCreePar() == null if (entity.getCreePar() == null
|| entity.getCreePar().isBlank()) { || entity.getCreePar().isBlank()) {
entity.setCreePar( entity.setCreePar(
obtenirUtilisateurCourant()); obtenirUtilisateurCourant());
} }
} }
/** /**
* Callback exécuté avant la mise à jour. * Callback exécuté avant la mise à jour.
* *
* <p> * <p>
* Renseigne {@code modifiePar} avec l'email * Renseigne {@code modifiePar} avec l'email
* de l'utilisateur authentifié. * de l'utilisateur authentifié.
* *
* @param entity l'entité en cours de modification * @param entity l'entité en cours de modification
*/ */
@PreUpdate @PreUpdate
public void avantModification(BaseEntity entity) { public void avantModification(BaseEntity entity) {
entity.setModifiePar( entity.setModifiePar(
obtenirUtilisateurCourant()); obtenirUtilisateurCourant());
} }
/** /**
* Obtient l'email de l'utilisateur courant. * Obtient l'email de l'utilisateur courant.
* *
* <p> * <p>
* Utilise {@link Arc#container()} pour * Utilise {@link Arc#container()} pour
* résoudre le {@link KeycloakService} depuis * résoudre le {@link KeycloakService} depuis
* le conteneur CDI de Quarkus. * le conteneur CDI de Quarkus.
* *
* @return l'email ou {@code "system"} en fallback * @return l'email ou {@code "system"} en fallback
*/ */
private String obtenirUtilisateurCourant() { private String obtenirUtilisateurCourant() {
try { try {
KeycloakService keycloakService = Arc.container() KeycloakService keycloakService = Arc.container()
.instance(KeycloakService.class) .instance(KeycloakService.class)
.get(); .get();
if (keycloakService != null if (keycloakService != null
&& keycloakService.isAuthenticated()) { && keycloakService.isAuthenticated()) {
String email = keycloakService.getCurrentUserEmail(); String email = keycloakService.getCurrentUserEmail();
if (email != null && !email.isBlank()) { if (email != null && !email.isBlank()) {
return email; return email;
} }
} }
} catch (Exception e) { } catch (Exception e) {
LOG.debugf( LOG.debugf(
"Contexte de sécurité indisponible: %s", "Contexte de sécurité indisponible: %s",
e.getMessage()); e.getMessage());
} }
return UTILISATEUR_SYSTEME; return UTILISATEUR_SYSTEME;
} }
} }

View File

@@ -1,97 +1,97 @@
package dev.lions.unionflow.server.entity.mutuelle.credit; 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.StatutDemandeCredit;
import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit; import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne; import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Entity @Entity
@Table(name = "demandes_credit", indexes = { @Table(name = "demandes_credit", indexes = {
@Index(name = "idx_credit_membre", columnList = "membre_id"), @Index(name = "idx_credit_membre", columnList = "membre_id"),
@Index(name = "idx_credit_numero", columnList = "numero_dossier", unique = true) @Index(name = "idx_credit_numero", columnList = "numero_dossier", unique = true)
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class DemandeCredit extends BaseEntity { public class DemandeCredit extends BaseEntity {
@Column(name = "numero_dossier", unique = true, nullable = false, length = 50) @Column(name = "numero_dossier", unique = true, nullable = false, length = 50)
private String numeroDossier; private String numeroDossier;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_credit", nullable = false, length = 50) @Column(name = "type_credit", nullable = false, length = 50)
private TypeCredit typeCredit; private TypeCredit typeCredit;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_lie_id") @JoinColumn(name = "compte_lie_id")
private CompteEpargne compteLie; private CompteEpargne compteLie;
@NotNull @NotNull
@Column(name = "montant_demande", nullable = false, precision = 19, scale = 4) @Column(name = "montant_demande", nullable = false, precision = 19, scale = 4)
private BigDecimal montantDemande; private BigDecimal montantDemande;
@NotNull @NotNull
@Column(name = "duree_mois_demande", nullable = false) @Column(name = "duree_mois_demande", nullable = false)
private Integer dureeMoisDemande; private Integer dureeMoisDemande;
@Column(name = "justification_detaillee", columnDefinition = "TEXT") @Column(name = "justification_detaillee", columnDefinition = "TEXT")
private String justificationDetaillee; private String justificationDetaillee;
@Column(name = "montant_approuve", precision = 19, scale = 4) @Column(name = "montant_approuve", precision = 19, scale = 4)
private BigDecimal montantApprouve; private BigDecimal montantApprouve;
@Column(name = "duree_mois_approuvee") @Column(name = "duree_mois_approuvee")
private Integer dureeMoisApprouvee; private Integer dureeMoisApprouvee;
@Column(name = "taux_interet_annuel", precision = 5, scale = 2) @Column(name = "taux_interet_annuel", precision = 5, scale = 2)
private BigDecimal tauxInteretAnnuel; private BigDecimal tauxInteretAnnuel;
@Column(name = "cout_total_credit", precision = 19, scale = 4) @Column(name = "cout_total_credit", precision = 19, scale = 4)
private BigDecimal coutTotalCredit; private BigDecimal coutTotalCredit;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutDemandeCredit statut = StatutDemandeCredit.SOUMISE; private StatutDemandeCredit statut = StatutDemandeCredit.SOUMISE;
@Column(name = "notes_comite", columnDefinition = "TEXT") @Column(name = "notes_comite", columnDefinition = "TEXT")
private String notesComite; private String notesComite;
@NotNull @NotNull
@Column(name = "date_soumission", nullable = false) @Column(name = "date_soumission", nullable = false)
@Builder.Default @Builder.Default
private LocalDate dateSoumission = LocalDate.now(); private LocalDate dateSoumission = LocalDate.now();
@Column(name = "date_validation") @Column(name = "date_validation")
private LocalDate dateValidation; private LocalDate dateValidation;
@Column(name = "date_premier_echeance") @Column(name = "date_premier_echeance")
private LocalDate datePremierEcheance; private LocalDate datePremierEcheance;
@OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default @Builder.Default
private List<GarantieDemande> garanties = new ArrayList<>(); private List<GarantieDemande> garanties = new ArrayList<>();
@OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "demandeCredit", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("ordre ASC") @OrderBy("ordre ASC")
@Builder.Default @Builder.Default
private List<EcheanceCredit> echeancier = new ArrayList<>(); private List<EcheanceCredit> echeancier = new ArrayList<>();
} }

View File

@@ -1,69 +1,69 @@
package dev.lions.unionflow.server.entity.mutuelle.credit; package dev.lions.unionflow.server.entity.mutuelle.credit;
import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit; import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@Entity @Entity
@Table(name = "echeances_credit", indexes = { @Table(name = "echeances_credit", indexes = {
@Index(name = "idx_echeance_demande", columnList = "demande_credit_id") @Index(name = "idx_echeance_demande", columnList = "demande_credit_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(exclude = "demandeCredit") @ToString(exclude = "demandeCredit")
public class EcheanceCredit extends BaseEntity { public class EcheanceCredit extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demande_credit_id", nullable = false) @JoinColumn(name = "demande_credit_id", nullable = false)
private DemandeCredit demandeCredit; private DemandeCredit demandeCredit;
@NotNull @NotNull
@Column(name = "ordre", nullable = false) @Column(name = "ordre", nullable = false)
private Integer ordre; private Integer ordre;
@NotNull @NotNull
@Column(name = "date_echeance_prevue", nullable = false) @Column(name = "date_echeance_prevue", nullable = false)
private LocalDate dateEcheancePrevue; private LocalDate dateEcheancePrevue;
@Column(name = "date_paiement_effectif") @Column(name = "date_paiement_effectif")
private LocalDate datePaiementEffectif; private LocalDate datePaiementEffectif;
@NotNull @NotNull
@Column(name = "capital_amorti", nullable = false, precision = 19, scale = 4) @Column(name = "capital_amorti", nullable = false, precision = 19, scale = 4)
private BigDecimal capitalAmorti; private BigDecimal capitalAmorti;
@NotNull @NotNull
@Column(name = "interets_periode", nullable = false, precision = 19, scale = 4) @Column(name = "interets_periode", nullable = false, precision = 19, scale = 4)
private BigDecimal interetsDeLaPeriode; private BigDecimal interetsDeLaPeriode;
@NotNull @NotNull
@Column(name = "montant_total_exigible", nullable = false, precision = 19, scale = 4) @Column(name = "montant_total_exigible", nullable = false, precision = 19, scale = 4)
private BigDecimal montantTotalExigible; private BigDecimal montantTotalExigible;
@NotNull @NotNull
@Column(name = "capital_restant_du", nullable = false, precision = 19, scale = 4) @Column(name = "capital_restant_du", nullable = false, precision = 19, scale = 4)
private BigDecimal capitalRestantDu; private BigDecimal capitalRestantDu;
@Column(name = "penalites_retard", precision = 19, scale = 4) @Column(name = "penalites_retard", precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal penalitesRetard = BigDecimal.ZERO; private BigDecimal penalitesRetard = BigDecimal.ZERO;
@Column(name = "montant_regle", precision = 19, scale = 4) @Column(name = "montant_regle", precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal montantRegle = BigDecimal.ZERO; private BigDecimal montantRegle = BigDecimal.ZERO;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutEcheanceCredit statut = StatutEcheanceCredit.A_VENIR; private StatutEcheanceCredit statut = StatutEcheanceCredit.A_VENIR;
} }

View File

@@ -1,39 +1,39 @@
package dev.lions.unionflow.server.entity.mutuelle.credit; package dev.lions.unionflow.server.entity.mutuelle.credit;
import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie; import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeGarantie;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@Entity @Entity
@Table(name = "garanties_demande") @Table(name = "garanties_demande")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(exclude = "demandeCredit") @ToString(exclude = "demandeCredit")
public class GarantieDemande extends BaseEntity { public class GarantieDemande extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demande_credit_id", nullable = false) @JoinColumn(name = "demande_credit_id", nullable = false)
private DemandeCredit demandeCredit; private DemandeCredit demandeCredit;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_garantie", nullable = false, length = 50) @Column(name = "type_garantie", nullable = false, length = 50)
private TypeGarantie typeGarantie; private TypeGarantie typeGarantie;
@Column(name = "valeur_estimee", precision = 19, scale = 4) @Column(name = "valeur_estimee", precision = 19, scale = 4)
private BigDecimal valeurEstimee; private BigDecimal valeurEstimee;
@Column(name = "reference_description", length = 500) @Column(name = "reference_description", length = 500)
private String referenceOuDescription; private String referenceOuDescription;
@Column(name = "document_preuve_id", length = 36) @Column(name = "document_preuve_id", length = 36)
private String documentPreuveId; private String documentPreuveId;
} }

View File

@@ -1,73 +1,73 @@
package dev.lions.unionflow.server.entity.mutuelle.epargne; package dev.lions.unionflow.server.entity.mutuelle.epargne;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation; 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.StatutCompteEpargne;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne; import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeCompteEpargne;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@Entity @Entity
@Table(name = "comptes_epargne", indexes = { @Table(name = "comptes_epargne", indexes = {
@Index(name = "idx_compte_epargne_numero", columnList = "numero_compte", unique = true), @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_membre", columnList = "membre_id"),
@Index(name = "idx_compte_epargne_orga", columnList = "organisation_id") @Index(name = "idx_compte_epargne_orga", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CompteEpargne extends BaseEntity { public class CompteEpargne extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Column(name = "numero_compte", unique = true, nullable = false, length = 50) @Column(name = "numero_compte", unique = true, nullable = false, length = 50)
private String numeroCompte; private String numeroCompte;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_compte", nullable = false, length = 50) @Column(name = "type_compte", nullable = false, length = 50)
private TypeCompteEpargne typeCompte; private TypeCompteEpargne typeCompte;
@NotNull @NotNull
@Column(name = "solde_actuel", nullable = false, precision = 19, scale = 4) @Column(name = "solde_actuel", nullable = false, precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal soldeActuel = BigDecimal.ZERO; private BigDecimal soldeActuel = BigDecimal.ZERO;
@NotNull @NotNull
@Column(name = "solde_bloque", nullable = false, precision = 19, scale = 4) @Column(name = "solde_bloque", nullable = false, precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal soldeBloque = BigDecimal.ZERO; private BigDecimal soldeBloque = BigDecimal.ZERO;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 30) @Column(name = "statut", nullable = false, length = 30)
@Builder.Default @Builder.Default
private StatutCompteEpargne statut = StatutCompteEpargne.ACTIF; private StatutCompteEpargne statut = StatutCompteEpargne.ACTIF;
@NotNull @NotNull
@Column(name = "date_ouverture", nullable = false) @Column(name = "date_ouverture", nullable = false)
@Builder.Default @Builder.Default
private LocalDate dateOuverture = LocalDate.now(); private LocalDate dateOuverture = LocalDate.now();
@Column(name = "date_derniere_transaction") @Column(name = "date_derniere_transaction")
private LocalDate dateDerniereTransaction; private LocalDate dateDerniereTransaction;
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
private String description; private String description;
} }

View File

@@ -1,70 +1,70 @@
package dev.lions.unionflow.server.entity.mutuelle.epargne; 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.mutuelle.epargne.TypeTransactionEpargne;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "transactions_epargne", indexes = { @Table(name = "transactions_epargne", indexes = {
@Index(name = "idx_tx_epargne_compte", columnList = "compte_id"), @Index(name = "idx_tx_epargne_compte", columnList = "compte_id"),
@Index(name = "idx_tx_epargne_reference", columnList = "reference_externe") @Index(name = "idx_tx_epargne_reference", columnList = "reference_externe")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TransactionEpargne extends BaseEntity { public class TransactionEpargne extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_id", nullable = false) @JoinColumn(name = "compte_id", nullable = false)
private CompteEpargne compte; private CompteEpargne compte;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_transaction", nullable = false, length = 50) @Column(name = "type_transaction", nullable = false, length = 50)
private TypeTransactionEpargne type; private TypeTransactionEpargne type;
@NotNull @NotNull
@Column(name = "montant", nullable = false, precision = 19, scale = 4) @Column(name = "montant", nullable = false, precision = 19, scale = 4)
private BigDecimal montant; private BigDecimal montant;
@Column(name = "solde_avant", precision = 19, scale = 4) @Column(name = "solde_avant", precision = 19, scale = 4)
private BigDecimal soldeAvant; private BigDecimal soldeAvant;
@Column(name = "solde_apres", precision = 19, scale = 4) @Column(name = "solde_apres", precision = 19, scale = 4)
private BigDecimal soldeApres; private BigDecimal soldeApres;
@Column(name = "motif", length = 500) @Column(name = "motif", length = 500)
private String motif; private String motif;
@NotNull @NotNull
@Column(name = "date_transaction", nullable = false) @Column(name = "date_transaction", nullable = false)
@Builder.Default @Builder.Default
private LocalDateTime dateTransaction = LocalDateTime.now(); private LocalDateTime dateTransaction = LocalDateTime.now();
@Column(name = "operateur_id", length = 36) @Column(name = "operateur_id", length = 36)
private String operateurId; private String operateurId;
@Column(name = "reference_externe", length = 100) @Column(name = "reference_externe", length = 100)
private String referenceExterne; private String referenceExterne;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut_execution", length = 50) @Column(name = "statut_execution", length = 50)
private StatutTransactionWave statutExecution; private StatutTransactionWave statutExecution;
/** Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré */ /** Origine des fonds (LCB-FT) — obligatoire au-dessus du seuil configuré */
@Column(name = "origine_fonds", length = 200) @Column(name = "origine_fonds", length = 200)
private String origineFonds; private String origineFonds;
/** Pièce justificative (document) pour opérations au-dessus du seuil LCB-FT */ /** Pièce justificative (document) pour opérations au-dessus du seuil LCB-FT */
@Column(name = "piece_justificative_id") @Column(name = "piece_justificative_id")
private java.util.UUID pieceJustificativeId; private java.util.UUID pieceJustificativeId;
} }

View File

@@ -1,58 +1,58 @@
package dev.lions.unionflow.server.entity.ong; package dev.lions.unionflow.server.entity.ong;
import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng; import dev.lions.unionflow.server.api.enums.ong.StatutProjetOng;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@Entity @Entity
@Table(name = "projets_ong", indexes = { @Table(name = "projets_ong", indexes = {
@Index(name = "idx_projet_ong_organisation", columnList = "organisation_id") @Index(name = "idx_projet_ong_organisation", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ProjetOng extends BaseEntity { public class ProjetOng extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotBlank @NotBlank
@Column(name = "nom_projet", nullable = false, length = 200) @Column(name = "nom_projet", nullable = false, length = 200)
private String nomProjet; private String nomProjet;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String descriptionMandat; private String descriptionMandat;
@Column(name = "zone_geographique", length = 200) @Column(name = "zone_geographique", length = 200)
private String zoneGeographiqueIntervention; private String zoneGeographiqueIntervention;
@Column(name = "budget_previsionnel", precision = 19, scale = 4) @Column(name = "budget_previsionnel", precision = 19, scale = 4)
private BigDecimal budgetPrevisionnel; private BigDecimal budgetPrevisionnel;
@Column(name = "depenses_reelles", precision = 19, scale = 4) @Column(name = "depenses_reelles", precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal depensesReelles = BigDecimal.ZERO; private BigDecimal depensesReelles = BigDecimal.ZERO;
@Column(name = "date_lancement") @Column(name = "date_lancement")
private LocalDate dateLancement; private LocalDate dateLancement;
@Column(name = "date_fin_estimee") @Column(name = "date_fin_estimee")
private LocalDate dateFinEstimee; private LocalDate dateFinEstimee;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutProjetOng statut = StatutProjetOng.EN_ETUDE; private StatutProjetOng statut = StatutProjetOng.EN_ETUDE;
} }

View File

@@ -1,54 +1,54 @@
package dev.lions.unionflow.server.entity.registre; package dev.lions.unionflow.server.entity.registre;
import dev.lions.unionflow.server.api.enums.registre.StatutAgrement; import dev.lions.unionflow.server.api.enums.registre.StatutAgrement;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.time.LocalDate; import java.time.LocalDate;
@Entity @Entity
@Table(name = "agrements_professionnels", indexes = { @Table(name = "agrements_professionnels", indexes = {
@Index(name = "idx_agrement_membre", columnList = "membre_id"), @Index(name = "idx_agrement_membre", columnList = "membre_id"),
@Index(name = "idx_agrement_orga", columnList = "organisation_id") @Index(name = "idx_agrement_orga", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class AgrementProfessionnel extends BaseEntity { public class AgrementProfessionnel extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false) @JoinColumn(name = "membre_id", nullable = false)
private Membre membre; private Membre membre;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@Column(name = "secteur_ordre", length = 150) @Column(name = "secteur_ordre", length = 150)
private String secteurOuOrdre; private String secteurOuOrdre;
@Column(name = "numero_licence", length = 100) @Column(name = "numero_licence", length = 100)
private String numeroLicenceOuRegistre; private String numeroLicenceOuRegistre;
@Column(name = "categorie_classement", length = 100) @Column(name = "categorie_classement", length = 100)
private String categorieClassement; private String categorieClassement;
@Column(name = "date_delivrance") @Column(name = "date_delivrance")
private LocalDate dateDelivrance; private LocalDate dateDelivrance;
@Column(name = "date_expiration") @Column(name = "date_expiration")
private LocalDate dateExpiration; private LocalDate dateExpiration;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutAgrement statut = StatutAgrement.PROVISOIRE; private StatutAgrement statut = StatutAgrement.PROVISOIRE;
} }

View File

@@ -1,73 +1,73 @@
package dev.lions.unionflow.server.entity.tontine; package dev.lions.unionflow.server.entity.tontine;
import dev.lions.unionflow.server.api.enums.tontine.FrequenceTour; 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.StatutTontine;
import dev.lions.unionflow.server.api.enums.tontine.TypeTontine; import dev.lions.unionflow.server.api.enums.tontine.TypeTontine;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Entity @Entity
@Table(name = "tontines", indexes = { @Table(name = "tontines", indexes = {
@Index(name = "idx_tontine_organisation", columnList = "organisation_id") @Index(name = "idx_tontine_organisation", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Tontine extends BaseEntity { public class Tontine extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "nom", nullable = false, length = 150) @Column(name = "nom", nullable = false, length = 150)
private String nom; private String nom;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String description; private String description;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_tontine", nullable = false, length = 50) @Column(name = "type_tontine", nullable = false, length = 50)
private TypeTontine typeTontine; private TypeTontine typeTontine;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "frequence", nullable = false, length = 50) @Column(name = "frequence", nullable = false, length = 50)
private FrequenceTour frequence; private FrequenceTour frequence;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutTontine statut = StatutTontine.PLANIFIEE; private StatutTontine statut = StatutTontine.PLANIFIEE;
@Column(name = "date_debut_effective") @Column(name = "date_debut_effective")
private LocalDate dateDebutEffective; private LocalDate dateDebutEffective;
@Column(name = "date_fin_prevue") @Column(name = "date_fin_prevue")
private LocalDate dateFinPrevue; private LocalDate dateFinPrevue;
@Column(name = "montant_mise_tour", precision = 19, scale = 4) @Column(name = "montant_mise_tour", precision = 19, scale = 4)
private BigDecimal montantMiseParTour; private BigDecimal montantMiseParTour;
@Column(name = "limite_participants") @Column(name = "limite_participants")
private Integer limiteParticipants; private Integer limiteParticipants;
@OneToMany(mappedBy = "tontine", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "tontine", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("ordreTour ASC") @OrderBy("ordreTour ASC")
@Builder.Default @Builder.Default
private List<TourTontine> calendrierTours = new ArrayList<>(); private List<TourTontine> calendrierTours = new ArrayList<>();
} }

View File

@@ -1,56 +1,56 @@
package dev.lions.unionflow.server.entity.tontine; package dev.lions.unionflow.server.entity.tontine;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@Entity @Entity
@Table(name = "tours_tontine", indexes = { @Table(name = "tours_tontine", indexes = {
@Index(name = "idx_tour_tontine", columnList = "tontine_id") @Index(name = "idx_tour_tontine", columnList = "tontine_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(exclude = { "tontine", "membreBeneficiaire" }) @ToString(exclude = { "tontine", "membreBeneficiaire" })
public class TourTontine extends BaseEntity { public class TourTontine extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tontine_id", nullable = false) @JoinColumn(name = "tontine_id", nullable = false)
private Tontine tontine; private Tontine tontine;
@NotNull @NotNull
@Column(name = "ordre_tour", nullable = false) @Column(name = "ordre_tour", nullable = false)
private Integer ordreTour; private Integer ordreTour;
@NotNull @NotNull
@Column(name = "date_ouverture_cotisations", nullable = false) @Column(name = "date_ouverture_cotisations", nullable = false)
private LocalDate dateOuvertureCotisations; private LocalDate dateOuvertureCotisations;
@Column(name = "date_tirage_remise") @Column(name = "date_tirage_remise")
private LocalDate dateTirageOuRemise; private LocalDate dateTirageOuRemise;
@NotNull @NotNull
@Column(name = "montant_cible", nullable = false, precision = 19, scale = 4) @Column(name = "montant_cible", nullable = false, precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal montantCible = BigDecimal.ZERO; private BigDecimal montantCible = BigDecimal.ZERO;
@NotNull @NotNull
@Column(name = "cagnotte_collectee", nullable = false, precision = 19, scale = 4) @Column(name = "cagnotte_collectee", nullable = false, precision = 19, scale = 4)
@Builder.Default @Builder.Default
private BigDecimal cagnotteCollectee = BigDecimal.ZERO; private BigDecimal cagnotteCollectee = BigDecimal.ZERO;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_beneficiaire_id") @JoinColumn(name = "membre_beneficiaire_id")
private Membre membreBeneficiaire; private Membre membreBeneficiaire;
@Column(name = "statut_interne", length = 30) @Column(name = "statut_interne", length = 30)
private String statutInterne; private String statutInterne;
} }

View File

@@ -1,84 +1,84 @@
package dev.lions.unionflow.server.entity.vote; package dev.lions.unionflow.server.entity.vote;
import dev.lions.unionflow.server.api.enums.vote.ModeScrutin; 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.StatutVote;
import dev.lions.unionflow.server.api.enums.vote.TypeVote; import dev.lions.unionflow.server.api.enums.vote.TypeVote;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.*; import lombok.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Entity @Entity
@Table(name = "campagnes_vote", indexes = { @Table(name = "campagnes_vote", indexes = {
@Index(name = "idx_vote_orga", columnList = "organisation_id") @Index(name = "idx_vote_orga", columnList = "organisation_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class CampagneVote extends BaseEntity { public class CampagneVote extends BaseEntity {
@NotBlank @NotBlank
@Column(name = "titre", nullable = false, length = 200) @Column(name = "titre", nullable = false, length = 200)
private String titre; private String titre;
@Column(name = "description", columnDefinition = "TEXT") @Column(name = "description", columnDefinition = "TEXT")
private String descriptionOuResolution; private String descriptionOuResolution;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false) @JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation; private Organisation organisation;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "type_vote", nullable = false, length = 50) @Column(name = "type_vote", nullable = false, length = 50)
private TypeVote typeVote; private TypeVote typeVote;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "mode_scrutin", nullable = false, length = 50) @Column(name = "mode_scrutin", nullable = false, length = 50)
private ModeScrutin modeScrutin; private ModeScrutin modeScrutin;
@NotNull @NotNull
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false, length = 50) @Column(name = "statut", nullable = false, length = 50)
@Builder.Default @Builder.Default
private StatutVote statut = StatutVote.BROUILLON; private StatutVote statut = StatutVote.BROUILLON;
@NotNull @NotNull
@Column(name = "date_ouverture", nullable = false) @Column(name = "date_ouverture", nullable = false)
private LocalDateTime dateOuverture; private LocalDateTime dateOuverture;
@NotNull @NotNull
@Column(name = "date_fermeture", nullable = false) @Column(name = "date_fermeture", nullable = false)
private LocalDateTime dateFermeture; private LocalDateTime dateFermeture;
@Column(name = "restreindre_membres_ajour", nullable = false) @Column(name = "restreindre_membres_ajour", nullable = false)
@Builder.Default @Builder.Default
private Boolean restreindreMembresAJour = true; private Boolean restreindreMembresAJour = true;
@Column(name = "autoriser_vote_blanc", nullable = false) @Column(name = "autoriser_vote_blanc", nullable = false)
@Builder.Default @Builder.Default
private Boolean autoriserVoteBlanc = true; private Boolean autoriserVoteBlanc = true;
@Column(name = "total_electeurs") @Column(name = "total_electeurs")
private Integer totalElecteursInscrits; private Integer totalElecteursInscrits;
@Column(name = "total_votants") @Column(name = "total_votants")
private Integer totalVotantsEffectifs; private Integer totalVotantsEffectifs;
@Column(name = "total_blancs_nuls") @Column(name = "total_blancs_nuls")
private Integer totalVotesBlancsOuNuls; private Integer totalVotesBlancsOuNuls;
@OneToMany(mappedBy = "campagneVote", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "campagneVote", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default @Builder.Default
private List<Candidat> candidats = new ArrayList<>(); private List<Candidat> candidats = new ArrayList<>();
} }

View File

@@ -1,45 +1,45 @@
package dev.lions.unionflow.server.entity.vote; package dev.lions.unionflow.server.entity.vote;
import dev.lions.unionflow.server.entity.BaseEntity; import dev.lions.unionflow.server.entity.BaseEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.*; import lombok.*;
import java.math.BigDecimal; import java.math.BigDecimal;
@Entity @Entity
@Table(name = "candidats", indexes = { @Table(name = "candidats", indexes = {
@Index(name = "idx_candidat_campagne", columnList = "campagne_vote_id") @Index(name = "idx_candidat_campagne", columnList = "campagne_vote_id")
}) })
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(exclude = "campagneVote") @ToString(exclude = "campagneVote")
public class Candidat extends BaseEntity { public class Candidat extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "campagne_vote_id", nullable = false) @JoinColumn(name = "campagne_vote_id", nullable = false)
private CampagneVote campagneVote; private CampagneVote campagneVote;
@NotBlank @NotBlank
@Column(name = "nom_candidature", nullable = false, length = 150) @Column(name = "nom_candidature", nullable = false, length = 150)
private String nomCandidatureOuChoix; private String nomCandidatureOuChoix;
@Column(name = "membre_associe_id", length = 36) @Column(name = "membre_associe_id", length = 36)
private String membreIdAssocie; private String membreIdAssocie;
@Column(name = "profession_foi", columnDefinition = "TEXT") @Column(name = "profession_foi", columnDefinition = "TEXT")
private String professionDeFoi; private String professionDeFoi;
@Column(name = "photo_url", length = 500) @Column(name = "photo_url", length = 500)
private String photoUrl; private String photoUrl;
@Column(name = "nombre_voix") @Column(name = "nombre_voix")
@Builder.Default @Builder.Default
private Integer nombreDeVoix = 0; private Integer nombreDeVoix = 0;
@Column(name = "pourcentage", precision = 5, scale = 2) @Column(name = "pourcentage", precision = 5, scale = 2)
@Builder.Default @Builder.Default
private BigDecimal pourcentageObtenu = BigDecimal.ZERO; private BigDecimal pourcentageObtenu = BigDecimal.ZERO;
} }

View File

@@ -98,7 +98,9 @@ public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {
return exception instanceof NotFoundException return exception instanceof NotFoundException
|| exception instanceof ForbiddenException || exception instanceof ForbiddenException
|| exception instanceof NotAuthorizedException || exception instanceof NotAuthorizedException
|| exception instanceof NotAllowedException; || exception instanceof NotAllowedException
|| exception instanceof IllegalArgumentException
|| exception instanceof IllegalStateException;
} }
private int determineStatusCode(Throwable exception) { private int determineStatusCode(Throwable exception) {

View File

@@ -1,40 +1,40 @@
package dev.lions.unionflow.server.exception; package dev.lions.unionflow.server.exception;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* Exception Mapper pour les erreurs de traitement JSON (parsing, format, etc.). * Exception Mapper pour les erreurs de traitement JSON (parsing, format, etc.).
* Retourne un 400 Bad Request avec un message détaillé. * Retourne un 400 Bad Request avec un message détaillé.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-19 * @since 2026-03-19
*/ */
@Slf4j @Slf4j
@Provider @Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> { public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> {
@Override @Override
public Response toResponse(JsonProcessingException exception) { public Response toResponse(JsonProcessingException exception) {
log.warn("JSON processing error: {}", exception.getMessage()); log.warn("JSON processing error: {}", exception.getMessage());
Map<String, Object> errorBody = new HashMap<>(); Map<String, Object> errorBody = new HashMap<>();
errorBody.put("message", "Erreur de traitement JSON"); errorBody.put("message", "Erreur de traitement JSON");
errorBody.put("details", exception.getOriginalMessage() != null errorBody.put("details", exception.getOriginalMessage() != null
? exception.getOriginalMessage() ? exception.getOriginalMessage()
: exception.getMessage()); : exception.getMessage());
errorBody.put("status", 400); errorBody.put("status", 400);
errorBody.put("timestamp", java.time.LocalDateTime.now().toString()); errorBody.put("timestamp", java.time.LocalDateTime.now().toString());
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(errorBody) .entity(errorBody)
.build(); .build();
} }
} }

View File

@@ -1,154 +1,154 @@
package dev.lions.unionflow.server.filter; package dev.lions.unionflow.server.filter;
import dev.lions.unionflow.server.service.SystemLoggingService; import dev.lions.unionflow.server.service.SystemLoggingService;
import jakarta.annotation.Priority; import jakarta.annotation.Priority;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
/** /**
* Filtre JAX-RS pour capturer toutes les requêtes HTTP et les persister dans system_logs. * Filtre JAX-RS pour capturer toutes les requêtes HTTP et les persister dans system_logs.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-03-15 * @since 2026-03-15
*/ */
@Slf4j @Slf4j
@Provider @Provider
@Priority(1000) @Priority(1000)
public class HttpLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter { public class HttpLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final String REQUEST_START_TIME = "REQUEST_START_TIME"; private static final String REQUEST_START_TIME = "REQUEST_START_TIME";
private static final String REQUEST_METHOD = "REQUEST_METHOD"; private static final String REQUEST_METHOD = "REQUEST_METHOD";
private static final String REQUEST_PATH = "REQUEST_PATH"; private static final String REQUEST_PATH = "REQUEST_PATH";
@Inject @Inject
SystemLoggingService systemLoggingService; SystemLoggingService systemLoggingService;
@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(ContainerRequestContext requestContext) throws IOException {
// Enregistrer le timestamp de début de requête // Enregistrer le timestamp de début de requête
requestContext.setProperty(REQUEST_START_TIME, System.currentTimeMillis()); requestContext.setProperty(REQUEST_START_TIME, System.currentTimeMillis());
requestContext.setProperty(REQUEST_METHOD, requestContext.getMethod()); requestContext.setProperty(REQUEST_METHOD, requestContext.getMethod());
requestContext.setProperty(REQUEST_PATH, requestContext.getUriInfo().getPath()); requestContext.setProperty(REQUEST_PATH, requestContext.getUriInfo().getPath());
} }
@Override @Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
try { try {
// Calculer la durée de la requête // Calculer la durée de la requête
Long startTime = (Long) requestContext.getProperty(REQUEST_START_TIME); Long startTime = (Long) requestContext.getProperty(REQUEST_START_TIME);
long durationMs = startTime != null ? System.currentTimeMillis() - startTime : 0; long durationMs = startTime != null ? System.currentTimeMillis() - startTime : 0;
// Récupérer les informations de la requête // Récupérer les informations de la requête
String method = (String) requestContext.getProperty(REQUEST_METHOD); String method = (String) requestContext.getProperty(REQUEST_METHOD);
String path = (String) requestContext.getProperty(REQUEST_PATH); String path = (String) requestContext.getProperty(REQUEST_PATH);
int statusCode = responseContext.getStatus(); int statusCode = responseContext.getStatus();
// Récupérer l'utilisateur connecté // Récupérer l'utilisateur connecté
String userId = extractUserId(requestContext); String userId = extractUserId(requestContext);
// Récupérer l'IP // Récupérer l'IP
String ipAddress = extractIpAddress(requestContext); String ipAddress = extractIpAddress(requestContext);
// Récupérer le sessionId (optionnel) // Récupérer le sessionId (optionnel)
String sessionId = extractSessionId(requestContext); String sessionId = extractSessionId(requestContext);
// Ne logger que les endpoints API (ignorer /q/*, /static/*, etc.) // Ne logger que les endpoints API (ignorer /q/*, /static/*, etc.)
if (shouldLog(path)) { if (shouldLog(path)) {
systemLoggingService.logRequest( systemLoggingService.logRequest(
method, method,
"/" + path, "/" + path,
statusCode, statusCode,
userId, userId,
ipAddress, ipAddress,
sessionId, sessionId,
durationMs durationMs
); );
} }
} catch (Exception e) { } catch (Exception e) {
// Ne jamais laisser le logging casser l'application // Ne jamais laisser le logging casser l'application
log.error("Error in HttpLoggingFilter", e); log.error("Error in HttpLoggingFilter", e);
} }
} }
/** /**
* Extraire l'ID utilisateur depuis le contexte de sécurité * Extraire l'ID utilisateur depuis le contexte de sécurité
*/ */
private String extractUserId(ContainerRequestContext requestContext) { private String extractUserId(ContainerRequestContext requestContext) {
SecurityContext securityContext = requestContext.getSecurityContext(); SecurityContext securityContext = requestContext.getSecurityContext();
if (securityContext != null) { if (securityContext != null) {
Principal principal = securityContext.getUserPrincipal(); Principal principal = securityContext.getUserPrincipal();
if (principal != null) { if (principal != null) {
return principal.getName(); return principal.getName();
} }
} }
return "anonymous"; return "anonymous";
} }
/** /**
* Extraire l'adresse IP du client * Extraire l'adresse IP du client
*/ */
private String extractIpAddress(ContainerRequestContext requestContext) { private String extractIpAddress(ContainerRequestContext requestContext) {
// Essayer d'abord les headers de proxy // Essayer d'abord les headers de proxy
String xForwardedFor = requestContext.getHeaderString("X-Forwarded-For"); String xForwardedFor = requestContext.getHeaderString("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) { if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
// Prendre la première IP de la liste // Prendre la première IP de la liste
return xForwardedFor.split(",")[0].trim(); return xForwardedFor.split(",")[0].trim();
} }
String xRealIp = requestContext.getHeaderString("X-Real-IP"); String xRealIp = requestContext.getHeaderString("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) { if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp; return xRealIp;
} }
// Sinon retourner "unknown" // Sinon retourner "unknown"
return "unknown"; return "unknown";
} }
/** /**
* Extraire le session ID (si disponible) * Extraire le session ID (si disponible)
*/ */
private String extractSessionId(ContainerRequestContext requestContext) { private String extractSessionId(ContainerRequestContext requestContext) {
// Essayer de récupérer depuis les cookies ou headers // Essayer de récupérer depuis les cookies ou headers
String sessionId = requestContext.getHeaderString("X-Session-ID"); String sessionId = requestContext.getHeaderString("X-Session-ID");
if (sessionId != null && !sessionId.isEmpty()) { if (sessionId != null && !sessionId.isEmpty()) {
return sessionId; return sessionId;
} }
// Par défaut, retourner null // Par défaut, retourner null
return null; return null;
} }
/** /**
* Déterminer si on doit logger cette requête * Déterminer si on doit logger cette requête
* Ignorer les endpoints techniques (health, metrics, swagger, etc.) * Ignorer les endpoints techniques (health, metrics, swagger, etc.)
*/ */
private boolean shouldLog(String path) { private boolean shouldLog(String path) {
if (path == null) { if (path == null) {
return false; return false;
} }
// Ignorer les endpoints techniques Quarkus // Ignorer les endpoints techniques Quarkus
if (path.startsWith("q/")) { if (path.startsWith("q/")) {
return false; return false;
} }
// Ignorer les ressources statiques // Ignorer les ressources statiques
if (path.startsWith("static/") || path.startsWith("webjars/")) { if (path.startsWith("static/") || path.startsWith("webjars/")) {
return false; return false;
} }
// Logger uniquement les endpoints API // Logger uniquement les endpoints API
return path.startsWith("api/"); return path.startsWith("api/");
} }
} }

View File

@@ -1,131 +1,131 @@
package dev.lions.unionflow.server.mapper; 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.CreateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest; 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.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.entity.DemandeAide; import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.entity.Organisation;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
/** /**
* Mapper entre l'entité DemandeAide et le DTO DemandeAideDTO. * Mapper entre l'entité DemandeAide et le DTO DemandeAideDTO.
* *
* @author UnionFlow Team * @author UnionFlow Team
* @version 1.0 * @version 1.0
* @since 2026-01-31 * @since 2026-01-31
*/ */
@ApplicationScoped @ApplicationScoped
public class DemandeAideMapper { public class DemandeAideMapper {
/** /**
* Convertit une entité DemandeAide en DTO Response. * Convertit une entité DemandeAide en DTO Response.
*/ */
public DemandeAideResponse toDTO(DemandeAide entity) { public DemandeAideResponse toDTO(DemandeAide entity) {
if (entity == null) { if (entity == null) {
return null; return null;
} }
DemandeAideResponse dto = new DemandeAideResponse(); DemandeAideResponse dto = new DemandeAideResponse();
dto.setId(entity.getId()); dto.setId(entity.getId());
dto.setDateCreation(entity.getDateCreation()); dto.setDateCreation(entity.getDateCreation());
dto.setDateModification(entity.getDateModification()); dto.setDateModification(entity.getDateModification());
dto.setVersion(entity.getVersion() != null ? entity.getVersion() : 0L); dto.setVersion(entity.getVersion() != null ? entity.getVersion() : 0L);
dto.setActif(entity.getActif()); dto.setActif(entity.getActif());
dto.setTitre(entity.getTitre()); dto.setTitre(entity.getTitre());
dto.setDescription(entity.getDescription()); dto.setDescription(entity.getDescription());
dto.setTypeAide(entity.getTypeAide()); dto.setTypeAide(entity.getTypeAide());
dto.setStatut(entity.getStatut()); dto.setStatut(entity.getStatut());
dto.setMontantDemande(entity.getMontantDemande()); dto.setMontantDemande(entity.getMontantDemande());
dto.setMontantApprouve(entity.getMontantApprouve()); dto.setMontantApprouve(entity.getMontantApprouve());
dto.setJustification(entity.getJustification()); dto.setJustification(entity.getJustification());
dto.setCommentairesEvaluateur(entity.getCommentaireEvaluation()); dto.setCommentairesEvaluateur(entity.getCommentaireEvaluation());
dto.setDocumentsJoints(entity.getDocumentsFournis()); dto.setDocumentsJoints(entity.getDocumentsFournis());
dto.setDateSoumission(entity.getDateDemande()); dto.setDateSoumission(entity.getDateDemande());
dto.setDateEvaluation(entity.getDateEvaluation()); dto.setDateEvaluation(entity.getDateEvaluation());
dto.setDateVersement(entity.getDateVersement()); dto.setDateVersement(entity.getDateVersement());
dto.setPriorite(entity.getUrgence() != null && entity.getUrgence() dto.setPriorite(entity.getUrgence() != null && entity.getUrgence()
? PrioriteAide.URGENTE ? PrioriteAide.URGENTE
: PrioriteAide.NORMALE); : PrioriteAide.NORMALE);
if (entity.getDemandeur() != null) { if (entity.getDemandeur() != null) {
dto.setMembreDemandeurId(entity.getDemandeur().getId()); dto.setMembreDemandeurId(entity.getDemandeur().getId());
dto.setNomDemandeur(entity.getDemandeur().getPrenom() + " " + entity.getDemandeur().getNom()); dto.setNomDemandeur(entity.getDemandeur().getPrenom() + " " + entity.getDemandeur().getNom());
dto.setNumeroMembreDemandeur(entity.getDemandeur().getNumeroMembre()); dto.setNumeroMembreDemandeur(entity.getDemandeur().getNumeroMembre());
} }
if (entity.getEvaluateur() != null) { if (entity.getEvaluateur() != null) {
dto.setEvaluateurId(entity.getEvaluateur().getId().toString()); dto.setEvaluateurId(entity.getEvaluateur().getId().toString());
dto.setEvaluateurNom(entity.getEvaluateur().getPrenom() + " " + entity.getEvaluateur().getNom()); dto.setEvaluateurNom(entity.getEvaluateur().getPrenom() + " " + entity.getEvaluateur().getNom());
} }
if (entity.getOrganisation() != null) { if (entity.getOrganisation() != null) {
dto.setAssociationId(entity.getOrganisation().getId()); dto.setAssociationId(entity.getOrganisation().getId());
dto.setNomAssociation(entity.getOrganisation().getNom()); dto.setNomAssociation(entity.getOrganisation().getNom());
} }
return dto; return dto;
} }
/** /**
* Met à jour une entité existante à partir du DTO UpdateRequest. * Met à jour une entité existante à partir du DTO UpdateRequest.
*/ */
public void updateEntityFromDTO(DemandeAide entity, UpdateDemandeAideRequest request) { public void updateEntityFromDTO(DemandeAide entity, UpdateDemandeAideRequest request) {
if (entity == null || request == null) { if (entity == null || request == null) {
return; return;
} }
if (request.titre() != null) { if (request.titre() != null) {
entity.setTitre(request.titre()); entity.setTitre(request.titre());
} }
if (request.description() != null) { if (request.description() != null) {
entity.setDescription(request.description()); entity.setDescription(request.description());
} }
if (request.typeAide() != null) { if (request.typeAide() != null) {
entity.setTypeAide(request.typeAide()); entity.setTypeAide(request.typeAide());
} }
if (request.statut() != null) { if (request.statut() != null) {
entity.setStatut(request.statut()); entity.setStatut(request.statut());
} }
entity.setMontantDemande(request.montantDemande()); entity.setMontantDemande(request.montantDemande());
entity.setMontantApprouve(request.montantApprouve()); entity.setMontantApprouve(request.montantApprouve());
entity.setJustification(request.justification()); entity.setJustification(request.justification());
entity.setCommentaireEvaluation(request.commentairesEvaluateur()); entity.setCommentaireEvaluation(request.commentairesEvaluateur());
entity.setDocumentsFournis(request.documentsJoints()); entity.setDocumentsFournis(request.documentsJoints());
entity.setUrgence(request.priorite() != null && request.priorite().isUrgente()); entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
if (request.dateSoumission() != null) { if (request.dateSoumission() != null) {
entity.setDateDemande(request.dateSoumission()); entity.setDateDemande(request.dateSoumission());
} }
entity.setDateEvaluation(request.dateEvaluation()); entity.setDateEvaluation(request.dateEvaluation());
entity.setDateVersement(request.dateVersement()); entity.setDateVersement(request.dateVersement());
} }
/** /**
* Crée une nouvelle entité à partir du DTO CreateRequest. * Crée une nouvelle entité à partir du DTO CreateRequest.
*/ */
public DemandeAide toEntity( public DemandeAide toEntity(
CreateDemandeAideRequest request, CreateDemandeAideRequest request,
Membre demandeur, Membre demandeur,
Membre evaluateur, Membre evaluateur,
Organisation organisation) { Organisation organisation) {
if (request == null) { if (request == null) {
return null; return null;
} }
DemandeAide entity = new DemandeAide(); DemandeAide entity = new DemandeAide();
entity.setTitre(request.titre()); entity.setTitre(request.titre());
entity.setDescription(request.description()); entity.setDescription(request.description());
entity.setTypeAide(request.typeAide()); entity.setTypeAide(request.typeAide());
entity.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE); entity.setStatut(dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE);
entity.setMontantDemande(request.montantDemande()); entity.setMontantDemande(request.montantDemande());
entity.setJustification(request.justification()); entity.setJustification(request.justification());
entity.setUrgence(request.priorite() != null && request.priorite().isUrgente()); entity.setUrgence(request.priorite() != null && request.priorite().isUrgente());
entity.setDateDemande(java.time.LocalDateTime.now()); entity.setDateDemande(java.time.LocalDateTime.now());
entity.setDemandeur(demandeur); entity.setDemandeur(demandeur);
entity.setEvaluateur(evaluateur); entity.setEvaluateur(evaluateur);
entity.setOrganisation(organisation); entity.setOrganisation(organisation);
return entity; return entity;
} }
} }

View File

@@ -1,34 +1,34 @@
package dev.lions.unionflow.server.mapper.agricole; package dev.lions.unionflow.server.mapper.agricole;
import dev.lions.unionflow.server.api.dto.agricole.CampagneAgricoleDTO; import dev.lions.unionflow.server.api.dto.agricole.CampagneAgricoleDTO;
import dev.lions.unionflow.server.entity.agricole.CampagneAgricole; import dev.lions.unionflow.server.entity.agricole.CampagneAgricole;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface CampagneAgricoleMapper { public interface CampagneAgricoleMapper {
@Mapping(target = "organisationCoopId", source = "organisation.id") @Mapping(target = "organisationCoopId", source = "organisation.id")
CampagneAgricoleDTO toDto(CampagneAgricole entity); CampagneAgricoleDTO toDto(CampagneAgricole entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
CampagneAgricole toEntity(CampagneAgricoleDTO dto); CampagneAgricole toEntity(CampagneAgricoleDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
void updateEntityFromDto(CampagneAgricoleDTO dto, @MappingTarget CampagneAgricole entity); void updateEntityFromDto(CampagneAgricoleDTO dto, @MappingTarget CampagneAgricole entity);
} }

View File

@@ -1,28 +1,28 @@
package dev.lions.unionflow.server.mapper.collectefonds; package dev.lions.unionflow.server.mapper.collectefonds;
import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse; import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse;
import dev.lions.unionflow.server.entity.collectefonds.CampagneCollecte; import dev.lions.unionflow.server.entity.collectefonds.CampagneCollecte;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface CampagneCollecteMapper { public interface CampagneCollecteMapper {
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
CampagneCollecteResponse toDto(CampagneCollecte entity); CampagneCollecteResponse toDto(CampagneCollecte entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "montantCollecteActuel", ignore = true) @Mapping(target = "montantCollecteActuel", ignore = true)
@Mapping(target = "nombreDonateurs", ignore = true) @Mapping(target = "nombreDonateurs", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateOuverture", ignore = true) @Mapping(target = "dateOuverture", ignore = true)
void updateEntityFromDto(CampagneCollecteResponse dto, @MappingTarget CampagneCollecte entity); void updateEntityFromDto(CampagneCollecteResponse dto, @MappingTarget CampagneCollecte entity);
} }

View File

@@ -1,37 +1,37 @@
package dev.lions.unionflow.server.mapper.collectefonds; package dev.lions.unionflow.server.mapper.collectefonds;
import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO; import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO;
import dev.lions.unionflow.server.entity.collectefonds.ContributionCollecte; import dev.lions.unionflow.server.entity.collectefonds.ContributionCollecte;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface ContributionCollecteMapper { public interface ContributionCollecteMapper {
@Mapping(target = "campagneId", source = "campagne.id") @Mapping(target = "campagneId", source = "campagne.id")
@Mapping(target = "membreDonateurId", source = "membreDonateur.id") @Mapping(target = "membreDonateurId", source = "membreDonateur.id")
ContributionCollecteDTO toDto(ContributionCollecte entity); ContributionCollecteDTO toDto(ContributionCollecte entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "campagne", ignore = true) @Mapping(target = "campagne", ignore = true)
@Mapping(target = "membreDonateur", ignore = true) @Mapping(target = "membreDonateur", ignore = true)
ContributionCollecte toEntity(ContributionCollecteDTO dto); ContributionCollecte toEntity(ContributionCollecteDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "campagne", ignore = true) @Mapping(target = "campagne", ignore = true)
@Mapping(target = "membreDonateur", ignore = true) @Mapping(target = "membreDonateur", ignore = true)
void updateEntityFromDto(ContributionCollecteDTO dto, @MappingTarget ContributionCollecte entity); void updateEntityFromDto(ContributionCollecteDTO dto, @MappingTarget ContributionCollecte entity);
} }

View File

@@ -1,37 +1,37 @@
package dev.lions.unionflow.server.mapper.culte; package dev.lions.unionflow.server.mapper.culte;
import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO; import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO;
import dev.lions.unionflow.server.entity.culte.DonReligieux; import dev.lions.unionflow.server.entity.culte.DonReligieux;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface DonReligieuxMapper { public interface DonReligieuxMapper {
@Mapping(target = "institutionId", source = "institution.id") @Mapping(target = "institutionId", source = "institution.id")
@Mapping(target = "fideleId", source = "fidele.id") @Mapping(target = "fideleId", source = "fidele.id")
DonReligieuxDTO toDto(DonReligieux entity); DonReligieuxDTO toDto(DonReligieux entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "institution", ignore = true) @Mapping(target = "institution", ignore = true)
@Mapping(target = "fidele", ignore = true) @Mapping(target = "fidele", ignore = true)
DonReligieux toEntity(DonReligieuxDTO dto); DonReligieux toEntity(DonReligieuxDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "institution", ignore = true) @Mapping(target = "institution", ignore = true)
@Mapping(target = "fidele", ignore = true) @Mapping(target = "fidele", ignore = true)
void updateEntityFromDto(DonReligieuxDTO dto, @MappingTarget DonReligieux entity); void updateEntityFromDto(DonReligieuxDTO dto, @MappingTarget DonReligieux entity);
} }

View File

@@ -1,37 +1,37 @@
package dev.lions.unionflow.server.mapper.gouvernance; package dev.lions.unionflow.server.mapper.gouvernance;
import dev.lions.unionflow.server.api.dto.gouvernance.EchelonOrganigrammeDTO; import dev.lions.unionflow.server.api.dto.gouvernance.EchelonOrganigrammeDTO;
import dev.lions.unionflow.server.entity.gouvernance.EchelonOrganigramme; import dev.lions.unionflow.server.entity.gouvernance.EchelonOrganigramme;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface EchelonOrganigrammeMapper { public interface EchelonOrganigrammeMapper {
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
@Mapping(target = "echelonParentId", source = "echelonParent.id") @Mapping(target = "echelonParentId", source = "echelonParent.id")
EchelonOrganigrammeDTO toDto(EchelonOrganigramme entity); EchelonOrganigrammeDTO toDto(EchelonOrganigramme entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "echelonParent", ignore = true) @Mapping(target = "echelonParent", ignore = true)
EchelonOrganigramme toEntity(EchelonOrganigrammeDTO dto); EchelonOrganigramme toEntity(EchelonOrganigrammeDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "echelonParent", ignore = true) @Mapping(target = "echelonParent", ignore = true)
void updateEntityFromDto(EchelonOrganigrammeDTO dto, @MappingTarget EchelonOrganigramme entity); void updateEntityFromDto(EchelonOrganigrammeDTO dto, @MappingTarget EchelonOrganigramme entity);
} }

View File

@@ -1,65 +1,65 @@
package dev.lions.unionflow.server.mapper.mutuelle.credit; 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.DemandeCreditRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse; import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse;
import dev.lions.unionflow.server.entity.mutuelle.credit.DemandeCredit; import dev.lions.unionflow.server.entity.mutuelle.credit.DemandeCredit;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "jakarta-cdi", uses = { @Mapper(componentModel = "jakarta-cdi", uses = {
EcheanceCreditMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true)) EcheanceCreditMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
public interface DemandeCreditMapper { public interface DemandeCreditMapper {
@Mapping(target = "membreId", source = "membre.id") @Mapping(target = "membreId", source = "membre.id")
@Mapping(target = "compteLieId", source = "compteLie.id") @Mapping(target = "compteLieId", source = "compteLie.id")
DemandeCreditResponse toDto(DemandeCredit entity); DemandeCreditResponse toDto(DemandeCredit entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "compteLie", ignore = true) @Mapping(target = "compteLie", ignore = true)
@Mapping(target = "echeancier", ignore = true) @Mapping(target = "echeancier", ignore = true)
@Mapping(target = "garanties", ignore = true) @Mapping(target = "garanties", ignore = true)
@Mapping(target = "numeroDossier", ignore = true) @Mapping(target = "numeroDossier", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateSoumission", ignore = true) @Mapping(target = "dateSoumission", ignore = true)
@Mapping(target = "dateValidation", ignore = true) @Mapping(target = "dateValidation", ignore = true)
@Mapping(target = "notesComite", ignore = true) @Mapping(target = "notesComite", ignore = true)
@Mapping(target = "dureeMoisDemande", source = "dureeMois") @Mapping(target = "dureeMoisDemande", source = "dureeMois")
@Mapping(target = "montantApprouve", ignore = true) @Mapping(target = "montantApprouve", ignore = true)
@Mapping(target = "dureeMoisApprouvee", ignore = true) @Mapping(target = "dureeMoisApprouvee", ignore = true)
@Mapping(target = "tauxInteretAnnuel", ignore = true) @Mapping(target = "tauxInteretAnnuel", ignore = true)
@Mapping(target = "coutTotalCredit", ignore = true) @Mapping(target = "coutTotalCredit", ignore = true)
@Mapping(target = "datePremierEcheance", ignore = true) @Mapping(target = "datePremierEcheance", ignore = true)
DemandeCredit toEntity(DemandeCreditRequest request); DemandeCredit toEntity(DemandeCreditRequest request);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "compteLie", ignore = true) @Mapping(target = "compteLie", ignore = true)
@Mapping(target = "echeancier", ignore = true) @Mapping(target = "echeancier", ignore = true)
@Mapping(target = "garanties", ignore = true) @Mapping(target = "garanties", ignore = true)
@Mapping(target = "numeroDossier", ignore = true) @Mapping(target = "numeroDossier", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateSoumission", ignore = true) @Mapping(target = "dateSoumission", ignore = true)
@Mapping(target = "dateValidation", ignore = true) @Mapping(target = "dateValidation", ignore = true)
@Mapping(target = "notesComite", ignore = true) @Mapping(target = "notesComite", ignore = true)
@Mapping(target = "dureeMoisDemande", ignore = true) @Mapping(target = "dureeMoisDemande", ignore = true)
@Mapping(target = "montantApprouve", ignore = true) @Mapping(target = "montantApprouve", ignore = true)
@Mapping(target = "dureeMoisApprouvee", ignore = true) @Mapping(target = "dureeMoisApprouvee", ignore = true)
@Mapping(target = "tauxInteretAnnuel", ignore = true) @Mapping(target = "tauxInteretAnnuel", ignore = true)
@Mapping(target = "coutTotalCredit", ignore = true) @Mapping(target = "coutTotalCredit", ignore = true)
@Mapping(target = "datePremierEcheance", ignore = true) @Mapping(target = "datePremierEcheance", ignore = true)
void updateEntityFromDto(DemandeCreditRequest request, @MappingTarget DemandeCredit entity); void updateEntityFromDto(DemandeCreditRequest request, @MappingTarget DemandeCredit entity);
} }

View File

@@ -1,34 +1,34 @@
package dev.lions.unionflow.server.mapper.mutuelle.credit; package dev.lions.unionflow.server.mapper.mutuelle.credit;
import dev.lions.unionflow.server.api.dto.mutuelle.credit.EcheanceCreditDTO; import dev.lions.unionflow.server.api.dto.mutuelle.credit.EcheanceCreditDTO;
import dev.lions.unionflow.server.entity.mutuelle.credit.EcheanceCredit; import dev.lions.unionflow.server.entity.mutuelle.credit.EcheanceCredit;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface EcheanceCreditMapper { public interface EcheanceCreditMapper {
@Mapping(target = "demandeCreditId", source = "demandeCredit.id") @Mapping(target = "demandeCreditId", source = "demandeCredit.id")
EcheanceCreditDTO toDto(EcheanceCredit entity); EcheanceCreditDTO toDto(EcheanceCredit entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "demandeCredit", ignore = true) @Mapping(target = "demandeCredit", ignore = true)
EcheanceCredit toEntity(EcheanceCreditDTO dto); EcheanceCredit toEntity(EcheanceCreditDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "demandeCredit", ignore = true) @Mapping(target = "demandeCredit", ignore = true)
void updateEntityFromDto(EcheanceCreditDTO dto, @MappingTarget EcheanceCredit entity); void updateEntityFromDto(EcheanceCreditDTO dto, @MappingTarget EcheanceCredit entity);
} }

View File

@@ -1,33 +1,33 @@
package dev.lions.unionflow.server.mapper.mutuelle.credit; package dev.lions.unionflow.server.mapper.mutuelle.credit;
import dev.lions.unionflow.server.api.dto.mutuelle.credit.GarantieDemandeDTO; import dev.lions.unionflow.server.api.dto.mutuelle.credit.GarantieDemandeDTO;
import dev.lions.unionflow.server.entity.mutuelle.credit.GarantieDemande; import dev.lions.unionflow.server.entity.mutuelle.credit.GarantieDemande;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface GarantieDemandeMapper { public interface GarantieDemandeMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "demandeCredit", ignore = true) @Mapping(target = "demandeCredit", ignore = true)
GarantieDemande toEntity(GarantieDemandeDTO dto); GarantieDemande toEntity(GarantieDemandeDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "demandeCredit", ignore = true) @Mapping(target = "demandeCredit", ignore = true)
void updateEntityFromDto(GarantieDemandeDTO dto, @MappingTarget GarantieDemande entity); void updateEntityFromDto(GarantieDemandeDTO dto, @MappingTarget GarantieDemande entity);
GarantieDemandeDTO toDto(GarantieDemande entity); GarantieDemandeDTO toDto(GarantieDemande entity);
} }

View File

@@ -1,52 +1,52 @@
package dev.lions.unionflow.server.mapper.mutuelle.epargne; 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.CompteEpargneRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse; import dev.lions.unionflow.server.api.dto.mutuelle.epargne.CompteEpargneResponse;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne; import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface CompteEpargneMapper { public interface CompteEpargneMapper {
@Mapping(target = "membreId", source = "membre.id") @Mapping(target = "membreId", source = "membre.id")
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
CompteEpargneResponse toDto(CompteEpargne entity); CompteEpargneResponse toDto(CompteEpargne entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "numeroCompte", ignore = true) @Mapping(target = "numeroCompte", ignore = true)
@Mapping(target = "soldeActuel", ignore = true) @Mapping(target = "soldeActuel", ignore = true)
@Mapping(target = "soldeBloque", ignore = true) @Mapping(target = "soldeBloque", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateOuverture", ignore = true) @Mapping(target = "dateOuverture", ignore = true)
@Mapping(target = "dateDerniereTransaction", ignore = true) @Mapping(target = "dateDerniereTransaction", ignore = true)
@Mapping(target = "description", source = "notesOuverture") @Mapping(target = "description", source = "notesOuverture")
CompteEpargne toEntity(CompteEpargneRequest request); CompteEpargne toEntity(CompteEpargneRequest request);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "numeroCompte", ignore = true) @Mapping(target = "numeroCompte", ignore = true)
@Mapping(target = "soldeActuel", ignore = true) @Mapping(target = "soldeActuel", ignore = true)
@Mapping(target = "soldeBloque", ignore = true) @Mapping(target = "soldeBloque", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateOuverture", ignore = true) @Mapping(target = "dateOuverture", ignore = true)
@Mapping(target = "dateDerniereTransaction", ignore = true) @Mapping(target = "dateDerniereTransaction", ignore = true)
@Mapping(target = "description", source = "notesOuverture") @Mapping(target = "description", source = "notesOuverture")
void updateEntityFromDto(CompteEpargneRequest request, @MappingTarget CompteEpargne entity); void updateEntityFromDto(CompteEpargneRequest request, @MappingTarget CompteEpargne entity);
} }

View File

@@ -1,54 +1,54 @@
package dev.lions.unionflow.server.mapper.mutuelle.epargne; 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.TransactionEpargneRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse; import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse;
import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne; import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface TransactionEpargneMapper { public interface TransactionEpargneMapper {
@Mapping(target = "compteId", source = "compte.id") @Mapping(target = "compteId", source = "compte.id")
@Mapping(target = "pieceJustificativeId", expression = "java(entity.getPieceJustificativeId() != null ? entity.getPieceJustificativeId().toString() : null)") @Mapping(target = "pieceJustificativeId", expression = "java(entity.getPieceJustificativeId() != null ? entity.getPieceJustificativeId().toString() : null)")
TransactionEpargneResponse toDto(TransactionEpargne entity); TransactionEpargneResponse toDto(TransactionEpargne entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "compte", ignore = true) @Mapping(target = "compte", ignore = true)
@Mapping(target = "type", source = "typeTransaction") @Mapping(target = "type", source = "typeTransaction")
@Mapping(target = "soldeAvant", ignore = true) @Mapping(target = "soldeAvant", ignore = true)
@Mapping(target = "soldeApres", ignore = true) @Mapping(target = "soldeApres", ignore = true)
@Mapping(target = "dateTransaction", ignore = true) @Mapping(target = "dateTransaction", ignore = true)
@Mapping(target = "operateurId", ignore = true) @Mapping(target = "operateurId", ignore = true)
@Mapping(target = "referenceExterne", ignore = true) @Mapping(target = "referenceExterne", ignore = true)
@Mapping(target = "statutExecution", ignore = true) @Mapping(target = "statutExecution", ignore = true)
@Mapping(target = "origineFonds", source = "origineFonds") @Mapping(target = "origineFonds", source = "origineFonds")
@Mapping(target = "pieceJustificativeId", ignore = true) @Mapping(target = "pieceJustificativeId", ignore = true)
TransactionEpargne toEntity(TransactionEpargneRequest request); TransactionEpargne toEntity(TransactionEpargneRequest request);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "compte", ignore = true) @Mapping(target = "compte", ignore = true)
@Mapping(target = "type", source = "typeTransaction") @Mapping(target = "type", source = "typeTransaction")
@Mapping(target = "soldeAvant", ignore = true) @Mapping(target = "soldeAvant", ignore = true)
@Mapping(target = "soldeApres", ignore = true) @Mapping(target = "soldeApres", ignore = true)
@Mapping(target = "dateTransaction", ignore = true) @Mapping(target = "dateTransaction", ignore = true)
@Mapping(target = "operateurId", ignore = true) @Mapping(target = "operateurId", ignore = true)
@Mapping(target = "referenceExterne", ignore = true) @Mapping(target = "referenceExterne", ignore = true)
@Mapping(target = "statutExecution", ignore = true) @Mapping(target = "statutExecution", ignore = true)
@Mapping(target = "origineFonds", source = "origineFonds") @Mapping(target = "origineFonds", source = "origineFonds")
@Mapping(target = "pieceJustificativeId", ignore = true) @Mapping(target = "pieceJustificativeId", ignore = true)
void updateEntityFromDto(TransactionEpargneRequest request, @MappingTarget TransactionEpargne entity); void updateEntityFromDto(TransactionEpargneRequest request, @MappingTarget TransactionEpargne entity);
} }

View File

@@ -1,38 +1,38 @@
package dev.lions.unionflow.server.mapper.ong; package dev.lions.unionflow.server.mapper.ong;
import dev.lions.unionflow.server.api.dto.ong.ProjetOngDTO; import dev.lions.unionflow.server.api.dto.ong.ProjetOngDTO;
import dev.lions.unionflow.server.entity.ong.ProjetOng; import dev.lions.unionflow.server.entity.ong.ProjetOng;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface ProjetOngMapper { public interface ProjetOngMapper {
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
ProjetOngDTO toDto(ProjetOng entity); ProjetOngDTO toDto(ProjetOng entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "depensesReelles", ignore = true) @Mapping(target = "depensesReelles", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
ProjetOng toEntity(ProjetOngDTO dto); ProjetOng toEntity(ProjetOngDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "depensesReelles", ignore = true) @Mapping(target = "depensesReelles", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
void updateEntityFromDto(ProjetOngDTO dto, @MappingTarget ProjetOng entity); void updateEntityFromDto(ProjetOngDTO dto, @MappingTarget ProjetOng entity);
} }

View File

@@ -1,37 +1,37 @@
package dev.lions.unionflow.server.mapper.registre; package dev.lions.unionflow.server.mapper.registre;
import dev.lions.unionflow.server.api.dto.registre.AgrementProfessionnelDTO; import dev.lions.unionflow.server.api.dto.registre.AgrementProfessionnelDTO;
import dev.lions.unionflow.server.entity.registre.AgrementProfessionnel; import dev.lions.unionflow.server.entity.registre.AgrementProfessionnel;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface AgrementProfessionnelMapper { public interface AgrementProfessionnelMapper {
@Mapping(target = "membreId", source = "membre.id") @Mapping(target = "membreId", source = "membre.id")
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
AgrementProfessionnelDTO toDto(AgrementProfessionnel entity); AgrementProfessionnelDTO toDto(AgrementProfessionnel entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
AgrementProfessionnel toEntity(AgrementProfessionnelDTO dto); AgrementProfessionnel toEntity(AgrementProfessionnelDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "membre", ignore = true) @Mapping(target = "membre", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
void updateEntityFromDto(AgrementProfessionnelDTO dto, @MappingTarget AgrementProfessionnel entity); void updateEntityFromDto(AgrementProfessionnelDTO dto, @MappingTarget AgrementProfessionnel entity);
} }

View File

@@ -1,46 +1,46 @@
package dev.lions.unionflow.server.mapper.tontine; package dev.lions.unionflow.server.mapper.tontine;
import dev.lions.unionflow.server.api.dto.tontine.TontineRequest; import dev.lions.unionflow.server.api.dto.tontine.TontineRequest;
import dev.lions.unionflow.server.api.dto.tontine.TontineResponse; import dev.lions.unionflow.server.api.dto.tontine.TontineResponse;
import dev.lions.unionflow.server.entity.tontine.Tontine; import dev.lions.unionflow.server.entity.tontine.Tontine;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "jakarta-cdi", uses = { @Mapper(componentModel = "jakarta-cdi", uses = {
TourTontineMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true)) TourTontineMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
public interface TontineMapper { public interface TontineMapper {
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
@Mapping(target = "nombreParticipantsActuels", ignore = true) @Mapping(target = "nombreParticipantsActuels", ignore = true)
@Mapping(target = "fondTotalCollecte", ignore = true) @Mapping(target = "fondTotalCollecte", ignore = true)
TontineResponse toDto(Tontine entity); TontineResponse toDto(Tontine entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "calendrierTours", ignore = true) @Mapping(target = "calendrierTours", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateDebutEffective", ignore = true) @Mapping(target = "dateDebutEffective", ignore = true)
@Mapping(target = "dateFinPrevue", ignore = true) @Mapping(target = "dateFinPrevue", ignore = true)
Tontine toEntity(TontineRequest request); Tontine toEntity(TontineRequest request);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "calendrierTours", ignore = true) @Mapping(target = "calendrierTours", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "dateDebutEffective", ignore = true) @Mapping(target = "dateDebutEffective", ignore = true)
@Mapping(target = "dateFinPrevue", ignore = true) @Mapping(target = "dateFinPrevue", ignore = true)
void updateEntityFromDto(TontineRequest request, @MappingTarget Tontine entity); void updateEntityFromDto(TontineRequest request, @MappingTarget Tontine entity);
} }

View File

@@ -1,37 +1,37 @@
package dev.lions.unionflow.server.mapper.tontine; package dev.lions.unionflow.server.mapper.tontine;
import dev.lions.unionflow.server.api.dto.tontine.TourTontineDTO; import dev.lions.unionflow.server.api.dto.tontine.TourTontineDTO;
import dev.lions.unionflow.server.entity.tontine.TourTontine; import dev.lions.unionflow.server.entity.tontine.TourTontine;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface TourTontineMapper { public interface TourTontineMapper {
@Mapping(target = "tontineId", source = "tontine.id") @Mapping(target = "tontineId", source = "tontine.id")
@Mapping(target = "membreBeneficiaireId", source = "membreBeneficiaire.id") @Mapping(target = "membreBeneficiaireId", source = "membreBeneficiaire.id")
TourTontineDTO toDto(TourTontine entity); TourTontineDTO toDto(TourTontine entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "tontine", ignore = true) @Mapping(target = "tontine", ignore = true)
@Mapping(target = "membreBeneficiaire", ignore = true) @Mapping(target = "membreBeneficiaire", ignore = true)
TourTontine toEntity(TourTontineDTO dto); TourTontine toEntity(TourTontineDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "tontine", ignore = true) @Mapping(target = "tontine", ignore = true)
@Mapping(target = "membreBeneficiaire", ignore = true) @Mapping(target = "membreBeneficiaire", ignore = true)
void updateEntityFromDto(TourTontineDTO dto, @MappingTarget TourTontine entity); void updateEntityFromDto(TourTontineDTO dto, @MappingTarget TourTontine entity);
} }

View File

@@ -1,48 +1,48 @@
package dev.lions.unionflow.server.mapper.vote; package dev.lions.unionflow.server.mapper.vote;
import dev.lions.unionflow.server.api.dto.vote.CampagneVoteRequest; import dev.lions.unionflow.server.api.dto.vote.CampagneVoteRequest;
import dev.lions.unionflow.server.api.dto.vote.CampagneVoteResponse; import dev.lions.unionflow.server.api.dto.vote.CampagneVoteResponse;
import dev.lions.unionflow.server.entity.vote.CampagneVote; import dev.lions.unionflow.server.entity.vote.CampagneVote;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "jakarta-cdi", uses = { @Mapper(componentModel = "jakarta-cdi", uses = {
CandidatMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true)) CandidatMapper.class }, builder = @org.mapstruct.Builder(disableBuilder = true))
public interface CampagneVoteMapper { public interface CampagneVoteMapper {
@Mapping(target = "organisationId", source = "organisation.id") @Mapping(target = "organisationId", source = "organisation.id")
@Mapping(target = "candidatsExposes", source = "candidats") @Mapping(target = "candidatsExposes", source = "candidats")
@Mapping(target = "tauxDeParticipation", ignore = true) @Mapping(target = "tauxDeParticipation", ignore = true)
CampagneVoteResponse toDto(CampagneVote entity); CampagneVoteResponse toDto(CampagneVote entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "candidats", ignore = true) @Mapping(target = "candidats", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "totalElecteursInscrits", ignore = true) @Mapping(target = "totalElecteursInscrits", ignore = true)
@Mapping(target = "totalVotantsEffectifs", ignore = true) @Mapping(target = "totalVotantsEffectifs", ignore = true)
@Mapping(target = "totalVotesBlancsOuNuls", ignore = true) @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
CampagneVote toEntity(CampagneVoteRequest request); CampagneVote toEntity(CampagneVoteRequest request);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "organisation", ignore = true) @Mapping(target = "organisation", ignore = true)
@Mapping(target = "candidats", ignore = true) @Mapping(target = "candidats", ignore = true)
@Mapping(target = "statut", ignore = true) @Mapping(target = "statut", ignore = true)
@Mapping(target = "totalElecteursInscrits", ignore = true) @Mapping(target = "totalElecteursInscrits", ignore = true)
@Mapping(target = "totalVotantsEffectifs", ignore = true) @Mapping(target = "totalVotantsEffectifs", ignore = true)
@Mapping(target = "totalVotesBlancsOuNuls", ignore = true) @Mapping(target = "totalVotesBlancsOuNuls", ignore = true)
void updateEntityFromDto(CampagneVoteRequest request, @MappingTarget CampagneVote entity); void updateEntityFromDto(CampagneVoteRequest request, @MappingTarget CampagneVote entity);
} }

View File

@@ -1,38 +1,38 @@
package dev.lions.unionflow.server.mapper.vote; package dev.lions.unionflow.server.mapper.vote;
import dev.lions.unionflow.server.api.dto.vote.CandidatDTO; import dev.lions.unionflow.server.api.dto.vote.CandidatDTO;
import dev.lions.unionflow.server.entity.vote.Candidat; import dev.lions.unionflow.server.entity.vote.Candidat;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
@Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true)) @Mapper(componentModel = "cdi", builder = @org.mapstruct.Builder(disableBuilder = true))
public interface CandidatMapper { public interface CandidatMapper {
@Mapping(target = "campagneVoteId", source = "campagneVote.id") @Mapping(target = "campagneVoteId", source = "campagneVote.id")
CandidatDTO toDto(Candidat entity); CandidatDTO toDto(Candidat entity);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "campagneVote", ignore = true) @Mapping(target = "campagneVote", ignore = true)
@Mapping(target = "nombreDeVoix", ignore = true) @Mapping(target = "nombreDeVoix", ignore = true)
@Mapping(target = "pourcentageObtenu", ignore = true) @Mapping(target = "pourcentageObtenu", ignore = true)
Candidat toEntity(CandidatDTO dto); Candidat toEntity(CandidatDTO dto);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
@Mapping(target = "dateCreation", ignore = true) @Mapping(target = "dateCreation", ignore = true)
@Mapping(target = "dateModification", ignore = true) @Mapping(target = "dateModification", ignore = true)
@Mapping(target = "creePar", ignore = true) @Mapping(target = "creePar", ignore = true)
@Mapping(target = "modifiePar", ignore = true) @Mapping(target = "modifiePar", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "actif", ignore = true) @Mapping(target = "actif", ignore = true)
@Mapping(target = "campagneVote", ignore = true) @Mapping(target = "campagneVote", ignore = true)
@Mapping(target = "nombreDeVoix", ignore = true) @Mapping(target = "nombreDeVoix", ignore = true)
@Mapping(target = "pourcentageObtenu", ignore = true) @Mapping(target = "pourcentageObtenu", ignore = true)
void updateEntityFromDto(CandidatDTO dto, @MappingTarget Candidat entity); void updateEntityFromDto(CandidatDTO dto, @MappingTarget Candidat entity);
} }

View File

@@ -1,103 +1,103 @@
package dev.lions.unionflow.server.messaging; package dev.lions.unionflow.server.messaging;
import dev.lions.unionflow.server.service.WebSocketBroadcastService; import dev.lions.unionflow.server.service.WebSocketBroadcastService;
import io.smallrye.reactive.messaging.kafka.Record; import io.smallrye.reactive.messaging.kafka.Record;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Incoming;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Consumer Kafka pour consommer les events et les broadcaster via WebSocket. * Consumer Kafka pour consommer les events et les broadcaster via WebSocket.
* <p> * <p>
* Ce consumer écoute tous les topics Kafka et transmet les events * Ce consumer écoute tous les topics Kafka et transmet les events
* en temps réel aux clients mobiles/web connectés via WebSocket. * en temps réel aux clients mobiles/web connectés via WebSocket.
*/ */
@ApplicationScoped @ApplicationScoped
public class KafkaEventConsumer { public class KafkaEventConsumer {
private static final Logger LOG = Logger.getLogger(KafkaEventConsumer.class); private static final Logger LOG = Logger.getLogger(KafkaEventConsumer.class);
@Inject @Inject
WebSocketBroadcastService webSocketBroadcastService; WebSocketBroadcastService webSocketBroadcastService;
/** /**
* Consomme les events d'approbations financières. * Consomme les events d'approbations financières.
*/ */
@Incoming("finance-approvals-in") @Incoming("finance-approvals-in")
public void consumeFinanceApprovals(Record<String, String> record) { public void consumeFinanceApprovals(Record<String, String> record) {
LOG.debugf("Received finance approval event: key=%s, value=%s", record.key(), record.value()); LOG.debugf("Received finance approval event: key=%s, value=%s", record.key(), record.value());
try { try {
// Broadcast aux clients WebSocket // Broadcast aux clients WebSocket
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast finance approval event"); LOG.errorf(e, "Failed to broadcast finance approval event");
} }
} }
/** /**
* Consomme les mises à jour de stats dashboard. * Consomme les mises à jour de stats dashboard.
*/ */
@Incoming("dashboard-stats-in") @Incoming("dashboard-stats-in")
public void consumeDashboardStats(Record<String, String> record) { public void consumeDashboardStats(Record<String, String> record) {
LOG.debugf("Received dashboard stats event: key=%s", record.key()); LOG.debugf("Received dashboard stats event: key=%s", record.key());
try { try {
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast dashboard stats event"); LOG.errorf(e, "Failed to broadcast dashboard stats event");
} }
} }
/** /**
* Consomme les notifications. * Consomme les notifications.
*/ */
@Incoming("notifications-in") @Incoming("notifications-in")
public void consumeNotifications(Record<String, String> record) { public void consumeNotifications(Record<String, String> record) {
LOG.debugf("Received notification event: key=%s", record.key()); LOG.debugf("Received notification event: key=%s", record.key());
try { try {
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast notification event"); LOG.errorf(e, "Failed to broadcast notification event");
} }
} }
/** /**
* Consomme les events membres. * Consomme les events membres.
*/ */
@Incoming("members-events-in") @Incoming("members-events-in")
public void consumeMembersEvents(Record<String, String> record) { public void consumeMembersEvents(Record<String, String> record) {
LOG.debugf("Received member event: key=%s", record.key()); LOG.debugf("Received member event: key=%s", record.key());
try { try {
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast member event"); LOG.errorf(e, "Failed to broadcast member event");
} }
} }
/** /**
* Consomme les events cotisations. * Consomme les events cotisations.
*/ */
@Incoming("contributions-events-in") @Incoming("contributions-events-in")
public void consumeContributionsEvents(Record<String, String> record) { public void consumeContributionsEvents(Record<String, String> record) {
LOG.debugf("Received contribution event: key=%s", record.key()); LOG.debugf("Received contribution event: key=%s", record.key());
try { try {
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast contribution event"); LOG.errorf(e, "Failed to broadcast contribution event");
} }
} }
/** /**
* Consomme les messages de chat (nouveaux messages envoyés dans une conversation). * 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. * Broadcaste l'event en temps réel aux clients WebSocket pour mise à jour instantanée.
*/ */
@Incoming("chat-messages-in") @Incoming("chat-messages-in")
public void consumeChatMessages(Record<String, String> record) { public void consumeChatMessages(Record<String, String> record) {
LOG.debugf("Received chat message event: key=%s", record.key()); LOG.debugf("Received chat message event: key=%s", record.key());
try { try {
webSocketBroadcastService.broadcast(record.value()); webSocketBroadcastService.broadcast(record.value());
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to broadcast chat message event"); LOG.errorf(e, "Failed to broadcast chat message event");
} }
} }
} }

View File

@@ -1,189 +1,189 @@
package dev.lions.unionflow.server.messaging; package dev.lions.unionflow.server.messaging;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.smallrye.reactive.messaging.kafka.Record; import io.smallrye.reactive.messaging.kafka.Record;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.reactive.messaging.Channel; import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter; import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
* Producer Kafka pour publier des events UnionFlow. * Producer Kafka pour publier des events UnionFlow.
* <p> * <p>
* Publie sur différents topics Kafka qui sont ensuite consommés * Publie sur différents topics Kafka qui sont ensuite consommés
* par le WebSocket server pour broadcast aux clients mobiles/web. * par le WebSocket server pour broadcast aux clients mobiles/web.
*/ */
@ApplicationScoped @ApplicationScoped
public class KafkaEventProducer { public class KafkaEventProducer {
private static final Logger LOG = Logger.getLogger(KafkaEventProducer.class); private static final Logger LOG = Logger.getLogger(KafkaEventProducer.class);
@Inject @Inject
ObjectMapper objectMapper; ObjectMapper objectMapper;
@Channel("finance-approvals-out") @Channel("finance-approvals-out")
Emitter<Record<String, String>> financeApprovalsEmitter; Emitter<Record<String, String>> financeApprovalsEmitter;
@Channel("dashboard-stats-out") @Channel("dashboard-stats-out")
Emitter<Record<String, String>> dashboardStatsEmitter; Emitter<Record<String, String>> dashboardStatsEmitter;
@Channel("notifications-out") @Channel("notifications-out")
Emitter<Record<String, String>> notificationsEmitter; Emitter<Record<String, String>> notificationsEmitter;
@Channel("members-events-out") @Channel("members-events-out")
Emitter<Record<String, String>> membersEventsEmitter; Emitter<Record<String, String>> membersEventsEmitter;
@Channel("contributions-events-out") @Channel("contributions-events-out")
Emitter<Record<String, String>> contributionsEventsEmitter; Emitter<Record<String, String>> contributionsEventsEmitter;
@Channel("chat-messages-out") @Channel("chat-messages-out")
Emitter<Record<String, String>> chatMessagesEmitter; Emitter<Record<String, String>> chatMessagesEmitter;
/** /**
* Publie un event d'approbation en attente. * Publie un event d'approbation en attente.
*/ */
public void publishApprovalPending(UUID approvalId, String organizationId, Map<String, Object> approvalData) { public void publishApprovalPending(UUID approvalId, String organizationId, Map<String, Object> approvalData) {
var event = buildEvent("APPROVAL_PENDING", organizationId, approvalData); var event = buildEvent("APPROVAL_PENDING", organizationId, approvalData);
publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals"); publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
} }
/** /**
* Publie un event d'approbation approuvée. * Publie un event d'approbation approuvée.
*/ */
public void publishApprovalApproved(UUID approvalId, String organizationId, Map<String, Object> approvalData) { public void publishApprovalApproved(UUID approvalId, String organizationId, Map<String, Object> approvalData) {
var event = buildEvent("APPROVAL_APPROVED", organizationId, approvalData); var event = buildEvent("APPROVAL_APPROVED", organizationId, approvalData);
publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals"); publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
} }
/** /**
* Publie un event d'approbation rejetée. * Publie un event d'approbation rejetée.
*/ */
public void publishApprovalRejected(UUID approvalId, String organizationId, Map<String, Object> approvalData) { public void publishApprovalRejected(UUID approvalId, String organizationId, Map<String, Object> approvalData) {
var event = buildEvent("APPROVAL_REJECTED", organizationId, approvalData); var event = buildEvent("APPROVAL_REJECTED", organizationId, approvalData);
publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals"); publishToChannel(financeApprovalsEmitter, approvalId.toString(), event, "finance-approvals");
} }
/** /**
* Publie une mise à jour des stats dashboard. * Publie une mise à jour des stats dashboard.
*/ */
public void publishDashboardStatsUpdate(String organizationId, Map<String, Object> stats) { public void publishDashboardStatsUpdate(String organizationId, Map<String, Object> stats) {
var event = buildEvent("DASHBOARD_STATS_UPDATED", organizationId, stats); var event = buildEvent("DASHBOARD_STATS_UPDATED", organizationId, stats);
publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats"); publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
} }
/** /**
* Publie un KPI temps réel. * Publie un KPI temps réel.
*/ */
public void publishKpiUpdate(String organizationId, Map<String, Object> kpiData) { public void publishKpiUpdate(String organizationId, Map<String, Object> kpiData) {
var event = buildEvent("KPI_UPDATED", organizationId, kpiData); var event = buildEvent("KPI_UPDATED", organizationId, kpiData);
publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats"); publishToChannel(dashboardStatsEmitter, organizationId, event, "dashboard-stats");
} }
/** /**
* Publie une notification utilisateur. * Publie une notification utilisateur.
*/ */
public void publishUserNotification(String userId, Map<String, Object> notificationData) { public void publishUserNotification(String userId, Map<String, Object> notificationData) {
var event = buildEvent("USER_NOTIFICATION", null, notificationData); var event = buildEvent("USER_NOTIFICATION", null, notificationData);
event.put("userId", userId); event.put("userId", userId);
publishToChannel(notificationsEmitter, userId, event, "notifications"); publishToChannel(notificationsEmitter, userId, event, "notifications");
} }
/** /**
* Publie une notification broadcast (toute une organisation). * Publie une notification broadcast (toute une organisation).
*/ */
public void publishBroadcastNotification(String organizationId, Map<String, Object> notificationData) { public void publishBroadcastNotification(String organizationId, Map<String, Object> notificationData) {
var event = buildEvent("BROADCAST_NOTIFICATION", organizationId, notificationData); var event = buildEvent("BROADCAST_NOTIFICATION", organizationId, notificationData);
publishToChannel(notificationsEmitter, organizationId, event, "notifications"); publishToChannel(notificationsEmitter, organizationId, event, "notifications");
} }
/** /**
* Publie un event de création de membre. * Publie un event de création de membre.
*/ */
public void publishMemberCreated(UUID memberId, String organizationId, Map<String, Object> memberData) { public void publishMemberCreated(UUID memberId, String organizationId, Map<String, Object> memberData) {
var event = buildEvent("MEMBER_CREATED", organizationId, memberData); var event = buildEvent("MEMBER_CREATED", organizationId, memberData);
publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events"); publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
} }
/** /**
* Publie un event de modification de membre. * Publie un event de modification de membre.
*/ */
public void publishMemberUpdated(UUID memberId, String organizationId, Map<String, Object> memberData) { public void publishMemberUpdated(UUID memberId, String organizationId, Map<String, Object> memberData) {
var event = buildEvent("MEMBER_UPDATED", organizationId, memberData); var event = buildEvent("MEMBER_UPDATED", organizationId, memberData);
publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events"); publishToChannel(membersEventsEmitter, memberId.toString(), event, "members-events");
} }
/** /**
* Publie un event de désactivation de membre (soft delete). * Publie un event de désactivation de membre (soft delete).
* Les consommateurs peuvent réagir : bloquer comptes épargne, annuler inscriptions, * Les consommateurs peuvent réagir : bloquer comptes épargne, annuler inscriptions,
* reassigner approvals pending, nettoyer notifications, etc. * reassigner approvals pending, nettoyer notifications, etc.
*/ */
public void publishMemberDeactivated(dev.lions.unionflow.server.entity.Membre membre) { public void publishMemberDeactivated(dev.lions.unionflow.server.entity.Membre membre) {
if (membre == null || membre.getId() == null) return; if (membre == null || membre.getId() == null) return;
Map<String, Object> data = new java.util.HashMap<>(); Map<String, Object> data = new java.util.HashMap<>();
data.put("membreId", membre.getId().toString()); data.put("membreId", membre.getId().toString());
data.put("email", membre.getEmail()); data.put("email", membre.getEmail());
data.put("nomComplet", membre.getNomComplet()); data.put("nomComplet", membre.getNomComplet());
data.put("numeroMembre", membre.getNumeroMembre()); data.put("numeroMembre", membre.getNumeroMembre());
// organisationId principal (si présent) pour routage par org // organisationId principal (si présent) pour routage par org
String orgId = membre.getMembresOrganisations() != null String orgId = membre.getMembresOrganisations() != null
&& !membre.getMembresOrganisations().isEmpty() && !membre.getMembresOrganisations().isEmpty()
&& membre.getMembresOrganisations().get(0).getOrganisation() != null && membre.getMembresOrganisations().get(0).getOrganisation() != null
? membre.getMembresOrganisations().get(0).getOrganisation().getId().toString() ? membre.getMembresOrganisations().get(0).getOrganisation().getId().toString()
: ""; : "";
var event = buildEvent("MEMBER_DEACTIVATED", orgId, data); var event = buildEvent("MEMBER_DEACTIVATED", orgId, data);
publishToChannel(membersEventsEmitter, membre.getId().toString(), event, "members-events"); publishToChannel(membersEventsEmitter, membre.getId().toString(), event, "members-events");
} }
/** /**
* Publie un event de cotisation payée. * Publie un event de cotisation payée.
*/ */
public void publishContributionPaid(UUID contributionId, String organizationId, Map<String, Object> contributionData) { public void publishContributionPaid(UUID contributionId, String organizationId, Map<String, Object> contributionData) {
var event = buildEvent("CONTRIBUTION_PAID", organizationId, contributionData); var event = buildEvent("CONTRIBUTION_PAID", organizationId, contributionData);
publishToChannel(contributionsEventsEmitter, contributionId.toString(), event, "contributions-events"); publishToChannel(contributionsEventsEmitter, contributionId.toString(), event, "contributions-events");
} }
/** /**
* Publie un event de nouveau message de chat. * Publie un event de nouveau message de chat.
* Les clients WebSocket de l'organisation sont notifiés pour rafraîchir leurs messages. * Les clients WebSocket de l'organisation sont notifiés pour rafraîchir leurs messages.
*/ */
public void publishNouveauMessage(UUID conversationId, String organizationId, Map<String, Object> messageData) { public void publishNouveauMessage(UUID conversationId, String organizationId, Map<String, Object> messageData) {
var event = buildEvent("NOUVEAU_MESSAGE", organizationId, messageData); var event = buildEvent("NOUVEAU_MESSAGE", organizationId, messageData);
publishToChannel(chatMessagesEmitter, conversationId.toString(), event, "chat-messages"); publishToChannel(chatMessagesEmitter, conversationId.toString(), event, "chat-messages");
} }
/** /**
* Construit un event avec structure standardisée. * Construit un event avec structure standardisée.
*/ */
private Map<String, Object> buildEvent(String eventType, String organizationId, Map<String, Object> data) { private Map<String, Object> buildEvent(String eventType, String organizationId, Map<String, Object> data) {
var event = new HashMap<String, Object>(); var event = new HashMap<String, Object>();
event.put("eventType", eventType); event.put("eventType", eventType);
event.put("timestamp", Instant.now().toString()); event.put("timestamp", Instant.now().toString());
if (organizationId != null) { if (organizationId != null) {
event.put("organizationId", organizationId); event.put("organizationId", organizationId);
} }
event.put("data", data); event.put("data", data);
return event; return event;
} }
/** /**
* Publie un event sur un channel Kafka avec gestion d'erreur. * Publie un event sur un channel Kafka avec gestion d'erreur.
*/ */
private void publishToChannel(Emitter<Record<String, String>> emitter, String key, Map<String, Object> event, String topicName) { private void publishToChannel(Emitter<Record<String, String>> emitter, String key, Map<String, Object> event, String topicName) {
try { try {
String eventJson = objectMapper.writeValueAsString(event); String eventJson = objectMapper.writeValueAsString(event);
emitter.send(Record.of(key, eventJson)); emitter.send(Record.of(key, eventJson));
LOG.debugf("Published event to %s: %s", topicName, eventJson); LOG.debugf("Published event to %s: %s", topicName, eventJson);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOG.errorf(e, "Failed to serialize event for topic %s", topicName); LOG.errorf(e, "Failed to serialize event for topic %s", topicName);
} catch (Exception e) { } catch (Exception e) {
LOG.errorf(e, "Failed to publish event to topic %s", topicName); LOG.errorf(e, "Failed to publish event to topic %s", topicName);
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More