Compare commits
27 Commits
34056b7c03
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07302f2743 | ||
|
|
af8d237d01 | ||
|
|
d52b0f6f2b | ||
|
|
2826d75aa6 | ||
|
|
0e264b3c1f | ||
|
|
ed0d74e124 | ||
|
|
0c46d9bad6 | ||
|
|
e4d7c8e4b7 | ||
|
|
7c3352ed48 | ||
|
|
f267eeebfc | ||
|
|
241533efa6 | ||
|
|
4d400dc48d | ||
|
|
86842f27af | ||
|
|
a0b2690c17 | ||
|
|
b0ee8881fb | ||
|
|
8b589477ec | ||
| c54092bd78 | |||
| 7099f554fe | |||
| 144137656f | |||
| d8006c8425 | |||
| 6e9841b3bb | |||
| 044ca4bd7e | |||
| b434282000 | |||
| a72ab54abd | |||
| fb3a32817b | |||
|
|
8cec38f7b3 | ||
|
|
31330d95e9 |
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Dockerfile for unionflow-server-impl-quarkus
|
||||||
|
# Used by lionsctl pipeline. Expects `mvn clean package -Pprod` to have produced target/quarkus-app/ (fast-jar).
|
||||||
|
FROM registry.access.redhat.com/ubi8/openjdk-21:1.21
|
||||||
|
|
||||||
|
ENV LANGUAGE='en_US:en'
|
||||||
|
|
||||||
|
COPY --chown=1001:1001 target/quarkus-app/lib/ /deployments/lib/
|
||||||
|
COPY --chown=1001:1001 target/quarkus-app/*.jar /deployments/
|
||||||
|
COPY --chown=1001:1001 target/quarkus-app/app/ /deployments/app/
|
||||||
|
COPY --chown=1001:1001 target/quarkus-app/quarkus/ /deployments/quarkus/
|
||||||
|
|
||||||
|
USER 1001
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||||
|
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8080/q/health/live || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
|
||||||
21
README.md
21
README.md
@@ -1,7 +1,7 @@
|
|||||||
# UnionFlow Backend - API REST Quarkus
|
# UnionFlow Backend - API REST Quarkus
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -64,7 +64,7 @@ Tous les repositories étendent `PanacheRepositoryBase<Entity, UUID>` pour :
|
|||||||
| Composant | Version | Usage |
|
| Composant | Version | Usage |
|
||||||
|-----------|---------|-------|
|
|-----------|---------|-------|
|
||||||
| **Java** | 17 (LTS) | Langage |
|
| **Java** | 17 (LTS) | Langage |
|
||||||
| **Quarkus** | 3.15.1 | Framework application |
|
| **Quarkus** | 3.27.3 LTS | Framework application |
|
||||||
| **Hibernate ORM (Panache)** | 6.4+ | Persistence |
|
| **Hibernate ORM (Panache)** | 6.4+ | Persistence |
|
||||||
| **PostgreSQL** | 15 | Base de données |
|
| **PostgreSQL** | 15 | Base de données |
|
||||||
| **Flyway** | 9.22+ | Migrations DB |
|
| **Flyway** | 9.22+ | Migrations DB |
|
||||||
@@ -482,7 +482,7 @@ src/test/java/
|
|||||||
lionsctl pipeline \
|
lionsctl pipeline \
|
||||||
-u https://git.lions.dev/lionsdev/unionflow-server-impl-quarkus \
|
-u https://git.lions.dev/lionsdev/unionflow-server-impl-quarkus \
|
||||||
-b main \
|
-b main \
|
||||||
-j 17 \
|
-j 21 \
|
||||||
-e production \
|
-e production \
|
||||||
-c k1 \
|
-c k1 \
|
||||||
-p prod
|
-p prod
|
||||||
@@ -490,12 +490,19 @@ lionsctl pipeline \
|
|||||||
# Étapes :
|
# Étapes :
|
||||||
# 1. Clone repo Git
|
# 1. Clone repo Git
|
||||||
# 2. mvn clean package -Pprod
|
# 2. mvn clean package -Pprod
|
||||||
# 3. docker build + push registry.lions.dev
|
# 3. docker build -f Dockerfile (racine, fast-jar, ubi8/openjdk-21:1.21, UID 1001)
|
||||||
# 4. kubectl apply -f k8s/
|
# 4. push registry.lions.dev
|
||||||
# 5. Health check
|
# 5. kubectl apply (Deployment + Service + Ingress)
|
||||||
# 6. Email notification
|
# 6. Health check
|
||||||
|
# 7. Email notification
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Pré-requis infrastructure** avant pipeline (migration Helm → lionsctl pipeline) :
|
||||||
|
- Secret K8s `unionflow-server-impl-quarkus-db-secret` (clés `QUARKUS_DATASOURCE_USERNAME` + `QUARKUS_DATASOURCE_PASSWORD`)
|
||||||
|
- DB PostgreSQL `unionflow` (override `QUARKUS_DATASOURCE_JDBC_URL` sur le deployment car lionsctl nomme la DB comme l'app)
|
||||||
|
- Deployment Helm existant supprimé au préalable (selector immutable)
|
||||||
|
- Service selector à repatcher après pipeline (retirer les labels `app.kubernetes.io/*`)
|
||||||
|
|
||||||
### Fichiers Kubernetes
|
### Fichiers Kubernetes
|
||||||
|
|
||||||
**Localisation** : `src/main/kubernetes/`
|
**Localisation** : `src/main/kubernetes/`
|
||||||
|
|||||||
73
docker-compose.kc26.yml
Normal file
73
docker-compose.kc26.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
# Compose alternatif Keycloak 26.6.1 avec feature Organizations native (GA depuis 26.0).
|
||||||
|
# Usage : docker compose -f docker-compose.kc26.yml up -d
|
||||||
|
# But : valider la migration KC23 → KC26 + Organizations en local, sans toucher au compose dev.
|
||||||
|
#
|
||||||
|
# Une fois la migration validée, basculer ce contenu en production et supprimer la stack KC23.
|
||||||
|
#
|
||||||
|
# Réf : ARCH_KEYCLOAK_26.md
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres-keycloak:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: kc26-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: keycloak
|
||||||
|
POSTGRES_USER: keycloak
|
||||||
|
POSTGRES_PASSWORD: keycloak
|
||||||
|
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||||
|
volumes:
|
||||||
|
- kc26_postgres_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- kc26-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:26.6.1
|
||||||
|
container_name: kc26-server
|
||||||
|
command:
|
||||||
|
- start-dev
|
||||||
|
- --features=organization
|
||||||
|
- --http-port=8180
|
||||||
|
- --import-realm
|
||||||
|
environment:
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
|
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||||
|
KC_DB: postgres
|
||||||
|
KC_DB_URL: jdbc:postgresql://postgres-keycloak:5432/keycloak
|
||||||
|
KC_DB_USERNAME: keycloak
|
||||||
|
KC_DB_PASSWORD: keycloak
|
||||||
|
KC_HEALTH_ENABLED: "true"
|
||||||
|
KC_METRICS_ENABLED: "true"
|
||||||
|
KC_HOSTNAME_STRICT: "false"
|
||||||
|
KC_HTTP_ENABLED: "true"
|
||||||
|
ports:
|
||||||
|
- "8180:8180"
|
||||||
|
volumes:
|
||||||
|
- ./src/main/resources/keycloak/realms:/opt/keycloak/data/import:ro
|
||||||
|
depends_on:
|
||||||
|
postgres-keycloak:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- kc26-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8180 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'UP'"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 8
|
||||||
|
start_period: 60s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kc26_postgres_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
kc26-net:
|
||||||
|
driver: bridge
|
||||||
36
pom.xml
36
pom.xml
@@ -4,14 +4,9 @@
|
|||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<parent>
|
<groupId>dev.lions.unionflow</groupId>
|
||||||
<groupId>dev.lions.unionflow</groupId>
|
|
||||||
<artifactId>unionflow-parent</artifactId>
|
|
||||||
<version>1.0.6</version>
|
|
||||||
<relativePath/> <!-- Force resolution from Maven repo (Gitea); enables standalone clones -->
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>unionflow-server-impl-quarkus</artifactId>
|
<artifactId>unionflow-server-impl-quarkus</artifactId>
|
||||||
|
<version>1.0.7</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>UnionFlow Server Implementation (Quarkus)</name>
|
<name>UnionFlow Server Implementation (Quarkus)</name>
|
||||||
@@ -23,9 +18,13 @@
|
|||||||
<maven.compiler.release>21</maven.compiler.release>
|
<maven.compiler.release>21</maven.compiler.release>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<quarkus.platform.version>3.20.0</quarkus.platform.version>
|
<quarkus.platform.version>3.27.3</quarkus.platform.version>
|
||||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||||
|
<lombok.version>1.18.38</lombok.version>
|
||||||
|
<!-- Overrides BOM : Docker Desktop 29.x compat -->
|
||||||
|
<testcontainers.version>1.21.4</testcontainers.version>
|
||||||
|
<docker-java.version>3.4.2</docker-java.version>
|
||||||
|
|
||||||
<!-- Jacoco -->
|
<!-- Jacoco -->
|
||||||
<jacoco.version>0.8.12</jacoco.version>
|
<jacoco.version>0.8.12</jacoco.version>
|
||||||
@@ -40,6 +39,20 @@
|
|||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers-bom</artifactId>
|
||||||
|
<version>${testcontainers.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Lombok : pas dans Quarkus BOM -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -48,14 +61,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.lions.unionflow</groupId>
|
<groupId>dev.lions.unionflow</groupId>
|
||||||
<artifactId>unionflow-server-api</artifactId>
|
<artifactId>unionflow-server-api</artifactId>
|
||||||
<version>1.0.6</version>
|
<version>1.0.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Lions User Manager API (pour DTOs et client Keycloak) -->
|
<!-- Lions User Manager API (pour DTOs et client Keycloak) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.lions.user.manager</groupId>
|
<groupId>dev.lions.user.manager</groupId>
|
||||||
<artifactId>lions-user-manager-server-api</artifactId>
|
<artifactId>lions-user-manager-server-api</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.1.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Quarkus Core -->
|
<!-- Quarkus Core -->
|
||||||
@@ -321,7 +334,10 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<!-- Quarkus Qute @CheckedTemplate exige les noms de paramètres en bytecode -->
|
||||||
|
<parameters>true</parameters>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 & Paiements</b> — Campagnes récurrentes,
|
* <li><b>Cotisations & 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 & Dashboard</b> — KPIs temps réel via WebSocket,
|
* <li><b>Analytics & 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 (< 50ms).
|
* En mode natif, cette méthode démarre instantanément (< 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,77 @@
|
|||||||
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.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.ws.rs.client.ClientRequestContext;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.client.ClientRequestFilter;
|
import jakarta.ws.rs.client.ClientRequestContext;
|
||||||
import jakarta.ws.rs.core.HttpHeaders;
|
import jakarta.ws.rs.client.ClientRequestFilter;
|
||||||
import jakarta.ws.rs.ext.Provider;
|
import jakarta.ws.rs.core.HttpHeaders;
|
||||||
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>{@code @ApplicationScoped} est requis pour la découverte CDI (tests {@code @QuarkusTest}
|
||||||
* sur les clients qui en ont besoin ({@code @RegisterClientHeaders}).
|
* qui {@code @Inject} le filter). Cela ne provoque PAS d'enregistrement automatique JAX-RS
|
||||||
*/
|
* — l'opt-in se fait via {@code @RegisterProvider(JwtPropagationFilter.class)} sur les
|
||||||
public class JwtPropagationFilter implements ClientRequestFilter {
|
* REST clients qui le souhaitent.
|
||||||
|
*
|
||||||
private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class);
|
* <p>La propagation JWT par défaut est assurée par {@link OidcTokenPropagationHeadersFactory}
|
||||||
|
* sur les clients qui en ont besoin ({@code @RegisterClientHeaders}).
|
||||||
@Inject
|
*/
|
||||||
SecurityIdentity securityIdentity;
|
@ApplicationScoped
|
||||||
|
public class JwtPropagationFilter implements ClientRequestFilter {
|
||||||
@Override
|
|
||||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
private static final Logger LOG = Logger.getLogger(JwtPropagationFilter.class);
|
||||||
if (securityIdentity != null && !securityIdentity.isAnonymous()) {
|
|
||||||
// Récupérer le token JWT depuis le principal
|
@Inject
|
||||||
if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
|
SecurityIdentity securityIdentity;
|
||||||
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal();
|
|
||||||
String token = principal.getRawToken();
|
@Override
|
||||||
|
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||||
if (token != null && !token.isBlank()) {
|
if (securityIdentity != null && !securityIdentity.isAnonymous()) {
|
||||||
requestContext.getHeaders().putSingle(
|
// Récupérer le token JWT depuis le principal
|
||||||
HttpHeaders.AUTHORIZATION,
|
if (securityIdentity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
|
||||||
"Bearer " + token
|
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) securityIdentity.getPrincipal();
|
||||||
);
|
String token = principal.getRawToken();
|
||||||
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
|
|
||||||
} else {
|
if (token != null && !token.isBlank()) {
|
||||||
LOG.warnf("Token JWT vide pour %s", requestContext.getUri());
|
requestContext.getHeaders().putSingle(
|
||||||
}
|
HttpHeaders.AUTHORIZATION,
|
||||||
} else if (securityIdentity.getPrincipal() instanceof JsonWebToken) {
|
"Bearer " + token
|
||||||
JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal();
|
);
|
||||||
String token = jwt.getRawToken();
|
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
|
||||||
|
} else {
|
||||||
if (token != null && !token.isBlank()) {
|
LOG.warnf("Token JWT vide pour %s", requestContext.getUri());
|
||||||
requestContext.getHeaders().putSingle(
|
}
|
||||||
HttpHeaders.AUTHORIZATION,
|
} else if (securityIdentity.getPrincipal() instanceof JsonWebToken) {
|
||||||
"Bearer " + token
|
JsonWebToken jwt = (JsonWebToken) securityIdentity.getPrincipal();
|
||||||
);
|
String token = jwt.getRawToken();
|
||||||
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
|
|
||||||
}
|
if (token != null && !token.isBlank()) {
|
||||||
} else {
|
requestContext.getHeaders().putSingle(
|
||||||
LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)",
|
HttpHeaders.AUTHORIZATION,
|
||||||
requestContext.getUri(),
|
"Bearer " + token
|
||||||
securityIdentity.getPrincipal().getClass().getName());
|
);
|
||||||
}
|
LOG.debugf("Token JWT propagé vers %s", requestContext.getUri());
|
||||||
} else {
|
}
|
||||||
LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s",
|
} else {
|
||||||
requestContext.getUri());
|
LOG.warnf("Principal n'est pas un JWT pour %s (type: %s)",
|
||||||
}
|
requestContext.getUri(),
|
||||||
}
|
securityIdentity.getPrincipal().getClass().getName());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warnf("Pas de SecurityIdentity ou utilisateur anonyme pour %s",
|
||||||
|
requestContext.getUri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entrée d'audit trail enrichi (SYSCOHADA + AUDSCGIE OHADA).
|
||||||
|
*
|
||||||
|
* <p>Trace les opérations financières, le lifecycle membres, les changements de configuration,
|
||||||
|
* avec le contexte multi-org (rôle actif + organisation active) + vérifications de séparation des
|
||||||
|
* pouvoirs (SoD).
|
||||||
|
*
|
||||||
|
* <p>Cette entité ne dérive PAS de {@link BaseEntity} car elle représente un enregistrement
|
||||||
|
* immuable d'historique : ses propres champs d'audit ({@code operationAt}, {@code userId}) sont
|
||||||
|
* la donnée à tracer.
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 — exigences SYSCOHADA + Instruction BCEAO 003-03-2025 (audit KYC)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "audit_trail_operations")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class AuditTrailOperation {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
@Column(name = "id", updatable = false, nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
// Acteur
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private UUID userId;
|
||||||
|
|
||||||
|
@Column(name = "user_email", length = 255)
|
||||||
|
private String userEmail;
|
||||||
|
|
||||||
|
@Column(name = "role_actif", length = 50)
|
||||||
|
private String roleActif;
|
||||||
|
|
||||||
|
@Column(name = "organisation_active_id")
|
||||||
|
private UUID organisationActiveId;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "action_type", nullable = false, length = 50)
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "entity_type", nullable = false, length = 100)
|
||||||
|
private String entityType;
|
||||||
|
|
||||||
|
@Column(name = "entity_id")
|
||||||
|
private UUID entityId;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 500)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
// Contexte
|
||||||
|
@Column(name = "ip_address", length = 45)
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
@Column(name = "user_agent", length = 500)
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
@Column(name = "request_id")
|
||||||
|
private UUID requestId;
|
||||||
|
|
||||||
|
// Données (JSONB)
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "payload_avant", columnDefinition = "jsonb")
|
||||||
|
private String payloadAvant;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "payload_apres", columnDefinition = "jsonb")
|
||||||
|
private String payloadApres;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "metadata", columnDefinition = "jsonb")
|
||||||
|
private String metadata;
|
||||||
|
|
||||||
|
// SoD
|
||||||
|
@Column(name = "sod_check_passed")
|
||||||
|
private Boolean sodCheckPassed;
|
||||||
|
|
||||||
|
@Column(name = "sod_violations", length = 500)
|
||||||
|
private String sodViolations;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "operation_at", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private LocalDateTime operationAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bénéficiaire effectif (UBO — Ultimate Beneficial Owner) lié à un dossier KYC.
|
||||||
|
*
|
||||||
|
* <p>Implémente l'obligation introduite par l'<strong>Instruction BCEAO 003-03-2025 du 18 mars
|
||||||
|
* 2025</strong> : identification, vérification et connaissance du client par les institutions
|
||||||
|
* financières — vérification systématique des bénéficiaires effectifs obligatoire (approche par
|
||||||
|
* les risques).
|
||||||
|
*
|
||||||
|
* <p>Un bénéficiaire effectif est, selon la directive UEMOA et le GAFI/FATF, toute personne
|
||||||
|
* physique qui :
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>détient au moins <strong>25 %</strong> du capital ou des droits de vote d'une personne
|
||||||
|
* morale ;
|
||||||
|
* <li>OU exerce un contrôle effectif (de fait ou de droit) sur la gestion de l'entité ;
|
||||||
|
* <li>OU est bénéficiaire ultime d'une opération suspecte structurée.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Ces enregistrements doivent être conservés <strong>10 ans</strong> après la clôture de la
|
||||||
|
* relation d'affaires (directive 02/2015/CM/UEMOA).
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 — Instruction BCEAO 003-03-2025 (KYC + UBO)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "beneficiaires_effectifs",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_ubo_kyc_dossier", columnList = "kyc_dossier_id"),
|
||||||
|
@Index(name = "idx_ubo_organisation_cible", columnList = "organisation_cible_id"),
|
||||||
|
@Index(name = "idx_ubo_pays", columnList = "pays_residence")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class BeneficiaireEffectif extends BaseEntity {
|
||||||
|
|
||||||
|
/** Dossier KYC auquel ce bénéficiaire effectif est rattaché. */
|
||||||
|
@NotNull
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "kyc_dossier_id", nullable = false)
|
||||||
|
private KycDossier kycDossier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organisation cible dont cette personne est bénéficiaire effectif (utile en cas de KYC client
|
||||||
|
* personne morale — la chaîne de contrôle UBO peut traverser plusieurs entités).
|
||||||
|
*/
|
||||||
|
@Column(name = "organisation_cible_id")
|
||||||
|
private UUID organisationCibleId;
|
||||||
|
|
||||||
|
/** Lien vers le membre UnionFlow correspondant si applicable (UBO interne au système). */
|
||||||
|
@Column(name = "membre_id")
|
||||||
|
private UUID membreId;
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "nom", nullable = false, length = 100)
|
||||||
|
private String nom;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "prenoms", nullable = false, length = 200)
|
||||||
|
private String prenoms;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_naissance", nullable = false)
|
||||||
|
private LocalDate dateNaissance;
|
||||||
|
|
||||||
|
@Column(name = "lieu_naissance", length = 200)
|
||||||
|
private String lieuNaissance;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "nationalite", nullable = false, length = 3)
|
||||||
|
private String nationalite; // ISO 3166-1 alpha-3
|
||||||
|
|
||||||
|
@Column(name = "pays_residence", length = 3)
|
||||||
|
private String paysResidence;
|
||||||
|
|
||||||
|
// Pièce d'identité
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "type_piece_identite", length = 30)
|
||||||
|
private TypePieceIdentite typePieceIdentite;
|
||||||
|
|
||||||
|
@Column(name = "numero_piece_identite", length = 50)
|
||||||
|
private String numeroPieceIdentite;
|
||||||
|
|
||||||
|
@Column(name = "date_expiration_piece")
|
||||||
|
private LocalDate dateExpirationPiece;
|
||||||
|
|
||||||
|
// Contrôle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pourcentage de détention en capital (0-100). Si {@code >= 25} → UBO direct selon GAFI.
|
||||||
|
* Peut être null si le contrôle est exercé autrement (mandat, accord d'actionnaires).
|
||||||
|
*/
|
||||||
|
@DecimalMin("0.00")
|
||||||
|
@DecimalMax("100.00")
|
||||||
|
@Column(name = "pourcentage_capital", precision = 5, scale = 2)
|
||||||
|
private BigDecimal pourcentageCapital;
|
||||||
|
|
||||||
|
/** Pourcentage des droits de vote (0-100). */
|
||||||
|
@DecimalMin("0.00")
|
||||||
|
@DecimalMax("100.00")
|
||||||
|
@Column(name = "pourcentage_droits_vote", precision = 5, scale = 2)
|
||||||
|
private BigDecimal pourcentageDroitsVote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nature du contrôle exercé : DETENTION_CAPITAL, DROITS_VOTE, CONTROLE_DE_FAIT,
|
||||||
|
* BENEFICIAIRE_ULTIME, MANDAT_REPRESENTATION.
|
||||||
|
*/
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "nature_controle", nullable = false, length = 50)
|
||||||
|
private String natureControle;
|
||||||
|
|
||||||
|
// Politique d'exposition (PEP)
|
||||||
|
|
||||||
|
@Column(name = "est_pep", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private boolean estPep = false;
|
||||||
|
|
||||||
|
@Column(name = "pep_categorie", length = 100)
|
||||||
|
private String pepCategorie;
|
||||||
|
|
||||||
|
@Column(name = "pep_pays", length = 3)
|
||||||
|
private String pepPays;
|
||||||
|
|
||||||
|
@Column(name = "pep_fonction", length = 200)
|
||||||
|
private String pepFonction;
|
||||||
|
|
||||||
|
// Sanctions / vigilance
|
||||||
|
|
||||||
|
@Column(name = "presence_listes_sanctions", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private boolean presenceListesSanctions = false;
|
||||||
|
|
||||||
|
@Column(name = "details_listes_sanctions", length = 1000)
|
||||||
|
private String detailsListesSanctions;
|
||||||
|
|
||||||
|
// Vérification
|
||||||
|
|
||||||
|
@Column(name = "verifie_par_id")
|
||||||
|
private UUID verifieParId;
|
||||||
|
|
||||||
|
@Column(name = "date_verification")
|
||||||
|
private java.time.LocalDateTime dateVerification;
|
||||||
|
|
||||||
|
@Column(name = "source_verification", length = 200)
|
||||||
|
private String sourceVerification;
|
||||||
|
|
||||||
|
@Column(name = "notes", length = 2000)
|
||||||
|
private String notes;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,130 +1,177 @@
|
|||||||
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
|
// ========================================================
|
||||||
protected void onCreate() {
|
// Workflow v2 (P1-NEW-3, 2026-04-25) — DEPOSE → ENQUETE → AVIS_COMITE → DECISION_CA → PAYE → CLOTURE
|
||||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
// ========================================================
|
||||||
if (dateDemande == null) {
|
|
||||||
dateDemande = LocalDateTime.now();
|
/** Étape actuelle dans le workflow v2 (DEPOSE par défaut). */
|
||||||
}
|
@Column(name = "etape", length = 30)
|
||||||
if (statut == null) {
|
@Builder.Default
|
||||||
statut = StatutAide.EN_ATTENTE;
|
private String etape = "DEPOSE";
|
||||||
}
|
|
||||||
if (urgence == null) {
|
/** Animateur de zone responsable de l'enquête sociale (étape ENQUETE). */
|
||||||
urgence = false;
|
@Column(name = "animateur_zone_id")
|
||||||
}
|
private java.util.UUID animateurZoneId;
|
||||||
}
|
|
||||||
|
/** Rapport rédigé par l'animateur après visite (étape ENQUETE). */
|
||||||
@PreUpdate
|
@Column(name = "rapport_enquete_sociale", columnDefinition = "TEXT")
|
||||||
protected void onUpdate() {
|
private String rapportEnqueteSociale;
|
||||||
// Méthode appelée avant mise à jour
|
|
||||||
}
|
@Column(name = "date_enquete")
|
||||||
|
private LocalDateTime dateEnquete;
|
||||||
/** Vérifie si la demande est en attente */
|
|
||||||
public boolean isEnAttente() {
|
/** Géolocalisation GPS de l'enquête (preuve de visite terrain). */
|
||||||
return StatutAide.EN_ATTENTE.equals(statut);
|
@Column(name = "gps_enquete_lat", precision = 10, scale = 7)
|
||||||
}
|
private java.math.BigDecimal gpsEnqueteLat;
|
||||||
|
|
||||||
/** Vérifie si la demande est approuvée */
|
@Column(name = "gps_enquete_lon", precision = 10, scale = 7)
|
||||||
public boolean isApprouvee() {
|
private java.math.BigDecimal gpsEnqueteLon;
|
||||||
return StatutAide.APPROUVEE.equals(statut);
|
|
||||||
}
|
/** Avis du comité social ou commission solidarité (étape AVIS_COMITE). */
|
||||||
|
@Column(name = "avis_comite_social", columnDefinition = "TEXT")
|
||||||
/** Vérifie si la demande est rejetée */
|
private String avisComiteSocial;
|
||||||
public boolean isRejetee() {
|
|
||||||
return StatutAide.REJETEE.equals(statut);
|
@Column(name = "date_avis_comite")
|
||||||
}
|
private LocalDateTime dateAvisComite;
|
||||||
|
|
||||||
/** Vérifie si la demande est urgente */
|
/** Lien vers le PV CA dans lequel la décision a été votée (étape DECISION_CA). */
|
||||||
public boolean isUrgente() {
|
@Column(name = "decision_ca_id")
|
||||||
return Boolean.TRUE.equals(urgence);
|
private java.util.UUID decisionCaId;
|
||||||
}
|
|
||||||
|
@Column(name = "date_decision_ca")
|
||||||
/** Calcule le pourcentage d'approbation par rapport au montant demandé */
|
private LocalDateTime dateDecisionCa;
|
||||||
public BigDecimal getPourcentageApprobation() {
|
|
||||||
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
|
@Column(name = "date_paie")
|
||||||
return BigDecimal.ZERO;
|
private LocalDateTime datePaie;
|
||||||
}
|
|
||||||
if (montantApprouve == null) {
|
@Column(name = "reference_paiement", length = 100)
|
||||||
return BigDecimal.ZERO;
|
private String referencePaiement;
|
||||||
}
|
|
||||||
return montantApprouve
|
@PrePersist
|
||||||
.divide(montantDemande, 4, RoundingMode.HALF_UP)
|
protected void onCreate() {
|
||||||
.multiply(BigDecimal.valueOf(100));
|
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||||
}
|
if (dateDemande == null) {
|
||||||
}
|
dateDemande = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
if (statut == null) {
|
||||||
|
statut = StatutAide.EN_ATTENTE;
|
||||||
|
}
|
||||||
|
if (urgence == null) {
|
||||||
|
urgence = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
// Méthode appelée avant mise à jour
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vérifie si la demande est en attente */
|
||||||
|
public boolean isEnAttente() {
|
||||||
|
return StatutAide.EN_ATTENTE.equals(statut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vérifie si la demande est approuvée */
|
||||||
|
public boolean isApprouvee() {
|
||||||
|
return StatutAide.APPROUVEE.equals(statut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vérifie si la demande est rejetée */
|
||||||
|
public boolean isRejetee() {
|
||||||
|
return StatutAide.REJETEE.equals(statut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vérifie si la demande est urgente */
|
||||||
|
public boolean isUrgente() {
|
||||||
|
return Boolean.TRUE.equals(urgence);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calcule le pourcentage d'approbation par rapport au montant demandé */
|
||||||
|
public BigDecimal getPourcentageApprobation() {
|
||||||
|
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
if (montantApprouve == null) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return montantApprouve
|
||||||
|
.divide(montantDemande, 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(BigDecimal.valueOf(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
58
src/main/java/dev/lions/unionflow/server/entity/Devise.java
Normal file
58
src/main/java/dev/lions/unionflow/server/entity/Devise.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devises supportées par UnionFlow.
|
||||||
|
*
|
||||||
|
* <p>UnionFlow vise prioritairement la zone UEMOA (XOF/XAF) mais s'ouvre à la diaspora
|
||||||
|
* (EUR/USD/GBP/CAD). Le {@link ZoneDevise} permet de discriminer pour les règles
|
||||||
|
* AML (transferts internationaux, due diligence renforcée).
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P2-NEW-7)
|
||||||
|
*/
|
||||||
|
public enum Devise {
|
||||||
|
|
||||||
|
// Zone UEMOA / CEMAC
|
||||||
|
XOF("Franc CFA Ouest", ZoneDevise.UEMOA),
|
||||||
|
XAF("Franc CFA Centrale", ZoneDevise.CEMAC),
|
||||||
|
|
||||||
|
// Diaspora — Europe / Amérique
|
||||||
|
EUR("Euro", ZoneDevise.EUROPE),
|
||||||
|
USD("Dollar US", ZoneDevise.AMERIQUE),
|
||||||
|
GBP("Livre Sterling", ZoneDevise.EUROPE),
|
||||||
|
CAD("Dollar Canadien", ZoneDevise.AMERIQUE),
|
||||||
|
CHF("Franc Suisse", ZoneDevise.EUROPE),
|
||||||
|
|
||||||
|
// CEDEAO non-UEMOA (pour intégrations futures)
|
||||||
|
GHS("Cédi Ghanéen", ZoneDevise.CEDEAO),
|
||||||
|
NGN("Naira Nigérian", ZoneDevise.CEDEAO),
|
||||||
|
|
||||||
|
// Maghreb
|
||||||
|
MAD("Dirham Marocain", ZoneDevise.MAGHREB);
|
||||||
|
|
||||||
|
private final String libelle;
|
||||||
|
private final ZoneDevise zone;
|
||||||
|
|
||||||
|
Devise(String libelle, ZoneDevise zone) {
|
||||||
|
this.libelle = libelle;
|
||||||
|
this.zone = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String libelle() { return libelle; }
|
||||||
|
public ZoneDevise zone() { return zone; }
|
||||||
|
|
||||||
|
/** Devise de référence UnionFlow / BCEAO. */
|
||||||
|
public static Devise reference() { return XOF; }
|
||||||
|
|
||||||
|
/** Devises pour lesquelles un transfert depuis/vers UEMOA déclenche AML renforcé. */
|
||||||
|
public static final Set<Devise> DEVISES_INTERNATIONALES = Set.of(EUR, USD, GBP, CAD, CHF);
|
||||||
|
|
||||||
|
public boolean estInternationale() {
|
||||||
|
return DEVISES_INTERNATIONALES.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ZoneDevise {
|
||||||
|
UEMOA, CEMAC, CEDEAO, EUROPE, AMERIQUE, MAGHREB
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
src/main/java/dev/lions/unionflow/server/entity/DonRecu.java
Normal file
86
src/main/java/dev/lions/unionflow/server/entity/DonRecu.java
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don reçu (numéraire, nature, bénévolat, legs) — comptabilisé selon SYCEBNL :
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>NUMERAIRE → Crédit 755 (Dons et libéralités)
|
||||||
|
* <li>NATURE → valorisation obligatoire au prix marché, idem 755
|
||||||
|
* <li>BENEVOLAT → valorisation possible en notes annexes
|
||||||
|
* <li>LEGS → Crédit 756 ou poste dédié selon nature
|
||||||
|
* <li>FONDS_DEDIE → Crédit 19 (Fonds dédiés non utilisés, à reverser si finalité non remplie)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-13)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "dons_recus", indexes = {
|
||||||
|
@Index(name = "idx_don_org", columnList = "organisation_id"),
|
||||||
|
@Index(name = "idx_don_donateur", columnList = "donateur_id"),
|
||||||
|
@Index(name = "idx_don_date", columnList = "date_don")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class DonRecu extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "donateur_id")
|
||||||
|
private Donateur donateur;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "type_don", nullable = false, length = 20)
|
||||||
|
private String typeDon; // NUMERAIRE, NATURE, BENEVOLAT, LEGS
|
||||||
|
|
||||||
|
@Column(name = "montant_xof", precision = 15, scale = 2)
|
||||||
|
private BigDecimal montantXof;
|
||||||
|
|
||||||
|
@Column(name = "valorisation_xof", precision = 15, scale = 2)
|
||||||
|
private BigDecimal valorisationXof;
|
||||||
|
|
||||||
|
@Column(name = "description", columnDefinition = "TEXT")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_don", nullable = false)
|
||||||
|
private LocalDate dateDon;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "affectation", nullable = false, length = 50)
|
||||||
|
@Builder.Default
|
||||||
|
private String affectation = "LIBRE"; // LIBRE, FONDS_DEDIE, PROJET_SPECIFIQUE
|
||||||
|
|
||||||
|
@Column(name = "fonds_dedie_id")
|
||||||
|
private UUID fondsDedieId;
|
||||||
|
|
||||||
|
@Column(name = "projet_id")
|
||||||
|
private UUID projetId;
|
||||||
|
|
||||||
|
@Column(name = "recu_emis", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private boolean recuEmis = false;
|
||||||
|
|
||||||
|
@Column(name = "numero_recu", length = 50)
|
||||||
|
private String numeroRecu;
|
||||||
|
|
||||||
|
@Column(name = "date_emission_recu")
|
||||||
|
private LocalDate dateEmissionRecu;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Donateur — registre obligatoire pour les entités relevant de SYCEBNL (associations, ONG,
|
||||||
|
* mutuelles sociales).
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-13)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "donateurs", indexes = {
|
||||||
|
@Index(name = "idx_donateur_org", columnList = "organisation_id"),
|
||||||
|
@Index(name = "idx_donateur_type", columnList = "type_donateur")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Donateur extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "type_donateur", nullable = false, length = 20)
|
||||||
|
private String typeDonateur; // PERSONNE_PHYSIQUE, PERSONNE_MORALE, ANONYME
|
||||||
|
|
||||||
|
@Column(name = "nom_prenoms", length = 255)
|
||||||
|
private String nomPrenoms;
|
||||||
|
|
||||||
|
@Column(name = "raison_sociale", length = 255)
|
||||||
|
private String raisonSociale;
|
||||||
|
|
||||||
|
@Column(name = "pays", length = 3)
|
||||||
|
private String pays;
|
||||||
|
|
||||||
|
@Column(name = "email", length = 255)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "telephone", length = 20)
|
||||||
|
private String telephone;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session de formation LBC/FT (lutte contre le blanchiment de capitaux et le financement du
|
||||||
|
* terrorisme).
|
||||||
|
*
|
||||||
|
* <p>Obligation annuelle posée par l'<strong>Instruction BCEAO 001-03-2025 du 18 mars 2025</strong>
|
||||||
|
* pour le compliance officer + les dirigeants + les membres exposés (trésorier, secrétaire,
|
||||||
|
* commissaires aux comptes).
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-12)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "formations_lbcft", indexes = {
|
||||||
|
@Index(name = "idx_formation_org_annee", columnList = "organisation_id,annee_reference"),
|
||||||
|
@Index(name = "idx_formation_date", columnList = "date_session")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class FormationLbcFt extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "titre", nullable = false, length = 255)
|
||||||
|
private String titre;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "type_formation", nullable = false, length = 30)
|
||||||
|
@Builder.Default
|
||||||
|
private String typeFormation = "STANDARD"; // STANDARD, AVANCE, COMPLIANCE_OFFICER, DIRIGEANT
|
||||||
|
|
||||||
|
@Column(name = "contenu", columnDefinition = "TEXT")
|
||||||
|
private String contenu;
|
||||||
|
|
||||||
|
@Column(name = "intervenant", length = 255)
|
||||||
|
private String intervenant;
|
||||||
|
|
||||||
|
@Column(name = "duree_heures", precision = 4, scale = 1, nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal dureeHeures = new BigDecimal("4.0");
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_session", nullable = false)
|
||||||
|
private LocalDateTime dateSession;
|
||||||
|
|
||||||
|
@Column(name = "lieu", length = 255)
|
||||||
|
private String lieu;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "annee_reference", nullable = false)
|
||||||
|
private Integer anneeReference;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "statut", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private String statut = "PLANIFIEE"; // PLANIFIEE, EN_COURS, TERMINEE, ANNULEE
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
135
src/main/java/dev/lions/unionflow/server/entity/KycDossier.java
Normal file
135
src/main/java/dev/lions/unionflow/server/entity/KycDossier.java
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.enums.membre.NiveauRisqueKyc;
|
||||||
|
import dev.lions.unionflow.server.api.enums.membre.StatutKyc;
|
||||||
|
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dossier KYC/AML d'un membre — conformité GIABA/BCEAO LCB-FT.
|
||||||
|
*
|
||||||
|
* <p>Rétention 10 ans requise par le GIABA. La colonne {@code anneeReference}
|
||||||
|
* sert à l'archivage logique par année (partitionnement futur PostgreSQL).
|
||||||
|
*
|
||||||
|
* <p>Un seul dossier actif ({@code actif=true}) par membre à la fois.
|
||||||
|
* Les dossiers expirés ou archivés ont {@code actif=false}.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "kyc_dossier",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_kyc_membre_id", columnList = "membre_id"),
|
||||||
|
@Index(name = "idx_kyc_statut", columnList = "statut"),
|
||||||
|
@Index(name = "idx_kyc_niveau_risque", columnList = "niveau_risque"),
|
||||||
|
@Index(name = "idx_kyc_annee", columnList = "annee_reference")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class KycDossier extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "membre_id", nullable = false)
|
||||||
|
private Membre membre;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "type_piece", nullable = false, length = 30)
|
||||||
|
private TypePieceIdentite typePiece;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 50)
|
||||||
|
@Column(name = "numero_piece", nullable = false, length = 50)
|
||||||
|
private String numeroPiece;
|
||||||
|
|
||||||
|
@Column(name = "date_expiration_piece")
|
||||||
|
private LocalDate dateExpirationPiece;
|
||||||
|
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "piece_identite_recto_file_id", length = 500)
|
||||||
|
private String pieceIdentiteRectoFileId;
|
||||||
|
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "piece_identite_verso_file_id", length = 500)
|
||||||
|
private String pieceIdentiteVersoFileId;
|
||||||
|
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "justif_domicile_file_id", length = 500)
|
||||||
|
private String justifDomicileFileId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "statut", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private StatutKyc statut = StatutKyc.NON_VERIFIE;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "niveau_risque", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private NiveauRisqueKyc niveauRisque = NiveauRisqueKyc.FAIBLE;
|
||||||
|
|
||||||
|
@Min(0) @Max(100)
|
||||||
|
@Column(name = "score_risque", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private int scoreRisque = 0;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "est_pep", nullable = false)
|
||||||
|
private boolean estPep = false;
|
||||||
|
|
||||||
|
@Size(max = 5)
|
||||||
|
@Column(name = "nationalite", length = 5)
|
||||||
|
private String nationalite;
|
||||||
|
|
||||||
|
@Column(name = "date_verification")
|
||||||
|
private LocalDateTime dateVerification;
|
||||||
|
|
||||||
|
@Column(name = "validateur_id")
|
||||||
|
private UUID validateurId;
|
||||||
|
|
||||||
|
@Size(max = 1000)
|
||||||
|
@Column(name = "notes_validateur", length = 1000)
|
||||||
|
private String notesValidateur;
|
||||||
|
|
||||||
|
@Column(name = "annee_reference", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private int anneeReference = java.time.LocalDate.now().getYear();
|
||||||
|
|
||||||
|
/** Pays d'origine des fonds (ISO-3) — anti-blanchiment transferts internationaux. */
|
||||||
|
@Size(max = 3)
|
||||||
|
@Column(name = "pays_origine_fonds", length = 3)
|
||||||
|
private String paysOrigineFonds;
|
||||||
|
|
||||||
|
/** URL/chemin justificatif domicile étranger (facture EDF/British Gas/etc.) pour non-résidents. */
|
||||||
|
@Size(max = 500)
|
||||||
|
@Column(name = "justificatif_residence_etrangere", length = 500)
|
||||||
|
private String justificatifResidenceEtrangere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Niveau de due diligence (Instr. BCEAO 001-03-2025) :
|
||||||
|
* <ul>
|
||||||
|
* <li>SIMPLIFIE — risque faible, opérations limitées</li>
|
||||||
|
* <li>STANDARD — défaut</li>
|
||||||
|
* <li>RENFORCE — non-résidents, PEP, FATF grey-list</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Size(max = 20)
|
||||||
|
@Column(name = "niveau_due_diligence", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private String niveauDueDiligence = "STANDARD";
|
||||||
|
|
||||||
|
public boolean isPieceExpiree() {
|
||||||
|
return dateExpirationPiece != null && dateExpirationPiece.isBefore(LocalDate.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,169 +1,215 @@
|
|||||||
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
|
/**
|
||||||
@Column(name = "date_naissance", nullable = false)
|
* Numéro CMU (Couverture Maladie Universelle) Côte d'Ivoire — auto-déclaré par le membre.
|
||||||
private LocalDate dateNaissance;
|
*
|
||||||
|
* <p>Obligatoire pour les organisations de type {@code MUTUELLE_SANTE} (Loi 2014-131
|
||||||
@Column(name = "profession", length = 100)
|
* exige enrôlement CMU comme préalable à toute mutuelle complémentaire). Format CNAM :
|
||||||
private String profession;
|
* 11 caractères alphanumériques. La vérification de la validité se fait manuellement
|
||||||
|
* (admin) faute d'API publique CNAM disponible au 2026-04-25.
|
||||||
@Column(name = "photo_url", length = 500)
|
*
|
||||||
private String photoUrl;
|
* @since 2026-04-25 — passage CMU à cotisation obligatoire 1er jan 2026
|
||||||
|
*/
|
||||||
@Builder.Default
|
@Pattern(regexp = "^[A-Z0-9]{11}$|^$", message = "Le numéro CMU doit faire 11 caractères alphanumériques majuscules")
|
||||||
@Column(name = "statut_compte", nullable = false, length = 30)
|
@Column(name = "numero_cmu", length = 11)
|
||||||
private String statutCompte = "EN_ATTENTE_VALIDATION";
|
private String numeroCMU;
|
||||||
|
|
||||||
/** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
|
/**
|
||||||
@Builder.Default
|
* Pays de résidence (ISO-3, ex: FRA, USA, CAN). Différent de {@code nationalite} :
|
||||||
@Column(name = "premiere_connexion", nullable = false)
|
* un Ivoirien (CIV) résidant en France a paysResidence=FRA. NULL ou CIV = résident UEMOA.
|
||||||
private Boolean premiereConnexion = true;
|
*
|
||||||
|
* @since 2026-04-25 (P2-NEW-7)
|
||||||
/**
|
*/
|
||||||
* Statut matrimonial (domaine
|
@Pattern(regexp = "^[A-Z]{3}$|^$", message = "Pays résidence doit être un code ISO-3")
|
||||||
* {@code STATUT_MATRIMONIAL} dans
|
@Column(name = "pays_residence", length = 3)
|
||||||
* {@code types_reference}).
|
private String paysResidence;
|
||||||
*/
|
|
||||||
@Column(name = "statut_matrimonial", length = 50)
|
/** Numéro de passeport pour non-résidents (CNI insuffisante hors UEMOA). */
|
||||||
private String statutMatrimonial;
|
@Column(name = "numero_passeport", length = 50)
|
||||||
|
private String numeroPasseport;
|
||||||
/** Nationalité. */
|
|
||||||
@Column(name = "nationalite", length = 100)
|
/** NIF/SSN/SIN — reporting fiscal accord bilatéral CI ↔ pays résidence. */
|
||||||
private String nationalite;
|
@Column(name = "numero_fiscal_etranger", length = 50)
|
||||||
|
private String numeroFiscalEtranger;
|
||||||
/**
|
|
||||||
* Type de pièce d'identité (domaine
|
/** TRUE si le membre est diaspora (résidence ≠ UEMOA). */
|
||||||
* {@code TYPE_IDENTITE} dans
|
@Builder.Default
|
||||||
* {@code types_reference}).
|
@Column(name = "est_diaspora", nullable = false)
|
||||||
*/
|
private Boolean estDiaspora = false;
|
||||||
@Column(name = "type_identite", length = 50)
|
|
||||||
private String typeIdentite;
|
/** Devise préférée pour affichages et notifications (XOF par défaut). */
|
||||||
|
@Builder.Default
|
||||||
/** Numéro de la pièce d'identité. */
|
@Column(name = "devise_preferee", nullable = false, length = 3)
|
||||||
@Column(name = "numero_identite", length = 100)
|
private String devisePreferee = "XOF";
|
||||||
private String numeroIdentite;
|
|
||||||
|
@Pattern(regexp = "^\\+[1-9][0-9]{6,14}$", message = "Le numéro Wave doit être au format international E.164 (ex: +22507XXXXXXXX)")
|
||||||
/** Notes / biographie libre du membre. */
|
@Column(name = "telephone_wave", length = 20)
|
||||||
@Column(name = "notes", length = 1000)
|
private String telephoneWave;
|
||||||
private String notes;
|
|
||||||
|
@NotNull
|
||||||
/** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
|
@Column(name = "date_naissance", nullable = false)
|
||||||
@Column(name = "niveau_vigilance_kyc", length = 20)
|
private LocalDate dateNaissance;
|
||||||
private String niveauVigilanceKyc;
|
|
||||||
|
@Column(name = "profession", length = 100)
|
||||||
/** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */
|
private String profession;
|
||||||
@Column(name = "statut_kyc", length = 20)
|
|
||||||
private String statutKyc;
|
@Column(name = "photo_url", length = 500)
|
||||||
|
private String photoUrl;
|
||||||
/** Date de dernière vérification d'identité. */
|
|
||||||
@Column(name = "date_verification_identite")
|
@Builder.Default
|
||||||
private LocalDate dateVerificationIdentite;
|
@Column(name = "statut_compte", nullable = false, length = 30)
|
||||||
|
private String statutCompte = "EN_ATTENTE_VALIDATION";
|
||||||
// ── Relations ────────────────────────────────────────────────────────────
|
|
||||||
|
/** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
|
||||||
/** Adhésions à des organisations */
|
@Builder.Default
|
||||||
@JsonIgnore
|
@Column(name = "premiere_connexion", nullable = false)
|
||||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
private Boolean premiereConnexion = true;
|
||||||
@Builder.Default
|
|
||||||
private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
|
/**
|
||||||
|
* Statut matrimonial (domaine
|
||||||
@JsonIgnore
|
* {@code STATUT_MATRIMONIAL} dans
|
||||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
* {@code types_reference}).
|
||||||
@Builder.Default
|
*/
|
||||||
private List<Adresse> adresses = new ArrayList<>();
|
@Column(name = "statut_matrimonial", length = 50)
|
||||||
|
private String statutMatrimonial;
|
||||||
@JsonIgnore
|
|
||||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
/** Nationalité. */
|
||||||
@Builder.Default
|
@Column(name = "nationalite", length = 100)
|
||||||
private List<CompteWave> comptesWave = new ArrayList<>();
|
private String nationalite;
|
||||||
|
|
||||||
@JsonIgnore
|
/**
|
||||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
* Type de pièce d'identité (domaine
|
||||||
@Builder.Default
|
* {@code TYPE_IDENTITE} dans
|
||||||
private List<Paiement> paiements = new ArrayList<>();
|
* {@code types_reference}).
|
||||||
|
*/
|
||||||
// ── Méthodes métier ───────────────────────────────────────────────────────
|
@Column(name = "type_identite", length = 50)
|
||||||
|
private String typeIdentite;
|
||||||
public String getNomComplet() {
|
|
||||||
return prenom + " " + nom;
|
/** Numéro de la pièce d'identité. */
|
||||||
}
|
@Column(name = "numero_identite", length = 100)
|
||||||
|
private String numeroIdentite;
|
||||||
public boolean isMajeur() {
|
|
||||||
return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18));
|
/** Notes / biographie libre du membre. */
|
||||||
}
|
@Column(name = "notes", length = 1000)
|
||||||
|
private String notes;
|
||||||
public int getAge() {
|
|
||||||
return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0;
|
/** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
|
||||||
}
|
@Column(name = "niveau_vigilance_kyc", length = 20)
|
||||||
|
private String niveauVigilanceKyc;
|
||||||
@PrePersist
|
|
||||||
protected void onCreate() {
|
/** Statut de vérification d'identité (NON_VERIFIE, EN_COURS, VERIFIE, REFUSE). */
|
||||||
super.onCreate();
|
@Column(name = "statut_kyc", length = 20)
|
||||||
if (statutCompte == null) {
|
private String statutKyc;
|
||||||
statutCompte = "EN_ATTENTE_VALIDATION";
|
|
||||||
}
|
/** Date de dernière vérification d'identité. */
|
||||||
}
|
@Column(name = "date_verification_identite")
|
||||||
}
|
private LocalDate dateVerificationIdentite;
|
||||||
|
|
||||||
|
// ── Relations ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Adhésions à des organisations */
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
@Builder.Default
|
||||||
|
private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
@Builder.Default
|
||||||
|
private List<Adresse> adresses = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
@Builder.Default
|
||||||
|
private List<CompteWave> comptesWave = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
@Builder.Default
|
||||||
|
private List<Paiement> paiements = new ArrayList<>();
|
||||||
|
|
||||||
|
// ── Méthodes métier ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public String getNomComplet() {
|
||||||
|
return prenom + " " + nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMajeur() {
|
||||||
|
return dateNaissance != null && dateNaissance.isBefore(LocalDate.now().minusYears(18));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return dateNaissance != null ? LocalDate.now().getYear() - dateNaissance.getYear() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
if (statutCompte == null) {
|
||||||
|
statutCompte = "EN_ATTENTE_VALIDATION";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 l’app mobile.
|
* Utilisé pour la fonctionnalité Réseau / Suivre dans l’app 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,326 +1,355 @@
|
|||||||
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
|
/**
|
||||||
private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
|
* Référentiel comptable applicable à cette organisation.
|
||||||
|
*
|
||||||
@JsonIgnore
|
* <p>Détermine quel plan comptable est appliqué et quels états financiers sont générés
|
||||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
* (bilan, compte de résultat, annexes). Mappage par défaut depuis {@code typeOrganisation}
|
||||||
@Builder.Default
|
* via {@link ReferentielComptable#defaultFor(String)} ; l'admin peut overrider manuellement.
|
||||||
private List<Adresse> adresses = new ArrayList<>();
|
*
|
||||||
|
* @since 2026-04-25 — découverte SYCEBNL (11ᵉ Acte uniforme OHADA en vigueur 1er jan 2024)
|
||||||
@JsonIgnore
|
*/
|
||||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
@Enumerated(EnumType.STRING)
|
||||||
@Builder.Default
|
@Column(name = "referentiel_comptable", nullable = false, length = 20)
|
||||||
private List<CompteWave> comptesWave = new ArrayList<>();
|
@Builder.Default
|
||||||
|
private ReferentielComptable referentielComptable = ReferentielComptable.SYSCOHADA;
|
||||||
/** Méthode métier pour obtenir le nom complet avec sigle */
|
|
||||||
public String getNomComplet() {
|
/**
|
||||||
if (nomCourt != null && !nomCourt.isEmpty()) {
|
* UUID du membre désigné comme Compliance Officer de l'organisation (rôle obligatoire selon
|
||||||
return nom + " (" + nomCourt + ")";
|
* Instruction BCEAO 001-03-2025). Doit être rattaché à la direction générale, distinct du
|
||||||
}
|
* trésorier (séparation des pouvoirs).
|
||||||
return nom;
|
*
|
||||||
}
|
* @since 2026-04-25 — Instruction BCEAO 001-03-2025 (LBC/FT)
|
||||||
|
*/
|
||||||
/** Méthode métier pour calculer l'ancienneté en années */
|
@Column(name = "compliance_officer_id")
|
||||||
public int getAncienneteAnnees() {
|
private UUID complianceOfficerId;
|
||||||
if (dateFondation == null) {
|
|
||||||
return 0;
|
// Relations
|
||||||
}
|
|
||||||
return Period.between(dateFondation, LocalDate.now()).getYears();
|
/** Adhésions des membres à cette organisation */
|
||||||
}
|
@JsonIgnore
|
||||||
|
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
/**
|
@Builder.Default
|
||||||
* Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans)
|
private List<MembreOrganisation> membresOrganisations = new ArrayList<>();
|
||||||
*/
|
|
||||||
public boolean isRecente() {
|
@JsonIgnore
|
||||||
return getAncienneteAnnees() < 2;
|
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
}
|
@Builder.Default
|
||||||
|
private List<Adresse> adresses = new ArrayList<>();
|
||||||
/** Méthode métier pour vérifier si l'organisation est active */
|
|
||||||
public boolean isActive() {
|
@JsonIgnore
|
||||||
return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
|
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
}
|
@Builder.Default
|
||||||
|
private List<CompteWave> comptesWave = new ArrayList<>();
|
||||||
/** Méthode métier pour ajouter un membre */
|
|
||||||
public void ajouterMembre() {
|
/** Méthode métier pour obtenir le nom complet avec sigle */
|
||||||
if (nombreMembres == null) {
|
public String getNomComplet() {
|
||||||
nombreMembres = 0;
|
if (nomCourt != null && !nomCourt.isEmpty()) {
|
||||||
}
|
return nom + " (" + nomCourt + ")";
|
||||||
nombreMembres++;
|
}
|
||||||
}
|
return nom;
|
||||||
|
}
|
||||||
/** Méthode métier pour retirer un membre */
|
|
||||||
public void retirerMembre() {
|
/** Méthode métier pour calculer l'ancienneté en années */
|
||||||
if (nombreMembres != null && nombreMembres > 0) {
|
public int getAncienneteAnnees() {
|
||||||
nombreMembres--;
|
if (dateFondation == null) {
|
||||||
}
|
return 0;
|
||||||
}
|
}
|
||||||
|
return Period.between(dateFondation, LocalDate.now()).getYears();
|
||||||
/** Méthode métier pour activer l'organisation */
|
}
|
||||||
public void activer(String utilisateur) {
|
|
||||||
this.statut = "ACTIVE";
|
/**
|
||||||
this.setActif(true);
|
* Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans)
|
||||||
marquerCommeModifie(utilisateur);
|
*/
|
||||||
}
|
public boolean isRecente() {
|
||||||
|
return getAncienneteAnnees() < 2;
|
||||||
/** Méthode métier pour suspendre l'organisation */
|
}
|
||||||
public void suspendre(String utilisateur) {
|
|
||||||
this.statut = "SUSPENDUE";
|
/** Méthode métier pour vérifier si l'organisation est active */
|
||||||
this.accepteNouveauxMembres = false;
|
public boolean isActive() {
|
||||||
marquerCommeModifie(utilisateur);
|
return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Méthode métier pour dissoudre l'organisation */
|
/** Méthode métier pour ajouter un membre */
|
||||||
public void dissoudre(String utilisateur) {
|
public void ajouterMembre() {
|
||||||
this.statut = "DISSOUTE";
|
if (nombreMembres == null) {
|
||||||
this.setActif(false);
|
nombreMembres = 0;
|
||||||
this.accepteNouveauxMembres = false;
|
}
|
||||||
marquerCommeModifie(utilisateur);
|
nombreMembres++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Callback JPA avant la persistance */
|
/** Méthode métier pour retirer un membre */
|
||||||
@PrePersist
|
public void retirerMembre() {
|
||||||
protected void onCreate() {
|
if (nombreMembres != null && nombreMembres > 0) {
|
||||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
nombreMembres--;
|
||||||
if (statut == null) {
|
}
|
||||||
statut = "ACTIVE";
|
}
|
||||||
}
|
|
||||||
if (typeOrganisation == null) {
|
/** Méthode métier pour activer l'organisation */
|
||||||
typeOrganisation = "ASSOCIATION";
|
public void activer(String utilisateur) {
|
||||||
}
|
this.statut = "ACTIVE";
|
||||||
if (devise == null) {
|
this.setActif(true);
|
||||||
devise = "XOF";
|
marquerCommeModifie(utilisateur);
|
||||||
}
|
}
|
||||||
if (niveauHierarchique == null) {
|
|
||||||
niveauHierarchique = 0;
|
/** Méthode métier pour suspendre l'organisation */
|
||||||
}
|
public void suspendre(String utilisateur) {
|
||||||
if (estOrganisationRacine == null) {
|
this.statut = "SUSPENDUE";
|
||||||
estOrganisationRacine = (organisationParente == null);
|
this.accepteNouveauxMembres = false;
|
||||||
}
|
marquerCommeModifie(utilisateur);
|
||||||
if (nombreMembres == null) {
|
}
|
||||||
nombreMembres = 0;
|
|
||||||
}
|
/** Méthode métier pour dissoudre l'organisation */
|
||||||
if (nombreAdministrateurs == null) {
|
public void dissoudre(String utilisateur) {
|
||||||
nombreAdministrateurs = 0;
|
this.statut = "DISSOUTE";
|
||||||
}
|
this.setActif(false);
|
||||||
if (organisationPublique == null) {
|
this.accepteNouveauxMembres = false;
|
||||||
organisationPublique = true;
|
marquerCommeModifie(utilisateur);
|
||||||
}
|
}
|
||||||
if (accepteNouveauxMembres == null) {
|
|
||||||
accepteNouveauxMembres = true;
|
/** Callback JPA avant la persistance */
|
||||||
}
|
@PrePersist
|
||||||
if (cotisationObligatoire == null) {
|
protected void onCreate() {
|
||||||
cotisationObligatoire = false;
|
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||||
}
|
if (statut == null) {
|
||||||
}
|
statut = "ACTIVE";
|
||||||
}
|
}
|
||||||
|
if (typeOrganisation == null) {
|
||||||
|
typeOrganisation = "ASSOCIATION";
|
||||||
|
}
|
||||||
|
if (devise == null) {
|
||||||
|
devise = "XOF";
|
||||||
|
}
|
||||||
|
if (niveauHierarchique == null) {
|
||||||
|
niveauHierarchique = 0;
|
||||||
|
}
|
||||||
|
if (estOrganisationRacine == null) {
|
||||||
|
estOrganisationRacine = (organisationParente == null);
|
||||||
|
}
|
||||||
|
if (nombreMembres == null) {
|
||||||
|
nombreMembres = 0;
|
||||||
|
}
|
||||||
|
if (nombreAdministrateurs == null) {
|
||||||
|
nombreAdministrateurs = 0;
|
||||||
|
}
|
||||||
|
if (organisationPublique == null) {
|
||||||
|
organisationPublique = true;
|
||||||
|
}
|
||||||
|
if (accepteNouveauxMembres == null) {
|
||||||
|
accepteNouveauxMembres = true;
|
||||||
|
}
|
||||||
|
if (cotisationObligatoire == null) {
|
||||||
|
cotisationObligatoire = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Participation d'un membre à une session de formation LBC/FT.
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-12)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "participations_formation_lbcft",
|
||||||
|
uniqueConstraints = @UniqueConstraint(columnNames = {"formation_id", "membre_id"}),
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_participation_membre", columnList = "membre_id"),
|
||||||
|
@Index(name = "idx_participation_statut", columnList = "statut_participation")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ParticipationFormationLbcFt extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "formation_id", nullable = false)
|
||||||
|
private FormationLbcFt formation;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "membre_id", nullable = false)
|
||||||
|
private UUID membreId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "statut_participation", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private String statutParticipation = "INSCRIT"; // INSCRIT, PRESENT, ABSENT, CERTIFIE
|
||||||
|
|
||||||
|
@Column(name = "date_certification")
|
||||||
|
private LocalDateTime dateCertification;
|
||||||
|
|
||||||
|
@Column(name = "numero_certificat", length = 100)
|
||||||
|
private String numeroCertificat;
|
||||||
|
|
||||||
|
@Column(name = "score_quiz", precision = 5, scale = 2)
|
||||||
|
private BigDecimal scoreQuiz;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procès-verbal d'AG ou de CA conforme OHADA AUDSCGIE.
|
||||||
|
*
|
||||||
|
* <p>Structure obligatoire selon l'Acte Uniforme OHADA Sociétés Coopératives (15 décembre 2010,
|
||||||
|
* applicable depuis 15 mai 2011) + AUDSCGIE révisé 30 janvier 2014 :
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Date, lieu, heures d'ouverture et clôture
|
||||||
|
* <li>Quorum calculé (selon convoqués / présents / représentés)
|
||||||
|
* <li>Ordre du jour structuré
|
||||||
|
* <li>Résolutions votées avec décompte (pour / contre / abstentions / adoptée)
|
||||||
|
* <li>Signatures président + secrétaire
|
||||||
|
* <li>Archivage immuable au siège (hash SHA-256 pour intégrité)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-2)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "proces_verbaux")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ProcesVerbal extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "type_seance", nullable = false, length = 20)
|
||||||
|
private String typeSeance; // AG_CONSTITUTIVE, AG_ORDINAIRE, AG_EXTRAORDINAIRE, CA, BUREAU
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "titre", nullable = false, length = 255)
|
||||||
|
private String titre;
|
||||||
|
|
||||||
|
@Column(name = "numero_seance", length = 50)
|
||||||
|
private String numeroSeance;
|
||||||
|
|
||||||
|
// Convocation
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_convocation", nullable = false)
|
||||||
|
private LocalDateTime dateConvocation;
|
||||||
|
|
||||||
|
@Column(name = "mode_convocation", length = 50)
|
||||||
|
private String modeConvocation;
|
||||||
|
|
||||||
|
// Tenue
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_seance", nullable = false)
|
||||||
|
private LocalDateTime dateSeance;
|
||||||
|
|
||||||
|
@Column(name = "lieu", length = 255)
|
||||||
|
private String lieu;
|
||||||
|
|
||||||
|
@Column(name = "heure_ouverture")
|
||||||
|
private LocalTime heureOuverture;
|
||||||
|
|
||||||
|
@Column(name = "heure_cloture")
|
||||||
|
private LocalTime heureCloture;
|
||||||
|
|
||||||
|
// Quorum
|
||||||
|
@Column(name = "nombre_convoques", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private int nombreConvoques = 0;
|
||||||
|
|
||||||
|
@Column(name = "nombre_presents", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private int nombrePresents = 0;
|
||||||
|
|
||||||
|
@Column(name = "nombre_representes", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private int nombreRepresentes = 0;
|
||||||
|
|
||||||
|
@Column(name = "quorum_atteint", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private boolean quorumAtteint = false;
|
||||||
|
|
||||||
|
@Column(name = "quorum_requis_pct", precision = 5, scale = 2)
|
||||||
|
private BigDecimal quorumRequisPct;
|
||||||
|
|
||||||
|
@Column(name = "quorum_calcule_pct", precision = 5, scale = 2)
|
||||||
|
private BigDecimal quorumCalculePct;
|
||||||
|
|
||||||
|
// Présidence
|
||||||
|
@Column(name = "president_seance_id")
|
||||||
|
private UUID presidentSeanceId;
|
||||||
|
|
||||||
|
@Column(name = "secretaire_seance_id")
|
||||||
|
private UUID secretaireSeanceId;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "scrutateurs_ids", columnDefinition = "jsonb")
|
||||||
|
private String scrutateursIds; // JSON array of UUIDs
|
||||||
|
|
||||||
|
// Contenu
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "ordre_du_jour", columnDefinition = "jsonb", nullable = false)
|
||||||
|
private String ordreDuJour; // JSON: [{numero, intitule, type}]
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "resolutions", columnDefinition = "jsonb", nullable = false)
|
||||||
|
private String resolutions; // JSON: [{numero, intitule, votesPour, votesContre, votesAbstention, adoptee}]
|
||||||
|
|
||||||
|
@Column(name = "deliberations", columnDefinition = "TEXT")
|
||||||
|
private String deliberations;
|
||||||
|
|
||||||
|
// Signature & archivage
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "statut", nullable = false, length = 30)
|
||||||
|
@Builder.Default
|
||||||
|
private String statut = "BROUILLON"; // BROUILLON, ADOPTE, SIGNE, ARCHIVE
|
||||||
|
|
||||||
|
@Column(name = "hash_sha256", length = 64)
|
||||||
|
private String hashSha256;
|
||||||
|
|
||||||
|
@Column(name = "date_signature")
|
||||||
|
private LocalDateTime dateSignature;
|
||||||
|
|
||||||
|
@Column(name = "signature_president", length = 500)
|
||||||
|
private String signaturePresident;
|
||||||
|
|
||||||
|
@Column(name = "signature_secretaire", length = 500)
|
||||||
|
private String signatureSecretaire;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Index;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rapport trimestriel agrégé généré par/pour le Contrôleur Interne d'une organisation.
|
||||||
|
*
|
||||||
|
* <p>Source documentaire pour :
|
||||||
|
* <ul>
|
||||||
|
* <li>Présentation lors des AG (rapport moral / financier / technique)</li>
|
||||||
|
* <li>Inspections BCEAO Instruction 001-03-2025 (LBC/FT)</li>
|
||||||
|
* <li>Audits ARTCI Décision 2025-1312 (DPO / sécurité données)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Cycle de vie : {@code DRAFT} → {@code SIGNE} (hash SHA-256 calculé) → {@code ARCHIVE}.
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P2-NEW-3)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "rapports_trimestriels_controleur_interne",
|
||||||
|
uniqueConstraints = @UniqueConstraint(
|
||||||
|
name = "uq_rapport_trim_org_annee_trim",
|
||||||
|
columnNames = {"organisation_id", "annee", "trimestre"}),
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_rapport_trim_org", columnList = "organisation_id"),
|
||||||
|
@Index(name = "idx_rapport_trim_annee_trim", columnList = "annee,trimestre"),
|
||||||
|
@Index(name = "idx_rapport_trim_statut", columnList = "statut")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class RapportTrimestrielControleurInterne extends BaseEntity {
|
||||||
|
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@Min(2024)
|
||||||
|
@Max(2099)
|
||||||
|
@Column(name = "annee", nullable = false)
|
||||||
|
private Integer annee;
|
||||||
|
|
||||||
|
@Min(1)
|
||||||
|
@Max(4)
|
||||||
|
@Column(name = "trimestre", nullable = false)
|
||||||
|
private Integer trimestre;
|
||||||
|
|
||||||
|
@Column(name = "date_generation", nullable = false)
|
||||||
|
private LocalDateTime dateGeneration;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "statut", nullable = false, length = 20)
|
||||||
|
private String statut = "DRAFT";
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
@Min(0)
|
||||||
|
@Max(100)
|
||||||
|
@Column(name = "score_conformite", nullable = false)
|
||||||
|
private Integer scoreConformite = 0;
|
||||||
|
|
||||||
|
/** Snapshot agrégé en JSON (compliance score, DOS count, KYC %, etc.). */
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "contenu_jsonb", columnDefinition = "jsonb")
|
||||||
|
private JsonNode contenuJsonb;
|
||||||
|
|
||||||
|
/** PDF généré par OpenPDF. Null avant génération. */
|
||||||
|
@Column(name = "pdf_bytes")
|
||||||
|
private byte[] pdfBytes;
|
||||||
|
|
||||||
|
/** UUID du membre Contrôleur Interne signataire. */
|
||||||
|
@Column(name = "signataire_id")
|
||||||
|
private UUID signataireId;
|
||||||
|
|
||||||
|
@Column(name = "date_signature")
|
||||||
|
private LocalDateTime dateSignature;
|
||||||
|
|
||||||
|
/** Hash SHA-256 du contenu_jsonb calculé à la signature — immuable ensuite. */
|
||||||
|
@Column(name = "hash_sha256", length = 64)
|
||||||
|
private String hashSha256;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Référentiel comptable applicable à une {@link Organisation}.
|
||||||
|
*
|
||||||
|
* <p>OHADA dispose désormais de plusieurs référentiels selon la nature de l'entité :
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #SYSCOHADA} — Système Comptable OHADA révisé (1er jan 2018) pour entités
|
||||||
|
* commerciales/coopératives à but lucratif.
|
||||||
|
* <li>{@link #SYCEBNL} — Système Comptable OHADA des Entités à But Non Lucratif (11ᵉ Acte
|
||||||
|
* uniforme, entré en vigueur <strong>1er jan 2024</strong>) pour mutuelles sociales,
|
||||||
|
* associations, ONG, fondations, syndicats, projets de développement.
|
||||||
|
* <li>{@link #PCSFD_UMOA} — Plan Comptable des Systèmes Financiers Décentralisés UMOA pour SFD
|
||||||
|
* soumis Commission Bancaire UMOA (article 44, encours ≥ 2 milliards FCFA = catégorie III).
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Le mapping par défaut depuis {@code Organisation.typeOrganisation} se trouve dans
|
||||||
|
* {@link #defaultFor(String)}. L'admin peut overrider manuellement (cas hybrides).
|
||||||
|
*
|
||||||
|
* <p>Voir : {@code unionflow/docs/COMPLIANCE_OHADA_SYCEBNL.md} et {@code
|
||||||
|
* unionflow/docs/COMPLIANCE_OHADA_SYSCOHADA.md}.
|
||||||
|
*
|
||||||
|
* @since 2026-04-25
|
||||||
|
*/
|
||||||
|
public enum ReferentielComptable {
|
||||||
|
/** Système Comptable OHADA révisé (entités commerciales / coopératives lucratives). */
|
||||||
|
SYSCOHADA,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Système Comptable OHADA des Entités à But Non Lucratif (mutuelles sociales, associations,
|
||||||
|
* ONG, fondations, Lions Clubs, syndicats). Acte uniforme entré en vigueur 1er janvier 2024.
|
||||||
|
*/
|
||||||
|
SYCEBNL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plan Comptable des Systèmes Financiers Décentralisés UMOA. Pour SFD article 44 (encours ≥ 2
|
||||||
|
* Md FCFA = catégorie III, commissaire aux comptes obligatoire agréé OHADA, sélection soumise
|
||||||
|
* approbation Commission Bancaire UMOA).
|
||||||
|
*/
|
||||||
|
PCSFD_UMOA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le référentiel par défaut suggéré pour un {@code typeOrganisation}. L'admin peut
|
||||||
|
* overrider manuellement à la création/édition d'une organisation.
|
||||||
|
*
|
||||||
|
* @param typeOrganisation valeur de {@link Organisation#getTypeOrganisation()}
|
||||||
|
* @return référentiel par défaut, jamais null (fallback {@link #SYSCOHADA})
|
||||||
|
*/
|
||||||
|
public static ReferentielComptable defaultFor(String typeOrganisation) {
|
||||||
|
if (typeOrganisation == null) {
|
||||||
|
return SYSCOHADA;
|
||||||
|
}
|
||||||
|
return switch (typeOrganisation.toUpperCase()) {
|
||||||
|
case "MUTUELLE_SANTE",
|
||||||
|
"ASSOCIATION",
|
||||||
|
"LIONS_CLUB",
|
||||||
|
"ONG",
|
||||||
|
"FONDATION",
|
||||||
|
"SYNDICAT",
|
||||||
|
"ORDRE_PROFESSIONNEL",
|
||||||
|
"PROJET_DEVELOPPEMENT" ->
|
||||||
|
SYCEBNL;
|
||||||
|
case "SFD_TIER_1", "SFD_CATEGORIE_III" -> PCSFD_UMOA;
|
||||||
|
default -> SYSCOHADA;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Délégation temporaire d'un rôle.
|
||||||
|
*
|
||||||
|
* <p>Cas d'usage : trésorier en congé délègue son rôle au trésorier adjoint pour 2 semaines.
|
||||||
|
*
|
||||||
|
* <p>Le {@code PermissionChecker} consulte cette table pour calculer le rôle effectif :
|
||||||
|
* <strong>roles directs ∪ roles délégués actifs</strong> (statut=ACTIVE et dateFin > now).
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P1-NEW-5)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "role_delegations", indexes = {
|
||||||
|
@Index(name = "idx_delegation_org", columnList = "organisation_id"),
|
||||||
|
@Index(name = "idx_delegation_delegataire", columnList = "delegataire_user_id")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class RoleDelegation extends BaseEntity {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "organisation_id", nullable = false)
|
||||||
|
private UUID organisationId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "delegant_user_id", nullable = false)
|
||||||
|
private UUID delegantUserId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "delegataire_user_id", nullable = false)
|
||||||
|
private UUID delegataireUserId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "role_delegue", nullable = false, length = 50)
|
||||||
|
private String roleDelegue;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_debut", nullable = false)
|
||||||
|
private LocalDateTime dateDebut;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_fin", nullable = false)
|
||||||
|
private LocalDateTime dateFin;
|
||||||
|
|
||||||
|
@Column(name = "motif", length = 500)
|
||||||
|
private String motif;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "statut", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private String statut = "ACTIVE"; // ACTIVE, EXPIREE, REVOQUEE
|
||||||
|
|
||||||
|
@Column(name = "date_revocation")
|
||||||
|
private LocalDateTime dateRevocation;
|
||||||
|
|
||||||
|
/** Vrai si la délégation est active à l'instant donné. */
|
||||||
|
public boolean isActiveAt(LocalDateTime instant) {
|
||||||
|
return "ACTIVE".equals(statut)
|
||||||
|
&& dateDebut != null && !dateDebut.isAfter(instant)
|
||||||
|
&& dateFin != null && dateFin.isAfter(instant);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
import jakarta.persistence.Index;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import jakarta.validation.constraints.DecimalMin;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taux de change quotidien entre deux {@link Devise}s.
|
||||||
|
*
|
||||||
|
* <p>Source : BCEAO (officiel UEMOA), ECB, Fixer.io ou import manuel. Conservation
|
||||||
|
* historique pour audit et conversions rétroactives.
|
||||||
|
*
|
||||||
|
* @since 2026-04-25 (P2-NEW-7)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "taux_change",
|
||||||
|
uniqueConstraints = @UniqueConstraint(
|
||||||
|
name = "uq_taux_change_paire_date",
|
||||||
|
columnNames = {"devise_source", "devise_cible", "date_validite"}),
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_taux_change_paire", columnList = "devise_source,devise_cible"),
|
||||||
|
@Index(name = "idx_taux_change_date_validite", columnList = "date_validite")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TauxChange extends BaseEntity {
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "devise_source", nullable = false, length = 3)
|
||||||
|
private Devise deviseSource;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "devise_cible", nullable = false, length = 3)
|
||||||
|
private Devise deviseCible;
|
||||||
|
|
||||||
|
/** 1 unité de {@code deviseSource} = {@code taux} unités de {@code deviseCible}. */
|
||||||
|
@DecimalMin(value = "0.00000001", inclusive = false)
|
||||||
|
@Column(name = "taux", nullable = false, precision = 18, scale = 8)
|
||||||
|
private BigDecimal taux;
|
||||||
|
|
||||||
|
@Column(name = "date_validite", nullable = false)
|
||||||
|
private LocalDate dateValidite;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "source", nullable = false, length = 50)
|
||||||
|
private String source = "BCEAO";
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.lions.unionflow.server.entity.mutuelle;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.entity.BaseEntity;
|
||||||
|
import dev.lions.unionflow.server.entity.Organisation;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "parametres_financiers_mutuelle", indexes = {
|
||||||
|
@Index(name = "idx_pfm_org", columnList = "organisation_id", unique = true)
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ParametresFinanciersMutuelle extends BaseEntity {
|
||||||
|
|
||||||
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "organisation_id", nullable = false, unique = true)
|
||||||
|
private Organisation organisation;
|
||||||
|
|
||||||
|
/** Valeur nominale par défaut d'une part sociale */
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "valeur_nominale_par_defaut", nullable = false, precision = 19, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal valeurNominaleParDefaut = new BigDecimal("5000");
|
||||||
|
|
||||||
|
/** Taux d'intérêt annuel sur l'épargne, ex: 0.03 = 3% */
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "taux_interet_annuel_epargne", nullable = false, precision = 6, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal tauxInteretAnnuelEpargne = new BigDecimal("0.03");
|
||||||
|
|
||||||
|
/** Taux de dividende annuel sur les parts sociales, ex: 0.05 = 5% */
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "taux_dividende_parts_annuel", nullable = false, precision = 6, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal tauxDividendePartsAnnuel = new BigDecimal("0.05");
|
||||||
|
|
||||||
|
/** MENSUEL | TRIMESTRIEL | ANNUEL */
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "periodicite_calcul", nullable = false, length = 20)
|
||||||
|
@Builder.Default
|
||||||
|
private String periodiciteCalcul = "MENSUEL";
|
||||||
|
|
||||||
|
/** Solde minimum en dessous duquel les intérêts ne s'appliquent pas */
|
||||||
|
@Column(name = "seuil_min_epargne_interets", precision = 19, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal seuilMinEpargneInterets = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
/** Date du prochain calcul planifié */
|
||||||
|
@Column(name = "prochaine_calcul_interets")
|
||||||
|
private LocalDate prochaineCalculInterets;
|
||||||
|
|
||||||
|
/** Date du dernier calcul effectué */
|
||||||
|
@Column(name = "dernier_calcul_interets")
|
||||||
|
private LocalDate dernierCalculInterets;
|
||||||
|
|
||||||
|
/** Nombre de comptes traités lors du dernier calcul */
|
||||||
|
@Column(name = "dernier_nb_comptes_traites")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer dernierNbComptesTraites = 0;
|
||||||
|
}
|
||||||
@@ -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<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package dev.lions.unionflow.server.entity.mutuelle.parts;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.enums.mutuelle.parts.StatutComptePartsSociales;
|
||||||
|
import dev.lions.unionflow.server.entity.BaseEntity;
|
||||||
|
import dev.lions.unionflow.server.entity.Membre;
|
||||||
|
import dev.lions.unionflow.server.entity.Organisation;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "comptes_parts_sociales", indexes = {
|
||||||
|
@Index(name = "idx_cps_numero", columnList = "numero_compte", unique = true),
|
||||||
|
@Index(name = "idx_cps_membre", columnList = "membre_id"),
|
||||||
|
@Index(name = "idx_cps_org", columnList = "organisation_id")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ComptePartsSociales extends BaseEntity {
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "membre_id", nullable = false)
|
||||||
|
private Membre membre;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "organisation_id", nullable = false)
|
||||||
|
private Organisation organisation;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "numero_compte", unique = true, nullable = false, length = 50)
|
||||||
|
private String numeroCompte;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Min(0)
|
||||||
|
@Column(name = "nombre_parts", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer nombreParts = 0;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "valeur_nominale", nullable = false, precision = 19, scale = 4)
|
||||||
|
private BigDecimal valeurNominale;
|
||||||
|
|
||||||
|
/** nombreParts × valeurNominale — mis à jour à chaque transaction */
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "montant_total", nullable = false, precision = 19, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal montantTotal = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "total_dividendes_recus", nullable = false, precision = 19, scale = 4)
|
||||||
|
@Builder.Default
|
||||||
|
private BigDecimal totalDividendesRecus = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "statut", nullable = false, length = 30)
|
||||||
|
@Builder.Default
|
||||||
|
private StatutComptePartsSociales statut = StatutComptePartsSociales.ACTIF;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_ouverture", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private LocalDate dateOuverture = LocalDate.now();
|
||||||
|
|
||||||
|
@Column(name = "date_derniere_operation")
|
||||||
|
private LocalDate dateDerniereOperation;
|
||||||
|
|
||||||
|
@Column(name = "notes", length = 500)
|
||||||
|
private String notes;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package dev.lions.unionflow.server.entity.mutuelle.parts;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.enums.mutuelle.parts.TypeTransactionPartsSociales;
|
||||||
|
import dev.lions.unionflow.server.entity.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "transactions_parts_sociales", indexes = {
|
||||||
|
@Index(name = "idx_tps_compte", columnList = "compte_id"),
|
||||||
|
@Index(name = "idx_tps_date", columnList = "date_transaction")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TransactionPartsSociales extends BaseEntity {
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "compte_id", nullable = false)
|
||||||
|
private ComptePartsSociales compte;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "type_transaction", nullable = false, length = 50)
|
||||||
|
private TypeTransactionPartsSociales typeTransaction;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Min(1)
|
||||||
|
@Column(name = "nombre_parts", nullable = false)
|
||||||
|
private Integer nombreParts;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "montant", nullable = false, precision = 19, scale = 4)
|
||||||
|
private BigDecimal montant;
|
||||||
|
|
||||||
|
@Column(name = "solde_parts_avant", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer soldePartsAvant = 0;
|
||||||
|
|
||||||
|
@Column(name = "solde_parts_apres", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private Integer soldePartsApres = 0;
|
||||||
|
|
||||||
|
@Column(name = "motif", length = 500)
|
||||||
|
private String motif;
|
||||||
|
|
||||||
|
@Column(name = "reference_externe", length = 100)
|
||||||
|
private String referenceExterne;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(name = "date_transaction", nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private LocalDateTime dateTransaction = LocalDateTime.now();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user