Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
91
Dockerfile.prod
Normal file
91
Dockerfile.prod
Normal file
@@ -0,0 +1,91 @@
|
||||
####
|
||||
# Dockerfile de production pour UnionFlow Server (Backend)
|
||||
# Multi-stage build optimisé avec sécurité renforcée
|
||||
####
|
||||
|
||||
## Stage 1 : Build avec Maven
|
||||
FROM maven:3.9.6-eclipse-temurin-17 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copier les fichiers de configuration Maven
|
||||
COPY pom.xml .
|
||||
COPY ../unionflow-server-api/pom.xml ../unionflow-server-api/
|
||||
|
||||
# Télécharger les dépendances (cache Docker)
|
||||
RUN mvn dependency:go-offline -B -pl unionflow-server-impl-quarkus -am
|
||||
|
||||
# Copier le code source
|
||||
COPY src ./src
|
||||
|
||||
# Construire l'application avec profil production
|
||||
RUN mvn clean package -DskipTests -B -Dquarkus.profile=prod -pl unionflow-server-impl-quarkus
|
||||
|
||||
## Stage 2 : Image de production optimisée
|
||||
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
# Configuration des variables d'environnement pour production
|
||||
ENV QUARKUS_PROFILE=prod
|
||||
ENV QUARKUS_HTTP_PORT=8085
|
||||
ENV QUARKUS_HTTP_HOST=0.0.0.0
|
||||
|
||||
# Configuration Base de données (à surcharger via variables d'environnement)
|
||||
ENV DB_URL=jdbc:postgresql://postgresql:5432/unionflow
|
||||
ENV DB_USERNAME=unionflow
|
||||
ENV DB_PASSWORD=changeme
|
||||
|
||||
# Configuration Keycloak/OIDC (production)
|
||||
ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/unionflow
|
||||
ENV QUARKUS_OIDC_CLIENT_ID=unionflow-server
|
||||
ENV KEYCLOAK_CLIENT_SECRET=changeme
|
||||
ENV QUARKUS_OIDC_TLS_VERIFICATION=required
|
||||
|
||||
# Configuration CORS pour production
|
||||
ENV CORS_ORIGINS=https://unionflow.lions.dev,https://security.lions.dev
|
||||
ENV QUARKUS_HTTP_CORS_ORIGINS=${CORS_ORIGINS}
|
||||
|
||||
# Configuration Wave Money (optionnel)
|
||||
ENV WAVE_API_KEY=
|
||||
ENV WAVE_API_SECRET=
|
||||
ENV WAVE_API_BASE_URL=https://api.wave.com/v1
|
||||
ENV WAVE_ENVIRONMENT=production
|
||||
ENV WAVE_WEBHOOK_SECRET=
|
||||
|
||||
# Installer curl pour les health checks
|
||||
USER root
|
||||
RUN microdnf install curl -y && microdnf clean all
|
||||
RUN mkdir -p /app/logs && chown -R 185:185 /app/logs
|
||||
USER 185
|
||||
|
||||
# Copier l'application depuis le builder
|
||||
COPY --from=builder --chown=185 /app/target/quarkus-app/lib/ /deployments/lib/
|
||||
COPY --from=builder --chown=185 /app/target/quarkus-app/*.jar /deployments/
|
||||
COPY --from=builder --chown=185 /app/target/quarkus-app/app/ /deployments/app/
|
||||
COPY --from=builder --chown=185 /app/target/quarkus-app/quarkus/ /deployments/quarkus/
|
||||
|
||||
# Exposer le port
|
||||
EXPOSE 8085
|
||||
|
||||
# Variables JVM optimisées pour production avec sécurité
|
||||
ENV JAVA_OPTS="-Xmx1g -Xms512m \
|
||||
-XX:+UseG1GC \
|
||||
-XX:MaxGCPauseMillis=200 \
|
||||
-XX:+UseStringDeduplication \
|
||||
-XX:+ParallelRefProcEnabled \
|
||||
-XX:+HeapDumpOnOutOfMemoryError \
|
||||
-XX:HeapDumpPath=/app/logs/heapdump.hprof \
|
||||
-Djava.security.egd=file:/dev/./urandom \
|
||||
-Djava.awt.headless=true \
|
||||
-Dfile.encoding=UTF-8 \
|
||||
-Djava.util.logging.manager=org.jboss.logmanager.LogManager \
|
||||
-Dquarkus.profile=${QUARKUS_PROFILE}"
|
||||
|
||||
# Point d'entrée avec profil production
|
||||
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"]
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:8085/q/health/ready || exit 1
|
||||
|
||||
43
docker-compose.dev.yml
Normal file
43
docker-compose.dev.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres-dev:
|
||||
image: postgres:15-alpine
|
||||
container_name: unionflow-postgres-dev
|
||||
environment:
|
||||
POSTGRES_DB: unionflow_dev
|
||||
POSTGRES_USER: unionflow_dev
|
||||
POSTGRES_PASSWORD: dev123
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_dev_data:/var/lib/postgresql/data
|
||||
- ./src/main/resources/db/init:/docker-entrypoint-initdb.d
|
||||
networks:
|
||||
- unionflow-dev
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U unionflow_dev -d unionflow_dev"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
adminer:
|
||||
image: adminer:4.8.1
|
||||
container_name: unionflow-adminer
|
||||
ports:
|
||||
- "8081:8080"
|
||||
networks:
|
||||
- unionflow-dev
|
||||
depends_on:
|
||||
- postgres-dev
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_dev_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
unionflow-dev:
|
||||
driver: bridge
|
||||
337
pom.xml
Normal file
337
pom.xml
Normal file
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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>
|
||||
|
||||
<groupId>dev.lions.unionflow</groupId>
|
||||
<artifactId>unionflow-server-impl-quarkus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>UnionFlow Server Implementation (Quarkus)</name>
|
||||
<description>Implémentation Quarkus du serveur UnionFlow</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<quarkus.platform.version>3.15.1</quarkus.platform.version>
|
||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||
|
||||
<!-- Jacoco -->
|
||||
<jacoco.version>0.8.11</jacoco.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${quarkus.platform.group-id}</groupId>
|
||||
<artifactId>${quarkus.platform.artifact-id}</artifactId>
|
||||
<version>${quarkus.platform.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Dépendance vers l'API -->
|
||||
<dependency>
|
||||
<groupId>dev.lions.unionflow</groupId>
|
||||
<artifactId>unionflow-server-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus Core -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Base de données PostgreSQL -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm-panache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-flyway</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sécurité Keycloak OIDC -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-keycloak-authorization</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Configuration et santé -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-health</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OpenAPI/Swagger -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-openapi</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI pour Excel -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml-lite</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons CSV pour CSV -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-test-security</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.24.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<name>Gitea Maven Repository</name>
|
||||
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>${quarkus.platform.group-id}</groupId>
|
||||
<artifactId>quarkus-maven-plugin</artifactId>
|
||||
<version>${quarkus.platform.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>generate-code</goal>
|
||||
<goal>generate-code-tests</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Surefire pour les tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<!-- Exclure les migrations Flyway du classpath des tests -->
|
||||
<quarkus.flyway.enabled>false</quarkus.flyway.enabled>
|
||||
<quarkus.flyway.migrate-at-start>false</quarkus.flyway.migrate-at-start>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Jacoco pour la couverture de code -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<!-- Exclure les classes générées par Lombok -->
|
||||
<exclude>**/*$*Builder*.class</exclude>
|
||||
<exclude>**/Membre$MembreBuilder.class</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<!-- Exclure les classes générées par Lombok -->
|
||||
<exclude>**/*$*Builder*.class</exclude>
|
||||
<exclude>**/Membre$MembreBuilder.class</exclude>
|
||||
</excludes>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1.00</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>BRANCH</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1.00</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1.00</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>METHOD</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1.00</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>native</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>${quarkus.platform.group-id}</groupId>
|
||||
<artifactId>quarkus-maven-plugin</artifactId>
|
||||
<version>${quarkus.platform.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>native-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
27
setup-postgres.sql
Normal file
27
setup-postgres.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
-- Script de configuration PostgreSQL pour UnionFlow
|
||||
-- Exécuter en tant que superuser (postgres)
|
||||
|
||||
-- Créer l'utilisateur unionflow s'il n'existe pas
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = 'unionflow') THEN
|
||||
CREATE USER unionflow WITH PASSWORD 'unionflow123';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Créer la base de données unionflow si elle n'existe pas
|
||||
SELECT 'CREATE DATABASE unionflow OWNER unionflow'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'unionflow')\gexec
|
||||
|
||||
-- Donner tous les privilèges à l'utilisateur unionflow
|
||||
GRANT ALL PRIVILEGES ON DATABASE unionflow TO unionflow;
|
||||
|
||||
-- Se connecter à la base unionflow et donner les privilèges sur le schéma public
|
||||
\c unionflow
|
||||
GRANT ALL ON SCHEMA public TO unionflow;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO unionflow;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO unionflow;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO unionflow;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO unionflow;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package de.lions.unionflow.server.auth;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application
|
||||
* mobile.
|
||||
*/
|
||||
@Path("/auth")
|
||||
public class AuthCallbackResource {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
|
||||
|
||||
/**
|
||||
* Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile
|
||||
* avec les paramètres reçus.
|
||||
*/
|
||||
@GET
|
||||
@Path("/callback")
|
||||
public Response handleCallback(
|
||||
@QueryParam("code") String code,
|
||||
@QueryParam("state") String state,
|
||||
@QueryParam("session_state") String sessionState,
|
||||
@QueryParam("error") String error,
|
||||
@QueryParam("error_description") String errorDescription) {
|
||||
|
||||
try {
|
||||
// Log des paramètres reçus pour debug
|
||||
log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s",
|
||||
code, state, sessionState, error, errorDescription);
|
||||
|
||||
// URL de redirection simple vers l'application mobile
|
||||
String redirectUrl = "dev.lions.unionflow-mobile://callback";
|
||||
|
||||
// Si nous avons un code d'autorisation, c'est un succès
|
||||
if (code != null && !code.isEmpty()) {
|
||||
redirectUrl += "?code=" + code;
|
||||
if (state != null && !state.isEmpty()) {
|
||||
redirectUrl += "&state=" + state;
|
||||
}
|
||||
} else if (error != null) {
|
||||
redirectUrl += "?error=" + error;
|
||||
if (errorDescription != null) {
|
||||
redirectUrl += "&error_description=" + errorDescription;
|
||||
}
|
||||
}
|
||||
|
||||
// Page HTML simple qui redirige automatiquement vers l'app mobile
|
||||
String html =
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirection vers UnionFlow</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.spinner {
|
||||
border: 4px solid rgba(255,255,255,0.3);
|
||||
border-top: 4px solid white;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 20px auto;
|
||||
}
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
a { color: #ffeb3b; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>🔐 Authentification réussie</h2>
|
||||
<div class="spinner"></div>
|
||||
<p>Redirection vers l'application UnionFlow...</p>
|
||||
<p><small>Si la redirection ne fonctionne pas automatiquement,
|
||||
<a href="%s">cliquez ici</a></small></p>
|
||||
</div>
|
||||
<script>
|
||||
// Tentative de redirection automatique
|
||||
setTimeout(function() {
|
||||
window.location.href = '%s';
|
||||
}, 2000);
|
||||
|
||||
// Fallback: ouvrir l'app mobile si possible
|
||||
setTimeout(function() {
|
||||
try {
|
||||
window.open('%s', '_self');
|
||||
} catch(e) {
|
||||
console.log('Redirection manuelle nécessaire');
|
||||
}
|
||||
}, 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
.formatted(redirectUrl, redirectUrl, redirectUrl);
|
||||
|
||||
return Response.ok(html).type("text/html").build();
|
||||
|
||||
} catch (Exception e) {
|
||||
// En cas d'erreur, retourner une page d'erreur simple
|
||||
String errorHtml =
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Erreur d'authentification</title></head>
|
||||
<body style="font-family: Arial; text-align: center; padding: 50px;">
|
||||
<h2>❌ Erreur d'authentification</h2>
|
||||
<p>Une erreur s'est produite lors de la redirection.</p>
|
||||
<p>Veuillez fermer cette page et réessayer.</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
return Response.status(500).entity(errorHtml).type("text/html").build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package dev.lions.unionflow.server;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import io.quarkus.runtime.QuarkusApplication;
|
||||
import io.quarkus.runtime.annotations.QuarkusMain;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Application principale UnionFlow Server
|
||||
*
|
||||
* @author Lions Dev Team
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@QuarkusMain
|
||||
@ApplicationScoped
|
||||
public class UnionFlowServerApplication implements QuarkusApplication {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class);
|
||||
|
||||
public static void main(String... args) {
|
||||
Quarkus.run(UnionFlowServerApplication.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run(String... args) throws Exception {
|
||||
LOG.info("🚀 UnionFlow Server démarré avec succès!");
|
||||
LOG.info("📊 API disponible sur http://localhost:8080");
|
||||
LOG.info("📖 Documentation OpenAPI sur http://localhost:8080/q/swagger-ui");
|
||||
LOG.info("💚 Health check sur http://localhost:8080/health");
|
||||
|
||||
Quarkus.waitForExit();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package dev.lions.unionflow.server.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le format attendu par
|
||||
* l'application mobile Flutter
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class EvenementMobileDTO {
|
||||
|
||||
private UUID id;
|
||||
private String titre;
|
||||
private String description;
|
||||
private LocalDateTime dateDebut;
|
||||
private LocalDateTime dateFin;
|
||||
private String lieu;
|
||||
private String adresse;
|
||||
private String ville;
|
||||
private String codePostal;
|
||||
|
||||
// Mapping: typeEvenement -> type
|
||||
private String type;
|
||||
|
||||
// Mapping: statut -> statut (OK)
|
||||
private String statut;
|
||||
|
||||
// Mapping: capaciteMax -> maxParticipants
|
||||
private Integer maxParticipants;
|
||||
|
||||
// Nombre de participants actuels (calculé depuis les inscriptions)
|
||||
private Integer participantsActuels;
|
||||
|
||||
// IDs et noms pour les relations
|
||||
private UUID organisateurId;
|
||||
private String organisateurNom;
|
||||
private UUID organisationId;
|
||||
private String organisationNom;
|
||||
|
||||
// Priorité (à ajouter dans l'entité si nécessaire)
|
||||
private String priorite;
|
||||
|
||||
// Mapping: visiblePublic -> estPublic
|
||||
private Boolean estPublic;
|
||||
|
||||
// Mapping: inscriptionRequise -> inscriptionRequise (OK)
|
||||
private Boolean inscriptionRequise;
|
||||
|
||||
// Mapping: prix -> cout
|
||||
private BigDecimal cout;
|
||||
|
||||
// Devise
|
||||
private String devise;
|
||||
|
||||
// Tags (à implémenter si nécessaire)
|
||||
private String[] tags;
|
||||
|
||||
// URLs
|
||||
private String imageUrl;
|
||||
private String documentUrl;
|
||||
|
||||
// Notes
|
||||
private String notes;
|
||||
|
||||
// Dates de création/modification
|
||||
private LocalDateTime dateCreation;
|
||||
private LocalDateTime dateModification;
|
||||
|
||||
// Actif
|
||||
private Boolean actif;
|
||||
|
||||
/**
|
||||
* Convertit une entité Evenement en DTO mobile
|
||||
*
|
||||
* @param evenement L'entité à convertir
|
||||
* @return Le DTO mobile
|
||||
*/
|
||||
public static EvenementMobileDTO fromEntity(Evenement evenement) {
|
||||
if (evenement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return EvenementMobileDTO.builder()
|
||||
.id(evenement.getId()) // Utilise getId() depuis BaseEntity
|
||||
.titre(evenement.getTitre())
|
||||
.description(evenement.getDescription())
|
||||
.dateDebut(evenement.getDateDebut())
|
||||
.dateFin(evenement.getDateFin())
|
||||
.lieu(evenement.getLieu())
|
||||
.adresse(evenement.getAdresse())
|
||||
.ville(null) // Pas de champ ville dans l'entité
|
||||
.codePostal(null) // Pas de champ codePostal dans l'entité
|
||||
// Mapping des enums
|
||||
.type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement().name() : null)
|
||||
.statut(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE")
|
||||
// Mapping des champs renommés
|
||||
.maxParticipants(evenement.getCapaciteMax())
|
||||
.participantsActuels(evenement.getNombreInscrits())
|
||||
// Relations (gestion sécurisée des lazy loading)
|
||||
.organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null)
|
||||
.organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null)
|
||||
.organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null)
|
||||
.organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null)
|
||||
// Priorité (valeur par défaut)
|
||||
.priorite("MOYENNE")
|
||||
// Mapping booléens
|
||||
.estPublic(evenement.getVisiblePublic())
|
||||
.inscriptionRequise(evenement.getInscriptionRequise())
|
||||
// Mapping prix -> cout
|
||||
.cout(evenement.getPrix())
|
||||
.devise("XOF")
|
||||
// Tags vides pour l'instant
|
||||
.tags(new String[] {})
|
||||
// URLs (à implémenter si nécessaire)
|
||||
.imageUrl(null)
|
||||
.documentUrl(null)
|
||||
// Notes
|
||||
.notes(evenement.getInstructionsParticulieres())
|
||||
// Dates
|
||||
.dateCreation(evenement.getDateCreation())
|
||||
.dateModification(evenement.getDateModification())
|
||||
// Actif
|
||||
.actif(evenement.getActif())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
132
src/main/java/dev/lions/unionflow/server/entity/Adhesion.java
Normal file
132
src/main/java/dev/lions/unionflow/server/entity/Adhesion.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Adhesion avec UUID
|
||||
* Représente une demande d'adhésion d'un membre à une organisation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "adhesions",
|
||||
indexes = {
|
||||
@Index(name = "idx_adhesion_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_adhesion_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_adhesion_reference", columnList = "numero_reference", unique = true),
|
||||
@Index(name = "idx_adhesion_statut", columnList = "statut"),
|
||||
@Index(name = "idx_adhesion_date_demande", columnList = "date_demande")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Adhesion extends BaseEntity {
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
|
||||
private String numeroReference;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id", nullable = false)
|
||||
private Organisation organisation;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_demande", nullable = false)
|
||||
private LocalDate dateDemande;
|
||||
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant des frais d'adhésion doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal fraisAdhesion;
|
||||
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal montantPaye = BigDecimal.ZERO;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
@Column(name = "code_devise", nullable = false, length = 3)
|
||||
private String codeDevise;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(
|
||||
regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$",
|
||||
message = "Statut invalide")
|
||||
@Column(name = "statut", nullable = false, length = 30)
|
||||
private String statut;
|
||||
|
||||
@Column(name = "date_approbation")
|
||||
private LocalDate dateApprobation;
|
||||
|
||||
@Column(name = "date_paiement")
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "methode_paiement", length = 20)
|
||||
private String methodePaiement;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "reference_paiement", length = 100)
|
||||
private String referencePaiement;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "motif_rejet", length = 1000)
|
||||
private String motifRejet;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "observations", length = 1000)
|
||||
private String observations;
|
||||
|
||||
@Column(name = "approuve_par", length = 255)
|
||||
private String approuvePar;
|
||||
|
||||
@Column(name = "date_validation")
|
||||
private LocalDate dateValidation;
|
||||
|
||||
/** Méthode métier pour vérifier si l'adhésion est payée intégralement */
|
||||
public boolean isPayeeIntegralement() {
|
||||
return montantPaye != null
|
||||
&& fraisAdhesion != null
|
||||
&& montantPaye.compareTo(fraisAdhesion) >= 0;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'adhésion est en attente de paiement */
|
||||
public boolean isEnAttentePaiement() {
|
||||
return "APPROUVEE".equals(statut) && !isPayeeIntegralement();
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer le montant restant à payer */
|
||||
public BigDecimal getMontantRestant() {
|
||||
if (fraisAdhesion == null) return BigDecimal.ZERO;
|
||||
if (montantPaye == null) return fraisAdhesion;
|
||||
BigDecimal restant = fraisAdhesion.subtract(montantPaye);
|
||||
return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
154
src/main/java/dev/lions/unionflow/server/entity/Adresse.java
Normal file
154
src/main/java/dev/lions/unionflow/server/entity/Adresse.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Adresse pour la gestion des adresses des organisations, membres et événements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "adresses",
|
||||
indexes = {
|
||||
@Index(name = "idx_adresse_ville", columnList = "ville"),
|
||||
@Index(name = "idx_adresse_pays", columnList = "pays"),
|
||||
@Index(name = "idx_adresse_type", columnList = "type_adresse"),
|
||||
@Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_adresse_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_adresse_evenement", columnList = "evenement_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Adresse extends BaseEntity {
|
||||
|
||||
/** Type d'adresse */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_adresse", nullable = false, length = 50)
|
||||
private TypeAdresse typeAdresse;
|
||||
|
||||
/** Adresse complète */
|
||||
@Column(name = "adresse", length = 500)
|
||||
private String adresse;
|
||||
|
||||
/** Complément d'adresse */
|
||||
@Column(name = "complement_adresse", length = 200)
|
||||
private String complementAdresse;
|
||||
|
||||
/** Code postal */
|
||||
@Column(name = "code_postal", length = 20)
|
||||
private String codePostal;
|
||||
|
||||
/** Ville */
|
||||
@Column(name = "ville", length = 100)
|
||||
private String ville;
|
||||
|
||||
/** Région */
|
||||
@Column(name = "region", length = 100)
|
||||
private String region;
|
||||
|
||||
/** Pays */
|
||||
@Column(name = "pays", length = 100)
|
||||
private String pays;
|
||||
|
||||
/** Coordonnées géographiques - Latitude */
|
||||
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
|
||||
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
|
||||
@Digits(integer = 3, fraction = 6)
|
||||
@Column(name = "latitude", precision = 9, scale = 6)
|
||||
private BigDecimal latitude;
|
||||
|
||||
/** Coordonnées géographiques - Longitude */
|
||||
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
|
||||
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
|
||||
@Digits(integer = 3, fraction = 6)
|
||||
@Column(name = "longitude", precision = 9, scale = 6)
|
||||
private BigDecimal longitude;
|
||||
|
||||
/** Adresse principale (une seule par entité) */
|
||||
@Builder.Default
|
||||
@Column(name = "principale", nullable = false)
|
||||
private Boolean principale = false;
|
||||
|
||||
/** Libellé personnalisé */
|
||||
@Column(name = "libelle", length = 100)
|
||||
private String libelle;
|
||||
|
||||
/** Notes et commentaires */
|
||||
@Column(name = "notes", length = 500)
|
||||
private String notes;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id")
|
||||
private Membre membre;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "evenement_id")
|
||||
private Evenement evenement;
|
||||
|
||||
/** Méthode métier pour obtenir l'adresse complète formatée */
|
||||
public String getAdresseComplete() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (adresse != null && !adresse.isEmpty()) {
|
||||
sb.append(adresse);
|
||||
}
|
||||
if (complementAdresse != null && !complementAdresse.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(complementAdresse);
|
||||
}
|
||||
if (codePostal != null && !codePostal.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(codePostal);
|
||||
}
|
||||
if (ville != null && !ville.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(" ");
|
||||
sb.append(ville);
|
||||
}
|
||||
if (region != null && !region.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(region);
|
||||
}
|
||||
if (pays != null && !pays.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(pays);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
|
||||
public boolean hasCoordinates() {
|
||||
return latitude != null && longitude != null;
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||
if (typeAdresse == null) {
|
||||
typeAdresse = dev.lions.unionflow.server.api.enums.adresse.TypeAdresse.AUTRE;
|
||||
}
|
||||
if (principale == null) {
|
||||
principale = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Entité pour les logs d'audit
|
||||
* Enregistre toutes les actions importantes du système
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "audit_logs", indexes = {
|
||||
@Index(name = "idx_audit_date_heure", columnList = "date_heure"),
|
||||
@Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
|
||||
@Index(name = "idx_audit_module", columnList = "module"),
|
||||
@Index(name = "idx_audit_type_action", columnList = "type_action"),
|
||||
@Index(name = "idx_audit_severite", columnList = "severite")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuditLog extends BaseEntity {
|
||||
|
||||
@Column(name = "type_action", nullable = false, length = 50)
|
||||
private String typeAction;
|
||||
|
||||
@Column(name = "severite", nullable = false, length = 20)
|
||||
private String severite;
|
||||
|
||||
@Column(name = "utilisateur", length = 255)
|
||||
private String utilisateur;
|
||||
|
||||
@Column(name = "role", length = 50)
|
||||
private String role;
|
||||
|
||||
@Column(name = "module", length = 50)
|
||||
private String module;
|
||||
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(name = "details", columnDefinition = "TEXT")
|
||||
private String details;
|
||||
|
||||
@Column(name = "ip_address", length = 45)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent", length = 500)
|
||||
private String userAgent;
|
||||
|
||||
@Column(name = "session_id", length = 255)
|
||||
private String sessionId;
|
||||
|
||||
@Column(name = "date_heure", nullable = false)
|
||||
private LocalDateTime dateHeure;
|
||||
|
||||
@Column(name = "donnees_avant", columnDefinition = "TEXT")
|
||||
private String donneesAvant;
|
||||
|
||||
@Column(name = "donnees_apres", columnDefinition = "TEXT")
|
||||
private String donneesApres;
|
||||
|
||||
@Column(name = "entite_id", length = 255)
|
||||
private String entiteId;
|
||||
|
||||
@Column(name = "entite_type", length = 100)
|
||||
private String entiteType;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (dateHeure == null) {
|
||||
dateHeure = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
141
src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java
Normal file
141
src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Classe de base pour les entités UnionFlow utilisant UUID comme identifiant
|
||||
*
|
||||
* <p>Remplace PanacheEntity pour utiliser UUID au lieu de Long comme ID.
|
||||
* Fournit les fonctionnalités de base de Panache avec UUID.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class BaseEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(name = "id", updatable = false, nullable = false)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "date_creation", nullable = false, updatable = false)
|
||||
protected LocalDateTime dateCreation;
|
||||
|
||||
@Column(name = "date_modification")
|
||||
protected LocalDateTime dateModification;
|
||||
|
||||
@Column(name = "cree_par", length = 255)
|
||||
protected String creePar;
|
||||
|
||||
@Column(name = "modifie_par", length = 255)
|
||||
protected String modifiePar;
|
||||
|
||||
@Version
|
||||
@Column(name = "version")
|
||||
protected Long version;
|
||||
|
||||
@Column(name = "actif", nullable = false)
|
||||
protected Boolean actif = true;
|
||||
|
||||
// Constructeur par défaut
|
||||
public BaseEntity() {
|
||||
this.dateCreation = LocalDateTime.now();
|
||||
this.actif = true;
|
||||
this.version = 0L;
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDateTime getDateCreation() {
|
||||
return dateCreation;
|
||||
}
|
||||
|
||||
public void setDateCreation(LocalDateTime dateCreation) {
|
||||
this.dateCreation = dateCreation;
|
||||
}
|
||||
|
||||
public LocalDateTime getDateModification() {
|
||||
return dateModification;
|
||||
}
|
||||
|
||||
public void setDateModification(LocalDateTime dateModification) {
|
||||
this.dateModification = dateModification;
|
||||
}
|
||||
|
||||
public String getCreePar() {
|
||||
return creePar;
|
||||
}
|
||||
|
||||
public void setCreePar(String creePar) {
|
||||
this.creePar = creePar;
|
||||
}
|
||||
|
||||
public String getModifiePar() {
|
||||
return modifiePar;
|
||||
}
|
||||
|
||||
public void setModifiePar(String modifiePar) {
|
||||
this.modifiePar = modifiePar;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Boolean getActif() {
|
||||
return actif;
|
||||
}
|
||||
|
||||
public void setActif(Boolean actif) {
|
||||
this.actif = actif;
|
||||
}
|
||||
|
||||
// Callbacks JPA
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (this.dateCreation == null) {
|
||||
this.dateCreation = LocalDateTime.now();
|
||||
}
|
||||
if (this.actif == null) {
|
||||
this.actif = true;
|
||||
}
|
||||
if (this.version == null) {
|
||||
this.version = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Méthodes utilitaires Panache-like
|
||||
public void persist() {
|
||||
// Cette méthode sera implémentée par les repositories ou services
|
||||
// Pour l'instant, elle est là pour compatibilité avec le code existant
|
||||
throw new UnsupportedOperationException(
|
||||
"Utilisez le repository approprié pour persister cette entité");
|
||||
}
|
||||
|
||||
public static <T extends BaseEntity> T findById(UUID id) {
|
||||
// Cette méthode sera implémentée par les repositories
|
||||
throw new UnsupportedOperationException(
|
||||
"Utilisez le repository approprié pour rechercher par ID");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité CompteComptable pour le plan comptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "comptes_comptables",
|
||||
indexes = {
|
||||
@Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true),
|
||||
@Index(name = "idx_compte_type", columnList = "type_compte"),
|
||||
@Index(name = "idx_compte_classe", columnList = "classe_comptable")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CompteComptable extends BaseEntity {
|
||||
|
||||
/** Numéro de compte unique (ex: 411000, 512000) */
|
||||
@NotBlank
|
||||
@Column(name = "numero_compte", unique = true, nullable = false, length = 10)
|
||||
private String numeroCompte;
|
||||
|
||||
/** Libellé du compte */
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 200)
|
||||
private String libelle;
|
||||
|
||||
/** Type de compte */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_compte", nullable = false, length = 30)
|
||||
private TypeCompteComptable typeCompte;
|
||||
|
||||
/** Classe comptable (1-7) */
|
||||
@NotNull
|
||||
@Min(value = 1, message = "La classe comptable doit être entre 1 et 7")
|
||||
@Max(value = 7, message = "La classe comptable doit être entre 1 et 7")
|
||||
@Column(name = "classe_comptable", nullable = false)
|
||||
private Integer classeComptable;
|
||||
|
||||
/** Solde initial */
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "solde_initial", precision = 14, scale = 2)
|
||||
private BigDecimal soldeInitial = BigDecimal.ZERO;
|
||||
|
||||
/** Solde actuel (calculé) */
|
||||
@Builder.Default
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "solde_actuel", precision = 14, scale = 2)
|
||||
private BigDecimal soldeActuel = BigDecimal.ZERO;
|
||||
|
||||
/** Compte collectif (regroupe plusieurs sous-comptes) */
|
||||
@Builder.Default
|
||||
@Column(name = "compte_collectif", nullable = false)
|
||||
private Boolean compteCollectif = false;
|
||||
|
||||
/** Compte analytique */
|
||||
@Builder.Default
|
||||
@Column(name = "compte_analytique", nullable = false)
|
||||
private Boolean compteAnalytique = false;
|
||||
|
||||
/** Description du compte */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Lignes d'écriture associées */
|
||||
@OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<LigneEcriture> lignesEcriture = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour obtenir le numéro formaté */
|
||||
public String getNumeroFormate() {
|
||||
return String.format("%-10s", numeroCompte);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si c'est un compte de trésorerie */
|
||||
public boolean isTresorerie() {
|
||||
return TypeCompteComptable.TRESORERIE.equals(typeCompte);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (soldeInitial == null) {
|
||||
soldeInitial = BigDecimal.ZERO;
|
||||
}
|
||||
if (soldeActuel == null) {
|
||||
soldeActuel = soldeInitial;
|
||||
}
|
||||
if (compteCollectif == null) {
|
||||
compteCollectif = false;
|
||||
}
|
||||
if (compteAnalytique == null) {
|
||||
compteAnalytique = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
src/main/java/dev/lions/unionflow/server/entity/CompteWave.java
Normal file
107
src/main/java/dev/lions/unionflow/server/entity/CompteWave.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité CompteWave pour la gestion des comptes Wave Mobile Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "comptes_wave",
|
||||
indexes = {
|
||||
@Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true),
|
||||
@Index(name = "idx_compte_wave_statut", columnList = "statut_compte"),
|
||||
@Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_compte_wave_membre", columnList = "membre_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CompteWave extends BaseEntity {
|
||||
|
||||
/** Numéro de téléphone Wave (format +225XXXXXXXX) */
|
||||
@NotBlank
|
||||
@Pattern(
|
||||
regexp = "^\\+225[0-9]{8}$",
|
||||
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
|
||||
@Column(name = "numero_telephone", unique = true, nullable = false, length = 13)
|
||||
private String numeroTelephone;
|
||||
|
||||
/** Statut du compte */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut_compte", nullable = false, length = 30)
|
||||
private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE;
|
||||
|
||||
/** Identifiant Wave API (encrypté) */
|
||||
@Column(name = "wave_account_id", length = 255)
|
||||
private String waveAccountId;
|
||||
|
||||
/** Clé API Wave (encryptée) */
|
||||
@Column(name = "wave_api_key", length = 500)
|
||||
private String waveApiKey;
|
||||
|
||||
/** Environnement (SANDBOX ou PRODUCTION) */
|
||||
@Column(name = "environnement", length = 20)
|
||||
private String environnement;
|
||||
|
||||
/** Date de dernière vérification */
|
||||
@Column(name = "date_derniere_verification")
|
||||
private java.time.LocalDateTime dateDerniereVerification;
|
||||
|
||||
/** Commentaires */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id")
|
||||
private Membre membre;
|
||||
|
||||
@OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<TransactionWave> transactions = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour vérifier si le compte est vérifié */
|
||||
public boolean isVerifie() {
|
||||
return StatutCompteWave.VERIFIE.equals(statutCompte);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le compte peut être utilisé */
|
||||
public boolean peutEtreUtilise() {
|
||||
return StatutCompteWave.VERIFIE.equals(statutCompte);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (statutCompte == null) {
|
||||
statutCompte = StatutCompteWave.NON_VERIFIE;
|
||||
}
|
||||
if (environnement == null || environnement.isEmpty()) {
|
||||
environnement = "SANDBOX";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité ConfigurationWave pour la configuration de l'intégration Wave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "configurations_wave",
|
||||
indexes = {
|
||||
@Index(name = "idx_config_wave_cle", columnList = "cle", unique = true)
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ConfigurationWave extends BaseEntity {
|
||||
|
||||
/** Clé de configuration */
|
||||
@NotBlank
|
||||
@Column(name = "cle", unique = true, nullable = false, length = 100)
|
||||
private String cle;
|
||||
|
||||
/** Valeur de configuration (peut être encryptée) */
|
||||
@Column(name = "valeur", columnDefinition = "TEXT")
|
||||
private String valeur;
|
||||
|
||||
/** Description de la configuration */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */
|
||||
@Column(name = "type_valeur", length = 20)
|
||||
private String typeValeur;
|
||||
|
||||
/** Environnement (SANDBOX, PRODUCTION, COMMON) */
|
||||
@Column(name = "environnement", length = 20)
|
||||
private String environnement;
|
||||
|
||||
/** Méthode métier pour vérifier si la valeur est encryptée */
|
||||
public boolean isEncryptee() {
|
||||
return "ENCRYPTED".equals(typeValeur);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (typeValeur == null || typeValeur.isEmpty()) {
|
||||
typeValeur = "STRING";
|
||||
}
|
||||
if (environnement == null || environnement.isEmpty()) {
|
||||
environnement = "COMMON";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
184
src/main/java/dev/lions/unionflow/server/entity/Cotisation.java
Normal file
184
src/main/java/dev/lions/unionflow/server/entity/Cotisation.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Cotisation avec UUID Représente une cotisation d'un membre à son organisation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "cotisations",
|
||||
indexes = {
|
||||
@Index(name = "idx_cotisation_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
|
||||
@Index(name = "idx_cotisation_statut", columnList = "statut"),
|
||||
@Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
|
||||
@Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
|
||||
@Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Cotisation extends BaseEntity {
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
|
||||
private String numeroReference;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "type_cotisation", nullable = false, length = 50)
|
||||
private String typeCotisation;
|
||||
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal montantDu;
|
||||
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
|
||||
private BigDecimal montantPaye = BigDecimal.ZERO;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
@Column(name = "code_devise", nullable = false, length = 3)
|
||||
private String codeDevise;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
|
||||
@Column(name = "statut", nullable = false, length = 30)
|
||||
private String statut;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_echeance", nullable = false)
|
||||
private LocalDate dateEcheance;
|
||||
|
||||
@Column(name = "date_paiement")
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
@Size(max = 500)
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "periode", length = 20)
|
||||
private String periode;
|
||||
|
||||
@NotNull
|
||||
@Min(value = 2020, message = "L'année doit être supérieure à 2020")
|
||||
@Max(value = 2100, message = "L'année doit être inférieure à 2100")
|
||||
@Column(name = "annee", nullable = false)
|
||||
private Integer annee;
|
||||
|
||||
@Min(value = 1, message = "Le mois doit être entre 1 et 12")
|
||||
@Max(value = 12, message = "Le mois doit être entre 1 et 12")
|
||||
@Column(name = "mois")
|
||||
private Integer mois;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "observations", length = 1000)
|
||||
private String observations;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "recurrente", nullable = false)
|
||||
private Boolean recurrente = false;
|
||||
|
||||
@Builder.Default
|
||||
@Min(value = 0, message = "Le nombre de rappels doit être positif")
|
||||
@Column(name = "nombre_rappels", nullable = false)
|
||||
private Integer nombreRappels = 0;
|
||||
|
||||
@Column(name = "date_dernier_rappel")
|
||||
private LocalDateTime dateDernierRappel;
|
||||
|
||||
@Column(name = "valide_par_id")
|
||||
private UUID valideParId;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "nom_validateur", length = 100)
|
||||
private String nomValidateur;
|
||||
|
||||
@Column(name = "date_validation")
|
||||
private LocalDateTime dateValidation;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "methode_paiement", length = 50)
|
||||
private String methodePaiement;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "reference_paiement", length = 100)
|
||||
private String referencePaiement;
|
||||
|
||||
/** Méthode métier pour calculer le montant restant à payer */
|
||||
public BigDecimal getMontantRestant() {
|
||||
if (montantDu == null || montantPaye == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return montantDu.subtract(montantPaye);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si la cotisation est entièrement payée */
|
||||
public boolean isEntierementPayee() {
|
||||
return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si la cotisation est en retard */
|
||||
public boolean isEnRetard() {
|
||||
return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
|
||||
}
|
||||
|
||||
/** Méthode métier pour générer un numéro de référence unique */
|
||||
public static String genererNumeroReference() {
|
||||
return "COT-"
|
||||
+ LocalDate.now().getYear()
|
||||
+ "-"
|
||||
+ String.format("%08d", System.currentTimeMillis() % 100000000);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||
if (numeroReference == null || numeroReference.isEmpty()) {
|
||||
numeroReference = genererNumeroReference();
|
||||
}
|
||||
if (codeDevise == null) {
|
||||
codeDevise = "XOF";
|
||||
}
|
||||
if (statut == null) {
|
||||
statut = "EN_ATTENTE";
|
||||
}
|
||||
if (montantPaye == null) {
|
||||
montantPaye = BigDecimal.ZERO;
|
||||
}
|
||||
if (nombreRappels == null) {
|
||||
nombreRappels = 0;
|
||||
}
|
||||
if (recurrente == null) {
|
||||
recurrente = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java
Normal file
130
src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** Entité représentant une demande d'aide dans le système de solidarité */
|
||||
@Entity
|
||||
@Table(name = "demandes_aide")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DemandeAide extends BaseEntity {
|
||||
|
||||
@Column(name = "titre", nullable = false, length = 200)
|
||||
private String titre;
|
||||
|
||||
@Column(name = "description", nullable = false, columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_aide", nullable = false)
|
||||
private TypeAide typeAide;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "statut", nullable = false)
|
||||
private StatutAide statut;
|
||||
|
||||
@Column(name = "montant_demande", precision = 10, scale = 2)
|
||||
private BigDecimal montantDemande;
|
||||
|
||||
@Column(name = "montant_approuve", precision = 10, scale = 2)
|
||||
private BigDecimal montantApprouve;
|
||||
|
||||
@Column(name = "date_demande", nullable = false)
|
||||
private LocalDateTime dateDemande;
|
||||
|
||||
@Column(name = "date_evaluation")
|
||||
private LocalDateTime dateEvaluation;
|
||||
|
||||
@Column(name = "date_versement")
|
||||
private LocalDateTime dateVersement;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "demandeur_id", nullable = false)
|
||||
private Membre demandeur;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "evaluateur_id")
|
||||
private Membre evaluateur;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id", nullable = false)
|
||||
private Organisation organisation;
|
||||
|
||||
@Column(name = "justification", columnDefinition = "TEXT")
|
||||
private String justification;
|
||||
|
||||
@Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
|
||||
private String commentaireEvaluation;
|
||||
|
||||
@Column(name = "urgence", nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean urgence = false;
|
||||
|
||||
@Column(name = "documents_fournis")
|
||||
private String documentsFournis;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||
if (dateDemande == null) {
|
||||
dateDemande = LocalDateTime.now();
|
||||
}
|
||||
if (statut == null) {
|
||||
statut = StatutAide.EN_ATTENTE;
|
||||
}
|
||||
if (urgence == null) {
|
||||
urgence = false;
|
||||
}
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
// Méthode appelée avant mise à jour
|
||||
}
|
||||
|
||||
/** Vérifie si la demande est en attente */
|
||||
public boolean isEnAttente() {
|
||||
return StatutAide.EN_ATTENTE.equals(statut);
|
||||
}
|
||||
|
||||
/** Vérifie si la demande est approuvée */
|
||||
public boolean isApprouvee() {
|
||||
return StatutAide.APPROUVEE.equals(statut);
|
||||
}
|
||||
|
||||
/** Vérifie si la demande est rejetée */
|
||||
public boolean isRejetee() {
|
||||
return StatutAide.REJETEE.equals(statut);
|
||||
}
|
||||
|
||||
/** Vérifie si la demande est urgente */
|
||||
public boolean isUrgente() {
|
||||
return Boolean.TRUE.equals(urgence);
|
||||
}
|
||||
|
||||
/** Calcule le pourcentage d'approbation par rapport au montant demandé */
|
||||
public BigDecimal getPourcentageApprobation() {
|
||||
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
if (montantApprouve == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return montantApprouve
|
||||
.divide(montantDemande, 4, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
}
|
||||
128
src/main/java/dev/lions/unionflow/server/entity/Document.java
Normal file
128
src/main/java/dev/lions/unionflow/server/entity/Document.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.document.TypeDocument;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Document pour la gestion documentaire sécurisée
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "documents",
|
||||
indexes = {
|
||||
@Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"),
|
||||
@Index(name = "idx_document_type", columnList = "type_document"),
|
||||
@Index(name = "idx_document_hash_md5", columnList = "hash_md5"),
|
||||
@Index(name = "idx_document_hash_sha256", columnList = "hash_sha256")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Document extends BaseEntity {
|
||||
|
||||
/** Nom du fichier original */
|
||||
@NotBlank
|
||||
@Column(name = "nom_fichier", nullable = false, length = 255)
|
||||
private String nomFichier;
|
||||
|
||||
/** Nom original du fichier (tel que téléchargé) */
|
||||
@Column(name = "nom_original", length = 255)
|
||||
private String nomOriginal;
|
||||
|
||||
/** Chemin de stockage */
|
||||
@NotBlank
|
||||
@Column(name = "chemin_stockage", nullable = false, length = 1000)
|
||||
private String cheminStockage;
|
||||
|
||||
/** Type MIME */
|
||||
@Column(name = "type_mime", length = 100)
|
||||
private String typeMime;
|
||||
|
||||
/** Taille du fichier en octets */
|
||||
@NotNull
|
||||
@Min(value = 0, message = "La taille doit être positive")
|
||||
@Column(name = "taille_octets", nullable = false)
|
||||
private Long tailleOctets;
|
||||
|
||||
/** Type de document */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_document", length = 50)
|
||||
private TypeDocument typeDocument;
|
||||
|
||||
/** Hash MD5 pour vérification d'intégrité */
|
||||
@Column(name = "hash_md5", length = 32)
|
||||
private String hashMd5;
|
||||
|
||||
/** Hash SHA256 pour vérification d'intégrité */
|
||||
@Column(name = "hash_sha256", length = 64)
|
||||
private String hashSha256;
|
||||
|
||||
/** Description du document */
|
||||
@Column(name = "description", length = 1000)
|
||||
private String description;
|
||||
|
||||
/** Nombre de téléchargements */
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_telechargements", nullable = false)
|
||||
private Integer nombreTelechargements = 0;
|
||||
|
||||
/** Date de dernier téléchargement */
|
||||
@Column(name = "date_dernier_telechargement")
|
||||
private java.time.LocalDateTime dateDernierTelechargement;
|
||||
|
||||
/** Pièces jointes associées */
|
||||
@OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<PieceJointe> piecesJointes = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour vérifier l'intégrité avec MD5 */
|
||||
public boolean verifierIntegriteMd5(String hashAttendu) {
|
||||
return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier l'intégrité avec SHA256 */
|
||||
public boolean verifierIntegriteSha256(String hashAttendu) {
|
||||
return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu);
|
||||
}
|
||||
|
||||
/** Méthode métier pour obtenir la taille formatée */
|
||||
public String getTailleFormatee() {
|
||||
if (tailleOctets == null) {
|
||||
return "0 B";
|
||||
}
|
||||
if (tailleOctets < 1024) {
|
||||
return tailleOctets + " B";
|
||||
} else if (tailleOctets < 1024 * 1024) {
|
||||
return String.format("%.2f KB", tailleOctets / 1024.0);
|
||||
} else {
|
||||
return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (nombreTelechargements == null) {
|
||||
nombreTelechargements = 0;
|
||||
}
|
||||
if (typeDocument == null) {
|
||||
typeDocument = TypeDocument.AUTRE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité EcritureComptable pour les écritures comptables
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "ecritures_comptables",
|
||||
indexes = {
|
||||
@Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
|
||||
@Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
|
||||
@Index(name = "idx_ecriture_journal", columnList = "journal_id"),
|
||||
@Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EcritureComptable extends BaseEntity {
|
||||
|
||||
/** Numéro de pièce unique */
|
||||
@NotBlank
|
||||
@Column(name = "numero_piece", unique = true, nullable = false, length = 50)
|
||||
private String numeroPiece;
|
||||
|
||||
/** Date de l'écriture */
|
||||
@NotNull
|
||||
@Column(name = "date_ecriture", nullable = false)
|
||||
private LocalDate dateEcriture;
|
||||
|
||||
/** Libellé de l'écriture */
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 500)
|
||||
private String libelle;
|
||||
|
||||
/** Référence externe */
|
||||
@Column(name = "reference", length = 100)
|
||||
private String reference;
|
||||
|
||||
/** Lettrage (pour rapprochement) */
|
||||
@Column(name = "lettrage", length = 20)
|
||||
private String lettrage;
|
||||
|
||||
/** Pointage (pour rapprochement bancaire) */
|
||||
@Builder.Default
|
||||
@Column(name = "pointe", nullable = false)
|
||||
private Boolean pointe = false;
|
||||
|
||||
/** Montant total débit (somme des lignes) */
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_debit", precision = 14, scale = 2)
|
||||
private BigDecimal montantDebit = BigDecimal.ZERO;
|
||||
|
||||
/** Montant total crédit (somme des lignes) */
|
||||
@Builder.Default
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_credit", precision = 14, scale = 2)
|
||||
private BigDecimal montantCredit = BigDecimal.ZERO;
|
||||
|
||||
/** Commentaires */
|
||||
@Column(name = "commentaire", length = 1000)
|
||||
private String commentaire;
|
||||
|
||||
// Relations
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "journal_id", nullable = false)
|
||||
private JournalComptable journal;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id")
|
||||
private Paiement paiement;
|
||||
|
||||
/** Lignes d'écriture */
|
||||
@OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<LigneEcriture> lignes = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
|
||||
public boolean isEquilibree() {
|
||||
if (montantDebit == null || montantCredit == null) {
|
||||
return false;
|
||||
}
|
||||
return montantDebit.compareTo(montantCredit) == 0;
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer les totaux à partir des lignes */
|
||||
public void calculerTotaux() {
|
||||
if (lignes == null || lignes.isEmpty()) {
|
||||
montantDebit = BigDecimal.ZERO;
|
||||
montantCredit = BigDecimal.ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
montantDebit =
|
||||
lignes.stream()
|
||||
.map(LigneEcriture::getMontantDebit)
|
||||
.filter(amount -> amount != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
montantCredit =
|
||||
lignes.stream()
|
||||
.map(LigneEcriture::getMontantCredit)
|
||||
.filter(amount -> amount != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
/** Méthode métier pour générer un numéro de pièce unique */
|
||||
public static String genererNumeroPiece(String prefixe, LocalDate date) {
|
||||
return String.format(
|
||||
"%s-%04d%02d%02d-%012d",
|
||||
prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
|
||||
System.currentTimeMillis() % 1000000000000L);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (numeroPiece == null || numeroPiece.isEmpty()) {
|
||||
numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
|
||||
}
|
||||
if (dateEcriture == null) {
|
||||
dateEcriture = LocalDate.now();
|
||||
}
|
||||
if (montantDebit == null) {
|
||||
montantDebit = BigDecimal.ZERO;
|
||||
}
|
||||
if (montantCredit == null) {
|
||||
montantCredit = BigDecimal.ZERO;
|
||||
}
|
||||
if (pointe == null) {
|
||||
pointe = false;
|
||||
}
|
||||
// Calculer les totaux si les lignes sont déjà présentes
|
||||
if (lignes != null && !lignes.isEmpty()) {
|
||||
calculerTotaux();
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback JPA avant la mise à jour */
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
calculerTotaux();
|
||||
}
|
||||
}
|
||||
|
||||
267
src/main/java/dev/lions/unionflow/server/entity/Evenement.java
Normal file
267
src/main/java/dev/lions/unionflow/server/entity/Evenement.java
Normal file
@@ -0,0 +1,267 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* Entité Événement pour la gestion des événements de l'union
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "evenements",
|
||||
indexes = {
|
||||
@Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
|
||||
@Index(name = "idx_evenement_statut", columnList = "statut"),
|
||||
@Index(name = "idx_evenement_type", columnList = "type_evenement"),
|
||||
@Index(name = "idx_evenement_organisation", columnList = "organisation_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Evenement extends BaseEntity {
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 3, max = 200)
|
||||
@Column(name = "titre", nullable = false, length = 200)
|
||||
private String titre;
|
||||
|
||||
@Size(max = 2000)
|
||||
@Column(name = "description", length = 2000)
|
||||
private String description;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_debut", nullable = false)
|
||||
private LocalDateTime dateDebut;
|
||||
|
||||
@Column(name = "date_fin")
|
||||
private LocalDateTime dateFin;
|
||||
|
||||
@Size(max = 500)
|
||||
@Column(name = "lieu", length = 500)
|
||||
private String lieu;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "adresse", length = 1000)
|
||||
private String adresse;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_evenement", length = 50)
|
||||
private TypeEvenement typeEvenement;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut", nullable = false, length = 30)
|
||||
private StatutEvenement statut = StatutEvenement.PLANIFIE;
|
||||
|
||||
@Min(0)
|
||||
@Column(name = "capacite_max")
|
||||
private Integer capaciteMax;
|
||||
|
||||
@DecimalMin("0.00")
|
||||
@Digits(integer = 8, fraction = 2)
|
||||
@Column(name = "prix", precision = 10, scale = 2)
|
||||
private BigDecimal prix;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "inscription_requise", nullable = false)
|
||||
private Boolean inscriptionRequise = false;
|
||||
|
||||
@Column(name = "date_limite_inscription")
|
||||
private LocalDateTime dateLimiteInscription;
|
||||
|
||||
@Size(max = 1000)
|
||||
@Column(name = "instructions_particulieres", length = 1000)
|
||||
private String instructionsParticulieres;
|
||||
|
||||
@Size(max = 500)
|
||||
@Column(name = "contact_organisateur", length = 500)
|
||||
private String contactOrganisateur;
|
||||
|
||||
@Size(max = 2000)
|
||||
@Column(name = "materiel_requis", length = 2000)
|
||||
private String materielRequis;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "visible_public", nullable = false)
|
||||
private Boolean visiblePublic = true;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "actif", nullable = false)
|
||||
private Boolean actif = true;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisateur_id")
|
||||
private Membre organisateur;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "evenement",
|
||||
cascade = CascadeType.ALL,
|
||||
orphanRemoval = true,
|
||||
fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<InscriptionEvenement> inscriptions = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
/** Types d'événements */
|
||||
public enum TypeEvenement {
|
||||
ASSEMBLEE_GENERALE("Assemblée Générale"),
|
||||
REUNION("Réunion"),
|
||||
FORMATION("Formation"),
|
||||
CONFERENCE("Conférence"),
|
||||
ATELIER("Atelier"),
|
||||
SEMINAIRE("Séminaire"),
|
||||
EVENEMENT_SOCIAL("Événement Social"),
|
||||
MANIFESTATION("Manifestation"),
|
||||
CELEBRATION("Célébration"),
|
||||
AUTRE("Autre");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
TypeEvenement(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
|
||||
/** Statuts d'événement */
|
||||
public enum StatutEvenement {
|
||||
PLANIFIE("Planifié"),
|
||||
CONFIRME("Confirmé"),
|
||||
EN_COURS("En cours"),
|
||||
TERMINE("Terminé"),
|
||||
ANNULE("Annulé"),
|
||||
REPORTE("Reporté");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
StatutEvenement(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes métier
|
||||
|
||||
/** Vérifie si l'événement est ouvert aux inscriptions */
|
||||
public boolean isOuvertAuxInscriptions() {
|
||||
if (!inscriptionRequise || !actif) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
|
||||
// Vérifier si la date limite d'inscription n'est pas dépassée
|
||||
if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier si l'événement n'a pas déjà commencé
|
||||
if (maintenant.isAfter(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier la capacité
|
||||
if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return statut == StatutEvenement.PLANIFIE || statut == StatutEvenement.CONFIRME;
|
||||
}
|
||||
|
||||
/** Obtient le nombre d'inscrits à l'événement */
|
||||
public int getNombreInscrits() {
|
||||
return inscriptions != null
|
||||
? (int)
|
||||
inscriptions.stream()
|
||||
.filter(
|
||||
inscription ->
|
||||
inscription.getStatut() == InscriptionEvenement.StatutInscription.CONFIRMEE)
|
||||
.count()
|
||||
: 0;
|
||||
}
|
||||
|
||||
/** Vérifie si l'événement est complet */
|
||||
public boolean isComplet() {
|
||||
return capaciteMax != null && getNombreInscrits() >= capaciteMax;
|
||||
}
|
||||
|
||||
/** Vérifie si l'événement est en cours */
|
||||
public boolean isEnCours() {
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
|
||||
}
|
||||
|
||||
/** Vérifie si l'événement est terminé */
|
||||
public boolean isTermine() {
|
||||
if (statut == StatutEvenement.TERMINE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
return dateFin != null && maintenant.isAfter(dateFin);
|
||||
}
|
||||
|
||||
/** Calcule la durée de l'événement en heures */
|
||||
public Long getDureeEnHeures() {
|
||||
if (dateFin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return java.time.Duration.between(dateDebut, dateFin).toHours();
|
||||
}
|
||||
|
||||
/** Obtient le nombre de places restantes */
|
||||
public Integer getPlacesRestantes() {
|
||||
if (capaciteMax == null) {
|
||||
return null; // Capacité illimitée
|
||||
}
|
||||
|
||||
return Math.max(0, capaciteMax - getNombreInscrits());
|
||||
}
|
||||
|
||||
/** Vérifie si un membre est inscrit à l'événement */
|
||||
public boolean isMemberInscrit(UUID membreId) {
|
||||
return inscriptions != null
|
||||
&& inscriptions.stream()
|
||||
.anyMatch(
|
||||
inscription ->
|
||||
inscription.getMembre().getId().equals(membreId)
|
||||
&& inscription.getStatut()
|
||||
== InscriptionEvenement.StatutInscription.CONFIRMEE);
|
||||
}
|
||||
|
||||
/** Obtient le taux de remplissage en pourcentage */
|
||||
public Double getTauxRemplissage() {
|
||||
if (capaciteMax == null || capaciteMax == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (double) getNombreInscrits() / capaciteMax * 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* Entité InscriptionEvenement représentant l'inscription d'un membre à un événement
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "inscriptions_evenement",
|
||||
indexes = {
|
||||
@Index(name = "idx_inscription_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_inscription_evenement", columnList = "evenement_id"),
|
||||
@Index(name = "idx_inscription_date", columnList = "date_inscription")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class InscriptionEvenement extends BaseEntity {
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "evenement_id", nullable = false)
|
||||
private Evenement evenement;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "date_inscription", nullable = false)
|
||||
private LocalDateTime dateInscription = LocalDateTime.now();
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "statut", length = 20)
|
||||
@Builder.Default
|
||||
private StatutInscription statut = StatutInscription.CONFIRMEE;
|
||||
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Énumération des statuts d'inscription */
|
||||
public enum StatutInscription {
|
||||
CONFIRMEE("Confirmée"),
|
||||
EN_ATTENTE("En attente"),
|
||||
ANNULEE("Annulée"),
|
||||
REFUSEE("Refusée");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
StatutInscription(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
|
||||
/**
|
||||
* Vérifie si l'inscription est confirmée
|
||||
*
|
||||
* @return true si l'inscription est confirmée
|
||||
*/
|
||||
public boolean isConfirmee() {
|
||||
return StatutInscription.CONFIRMEE.equals(this.statut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'inscription est en attente
|
||||
*
|
||||
* @return true si l'inscription est en attente
|
||||
*/
|
||||
public boolean isEnAttente() {
|
||||
return StatutInscription.EN_ATTENTE.equals(this.statut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'inscription est annulée
|
||||
*
|
||||
* @return true si l'inscription est annulée
|
||||
*/
|
||||
public boolean isAnnulee() {
|
||||
return StatutInscription.ANNULEE.equals(this.statut);
|
||||
}
|
||||
|
||||
/** Confirme l'inscription */
|
||||
public void confirmer() {
|
||||
this.statut = StatutInscription.CONFIRMEE;
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Annule l'inscription
|
||||
*
|
||||
* @param commentaire le commentaire d'annulation
|
||||
*/
|
||||
public void annuler(String commentaire) {
|
||||
this.statut = StatutInscription.ANNULEE;
|
||||
this.commentaire = commentaire;
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met l'inscription en attente
|
||||
*
|
||||
* @param commentaire le commentaire de mise en attente
|
||||
*/
|
||||
public void mettreEnAttente(String commentaire) {
|
||||
this.statut = StatutInscription.EN_ATTENTE;
|
||||
this.commentaire = commentaire;
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse l'inscription
|
||||
*
|
||||
* @param commentaire le commentaire de refus
|
||||
*/
|
||||
public void refuser(String commentaire) {
|
||||
this.statut = StatutInscription.REFUSEE;
|
||||
this.commentaire = commentaire;
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Callbacks JPA
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
super.onUpdate(); // Appelle le onUpdate de BaseEntity
|
||||
this.dateModification = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}",
|
||||
getId(),
|
||||
membre != null ? membre.getEmail() : "null",
|
||||
evenement != null ? evenement.getTitre() : "null",
|
||||
statut,
|
||||
dateInscription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité JournalComptable pour la gestion des journaux
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "journaux_comptables",
|
||||
indexes = {
|
||||
@Index(name = "idx_journal_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_journal_type", columnList = "type_journal"),
|
||||
@Index(name = "idx_journal_periode", columnList = "date_debut, date_fin")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class JournalComptable extends BaseEntity {
|
||||
|
||||
/** Code unique du journal */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 10)
|
||||
private String code;
|
||||
|
||||
/** Libellé du journal */
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 100)
|
||||
private String libelle;
|
||||
|
||||
/** Type de journal */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_journal", nullable = false, length = 30)
|
||||
private TypeJournalComptable typeJournal;
|
||||
|
||||
/** Date de début de la période */
|
||||
@Column(name = "date_debut")
|
||||
private LocalDate dateDebut;
|
||||
|
||||
/** Date de fin de la période */
|
||||
@Column(name = "date_fin")
|
||||
private LocalDate dateFin;
|
||||
|
||||
/** Statut du journal (OUVERT, FERME, ARCHIVE) */
|
||||
@Builder.Default
|
||||
@Column(name = "statut", length = 20)
|
||||
private String statut = "OUVERT";
|
||||
|
||||
/** Description */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Écritures comptables associées */
|
||||
@OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<EcritureComptable> ecritures = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour vérifier si le journal est ouvert */
|
||||
public boolean isOuvert() {
|
||||
return "OUVERT".equals(statut);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si une date est dans la période */
|
||||
public boolean estDansPeriode(LocalDate date) {
|
||||
if (dateDebut == null || dateFin == null) {
|
||||
return true; // Période illimitée
|
||||
}
|
||||
return !date.isBefore(dateDebut) && !date.isAfter(dateFin);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (statut == null || statut.isEmpty()) {
|
||||
statut = "OUVERT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité LigneEcriture pour les lignes d'une écriture comptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "lignes_ecriture",
|
||||
indexes = {
|
||||
@Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"),
|
||||
@Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LigneEcriture extends BaseEntity {
|
||||
|
||||
/** Numéro de ligne */
|
||||
@NotNull
|
||||
@Min(value = 1, message = "Le numéro de ligne doit être positif")
|
||||
@Column(name = "numero_ligne", nullable = false)
|
||||
private Integer numeroLigne;
|
||||
|
||||
/** Montant débit */
|
||||
@DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_debit", precision = 14, scale = 2)
|
||||
private BigDecimal montantDebit;
|
||||
|
||||
/** Montant crédit */
|
||||
@DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_credit", precision = 14, scale = 2)
|
||||
private BigDecimal montantCredit;
|
||||
|
||||
/** Libellé de la ligne */
|
||||
@Column(name = "libelle", length = 500)
|
||||
private String libelle;
|
||||
|
||||
/** Référence */
|
||||
@Column(name = "reference", length = 100)
|
||||
private String reference;
|
||||
|
||||
// Relations
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "ecriture_id", nullable = false)
|
||||
private EcritureComptable ecriture;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "compte_comptable_id", nullable = false)
|
||||
private CompteComptable compteComptable;
|
||||
|
||||
/** Méthode métier pour vérifier que la ligne a soit un débit soit un crédit (pas les deux) */
|
||||
public boolean isValide() {
|
||||
boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0;
|
||||
boolean aCredit = montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0;
|
||||
return aDebit != aCredit; // XOR : soit débit, soit crédit, pas les deux
|
||||
}
|
||||
|
||||
/** Méthode métier pour obtenir le montant (débit ou crédit) */
|
||||
public BigDecimal getMontant() {
|
||||
if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return montantDebit;
|
||||
}
|
||||
if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return montantCredit;
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (montantDebit == null) {
|
||||
montantDebit = BigDecimal.ZERO;
|
||||
}
|
||||
if (montantCredit == null) {
|
||||
montantCredit = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
106
src/main/java/dev/lions/unionflow/server/entity/Membre.java
Normal file
106
src/main/java/dev/lions/unionflow/server/entity/Membre.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** Entité Membre avec UUID */
|
||||
@Entity
|
||||
@Table(
|
||||
name = "membres",
|
||||
indexes = {
|
||||
@Index(name = "idx_membre_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_membre_numero", columnList = "numero_membre", unique = true),
|
||||
@Index(name = "idx_membre_actif", columnList = "actif")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Membre extends BaseEntity {
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "numero_membre", unique = true, nullable = false, length = 20)
|
||||
private String numeroMembre;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "prenom", nullable = false, length = 100)
|
||||
private String prenom;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "nom", nullable = false, length = 100)
|
||||
private String nom;
|
||||
|
||||
@Email
|
||||
@NotBlank
|
||||
@Column(name = "email", unique = true, nullable = false, length = 255)
|
||||
private String email;
|
||||
|
||||
@Column(name = "mot_de_passe", length = 255)
|
||||
private String motDePasse;
|
||||
|
||||
@Column(name = "telephone", length = 20)
|
||||
private String telephone;
|
||||
|
||||
@Pattern(
|
||||
regexp = "^\\+225[0-9]{8}$",
|
||||
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
|
||||
@Column(name = "telephone_wave", length = 13)
|
||||
private String telephoneWave;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_naissance", nullable = false)
|
||||
private LocalDate dateNaissance;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "date_adhesion", nullable = false)
|
||||
private LocalDate dateAdhesion;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<MembreRole> roles = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<CompteWave> comptesWave = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Paiement> paiements = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour obtenir le nom complet */
|
||||
public String getNomComplet() {
|
||||
return prenom + " " + nom;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le membre est majeur */
|
||||
public boolean isMajeur() {
|
||||
return dateNaissance.isBefore(LocalDate.now().minusYears(18));
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer l'âge */
|
||||
public int getAge() {
|
||||
return LocalDate.now().getYear() - dateNaissance.getYear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Membre et Role
|
||||
* Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "membres_roles",
|
||||
indexes = {
|
||||
@Index(name = "idx_membre_role_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_membre_role_role", columnList = "role_id"),
|
||||
@Index(name = "idx_membre_role_actif", columnList = "actif")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_membre_role",
|
||||
columnNames = {"membre_id", "role_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MembreRole extends BaseEntity {
|
||||
|
||||
/** Membre */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
/** Rôle */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "role_id", nullable = false)
|
||||
private Role role;
|
||||
|
||||
/** Date de début d'attribution */
|
||||
@Column(name = "date_debut")
|
||||
private LocalDate dateDebut;
|
||||
|
||||
/** Date de fin d'attribution (null = permanent) */
|
||||
@Column(name = "date_fin")
|
||||
private LocalDate dateFin;
|
||||
|
||||
/** Commentaire sur l'attribution */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Méthode métier pour vérifier si l'attribution est active */
|
||||
public boolean isActif() {
|
||||
if (!Boolean.TRUE.equals(getActif())) {
|
||||
return false;
|
||||
}
|
||||
LocalDate aujourdhui = LocalDate.now();
|
||||
if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && aujourdhui.isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateDebut == null) {
|
||||
dateDebut = LocalDate.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification;
|
||||
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Notification pour la gestion des notifications
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "notifications",
|
||||
indexes = {
|
||||
@Index(name = "idx_notification_type", columnList = "type_notification"),
|
||||
@Index(name = "idx_notification_statut", columnList = "statut"),
|
||||
@Index(name = "idx_notification_priorite", columnList = "priorite"),
|
||||
@Index(name = "idx_notification_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_notification_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_notification_date_envoi", columnList = "date_envoi")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Notification extends BaseEntity {
|
||||
|
||||
/** Type de notification */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_notification", nullable = false, length = 30)
|
||||
private TypeNotification typeNotification;
|
||||
|
||||
/** Priorité */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "priorite", length = 20)
|
||||
private PrioriteNotification priorite = PrioriteNotification.NORMALE;
|
||||
|
||||
/** Statut */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut", length = 30)
|
||||
private dev.lions.unionflow.server.api.enums.notification.StatutNotification statut =
|
||||
dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE;
|
||||
|
||||
/** Sujet */
|
||||
@Column(name = "sujet", length = 500)
|
||||
private String sujet;
|
||||
|
||||
/** Corps du message */
|
||||
@Column(name = "corps", columnDefinition = "TEXT")
|
||||
private String corps;
|
||||
|
||||
/** Date d'envoi prévue */
|
||||
@Column(name = "date_envoi_prevue")
|
||||
private LocalDateTime dateEnvoiPrevue;
|
||||
|
||||
/** Date d'envoi réelle */
|
||||
@Column(name = "date_envoi")
|
||||
private LocalDateTime dateEnvoi;
|
||||
|
||||
/** Date de lecture */
|
||||
@Column(name = "date_lecture")
|
||||
private LocalDateTime dateLecture;
|
||||
|
||||
/** Nombre de tentatives d'envoi */
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_tentatives", nullable = false)
|
||||
private Integer nombreTentatives = 0;
|
||||
|
||||
/** Message d'erreur (si échec) */
|
||||
@Column(name = "message_erreur", length = 1000)
|
||||
private String messageErreur;
|
||||
|
||||
/** Données additionnelles (JSON) */
|
||||
@Column(name = "donnees_additionnelles", columnDefinition = "TEXT")
|
||||
private String donneesAdditionnelles;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id")
|
||||
private Membre membre;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "template_id")
|
||||
private TemplateNotification template;
|
||||
|
||||
/** Méthode métier pour vérifier si la notification est envoyée */
|
||||
public boolean isEnvoyee() {
|
||||
return dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.equals(statut);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si la notification est lue */
|
||||
public boolean isLue() {
|
||||
return dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.equals(statut);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (priorite == null) {
|
||||
priorite = PrioriteNotification.NORMALE;
|
||||
}
|
||||
if (statut == null) {
|
||||
statut = dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE;
|
||||
}
|
||||
if (nombreTentatives == null) {
|
||||
nombreTentatives = 0;
|
||||
}
|
||||
if (dateEnvoiPrevue == null) {
|
||||
dateEnvoiPrevue = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Organisation avec UUID Représente une organisation (Lions Club, Association,
|
||||
* Coopérative, etc.)
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "organisations",
|
||||
indexes = {
|
||||
@Index(name = "idx_organisation_nom", columnList = "nom"),
|
||||
@Index(name = "idx_organisation_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_organisation_statut", columnList = "statut"),
|
||||
@Index(name = "idx_organisation_type", columnList = "type_organisation"),
|
||||
@Index(name = "idx_organisation_ville", columnList = "ville"),
|
||||
@Index(name = "idx_organisation_pays", columnList = "pays"),
|
||||
@Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"),
|
||||
@Index(
|
||||
name = "idx_organisation_numero_enregistrement",
|
||||
columnList = "numero_enregistrement",
|
||||
unique = true)
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Organisation extends BaseEntity {
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "nom", nullable = false, length = 200)
|
||||
private String nom;
|
||||
|
||||
@Column(name = "nom_court", length = 50)
|
||||
private String nomCourt;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "type_organisation", nullable = false, length = 50)
|
||||
private String typeOrganisation;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "statut", nullable = false, length = 50)
|
||||
private String statut;
|
||||
|
||||
@Column(name = "description", length = 2000)
|
||||
private String description;
|
||||
|
||||
@Column(name = "date_fondation")
|
||||
private LocalDate dateFondation;
|
||||
|
||||
@Column(name = "numero_enregistrement", unique = true, length = 100)
|
||||
private String numeroEnregistrement;
|
||||
|
||||
// Informations de contact
|
||||
@Email
|
||||
@NotBlank
|
||||
@Column(name = "email", unique = true, nullable = false, length = 255)
|
||||
private String email;
|
||||
|
||||
@Column(name = "telephone", length = 20)
|
||||
private String telephone;
|
||||
|
||||
@Column(name = "telephone_secondaire", length = 20)
|
||||
private String telephoneSecondaire;
|
||||
|
||||
@Email
|
||||
@Column(name = "email_secondaire", length = 255)
|
||||
private String emailSecondaire;
|
||||
|
||||
// Adresse
|
||||
@Column(name = "adresse", length = 500)
|
||||
private String adresse;
|
||||
|
||||
@Column(name = "ville", length = 100)
|
||||
private String ville;
|
||||
|
||||
@Column(name = "code_postal", length = 20)
|
||||
private String codePostal;
|
||||
|
||||
@Column(name = "region", length = 100)
|
||||
private String region;
|
||||
|
||||
@Column(name = "pays", length = 100)
|
||||
private String pays;
|
||||
|
||||
// Coordonnées géographiques
|
||||
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
|
||||
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
|
||||
@Digits(integer = 3, fraction = 6)
|
||||
@Column(name = "latitude", precision = 9, scale = 6)
|
||||
private BigDecimal latitude;
|
||||
|
||||
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
|
||||
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
|
||||
@Digits(integer = 3, fraction = 6)
|
||||
@Column(name = "longitude", precision = 9, scale = 6)
|
||||
private BigDecimal longitude;
|
||||
|
||||
// Web et réseaux sociaux
|
||||
@Column(name = "site_web", length = 500)
|
||||
private String siteWeb;
|
||||
|
||||
@Column(name = "logo", length = 500)
|
||||
private String logo;
|
||||
|
||||
@Column(name = "reseaux_sociaux", length = 1000)
|
||||
private String reseauxSociaux;
|
||||
|
||||
// Hiérarchie
|
||||
@Column(name = "organisation_parente_id")
|
||||
private UUID organisationParenteId;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "niveau_hierarchique", nullable = false)
|
||||
private Integer niveauHierarchique = 0;
|
||||
|
||||
// Statistiques
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_membres", nullable = false)
|
||||
private Integer nombreMembres = 0;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_administrateurs", nullable = false)
|
||||
private Integer nombreAdministrateurs = 0;
|
||||
|
||||
// Finances
|
||||
@DecimalMin(value = "0.0", message = "Le budget annuel doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "budget_annuel", precision = 14, scale = 2)
|
||||
private BigDecimal budgetAnnuel;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "devise", length = 3)
|
||||
private String devise = "XOF";
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "cotisation_obligatoire", nullable = false)
|
||||
private Boolean cotisationObligatoire = false;
|
||||
|
||||
@DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
|
||||
private BigDecimal montantCotisationAnnuelle;
|
||||
|
||||
// Informations complémentaires
|
||||
@Column(name = "objectifs", length = 2000)
|
||||
private String objectifs;
|
||||
|
||||
@Column(name = "activites_principales", length = 2000)
|
||||
private String activitesPrincipales;
|
||||
|
||||
@Column(name = "certifications", length = 500)
|
||||
private String certifications;
|
||||
|
||||
@Column(name = "partenaires", length = 1000)
|
||||
private String partenaires;
|
||||
|
||||
@Column(name = "notes", length = 1000)
|
||||
private String notes;
|
||||
|
||||
// Paramètres
|
||||
@Builder.Default
|
||||
@Column(name = "organisation_publique", nullable = false)
|
||||
private Boolean organisationPublique = true;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "accepte_nouveaux_membres", nullable = false)
|
||||
private Boolean accepteNouveauxMembres = true;
|
||||
|
||||
// Relations
|
||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Membre> membres = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<CompteWave> comptesWave = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour obtenir le nom complet avec sigle */
|
||||
public String getNomComplet() {
|
||||
if (nomCourt != null && !nomCourt.isEmpty()) {
|
||||
return nom + " (" + nomCourt + ")";
|
||||
}
|
||||
return nom;
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer l'ancienneté en années */
|
||||
public int getAncienneteAnnees() {
|
||||
if (dateFondation == null) {
|
||||
return 0;
|
||||
}
|
||||
return Period.between(dateFondation, LocalDate.now()).getYears();
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans) */
|
||||
public boolean isRecente() {
|
||||
return getAncienneteAnnees() < 2;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'organisation est active */
|
||||
public boolean isActive() {
|
||||
return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
|
||||
}
|
||||
|
||||
/** Méthode métier pour ajouter un membre */
|
||||
public void ajouterMembre() {
|
||||
if (nombreMembres == null) {
|
||||
nombreMembres = 0;
|
||||
}
|
||||
nombreMembres++;
|
||||
}
|
||||
|
||||
/** Méthode métier pour retirer un membre */
|
||||
public void retirerMembre() {
|
||||
if (nombreMembres != null && nombreMembres > 0) {
|
||||
nombreMembres--;
|
||||
}
|
||||
}
|
||||
|
||||
/** Méthode métier pour activer l'organisation */
|
||||
public void activer(String utilisateur) {
|
||||
this.statut = "ACTIVE";
|
||||
this.setActif(true);
|
||||
marquerCommeModifie(utilisateur);
|
||||
}
|
||||
|
||||
/** Méthode métier pour suspendre l'organisation */
|
||||
public void suspendre(String utilisateur) {
|
||||
this.statut = "SUSPENDUE";
|
||||
this.accepteNouveauxMembres = false;
|
||||
marquerCommeModifie(utilisateur);
|
||||
}
|
||||
|
||||
/** Méthode métier pour dissoudre l'organisation */
|
||||
public void dissoudre(String utilisateur) {
|
||||
this.statut = "DISSOUTE";
|
||||
this.setActif(false);
|
||||
this.accepteNouveauxMembres = false;
|
||||
marquerCommeModifie(utilisateur);
|
||||
}
|
||||
|
||||
/** Marque l'entité comme modifiée */
|
||||
public void marquerCommeModifie(String utilisateur) {
|
||||
this.setDateModification(LocalDateTime.now());
|
||||
this.setModifiePar(utilisateur);
|
||||
if (this.getVersion() != null) {
|
||||
this.setVersion(this.getVersion() + 1);
|
||||
} else {
|
||||
this.setVersion(1L);
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate(); // Appelle le onCreate de BaseEntity
|
||||
if (statut == null) {
|
||||
statut = "ACTIVE";
|
||||
}
|
||||
if (typeOrganisation == null) {
|
||||
typeOrganisation = "ASSOCIATION";
|
||||
}
|
||||
if (devise == null) {
|
||||
devise = "XOF";
|
||||
}
|
||||
if (niveauHierarchique == null) {
|
||||
niveauHierarchique = 0;
|
||||
}
|
||||
if (nombreMembres == null) {
|
||||
nombreMembres = 0;
|
||||
}
|
||||
if (nombreAdministrateurs == null) {
|
||||
nombreAdministrateurs = 0;
|
||||
}
|
||||
if (organisationPublique == null) {
|
||||
organisationPublique = true;
|
||||
}
|
||||
if (accepteNouveauxMembres == null) {
|
||||
accepteNouveauxMembres = true;
|
||||
}
|
||||
if (cotisationObligatoire == null) {
|
||||
cotisationObligatoire = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/main/java/dev/lions/unionflow/server/entity/Paiement.java
Normal file
169
src/main/java/dev/lions/unionflow/server/entity/Paiement.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Paiement centralisée pour tous les types de paiements
|
||||
* Réutilisable pour cotisations, adhésions, événements, aides
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "paiements",
|
||||
indexes = {
|
||||
@Index(name = "idx_paiement_numero_reference", columnList = "numero_reference", unique = true),
|
||||
@Index(name = "idx_paiement_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_paiement_statut", columnList = "statut_paiement"),
|
||||
@Index(name = "idx_paiement_methode", columnList = "methode_paiement"),
|
||||
@Index(name = "idx_paiement_date", columnList = "date_paiement")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Paiement extends BaseEntity {
|
||||
|
||||
/** Numéro de référence unique */
|
||||
@NotBlank
|
||||
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
|
||||
private String numeroReference;
|
||||
|
||||
/** Montant du paiement */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montant;
|
||||
|
||||
/** Code devise (ISO 3 lettres) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
@Column(name = "code_devise", nullable = false, length = 3)
|
||||
private String codeDevise;
|
||||
|
||||
/** Méthode de paiement */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "methode_paiement", nullable = false, length = 50)
|
||||
private MethodePaiement methodePaiement;
|
||||
|
||||
/** Statut du paiement */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut_paiement", nullable = false, length = 30)
|
||||
private StatutPaiement statutPaiement = StatutPaiement.EN_ATTENTE;
|
||||
|
||||
/** Date de paiement */
|
||||
@Column(name = "date_paiement")
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
/** Date de validation */
|
||||
@Column(name = "date_validation")
|
||||
private LocalDateTime dateValidation;
|
||||
|
||||
/** Validateur (email de l'administrateur) */
|
||||
@Column(name = "validateur", length = 255)
|
||||
private String validateur;
|
||||
|
||||
/** Référence externe (numéro transaction, URL preuve, etc.) */
|
||||
@Column(name = "reference_externe", length = 500)
|
||||
private String referenceExterne;
|
||||
|
||||
/** URL de preuve de paiement */
|
||||
@Column(name = "url_preuve", length = 1000)
|
||||
private String urlPreuve;
|
||||
|
||||
/** Commentaires et notes */
|
||||
@Column(name = "commentaire", length = 1000)
|
||||
private String commentaire;
|
||||
|
||||
/** Adresse IP de l'initiateur */
|
||||
@Column(name = "ip_address", length = 45)
|
||||
private String ipAddress;
|
||||
|
||||
/** User-Agent de l'initiateur */
|
||||
@Column(name = "user_agent", length = 500)
|
||||
private String userAgent;
|
||||
|
||||
/** Membre payeur */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
/** Relations avec les tables de liaison */
|
||||
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<PaiementCotisation> paiementsCotisation = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<PaiementAdhesion> paiementsAdhesion = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<PaiementEvenement> paiementsEvenement = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<PaiementAide> paiementsAide = new ArrayList<>();
|
||||
|
||||
/** Relation avec TransactionWave (optionnelle) */
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "transaction_wave_id")
|
||||
private TransactionWave transactionWave;
|
||||
|
||||
/** Méthode métier pour générer un numéro de référence unique */
|
||||
public static String genererNumeroReference() {
|
||||
return "PAY-"
|
||||
+ LocalDateTime.now().getYear()
|
||||
+ "-"
|
||||
+ String.format("%012d", System.currentTimeMillis() % 1000000000000L);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le paiement est validé */
|
||||
public boolean isValide() {
|
||||
return StatutPaiement.VALIDE.equals(statutPaiement);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le paiement peut être modifié */
|
||||
public boolean peutEtreModifie() {
|
||||
return !statutPaiement.isFinalise();
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (numeroReference == null || numeroReference.isEmpty()) {
|
||||
numeroReference = genererNumeroReference();
|
||||
}
|
||||
if (codeDevise == null || codeDevise.isEmpty()) {
|
||||
codeDevise = "XOF";
|
||||
}
|
||||
if (statutPaiement == null) {
|
||||
statutPaiement = StatutPaiement.EN_ATTENTE;
|
||||
}
|
||||
if (datePaiement == null) {
|
||||
datePaiement = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Paiement et Adhesion
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "paiements_adhesions",
|
||||
indexes = {
|
||||
@Index(name = "idx_paiement_adhesion_paiement", columnList = "paiement_id"),
|
||||
@Index(name = "idx_paiement_adhesion_adhesion", columnList = "adhesion_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_paiement_adhesion",
|
||||
columnNames = {"paiement_id", "adhesion_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaiementAdhesion extends BaseEntity {
|
||||
|
||||
/** Paiement */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id", nullable = false)
|
||||
private Paiement paiement;
|
||||
|
||||
/** Adhésion */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "adhesion_id", nullable = false)
|
||||
private Adhesion adhesion;
|
||||
|
||||
/** Montant appliqué à cette adhésion */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montantApplique;
|
||||
|
||||
/** Date d'application */
|
||||
@Column(name = "date_application")
|
||||
private LocalDateTime dateApplication;
|
||||
|
||||
/** Commentaire sur l'application */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateApplication == null) {
|
||||
dateApplication = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Paiement et DemandeAide
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "paiements_aides",
|
||||
indexes = {
|
||||
@Index(name = "idx_paiement_aide_paiement", columnList = "paiement_id"),
|
||||
@Index(name = "idx_paiement_aide_demande", columnList = "demande_aide_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_paiement_aide",
|
||||
columnNames = {"paiement_id", "demande_aide_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaiementAide extends BaseEntity {
|
||||
|
||||
/** Paiement */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id", nullable = false)
|
||||
private Paiement paiement;
|
||||
|
||||
/** Demande d'aide */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "demande_aide_id", nullable = false)
|
||||
private DemandeAide demandeAide;
|
||||
|
||||
/** Montant appliqué à cette demande d'aide */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montantApplique;
|
||||
|
||||
/** Date d'application */
|
||||
@Column(name = "date_application")
|
||||
private LocalDateTime dateApplication;
|
||||
|
||||
/** Commentaire sur l'application */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateApplication == null) {
|
||||
dateApplication = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Paiement et Cotisation
|
||||
* Permet à un paiement de couvrir plusieurs cotisations
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "paiements_cotisations",
|
||||
indexes = {
|
||||
@Index(name = "idx_paiement_cotisation_paiement", columnList = "paiement_id"),
|
||||
@Index(name = "idx_paiement_cotisation_cotisation", columnList = "cotisation_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_paiement_cotisation",
|
||||
columnNames = {"paiement_id", "cotisation_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaiementCotisation extends BaseEntity {
|
||||
|
||||
/** Paiement */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id", nullable = false)
|
||||
private Paiement paiement;
|
||||
|
||||
/** Cotisation */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "cotisation_id", nullable = false)
|
||||
private Cotisation cotisation;
|
||||
|
||||
/** Montant appliqué à cette cotisation */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montantApplique;
|
||||
|
||||
/** Date d'application */
|
||||
@Column(name = "date_application")
|
||||
private LocalDateTime dateApplication;
|
||||
|
||||
/** Commentaire sur l'application */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateApplication == null) {
|
||||
dateApplication = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Paiement et InscriptionEvenement
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "paiements_evenements",
|
||||
indexes = {
|
||||
@Index(name = "idx_paiement_evenement_paiement", columnList = "paiement_id"),
|
||||
@Index(name = "idx_paiement_evenement_inscription", columnList = "inscription_evenement_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_paiement_evenement",
|
||||
columnNames = {"paiement_id", "inscription_evenement_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaiementEvenement extends BaseEntity {
|
||||
|
||||
/** Paiement */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id", nullable = false)
|
||||
private Paiement paiement;
|
||||
|
||||
/** Inscription à l'événement */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "inscription_evenement_id", nullable = false)
|
||||
private InscriptionEvenement inscriptionEvenement;
|
||||
|
||||
/** Montant appliqué à cette inscription */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montantApplique;
|
||||
|
||||
/** Date d'application */
|
||||
@Column(name = "date_application")
|
||||
private LocalDateTime dateApplication;
|
||||
|
||||
/** Commentaire sur l'application */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateApplication == null) {
|
||||
dateApplication = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Permission pour la gestion des permissions granulaires
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "permissions",
|
||||
indexes = {
|
||||
@Index(name = "idx_permission_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_permission_module", columnList = "module"),
|
||||
@Index(name = "idx_permission_ressource", columnList = "ressource")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Permission extends BaseEntity {
|
||||
|
||||
/** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 100)
|
||||
private String code;
|
||||
|
||||
/** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
|
||||
@NotBlank
|
||||
@Column(name = "module", nullable = false, length = 50)
|
||||
private String module;
|
||||
|
||||
/** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
|
||||
@NotBlank
|
||||
@Column(name = "ressource", nullable = false, length = 50)
|
||||
private String ressource;
|
||||
|
||||
/** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
|
||||
@NotBlank
|
||||
@Column(name = "action", nullable = false, length = 50)
|
||||
private String action;
|
||||
|
||||
/** Libellé de la permission */
|
||||
@Column(name = "libelle", length = 200)
|
||||
private String libelle;
|
||||
|
||||
/** Description de la permission */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Rôles associés */
|
||||
@OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<RolePermission> roles = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour générer le code à partir des composants */
|
||||
public static String genererCode(String module, String ressource, String action) {
|
||||
return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le code est valide */
|
||||
public boolean isCodeValide() {
|
||||
return code != null && code.contains(" > ") && code.split(" > ").length == 3;
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
// Générer le code si non fourni
|
||||
if (code == null || code.isEmpty()) {
|
||||
if (module != null && ressource != null && action != null) {
|
||||
code = genererCode(module, ressource, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java
Normal file
103
src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité PieceJointe pour l'association flexible de documents
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "pieces_jointes",
|
||||
indexes = {
|
||||
@Index(name = "idx_piece_jointe_document", columnList = "document_id"),
|
||||
@Index(name = "idx_piece_jointe_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_piece_jointe_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_piece_jointe_cotisation", columnList = "cotisation_id"),
|
||||
@Index(name = "idx_piece_jointe_adhesion", columnList = "adhesion_id"),
|
||||
@Index(name = "idx_piece_jointe_demande_aide", columnList = "demande_aide_id"),
|
||||
@Index(name = "idx_piece_jointe_transaction_wave", columnList = "transaction_wave_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PieceJointe extends BaseEntity {
|
||||
|
||||
/** Ordre d'affichage */
|
||||
@NotNull
|
||||
@Min(value = 1, message = "L'ordre doit être positif")
|
||||
@Column(name = "ordre", nullable = false)
|
||||
private Integer ordre;
|
||||
|
||||
/** Libellé de la pièce jointe */
|
||||
@Column(name = "libelle", length = 200)
|
||||
private String libelle;
|
||||
|
||||
/** Commentaire */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
/** Document associé */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "document_id", nullable = false)
|
||||
private Document document;
|
||||
|
||||
// Relations flexibles (une seule doit être renseignée)
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id")
|
||||
private Membre membre;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "cotisation_id")
|
||||
private Cotisation cotisation;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "adhesion_id")
|
||||
private Adhesion adhesion;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "demande_aide_id")
|
||||
private DemandeAide demandeAide;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "transaction_wave_id")
|
||||
private TransactionWave transactionWave;
|
||||
|
||||
/** Méthode métier pour vérifier qu'une seule relation est renseignée */
|
||||
public boolean isValide() {
|
||||
int count = 0;
|
||||
if (membre != null) count++;
|
||||
if (organisation != null) count++;
|
||||
if (cotisation != null) count++;
|
||||
if (adhesion != null) count++;
|
||||
if (demandeAide != null) count++;
|
||||
if (transactionWave != null) count++;
|
||||
return count == 1; // Exactement une relation doit être renseignée
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (ordre == null) {
|
||||
ordre = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
src/main/java/dev/lions/unionflow/server/entity/Role.java
Normal file
105
src/main/java/dev/lions/unionflow/server/entity/Role.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Role pour la gestion des rôles dans le système
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "roles",
|
||||
indexes = {
|
||||
@Index(name = "idx_role_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_role_actif", columnList = "actif"),
|
||||
@Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Role extends BaseEntity {
|
||||
|
||||
/** Code unique du rôle */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 50)
|
||||
private String code;
|
||||
|
||||
/** Libellé du rôle */
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 100)
|
||||
private String libelle;
|
||||
|
||||
/** Description du rôle */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Niveau hiérarchique (plus bas = plus prioritaire) */
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@Column(name = "niveau_hierarchique", nullable = false)
|
||||
private Integer niveauHierarchique = 100;
|
||||
|
||||
/** Type de rôle */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_role", nullable = false, length = 50)
|
||||
private TypeRole typeRole;
|
||||
|
||||
/** Organisation propriétaire (null pour rôles système) */
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
/** Permissions associées */
|
||||
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<RolePermission> permissions = new ArrayList<>();
|
||||
|
||||
/** Énumération des types de rôle */
|
||||
public enum TypeRole {
|
||||
SYSTEME("Rôle Système"),
|
||||
ORGANISATION("Rôle Organisation"),
|
||||
PERSONNALISE("Rôle Personnalisé");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
TypeRole(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si c'est un rôle système */
|
||||
public boolean isRoleSysteme() {
|
||||
return TypeRole.SYSTEME.equals(typeRole);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (typeRole == null) {
|
||||
typeRole = TypeRole.PERSONNALISE;
|
||||
}
|
||||
if (niveauHierarchique == null) {
|
||||
niveauHierarchique = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Table de liaison entre Role et Permission
|
||||
* Permet à un rôle d'avoir plusieurs permissions
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "roles_permissions",
|
||||
indexes = {
|
||||
@Index(name = "idx_role_permission_role", columnList = "role_id"),
|
||||
@Index(name = "idx_role_permission_permission", columnList = "permission_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_role_permission",
|
||||
columnNames = {"role_id", "permission_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RolePermission extends BaseEntity {
|
||||
|
||||
/** Rôle */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "role_id", nullable = false)
|
||||
private Role role;
|
||||
|
||||
/** Permission */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "permission_id", nullable = false)
|
||||
private Permission permission;
|
||||
|
||||
/** Commentaire sur l'association */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité TemplateNotification pour les templates de notifications réutilisables
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "templates_notifications",
|
||||
indexes = {
|
||||
@Index(name = "idx_template_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_template_actif", columnList = "actif")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TemplateNotification extends BaseEntity {
|
||||
|
||||
/** Code unique du template */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 100)
|
||||
private String code;
|
||||
|
||||
/** Sujet du template */
|
||||
@Column(name = "sujet", length = 500)
|
||||
private String sujet;
|
||||
|
||||
/** Corps du template (texte) */
|
||||
@Column(name = "corps_texte", columnDefinition = "TEXT")
|
||||
private String corpsTexte;
|
||||
|
||||
/** Corps du template (HTML) */
|
||||
@Column(name = "corps_html", columnDefinition = "TEXT")
|
||||
private String corpsHtml;
|
||||
|
||||
/** Variables disponibles (JSON) */
|
||||
@Column(name = "variables_disponibles", columnDefinition = "TEXT")
|
||||
private String variablesDisponibles;
|
||||
|
||||
/** Canaux supportés (JSON array) */
|
||||
@Column(name = "canaux_supportes", length = 500)
|
||||
private String canauxSupportes;
|
||||
|
||||
/** Langue du template */
|
||||
@Column(name = "langue", length = 10)
|
||||
private String langue;
|
||||
|
||||
/** Description */
|
||||
@Column(name = "description", length = 1000)
|
||||
private String description;
|
||||
|
||||
/** Notifications utilisant ce template */
|
||||
@OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Notification> notifications = new ArrayList<>();
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (langue == null || langue.isEmpty()) {
|
||||
langue = "fr";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité TransactionWave pour le suivi des transactions Wave Mobile Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "transactions_wave",
|
||||
indexes = {
|
||||
@Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true),
|
||||
@Index(name = "idx_transaction_wave_request_id", columnList = "wave_request_id"),
|
||||
@Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"),
|
||||
@Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"),
|
||||
@Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TransactionWave extends BaseEntity {
|
||||
|
||||
/** Identifiant Wave de la transaction (unique) */
|
||||
@NotBlank
|
||||
@Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100)
|
||||
private String waveTransactionId;
|
||||
|
||||
/** Identifiant de requête Wave */
|
||||
@Column(name = "wave_request_id", length = 100)
|
||||
private String waveRequestId;
|
||||
|
||||
/** Référence Wave */
|
||||
@Column(name = "wave_reference", length = 100)
|
||||
private String waveReference;
|
||||
|
||||
/** Type de transaction */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_transaction", nullable = false, length = 50)
|
||||
private TypeTransactionWave typeTransaction;
|
||||
|
||||
/** Statut de la transaction */
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut_transaction", nullable = false, length = 30)
|
||||
private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE;
|
||||
|
||||
/** Montant de la transaction */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal montant;
|
||||
|
||||
/** Frais de transaction */
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
@Column(name = "frais", precision = 12, scale = 2)
|
||||
private BigDecimal frais;
|
||||
|
||||
/** Montant net (montant - frais) */
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "montant_net", precision = 14, scale = 2)
|
||||
private BigDecimal montantNet;
|
||||
|
||||
/** Code devise */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$")
|
||||
@Column(name = "code_devise", nullable = false, length = 3)
|
||||
private String codeDevise;
|
||||
|
||||
/** Numéro téléphone payeur */
|
||||
@Column(name = "telephone_payeur", length = 13)
|
||||
private String telephonePayeur;
|
||||
|
||||
/** Numéro téléphone bénéficiaire */
|
||||
@Column(name = "telephone_beneficiaire", length = 13)
|
||||
private String telephoneBeneficiaire;
|
||||
|
||||
/** Métadonnées JSON (réponse complète de Wave API) */
|
||||
@Column(name = "metadonnees", columnDefinition = "TEXT")
|
||||
private String metadonnees;
|
||||
|
||||
/** Réponse complète de Wave API (JSON) */
|
||||
@Column(name = "reponse_wave_api", columnDefinition = "TEXT")
|
||||
private String reponseWaveApi;
|
||||
|
||||
/** Nombre de tentatives */
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_tentatives", nullable = false)
|
||||
private Integer nombreTentatives = 0;
|
||||
|
||||
/** Date de dernière tentative */
|
||||
@Column(name = "date_derniere_tentative")
|
||||
private LocalDateTime dateDerniereTentative;
|
||||
|
||||
/** Message d'erreur (si échec) */
|
||||
@Column(name = "message_erreur", length = 1000)
|
||||
private String messageErreur;
|
||||
|
||||
// Relations
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "compte_wave_id", nullable = false)
|
||||
private CompteWave compteWave;
|
||||
|
||||
@OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<WebhookWave> webhooks = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour vérifier si la transaction est réussie */
|
||||
public boolean isReussie() {
|
||||
return StatutTransactionWave.REUSSIE.equals(statutTransaction);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si la transaction peut être retentée */
|
||||
public boolean peutEtreRetentee() {
|
||||
return (statutTransaction == StatutTransactionWave.ECHOUE
|
||||
|| statutTransaction == StatutTransactionWave.EXPIRED)
|
||||
&& (nombreTentatives == null || nombreTentatives < 5);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (statutTransaction == null) {
|
||||
statutTransaction = StatutTransactionWave.INITIALISE;
|
||||
}
|
||||
if (codeDevise == null || codeDevise.isEmpty()) {
|
||||
codeDevise = "XOF";
|
||||
}
|
||||
if (nombreTentatives == null) {
|
||||
nombreTentatives = 0;
|
||||
}
|
||||
if (montantNet == null && montant != null && frais != null) {
|
||||
montantNet = montant.subtract(frais);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
|
||||
/**
|
||||
* Entité persistée représentant un type d'organisation.
|
||||
*
|
||||
* <p>Cette entité permet de gérer dynamiquement le catalogue des types d'organisations
|
||||
* (codes, libellés, description, ordre d'affichage, activation/désactivation).
|
||||
*
|
||||
* <p>Le champ {@code code} doit rester synchronisé avec l'enum {@link
|
||||
* dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation} pour les types
|
||||
* standards fournis par la plateforme.
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "uf_type_organisation",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_type_organisation_code",
|
||||
columnNames = {"code"})
|
||||
})
|
||||
public class TypeOrganisationEntity extends BaseEntity {
|
||||
|
||||
@Column(name = "code", length = 50, nullable = false, unique = true)
|
||||
private String code;
|
||||
|
||||
@Column(name = "libelle", length = 150, nullable = false)
|
||||
private String libelle;
|
||||
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(name = "ordre_affichage")
|
||||
private Integer ordreAffichage;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
|
||||
public void setLibelle(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Integer getOrdreAffichage() {
|
||||
return ordreAffichage;
|
||||
}
|
||||
|
||||
public void setOrdreAffichage(Integer ordreAffichage) {
|
||||
this.ordreAffichage = ordreAffichage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
118
src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java
Normal file
118
src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité WebhookWave pour le traitement des événements Wave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "webhooks_wave",
|
||||
indexes = {
|
||||
@Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true),
|
||||
@Index(name = "idx_webhook_wave_statut", columnList = "statut_traitement"),
|
||||
@Index(name = "idx_webhook_wave_type", columnList = "type_evenement"),
|
||||
@Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"),
|
||||
@Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WebhookWave extends BaseEntity {
|
||||
|
||||
/** Identifiant unique de l'événement Wave */
|
||||
@NotBlank
|
||||
@Column(name = "wave_event_id", unique = true, nullable = false, length = 100)
|
||||
private String waveEventId;
|
||||
|
||||
/** Type d'événement */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_evenement", length = 50)
|
||||
private TypeEvenementWebhook typeEvenement;
|
||||
|
||||
/** Statut de traitement */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut_traitement", nullable = false, length = 30)
|
||||
private StatutWebhook statutTraitement = StatutWebhook.EN_ATTENTE;
|
||||
|
||||
/** Payload JSON reçu */
|
||||
@Column(name = "payload", columnDefinition = "TEXT")
|
||||
private String payload;
|
||||
|
||||
/** Signature de validation */
|
||||
@Column(name = "signature", length = 500)
|
||||
private String signature;
|
||||
|
||||
/** Date de réception */
|
||||
@Column(name = "date_reception")
|
||||
private LocalDateTime dateReception;
|
||||
|
||||
/** Date de traitement */
|
||||
@Column(name = "date_traitement")
|
||||
private LocalDateTime dateTraitement;
|
||||
|
||||
/** Nombre de tentatives de traitement */
|
||||
@Builder.Default
|
||||
@Column(name = "nombre_tentatives", nullable = false)
|
||||
private Integer nombreTentatives = 0;
|
||||
|
||||
/** Message d'erreur (si échec) */
|
||||
@Column(name = "message_erreur", length = 1000)
|
||||
private String messageErreur;
|
||||
|
||||
/** Commentaires */
|
||||
@Column(name = "commentaire", length = 500)
|
||||
private String commentaire;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "transaction_wave_id")
|
||||
private TransactionWave transactionWave;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paiement_id")
|
||||
private Paiement paiement;
|
||||
|
||||
/** Méthode métier pour vérifier si le webhook est traité */
|
||||
public boolean isTraite() {
|
||||
return StatutWebhook.TRAITE.equals(statutTraitement);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le webhook peut être retenté */
|
||||
public boolean peutEtreRetente() {
|
||||
return (statutTraitement == StatutWebhook.ECHOUE || statutTraitement == StatutWebhook.EN_ATTENTE)
|
||||
&& (nombreTentatives == null || nombreTentatives < 5);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (statutTraitement == null) {
|
||||
statutTraitement = StatutWebhook.EN_ATTENTE;
|
||||
}
|
||||
if (dateReception == null) {
|
||||
dateReception = LocalDateTime.now();
|
||||
}
|
||||
if (nombreTentatives == null) {
|
||||
nombreTentatives = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.lions.unionflow.server.exception;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import java.util.Map;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Exception mapper pour gérer les erreurs de désérialisation JSON
|
||||
* Retourne 400 (Bad Request) au lieu de 500 (Internal Server Error)
|
||||
*/
|
||||
@Provider
|
||||
public class JsonProcessingExceptionMapper implements ExceptionMapper<com.fasterxml.jackson.core.JsonProcessingException> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(JsonProcessingExceptionMapper.class);
|
||||
|
||||
@Override
|
||||
public Response toResponse(com.fasterxml.jackson.core.JsonProcessingException exception) {
|
||||
LOG.warnf("Erreur de désérialisation JSON: %s", exception.getMessage());
|
||||
|
||||
String message = "Erreur de format JSON";
|
||||
if (exception instanceof MismatchedInputException) {
|
||||
message = "Format JSON invalide ou body manquant";
|
||||
} else if (exception instanceof InvalidFormatException) {
|
||||
message = "Format de données invalide dans le JSON";
|
||||
} else if (exception instanceof JsonMappingException) {
|
||||
message = "Erreur de mapping JSON: " + exception.getMessage();
|
||||
}
|
||||
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", message, "details", exception.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Adhesion;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Adhesion
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AdhesionRepository extends BaseRepository<Adhesion> {
|
||||
|
||||
public AdhesionRepository() {
|
||||
super(Adhesion.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une adhésion par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return Optional contenant l'adhésion si trouvée
|
||||
*/
|
||||
public Optional<Adhesion> findByNumeroReference(String numeroReference) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.numeroReference = :numeroReference", Adhesion.class);
|
||||
query.setParameter("numeroReference", numeroReference);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions d'un membre
|
||||
*
|
||||
* @param membreId identifiant du membre
|
||||
* @return liste des adhésions du membre
|
||||
*/
|
||||
public List<Adhesion> findByMembreId(UUID membreId) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.membre.id = :membreId", Adhesion.class);
|
||||
query.setParameter("membreId", membreId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions d'une organisation
|
||||
*
|
||||
* @param organisationId identifiant de l'organisation
|
||||
* @return liste des adhésions de l'organisation
|
||||
*/
|
||||
public List<Adhesion> findByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.organisation.id = :organisationId", Adhesion.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions par statut
|
||||
*
|
||||
* @param statut statut de l'adhésion
|
||||
* @return liste des adhésions avec le statut spécifié
|
||||
*/
|
||||
public List<Adhesion> findByStatut(String statut) {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery("SELECT a FROM Adhesion a WHERE a.statut = :statut", Adhesion.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions en attente
|
||||
*
|
||||
* @return liste des adhésions en attente
|
||||
*/
|
||||
public List<Adhesion> findEnAttente() {
|
||||
return findByStatut("EN_ATTENTE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adhésions approuvées en attente de paiement
|
||||
*
|
||||
* @return liste des adhésions approuvées non payées
|
||||
*/
|
||||
public List<Adhesion> findApprouveesEnAttentePaiement() {
|
||||
TypedQuery<Adhesion> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT a FROM Adhesion a WHERE a.statut = :statut AND (a.montantPaye IS NULL OR a.montantPaye < a.fraisAdhesion)",
|
||||
Adhesion.class);
|
||||
query.setParameter("statut", "APPROUVEE");
|
||||
return query.getResultList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
|
||||
import dev.lions.unionflow.server.entity.Adresse;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Adresse
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AdresseRepository implements PanacheRepository<Adresse> {
|
||||
|
||||
/**
|
||||
* Trouve une adresse par son UUID
|
||||
*
|
||||
* @param id UUID de l'adresse
|
||||
* @return Adresse ou Optional.empty()
|
||||
*/
|
||||
public Optional<Adresse> findAdresseById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adresses d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> findByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id", organisationId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse principale d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Adresse principale ou Optional.empty()
|
||||
*/
|
||||
public Optional<Adresse> findPrincipaleByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id = ?1 AND principale = true", organisationId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adresses d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> findByMembreId(UUID membreId) {
|
||||
return find("membre.id", membreId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse principale d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Adresse principale ou Optional.empty()
|
||||
*/
|
||||
public Optional<Adresse> findPrincipaleByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 AND principale = true", membreId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse d'un événement
|
||||
*
|
||||
* @param evenementId ID de l'événement
|
||||
* @return Adresse ou Optional.empty()
|
||||
*/
|
||||
public Optional<Adresse> findByEvenementId(UUID evenementId) {
|
||||
return find("evenement.id", evenementId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les adresses par type
|
||||
*
|
||||
* @param typeAdresse Type d'adresse
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> findByType(TypeAdresse typeAdresse) {
|
||||
return find("typeAdresse", typeAdresse).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les adresses par ville
|
||||
*
|
||||
* @param ville Nom de la ville
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> findByVille(String ville) {
|
||||
return find("LOWER(ville) = LOWER(?1)", ville).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les adresses par pays
|
||||
*
|
||||
* @param pays Nom du pays
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> findByPays(String pays) {
|
||||
return find("LOWER(pays) = LOWER(?1)", pays).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AuditLog;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour les logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AuditLogRepository extends BaseRepository<AuditLog> {
|
||||
|
||||
public AuditLogRepository() {
|
||||
super(AuditLog.class);
|
||||
}
|
||||
|
||||
// Les méthodes de recherche spécifiques peuvent être ajoutées ici si nécessaire
|
||||
// Pour l'instant, on utilise les méthodes de base et les requêtes dans le service
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.BaseEntity;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository de base pour les entités utilisant UUID comme identifiant
|
||||
*
|
||||
* <p>Remplace PanacheRepository pour utiliser UUID au lieu de Long.
|
||||
* Fournit les fonctionnalités de base de Panache avec UUID.
|
||||
*
|
||||
* @param <T> Le type d'entité qui étend BaseEntity
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
public abstract class BaseRepository<T extends BaseEntity> {
|
||||
|
||||
@PersistenceContext
|
||||
protected EntityManager entityManager;
|
||||
|
||||
protected final Class<T> entityClass;
|
||||
|
||||
protected BaseRepository(Class<T> entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une entité par son UUID
|
||||
*
|
||||
* @param id L'UUID de l'entité
|
||||
* @return L'entité trouvée ou null
|
||||
*/
|
||||
public T findById(UUID id) {
|
||||
return entityManager.find(entityClass, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une entité par son UUID (retourne Optional)
|
||||
*
|
||||
* @param id L'UUID de l'entité
|
||||
* @return Optional contenant l'entité si trouvée
|
||||
*/
|
||||
public Optional<T> findByIdOptional(UUID id) {
|
||||
return Optional.ofNullable(findById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Persiste une entité
|
||||
*
|
||||
* @param entity L'entité à persister
|
||||
*/
|
||||
@Transactional
|
||||
public void persist(T entity) {
|
||||
entityManager.persist(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une entité
|
||||
*
|
||||
* @param entity L'entité à mettre à jour
|
||||
* @return L'entité mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public T update(T entity) {
|
||||
return entityManager.merge(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une entité
|
||||
*
|
||||
* @param entity L'entité à supprimer
|
||||
*/
|
||||
@Transactional
|
||||
public void delete(T entity) {
|
||||
// Si l'entité n'est pas dans le contexte de persistance, la merger d'abord
|
||||
if (!entityManager.contains(entity)) {
|
||||
entity = entityManager.merge(entity);
|
||||
}
|
||||
entityManager.remove(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une entité par son UUID
|
||||
*
|
||||
* @param id L'UUID de l'entité à supprimer
|
||||
*/
|
||||
@Transactional
|
||||
public boolean deleteById(UUID id) {
|
||||
T entity = findById(id);
|
||||
if (entity != null) {
|
||||
// S'assurer que l'entité est dans le contexte de persistance
|
||||
if (!entityManager.contains(entity)) {
|
||||
entity = entityManager.merge(entity);
|
||||
}
|
||||
entityManager.remove(entity);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les entités
|
||||
*
|
||||
* @return La liste de toutes les entités
|
||||
*/
|
||||
public List<T> listAll() {
|
||||
return entityManager.createQuery(
|
||||
"SELECT e FROM " + entityClass.getSimpleName() + " e", entityClass)
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte toutes les entités
|
||||
*
|
||||
* @return Le nombre total d'entités
|
||||
*/
|
||||
public long count() {
|
||||
return entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM " + entityClass.getSimpleName() + " e", Long.class)
|
||||
.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une entité existe par son UUID
|
||||
*
|
||||
* @param id L'UUID de l'entité
|
||||
* @return true si l'entité existe
|
||||
*/
|
||||
public boolean existsById(UUID id) {
|
||||
return findById(id) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient l'EntityManager (pour les requêtes avancées)
|
||||
*
|
||||
* @return L'EntityManager
|
||||
*/
|
||||
public EntityManager getEntityManager() {
|
||||
return entityManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
|
||||
import dev.lions.unionflow.server.entity.CompteComptable;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité CompteComptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class CompteComptableRepository implements PanacheRepository<CompteComptable> {
|
||||
|
||||
/**
|
||||
* Trouve un compte comptable par son UUID
|
||||
*
|
||||
* @param id UUID du compte comptable
|
||||
* @return Compte comptable ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteComptable> findCompteComptableById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte par son numéro
|
||||
*
|
||||
* @param numeroCompte Numéro du compte
|
||||
* @return Compte ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteComptable> findByNumeroCompte(String numeroCompte) {
|
||||
return find("numeroCompte = ?1 AND actif = true", numeroCompte).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les comptes par type
|
||||
*
|
||||
* @param type Type de compte
|
||||
* @return Liste des comptes
|
||||
*/
|
||||
public List<CompteComptable> findByType(TypeCompteComptable type) {
|
||||
return find("typeCompte = ?1 AND actif = true ORDER BY numeroCompte ASC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les comptes par classe comptable
|
||||
*
|
||||
* @param classe Classe comptable (1-7)
|
||||
* @return Liste des comptes
|
||||
*/
|
||||
public List<CompteComptable> findByClasse(Integer classe) {
|
||||
return find("classeComptable = ?1 AND actif = true ORDER BY numeroCompte ASC", classe).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les comptes actifs
|
||||
*
|
||||
* @return Liste des comptes actifs
|
||||
*/
|
||||
public List<CompteComptable> findAllActifs() {
|
||||
return find("actif = true ORDER BY numeroCompte ASC").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les comptes de trésorerie
|
||||
*
|
||||
* @return Liste des comptes de trésorerie
|
||||
*/
|
||||
public List<CompteComptable> findComptesTresorerie() {
|
||||
return find("typeCompte = ?1 AND actif = true ORDER BY numeroCompte ASC", TypeCompteComptable.TRESORERIE)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
|
||||
import dev.lions.unionflow.server.entity.CompteWave;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité CompteWave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class CompteWaveRepository implements PanacheRepository<CompteWave> {
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par son UUID
|
||||
*
|
||||
* @param id UUID du compte Wave
|
||||
* @return Compte Wave ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteWave> findCompteWaveById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par numéro de téléphone
|
||||
*
|
||||
* @param numeroTelephone Numéro de téléphone
|
||||
* @return Compte Wave ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteWave> findByNumeroTelephone(String numeroTelephone) {
|
||||
return find("numeroTelephone = ?1 AND actif = true", numeroTelephone).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les comptes Wave d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des comptes Wave
|
||||
*/
|
||||
public List<CompteWave> findByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id = ?1 AND actif = true", organisationId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve le compte Wave principal d'une organisation (premier vérifié)
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Compte Wave ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteWave> findPrincipalByOrganisationId(UUID organisationId) {
|
||||
return find(
|
||||
"organisation.id = ?1 AND statutCompte = ?2 AND actif = true",
|
||||
organisationId,
|
||||
StatutCompteWave.VERIFIE)
|
||||
.firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les comptes Wave d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des comptes Wave
|
||||
*/
|
||||
public List<CompteWave> findByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 AND actif = true", membreId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve le compte Wave principal d'un membre (premier vérifié)
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Compte Wave ou Optional.empty()
|
||||
*/
|
||||
public Optional<CompteWave> findPrincipalByMembreId(UUID membreId) {
|
||||
return find(
|
||||
"membre.id = ?1 AND statutCompte = ?2 AND actif = true",
|
||||
membreId,
|
||||
StatutCompteWave.VERIFIE)
|
||||
.firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les comptes Wave vérifiés
|
||||
*
|
||||
* @return Liste des comptes Wave vérifiés
|
||||
*/
|
||||
public List<CompteWave> findComptesVerifies() {
|
||||
return find("statutCompte = ?1 AND actif = true", StatutCompteWave.VERIFIE).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.ConfigurationWave;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité ConfigurationWave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ConfigurationWaveRepository implements PanacheRepository<ConfigurationWave> {
|
||||
|
||||
/**
|
||||
* Trouve une configuration Wave par son UUID
|
||||
*
|
||||
* @param id UUID de la configuration
|
||||
* @return Configuration ou Optional.empty()
|
||||
*/
|
||||
public Optional<ConfigurationWave> findConfigurationWaveById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une configuration par sa clé
|
||||
*
|
||||
* @param cle Clé de configuration
|
||||
* @return Configuration ou Optional.empty()
|
||||
*/
|
||||
public Optional<ConfigurationWave> findByCle(String cle) {
|
||||
return find("cle = ?1 AND actif = true", cle).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les configurations d'un environnement
|
||||
*
|
||||
* @param environnement Environnement (SANDBOX, PRODUCTION, COMMON)
|
||||
* @return Liste des configurations
|
||||
*/
|
||||
public List<ConfigurationWave> findByEnvironnement(String environnement) {
|
||||
return find("environnement = ?1 AND actif = true", environnement).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les configurations actives
|
||||
*
|
||||
* @return Liste des configurations actives
|
||||
*/
|
||||
public List<ConfigurationWave> findAllActives() {
|
||||
return find("actif = true ORDER BY cle ASC").list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour la gestion des cotisations avec UUID
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class CotisationRepository extends BaseRepository<Cotisation> {
|
||||
|
||||
public CotisationRepository() {
|
||||
super(Cotisation.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une cotisation par son numéro de référence
|
||||
*
|
||||
* @param numeroReference le numéro de référence unique
|
||||
* @return Optional contenant la cotisation si trouvée
|
||||
*/
|
||||
public Optional<Cotisation> findByNumeroReference(String numeroReference) {
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
|
||||
Cotisation.class);
|
||||
query.setParameter("numeroReference", numeroReference);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les cotisations d'un membre
|
||||
*
|
||||
* @param membreId l'UUID du membre
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des cotisations
|
||||
*/
|
||||
public List<Cotisation> findByMembreId(UUID membreId, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy,
|
||||
Cotisation.class);
|
||||
query.setParameter("membreId", membreId);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par statut
|
||||
*
|
||||
* @param statut le statut recherché
|
||||
* @param page pagination
|
||||
* @return liste paginée des cotisations
|
||||
*/
|
||||
public List<Cotisation> findByStatut(String statut, Page page) {
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setParameter("statut", statut);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations en retard
|
||||
*
|
||||
* @param dateReference date de référence (généralement aujourd'hui)
|
||||
* @param page pagination
|
||||
* @return liste des cotisations en retard
|
||||
*/
|
||||
public List<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC",
|
||||
Cotisation.class);
|
||||
query.setParameter("dateReference", dateReference);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par période (année/mois)
|
||||
*
|
||||
* @param annee l'année
|
||||
* @param mois le mois (optionnel)
|
||||
* @param page pagination
|
||||
* @return liste des cotisations de la période
|
||||
*/
|
||||
public List<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
|
||||
TypedQuery<Cotisation> query;
|
||||
if (mois != null) {
|
||||
query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setParameter("annee", annee);
|
||||
query.setParameter("mois", mois);
|
||||
} else {
|
||||
query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setParameter("annee", annee);
|
||||
}
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations par type
|
||||
*
|
||||
* @param typeCotisation le type de cotisation
|
||||
* @param page pagination
|
||||
* @return liste des cotisations du type spécifié
|
||||
*/
|
||||
public List<Cotisation> findByType(String typeCotisation, Page page) {
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setParameter("typeCotisation", typeCotisation);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée avec filtres multiples
|
||||
*
|
||||
* @param membreId UUID du membre (optionnel)
|
||||
* @param statut statut (optionnel)
|
||||
* @param typeCotisation type (optionnel)
|
||||
* @param annee année (optionnel)
|
||||
* @param mois mois (optionnel)
|
||||
* @param page pagination
|
||||
* @return liste filtrée des cotisations
|
||||
*/
|
||||
public List<Cotisation> rechercheAvancee(
|
||||
UUID membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) {
|
||||
StringBuilder jpql = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1");
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
||||
if (membreId != null) {
|
||||
jpql.append(" AND c.membre.id = :membreId");
|
||||
params.put("membreId", membreId);
|
||||
}
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
jpql.append(" AND c.statut = :statut");
|
||||
params.put("statut", statut);
|
||||
}
|
||||
|
||||
if (typeCotisation != null && !typeCotisation.isEmpty()) {
|
||||
jpql.append(" AND c.typeCotisation = :typeCotisation");
|
||||
params.put("typeCotisation", typeCotisation);
|
||||
}
|
||||
|
||||
if (annee != null) {
|
||||
jpql.append(" AND c.annee = :annee");
|
||||
params.put("annee", annee);
|
||||
}
|
||||
|
||||
if (mois != null) {
|
||||
jpql.append(" AND c.mois = :mois");
|
||||
params.put("mois", mois);
|
||||
}
|
||||
|
||||
jpql.append(" ORDER BY c.dateEcheance DESC");
|
||||
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(jpql.toString(), Cotisation.class);
|
||||
for (Map.Entry<String, Object> param : params.entrySet()) {
|
||||
query.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le total des montants dus pour un membre
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @return montant total dû
|
||||
*/
|
||||
public BigDecimal calculerTotalMontantDu(UUID membreId) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
||||
BigDecimal.class);
|
||||
query.setParameter("membreId", membreId);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le total des montants payés pour un membre
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @return montant total payé
|
||||
*/
|
||||
public BigDecimal calculerTotalMontantPaye(UUID membreId) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
|
||||
BigDecimal.class);
|
||||
query.setParameter("membreId", membreId);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les cotisations par statut
|
||||
*
|
||||
* @param statut le statut
|
||||
* @return nombre de cotisations
|
||||
*/
|
||||
public long compterParStatut(String statut) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les cotisations nécessitant un rappel
|
||||
*
|
||||
* @param joursAvantEcheance nombre de jours avant échéance
|
||||
* @param nombreMaxRappels nombre maximum de rappels déjà envoyés
|
||||
* @return liste des cotisations à rappeler
|
||||
*/
|
||||
public List<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
|
||||
LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
|
||||
TypedQuery<Cotisation> query = entityManager.createQuery(
|
||||
"SELECT c FROM Cotisation c WHERE c.dateEcheance <= :dateRappel AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' AND c.nombreRappels < :nombreMaxRappels ORDER BY c.dateEcheance ASC",
|
||||
Cotisation.class);
|
||||
query.setParameter("dateRappel", dateRappel);
|
||||
query.setParameter("nombreMaxRappels", nombreMaxRappels);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le nombre de rappels pour une cotisation
|
||||
*
|
||||
* @param cotisationId UUID de la cotisation
|
||||
* @return true si mise à jour réussie
|
||||
*/
|
||||
public boolean incrementerNombreRappels(UUID cotisationId) {
|
||||
Cotisation cotisation = findByIdOptional(cotisationId).orElse(null);
|
||||
if (cotisation != null) {
|
||||
cotisation.setNombreRappels(
|
||||
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
|
||||
cotisation.setDateDernierRappel(LocalDateTime.now());
|
||||
update(cotisation);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiques des cotisations par période
|
||||
*
|
||||
* @param annee l'année
|
||||
* @param mois le mois (optionnel)
|
||||
* @return map avec les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesPeriode(Integer annee, Integer mois) {
|
||||
String baseQuery = mois != null
|
||||
? "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois"
|
||||
: "SELECT c FROM Cotisation c WHERE c.annee = :annee";
|
||||
|
||||
TypedQuery<Long> countQuery;
|
||||
TypedQuery<BigDecimal> montantTotalQuery;
|
||||
TypedQuery<BigDecimal> montantPayeQuery;
|
||||
TypedQuery<Long> payeesQuery;
|
||||
|
||||
if (mois != null) {
|
||||
countQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||
Long.class);
|
||||
montantTotalQuery = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||
BigDecimal.class);
|
||||
montantPayeQuery = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
|
||||
BigDecimal.class);
|
||||
payeesQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'",
|
||||
Long.class);
|
||||
|
||||
countQuery.setParameter("annee", annee);
|
||||
countQuery.setParameter("mois", mois);
|
||||
montantTotalQuery.setParameter("annee", annee);
|
||||
montantTotalQuery.setParameter("mois", mois);
|
||||
montantPayeQuery.setParameter("annee", annee);
|
||||
montantPayeQuery.setParameter("mois", mois);
|
||||
payeesQuery.setParameter("annee", annee);
|
||||
payeesQuery.setParameter("mois", mois);
|
||||
} else {
|
||||
countQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee", Long.class);
|
||||
montantTotalQuery = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee",
|
||||
BigDecimal.class);
|
||||
montantPayeQuery = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee",
|
||||
BigDecimal.class);
|
||||
payeesQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'",
|
||||
Long.class);
|
||||
|
||||
countQuery.setParameter("annee", annee);
|
||||
montantTotalQuery.setParameter("annee", annee);
|
||||
montantPayeQuery.setParameter("annee", annee);
|
||||
payeesQuery.setParameter("annee", annee);
|
||||
}
|
||||
|
||||
Long totalCotisations = countQuery.getSingleResult();
|
||||
BigDecimal montantTotal = montantTotalQuery.getSingleResult();
|
||||
BigDecimal montantPaye = montantPayeQuery.getSingleResult();
|
||||
Long cotisationsPayees = payeesQuery.getSingleResult();
|
||||
|
||||
return Map.of(
|
||||
"totalCotisations", totalCotisations != null ? totalCotisations : 0L,
|
||||
"montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO,
|
||||
"montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO,
|
||||
"cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L,
|
||||
"tauxPaiement",
|
||||
totalCotisations != null && totalCotisations > 0
|
||||
? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations
|
||||
: 0.0);
|
||||
}
|
||||
|
||||
/** Somme des montants payés dans une période */
|
||||
public BigDecimal sumMontantsPayes(
|
||||
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'PAYEE' AND c.datePaiement BETWEEN :debut AND :fin",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/** Somme des montants en attente dans une période */
|
||||
public BigDecimal sumMontantsEnAttente(
|
||||
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'EN_ATTENTE' AND c.dateCreation BETWEEN :debut AND :fin",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "c.dateEcheance DESC";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
for (int i = 0; i < sort.getColumns().size(); i++) {
|
||||
if (i > 0) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
Sort.Column column = sort.getColumns().get(i);
|
||||
orderBy.append("c.").append(column.getName());
|
||||
if (column.getDirection() == Sort.Direction.Descending) {
|
||||
orderBy.append(" DESC");
|
||||
} else {
|
||||
orderBy.append(" ASC");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import dev.lions.unionflow.server.entity.DemandeAide;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Repository pour les demandes d'aide avec UUID */
|
||||
@ApplicationScoped
|
||||
public class DemandeAideRepository extends BaseRepository<DemandeAide> {
|
||||
|
||||
public DemandeAideRepository() {
|
||||
super(DemandeAide.class);
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par organisation */
|
||||
public List<DemandeAide> findByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par organisation avec pagination */
|
||||
public List<DemandeAide> findByOrganisationId(UUID organisationId, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY d.dateDemande DESC";
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId" + orderBy,
|
||||
DemandeAide.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par demandeur */
|
||||
public List<DemandeAide> findByDemandeurId(UUID demandeurId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.demandeur.id = :demandeurId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("demandeurId", demandeurId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par statut */
|
||||
public List<DemandeAide> findByStatut(StatutAide statut) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.statut = :statut",
|
||||
DemandeAide.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par statut et organisation */
|
||||
public List<DemandeAide> findByStatutAndOrganisationId(StatutAide statut, UUID organisationId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("statut", statut);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide par type */
|
||||
public List<DemandeAide> findByTypeAide(TypeAide typeAide) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.typeAide = :typeAide",
|
||||
DemandeAide.class);
|
||||
query.setParameter("typeAide", typeAide);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide urgentes */
|
||||
public List<DemandeAide> findUrgentes() {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.urgence = true",
|
||||
DemandeAide.class);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide urgentes par organisation */
|
||||
public List<DemandeAide> findUrgentesByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.urgence = true AND d.organisation.id = :organisationId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide dans une période */
|
||||
public List<DemandeAide> findByPeriode(LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin",
|
||||
DemandeAide.class);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve toutes les demandes d'aide dans une période pour une organisation */
|
||||
public List<DemandeAide> findByPeriodeAndOrganisationId(
|
||||
LocalDateTime debut, LocalDateTime fin, UUID organisationId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin AND d.organisation.id = :organisationId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Compte le nombre de demandes par statut */
|
||||
public long countByStatut(StatutAide statut) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut",
|
||||
Long.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Compte le nombre de demandes par statut et organisation */
|
||||
public long countByStatutAndOrganisationId(StatutAide statut, UUID organisationId) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId",
|
||||
Long.class);
|
||||
query.setParameter("statut", statut);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Calcule le montant total demandé par organisation */
|
||||
public Optional<BigDecimal> sumMontantDemandeByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(d.montantDemande), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null && result.compareTo(BigDecimal.ZERO) > 0
|
||||
? Optional.of(result)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
/** Calcule le montant total approuvé par organisation */
|
||||
public Optional<BigDecimal> sumMontantApprouveByOrganisationId(UUID organisationId) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("statut", StatutAide.APPROUVEE);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null && result.compareTo(BigDecimal.ZERO) > 0
|
||||
? Optional.of(result)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
/** Trouve les demandes d'aide récentes (dernières 30 jours) */
|
||||
public List<DemandeAide> findRecentes() {
|
||||
LocalDateTime il30Jours = LocalDateTime.now().minusDays(30);
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours ORDER BY d.dateDemande DESC",
|
||||
DemandeAide.class);
|
||||
query.setParameter("il30Jours", il30Jours);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les demandes d'aide récentes par organisation */
|
||||
public List<DemandeAide> findRecentesByOrganisationId(UUID organisationId) {
|
||||
LocalDateTime il30Jours = LocalDateTime.now().minusDays(30);
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours AND d.organisation.id = :organisationId ORDER BY d.dateDemande DESC",
|
||||
DemandeAide.class);
|
||||
query.setParameter("il30Jours", il30Jours);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les demandes d'aide en attente depuis plus de X jours */
|
||||
public List<DemandeAide> findEnAttenteDepuis(int nombreJours) {
|
||||
LocalDateTime dateLimit = LocalDateTime.now().minusDays(nombreJours);
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.dateDemande <= :dateLimit",
|
||||
DemandeAide.class);
|
||||
query.setParameter("statut", StatutAide.EN_ATTENTE);
|
||||
query.setParameter("dateLimit", dateLimit);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les demandes d'aide par évaluateur */
|
||||
public List<DemandeAide> findByEvaluateurId(UUID evaluateurId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId",
|
||||
DemandeAide.class);
|
||||
query.setParameter("evaluateurId", evaluateurId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les demandes d'aide en cours d'évaluation par évaluateur */
|
||||
public List<DemandeAide> findEnCoursEvaluationByEvaluateurId(UUID evaluateurId) {
|
||||
TypedQuery<DemandeAide> query = entityManager.createQuery(
|
||||
"SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId AND d.statut = :statut",
|
||||
DemandeAide.class);
|
||||
query.setParameter("evaluateurId", evaluateurId);
|
||||
query.setParameter("statut", StatutAide.EN_COURS_EVALUATION);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Compte les demandes approuvées dans une période */
|
||||
public long countDemandesApprouvees(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("statut", StatutAide.APPROUVEE);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Compte toutes les demandes dans une période */
|
||||
public long countDemandes(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.dateCreation BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Somme des montants accordés dans une période */
|
||||
public BigDecimal sumMontantsAccordes(
|
||||
UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("statut", StatutAide.APPROUVEE);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "d.dateDemande DESC";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
for (int i = 0; i < sort.getColumns().size(); i++) {
|
||||
if (i > 0) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
Sort.Column column = sort.getColumns().get(i);
|
||||
orderBy.append("d.").append(column.getName());
|
||||
if (column.getDirection() == Sort.Direction.Descending) {
|
||||
orderBy.append(" DESC");
|
||||
} else {
|
||||
orderBy.append(" ASC");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.document.TypeDocument;
|
||||
import dev.lions.unionflow.server.entity.Document;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Document
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class DocumentRepository implements PanacheRepository<Document> {
|
||||
|
||||
/**
|
||||
* Trouve un document par son UUID
|
||||
*
|
||||
* @param id UUID du document
|
||||
* @return Document ou Optional.empty()
|
||||
*/
|
||||
public Optional<Document> findDocumentById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un document par son hash MD5
|
||||
*
|
||||
* @param hashMd5 Hash MD5
|
||||
* @return Document ou Optional.empty()
|
||||
*/
|
||||
public Optional<Document> findByHashMd5(String hashMd5) {
|
||||
return find("hashMd5 = ?1 AND actif = true", hashMd5).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un document par son hash SHA256
|
||||
*
|
||||
* @param hashSha256 Hash SHA256
|
||||
* @return Document ou Optional.empty()
|
||||
*/
|
||||
public Optional<Document> findByHashSha256(String hashSha256) {
|
||||
return find("hashSha256 = ?1 AND actif = true", hashSha256).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les documents par type
|
||||
*
|
||||
* @param type Type de document
|
||||
* @return Liste des documents
|
||||
*/
|
||||
public List<Document> findByType(TypeDocument type) {
|
||||
return find("typeDocument = ?1 AND actif = true ORDER BY dateCreation DESC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les documents actifs
|
||||
*
|
||||
* @return Liste des documents actifs
|
||||
*/
|
||||
public List<Document> findAllActifs() {
|
||||
return find("actif = true ORDER BY dateCreation DESC").list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.EcritureComptable;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité EcritureComptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class EcritureComptableRepository implements PanacheRepository<EcritureComptable> {
|
||||
|
||||
/**
|
||||
* Trouve une écriture comptable par son UUID
|
||||
*
|
||||
* @param id UUID de l'écriture comptable
|
||||
* @return Écriture comptable ou Optional.empty()
|
||||
*/
|
||||
public Optional<EcritureComptable> findEcritureComptableById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une écriture par son numéro de pièce
|
||||
*
|
||||
* @param numeroPiece Numéro de pièce
|
||||
* @return Écriture ou Optional.empty()
|
||||
*/
|
||||
public Optional<EcritureComptable> findByNumeroPiece(String numeroPiece) {
|
||||
return find("numeroPiece = ?1 AND actif = true", numeroPiece).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures d'un journal
|
||||
*
|
||||
* @param journalId ID du journal
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptable> findByJournalId(UUID journalId) {
|
||||
return find("journal.id = ?1 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC", journalId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptable> findByOrganisationId(UUID organisationId) {
|
||||
return find(
|
||||
"organisation.id = ?1 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC",
|
||||
organisationId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures d'un paiement
|
||||
*
|
||||
* @param paiementId ID du paiement
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptable> findByPaiementId(UUID paiementId) {
|
||||
return find("paiement.id = ?1 AND actif = true ORDER BY dateEcriture DESC", paiementId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures dans une période
|
||||
*
|
||||
* @param dateDebut Date de début
|
||||
* @param dateFin Date de fin
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptable> findByPeriode(LocalDate dateDebut, LocalDate dateFin) {
|
||||
return find(
|
||||
"dateEcriture >= ?1 AND dateEcriture <= ?2 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC",
|
||||
dateDebut,
|
||||
dateFin)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures non pointées
|
||||
*
|
||||
* @return Liste des écritures non pointées
|
||||
*/
|
||||
public List<EcritureComptable> findNonPointees() {
|
||||
return find("pointe = false AND actif = true ORDER BY dateEcriture ASC").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les écritures avec un lettrage spécifique
|
||||
*
|
||||
* @param lettrage Lettrage
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptable> findByLettrage(String lettrage) {
|
||||
return find("lettrage = ?1 AND actif = true ORDER BY dateEcriture DESC", lettrage).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Événement avec UUID
|
||||
*
|
||||
* <p>Fournit les méthodes d'accès aux données pour la gestion des événements avec des
|
||||
* fonctionnalités de recherche avancées et de filtrage.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class EvenementRepository extends BaseRepository<Evenement> {
|
||||
|
||||
public EvenementRepository() {
|
||||
super(Evenement.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un événement par son titre (recherche exacte)
|
||||
*
|
||||
* @param titre le titre de l'événement
|
||||
* @return l'événement trouvé ou Optional.empty()
|
||||
*/
|
||||
public Optional<Evenement> findByTitre(String titre) {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.titre = :titre", Evenement.class);
|
||||
query.setParameter("titre", titre);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les événements actifs
|
||||
*
|
||||
* @return la liste des événements actifs
|
||||
*/
|
||||
public List<Evenement> findAllActifs() {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.actif = true", Evenement.class);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les événements actifs avec pagination et tri
|
||||
*
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements actifs
|
||||
*/
|
||||
public List<Evenement> findAllActifs(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.actif = true" + orderBy, Evenement.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'événements actifs
|
||||
*
|
||||
* @return le nombre d'événements actifs
|
||||
*/
|
||||
public long countActifs() {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par statut
|
||||
*
|
||||
* @param statut le statut recherché
|
||||
* @return la liste des événements avec ce statut
|
||||
*/
|
||||
public List<Evenement> findByStatut(StatutEvenement statut) {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.statut = :statut", Evenement.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par statut avec pagination et tri
|
||||
*
|
||||
* @param statut le statut recherché
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements avec ce statut
|
||||
*/
|
||||
public List<Evenement> findByStatut(StatutEvenement statut, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.statut = :statut" + orderBy, Evenement.class);
|
||||
query.setParameter("statut", statut);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par type
|
||||
*
|
||||
* @param type le type d'événement recherché
|
||||
* @return la liste des événements de ce type
|
||||
*/
|
||||
public List<Evenement> findByType(TypeEvenement type) {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.typeEvenement = :type", Evenement.class);
|
||||
query.setParameter("type", type);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par type avec pagination et tri
|
||||
*
|
||||
* @param type le type d'événement recherché
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements de ce type
|
||||
*/
|
||||
public List<Evenement> findByType(TypeEvenement type, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.typeEvenement = :type" + orderBy, Evenement.class);
|
||||
query.setParameter("type", type);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par organisation
|
||||
*
|
||||
* @param organisationId l'UUID de l'organisation
|
||||
* @return la liste des événements de cette organisation
|
||||
*/
|
||||
public List<Evenement> findByOrganisation(UUID organisationId) {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId", Evenement.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements par organisation avec pagination et tri
|
||||
*
|
||||
* @param organisationId l'UUID de l'organisation
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements de cette organisation
|
||||
*/
|
||||
public List<Evenement> findByOrganisation(UUID organisationId, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId" + orderBy,
|
||||
Evenement.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements à venir (date de début future)
|
||||
*
|
||||
* @return la liste des événements à venir
|
||||
*/
|
||||
public List<Evenement> findEvenementsAVenir() {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true",
|
||||
Evenement.class);
|
||||
query.setParameter("maintenant", LocalDateTime.now());
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements à venir avec pagination et tri
|
||||
*
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements à venir
|
||||
*/
|
||||
public List<Evenement> findEvenementsAVenir(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true" + orderBy,
|
||||
Evenement.class);
|
||||
query.setParameter("maintenant", LocalDateTime.now());
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements visibles au public
|
||||
*
|
||||
* @return la liste des événements publics
|
||||
*/
|
||||
public List<Evenement> findEvenementsPublics() {
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true",
|
||||
Evenement.class);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les événements visibles au public avec pagination et tri
|
||||
*
|
||||
* @param page la page demandée
|
||||
* @param sort le tri à appliquer
|
||||
* @return la liste paginée des événements publics
|
||||
*/
|
||||
public List<Evenement> findEvenementsPublics(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(
|
||||
"SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true" + orderBy,
|
||||
Evenement.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée d'événements avec filtres multiples
|
||||
*
|
||||
* @param recherche terme de recherche (titre, description)
|
||||
* @param statut statut de l'événement (optionnel)
|
||||
* @param type type d'événement (optionnel)
|
||||
* @param organisationId UUID de l'organisation (optionnel)
|
||||
* @param organisateurId UUID de l'organisateur (optionnel)
|
||||
* @param dateDebutMin date de début minimum (optionnel)
|
||||
* @param dateDebutMax date de début maximum (optionnel)
|
||||
* @param visiblePublic visibilité publique (optionnel)
|
||||
* @param inscriptionRequise inscription requise (optionnel)
|
||||
* @param actif statut actif (optionnel)
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return la liste paginée des événements correspondants aux critères
|
||||
*/
|
||||
public List<Evenement> rechercheAvancee(
|
||||
String recherche,
|
||||
StatutEvenement statut,
|
||||
TypeEvenement type,
|
||||
UUID organisationId,
|
||||
UUID organisateurId,
|
||||
LocalDateTime dateDebutMin,
|
||||
LocalDateTime dateDebutMax,
|
||||
Boolean visiblePublic,
|
||||
Boolean inscriptionRequise,
|
||||
Boolean actif,
|
||||
Page page,
|
||||
Sort sort) {
|
||||
StringBuilder jpql = new StringBuilder("SELECT e FROM Evenement e WHERE 1=1");
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
||||
if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
jpql.append(
|
||||
" AND (LOWER(e.titre) LIKE LOWER(:recherche) OR LOWER(e.description) LIKE LOWER(:recherche) OR LOWER(e.lieu) LIKE LOWER(:recherche))");
|
||||
params.put("recherche", "%" + recherche.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
if (statut != null) {
|
||||
jpql.append(" AND e.statut = :statut");
|
||||
params.put("statut", statut);
|
||||
}
|
||||
|
||||
if (type != null) {
|
||||
jpql.append(" AND e.typeEvenement = :type");
|
||||
params.put("type", type);
|
||||
}
|
||||
|
||||
if (organisationId != null) {
|
||||
jpql.append(" AND e.organisation.id = :organisationId");
|
||||
params.put("organisationId", organisationId);
|
||||
}
|
||||
|
||||
if (organisateurId != null) {
|
||||
jpql.append(" AND e.organisateur.id = :organisateurId");
|
||||
params.put("organisateurId", organisateurId);
|
||||
}
|
||||
|
||||
if (dateDebutMin != null) {
|
||||
jpql.append(" AND e.dateDebut >= :dateDebutMin");
|
||||
params.put("dateDebutMin", dateDebutMin);
|
||||
}
|
||||
|
||||
if (dateDebutMax != null) {
|
||||
jpql.append(" AND e.dateDebut <= :dateDebutMax");
|
||||
params.put("dateDebutMax", dateDebutMax);
|
||||
}
|
||||
|
||||
if (visiblePublic != null) {
|
||||
jpql.append(" AND e.visiblePublic = :visiblePublic");
|
||||
params.put("visiblePublic", visiblePublic);
|
||||
}
|
||||
|
||||
if (inscriptionRequise != null) {
|
||||
jpql.append(" AND e.inscriptionRequise = :inscriptionRequise");
|
||||
params.put("inscriptionRequise", inscriptionRequise);
|
||||
}
|
||||
|
||||
if (actif != null) {
|
||||
jpql.append(" AND e.actif = :actif");
|
||||
params.put("actif", actif);
|
||||
}
|
||||
|
||||
if (sort != null) {
|
||||
jpql.append(" ORDER BY ").append(buildOrderBy(sort));
|
||||
}
|
||||
|
||||
TypedQuery<Evenement> query = entityManager.createQuery(jpql.toString(), Evenement.class);
|
||||
for (Map.Entry<String, Object> param : params.entrySet()) {
|
||||
query.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les statistiques des événements
|
||||
*
|
||||
* @return une map contenant les statistiques
|
||||
*/
|
||||
public Map<String, Long> getStatistiques() {
|
||||
Map<String, Long> stats = new HashMap<>();
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
|
||||
TypedQuery<Long> totalQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e", Long.class);
|
||||
stats.put("total", totalQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> actifsQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class);
|
||||
stats.put("actifs", actifsQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> inactifsQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.actif = false", Long.class);
|
||||
stats.put("inactifs", inactifsQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> aVenirQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true",
|
||||
Long.class);
|
||||
aVenirQuery.setParameter("maintenant", maintenant);
|
||||
stats.put("aVenir", aVenirQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> enCoursQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut <= :maintenant AND (e.dateFin IS NULL OR e.dateFin >= :maintenant) AND e.actif = true",
|
||||
Long.class);
|
||||
enCoursQuery.setParameter("maintenant", maintenant);
|
||||
stats.put("enCours", enCoursQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> passesQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE (e.dateFin < :maintenant OR (e.dateFin IS NULL AND e.dateDebut < :maintenant)) AND e.actif = true",
|
||||
Long.class);
|
||||
passesQuery.setParameter("maintenant", maintenant);
|
||||
stats.put("passes", passesQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> publicsQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true",
|
||||
Long.class);
|
||||
stats.put("publics", publicsQuery.getSingleResult());
|
||||
|
||||
TypedQuery<Long> avecInscriptionQuery = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.inscriptionRequise = true AND e.actif = true",
|
||||
Long.class);
|
||||
stats.put("avecInscription", avecInscriptionQuery.getSingleResult());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les événements dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut date de début
|
||||
* @param fin date de fin
|
||||
* @return nombre d'événements
|
||||
*/
|
||||
public long countEvenements(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la moyenne de participants dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut date de début
|
||||
* @param fin date de fin
|
||||
* @return moyenne de participants ou null
|
||||
*/
|
||||
public Double calculerMoyenneParticipants(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Double> query = entityManager.createQuery(
|
||||
"SELECT AVG(e.nombreParticipants) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
|
||||
Double.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le total des participations dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut date de début
|
||||
* @param fin date de fin
|
||||
* @return total des participations
|
||||
*/
|
||||
public Long countTotalParticipations(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COALESCE(SUM(e.nombreParticipants), 0) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "e.dateDebut";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
for (int i = 0; i < sort.getColumns().size(); i++) {
|
||||
if (i > 0) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
Sort.Column column = sort.getColumns().get(i);
|
||||
orderBy.append("e.").append(column.getName());
|
||||
if (column.getDirection() == Sort.Direction.Descending) {
|
||||
orderBy.append(" DESC");
|
||||
} else {
|
||||
orderBy.append(" ASC");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
|
||||
import dev.lions.unionflow.server.entity.JournalComptable;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité JournalComptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class JournalComptableRepository implements PanacheRepository<JournalComptable> {
|
||||
|
||||
/**
|
||||
* Trouve un journal comptable par son UUID
|
||||
*
|
||||
* @param id UUID du journal comptable
|
||||
* @return Journal comptable ou Optional.empty()
|
||||
*/
|
||||
public Optional<JournalComptable> findJournalComptableById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un journal par son code
|
||||
*
|
||||
* @param code Code du journal
|
||||
* @return Journal ou Optional.empty()
|
||||
*/
|
||||
public Optional<JournalComptable> findByCode(String code) {
|
||||
return find("code = ?1 AND actif = true", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les journaux par type
|
||||
*
|
||||
* @param type Type de journal
|
||||
* @return Liste des journaux
|
||||
*/
|
||||
public List<JournalComptable> findByType(TypeJournalComptable type) {
|
||||
return find("typeJournal = ?1 AND actif = true ORDER BY code ASC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les journaux ouverts
|
||||
*
|
||||
* @return Liste des journaux ouverts
|
||||
*/
|
||||
public List<JournalComptable> findJournauxOuverts() {
|
||||
return find("statut = ?1 AND actif = true ORDER BY code ASC", "OUVERT").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les journaux pour une date donnée
|
||||
*
|
||||
* @param date Date à vérifier
|
||||
* @return Liste des journaux actifs pour cette date
|
||||
*/
|
||||
public List<JournalComptable> findJournauxPourDate(LocalDate date) {
|
||||
return find(
|
||||
"(dateDebut IS NULL OR dateDebut <= ?1) AND (dateFin IS NULL OR dateFin >= ?1) AND actif = true ORDER BY code ASC",
|
||||
date)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les journaux actifs
|
||||
*
|
||||
* @return Liste des journaux actifs
|
||||
*/
|
||||
public List<JournalComptable> findAllActifs() {
|
||||
return find("actif = true ORDER BY code ASC").list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.LigneEcriture;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité LigneEcriture
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class LigneEcritureRepository implements PanacheRepository<LigneEcriture> {
|
||||
|
||||
/**
|
||||
* Trouve une ligne d'écriture par son UUID
|
||||
*
|
||||
* @param id UUID de la ligne d'écriture
|
||||
* @return Ligne d'écriture ou Optional.empty()
|
||||
*/
|
||||
public Optional<LigneEcriture> findLigneEcritureById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les lignes d'une écriture
|
||||
*
|
||||
* @param ecritureId ID de l'écriture
|
||||
* @return Liste des lignes
|
||||
*/
|
||||
public List<LigneEcriture> findByEcritureId(UUID ecritureId) {
|
||||
return find("ecriture.id = ?1 ORDER BY numeroLigne ASC", ecritureId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les lignes d'un compte comptable
|
||||
*
|
||||
* @param compteComptableId ID du compte comptable
|
||||
* @return Liste des lignes
|
||||
*/
|
||||
public List<LigneEcriture> findByCompteComptableId(UUID compteComptableId) {
|
||||
return find("compteComptable.id = ?1 ORDER BY ecriture.dateEcriture DESC, numeroLigne ASC", compteComptableId)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Repository pour l'entité Membre avec UUID */
|
||||
@ApplicationScoped
|
||||
public class MembreRepository extends BaseRepository<Membre> {
|
||||
|
||||
public MembreRepository() {
|
||||
super(Membre.class);
|
||||
}
|
||||
|
||||
/** Trouve un membre par son email */
|
||||
public Optional<Membre> findByEmail(String email) {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.email = :email", Membre.class);
|
||||
query.setParameter("email", email);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/** Trouve un membre par son numéro */
|
||||
public Optional<Membre> findByNumeroMembre(String numeroMembre) {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.numeroMembre = :numeroMembre", Membre.class);
|
||||
query.setParameter("numeroMembre", numeroMembre);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/** Trouve tous les membres actifs */
|
||||
public List<Membre> findAllActifs() {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.actif = true", Membre.class);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Compte le nombre de membres actifs */
|
||||
public long countActifs() {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(m) FROM Membre m WHERE m.actif = true", Long.class);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Trouve les membres par nom ou prénom (recherche partielle) */
|
||||
public List<Membre> findByNomOrPrenom(String recherche) {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)",
|
||||
Membre.class);
|
||||
query.setParameter("recherche", "%" + recherche + "%");
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve tous les membres actifs avec pagination et tri */
|
||||
public List<Membre> findAllActifs(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.actif = true" + orderBy, Membre.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les membres par nom ou prénom avec pagination et tri */
|
||||
public List<Membre> findByNomOrPrenom(String recherche, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)" + orderBy,
|
||||
Membre.class);
|
||||
query.setParameter("recherche", "%" + recherche + "%");
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Compte les nouveaux membres depuis une date donnée */
|
||||
public long countNouveauxMembres(LocalDate depuis) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(m) FROM Membre m WHERE m.dateAdhesion >= :depuis", Long.class);
|
||||
query.setParameter("depuis", depuis);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Trouve les membres par statut avec pagination */
|
||||
public List<Membre> findByStatut(boolean actif, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.actif = :actif" + orderBy, Membre.class);
|
||||
query.setParameter("actif", actif);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Trouve les membres par tranche d'âge */
|
||||
public List<Membre> findByTrancheAge(int ageMin, int ageMax, Page page, Sort sort) {
|
||||
LocalDate dateNaissanceMax = LocalDate.now().minusYears(ageMin);
|
||||
LocalDate dateNaissanceMin = LocalDate.now().minusYears(ageMax + 1);
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.dateNaissance BETWEEN :dateMin AND :dateMax" + orderBy,
|
||||
Membre.class);
|
||||
query.setParameter("dateMin", dateNaissanceMin);
|
||||
query.setParameter("dateMax", dateNaissanceMax);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Recherche avancée de membres */
|
||||
public List<Membre> rechercheAvancee(
|
||||
String recherche,
|
||||
Boolean actif,
|
||||
LocalDate dateAdhesionMin,
|
||||
LocalDate dateAdhesionMax,
|
||||
Page page,
|
||||
Sort sort) {
|
||||
StringBuilder jpql = new StringBuilder("SELECT m FROM Membre m WHERE 1=1");
|
||||
|
||||
if (recherche != null && !recherche.isEmpty()) {
|
||||
jpql.append(" AND (LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche) OR LOWER(m.email) LIKE LOWER(:recherche))");
|
||||
}
|
||||
if (actif != null) {
|
||||
jpql.append(" AND m.actif = :actif");
|
||||
}
|
||||
if (dateAdhesionMin != null) {
|
||||
jpql.append(" AND m.dateAdhesion >= :dateAdhesionMin");
|
||||
}
|
||||
if (dateAdhesionMax != null) {
|
||||
jpql.append(" AND m.dateAdhesion <= :dateAdhesionMax");
|
||||
}
|
||||
|
||||
if (sort != null) {
|
||||
jpql.append(" ORDER BY ").append(buildOrderBy(sort));
|
||||
}
|
||||
|
||||
TypedQuery<Membre> query = entityManager.createQuery(jpql.toString(), Membre.class);
|
||||
|
||||
if (recherche != null && !recherche.isEmpty()) {
|
||||
query.setParameter("recherche", "%" + recherche + "%");
|
||||
}
|
||||
if (actif != null) {
|
||||
query.setParameter("actif", actif);
|
||||
}
|
||||
if (dateAdhesionMin != null) {
|
||||
query.setParameter("dateAdhesionMin", dateAdhesionMin);
|
||||
}
|
||||
if (dateAdhesionMax != null) {
|
||||
query.setParameter("dateAdhesionMax", dateAdhesionMax);
|
||||
}
|
||||
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "m.id";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
for (int i = 0; i < sort.getColumns().size(); i++) {
|
||||
if (i > 0) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
Sort.Column column = sort.getColumns().get(i);
|
||||
orderBy.append("m.").append(column.getName());
|
||||
if (column.getDirection() == Sort.Direction.Descending) {
|
||||
orderBy.append(" DESC");
|
||||
} else {
|
||||
orderBy.append(" ASC");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les membres actifs dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut Date de début
|
||||
* @param fin Date de fin
|
||||
* @return Nombre de membres actifs
|
||||
*/
|
||||
public Long countMembresActifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = true AND m.dateAdhesion BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les membres inactifs dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut Date de début
|
||||
* @param fin Date de fin
|
||||
* @return Nombre de membres inactifs
|
||||
*/
|
||||
public Long countMembresInactifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = false AND m.dateAdhesion BETWEEN :debut AND :fin",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la moyenne d'âge des membres dans une période et organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @param debut Date de début
|
||||
* @param fin Date de fin
|
||||
* @return Moyenne d'âge ou null si aucun membre
|
||||
*/
|
||||
public Double calculerMoyenneAge(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
|
||||
TypedQuery<Double> query = entityManager.createQuery(
|
||||
"SELECT AVG(YEAR(CURRENT_DATE) - YEAR(m.dateNaissance)) FROM Membre m WHERE m.organisation.id = :organisationId AND m.dateAdhesion BETWEEN :debut AND :fin",
|
||||
Double.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.MembreRole;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité MembreRole
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class MembreRoleRepository implements PanacheRepository<MembreRole> {
|
||||
|
||||
/**
|
||||
* Trouve une attribution membre-role par son UUID
|
||||
*
|
||||
* @param id UUID de l'attribution
|
||||
* @return Attribution ou Optional.empty()
|
||||
*/
|
||||
public Optional<MembreRole> findMembreRoleById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des attributions de rôles
|
||||
*/
|
||||
public List<MembreRole> findByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 AND actif = true", membreId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles actifs d'un membre (dans la période valide)
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des attributions de rôles actives
|
||||
*/
|
||||
public List<MembreRole> findActifsByMembreId(UUID membreId) {
|
||||
LocalDate aujourdhui = LocalDate.now();
|
||||
return find(
|
||||
"membre.id = ?1 AND actif = true AND (dateDebut IS NULL OR dateDebut <= ?2) AND (dateFin IS NULL OR dateFin >= ?2)",
|
||||
membreId,
|
||||
aujourdhui)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les membres ayant un rôle spécifique
|
||||
*
|
||||
* @param roleId ID du rôle
|
||||
* @return Liste des attributions de rôles
|
||||
*/
|
||||
public List<MembreRole> findByRoleId(UUID roleId) {
|
||||
return find("role.id = ?1 AND actif = true", roleId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une attribution spécifique membre-role
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @param roleId ID du rôle
|
||||
* @return Attribution ou null
|
||||
*/
|
||||
public MembreRole findByMembreAndRole(UUID membreId, UUID roleId) {
|
||||
return find("membre.id = ?1 AND role.id = ?2", membreId, roleId).firstResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification;
|
||||
import dev.lions.unionflow.server.api.enums.notification.StatutNotification;
|
||||
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
|
||||
import dev.lions.unionflow.server.entity.Notification;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Notification
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class NotificationRepository implements PanacheRepository<Notification> {
|
||||
|
||||
/**
|
||||
* Trouve une notification par son UUID
|
||||
*
|
||||
* @param id UUID de la notification
|
||||
* @return Notification ou Optional.empty()
|
||||
*/
|
||||
public Optional<Notification> findNotificationById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les notifications d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
public List<Notification> findByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 ORDER BY dateEnvoiPrevue DESC, dateCreation DESC", membreId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les notifications non lues d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des notifications non lues
|
||||
*/
|
||||
public List<Notification> findNonLuesByMembreId(UUID membreId) {
|
||||
return find(
|
||||
"membre.id = ?1 AND statut = ?2 ORDER BY priorite ASC, dateEnvoiPrevue DESC",
|
||||
membreId,
|
||||
StatutNotification.NON_LUE)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les notifications d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
public List<Notification> findByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id = ?1 ORDER BY dateEnvoiPrevue DESC, dateCreation DESC", organisationId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les notifications par type
|
||||
*
|
||||
* @param type Type de notification
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
public List<Notification> findByType(TypeNotification type) {
|
||||
return find("typeNotification = ?1 ORDER BY dateEnvoiPrevue DESC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les notifications par statut
|
||||
*
|
||||
* @param statut Statut de la notification
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
public List<Notification> findByStatut(StatutNotification statut) {
|
||||
return find("statut = ?1 ORDER BY dateEnvoiPrevue DESC", statut).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les notifications par priorité
|
||||
*
|
||||
* @param priorite Priorité de la notification
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
public List<Notification> findByPriorite(PrioriteNotification priorite) {
|
||||
return find("priorite = ?1 ORDER BY dateEnvoiPrevue DESC", priorite).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les notifications en attente d'envoi
|
||||
*
|
||||
* @return Liste des notifications en attente
|
||||
*/
|
||||
public List<Notification> findEnAttenteEnvoi() {
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
return find(
|
||||
"statut IN (?1, ?2) AND dateEnvoiPrevue <= ?3 ORDER BY priorite DESC, dateEnvoiPrevue ASC",
|
||||
StatutNotification.EN_ATTENTE,
|
||||
StatutNotification.PROGRAMMEE,
|
||||
maintenant)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les notifications échouées pouvant être retentées
|
||||
*
|
||||
* @return Liste des notifications échouées
|
||||
*/
|
||||
public List<Notification> findEchoueesRetentables() {
|
||||
return find(
|
||||
"statut IN (?1, ?2) AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateEnvoiPrevue ASC",
|
||||
StatutNotification.ECHEC_ENVOI,
|
||||
StatutNotification.ERREUR_TECHNIQUE)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Organisation avec UUID
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class OrganisationRepository extends BaseRepository<Organisation> {
|
||||
|
||||
public OrganisationRepository() {
|
||||
super(Organisation.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une organisation par son email
|
||||
*
|
||||
* @param email l'email de l'organisation
|
||||
* @return Optional contenant l'organisation si trouvée
|
||||
*/
|
||||
public Optional<Organisation> findByEmail(String email) {
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.email = :email", Organisation.class);
|
||||
query.setParameter("email", email);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une organisation par son nom
|
||||
*
|
||||
* @param nom le nom de l'organisation
|
||||
* @return Optional contenant l'organisation si trouvée
|
||||
*/
|
||||
public Optional<Organisation> findByNom(String nom) {
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.nom = :nom", Organisation.class);
|
||||
query.setParameter("nom", nom);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une organisation par son numéro d'enregistrement
|
||||
*
|
||||
* @param numeroEnregistrement le numéro d'enregistrement officiel
|
||||
* @return Optional contenant l'organisation si trouvée
|
||||
*/
|
||||
public Optional<Organisation> findByNumeroEnregistrement(String numeroEnregistrement) {
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.numeroEnregistrement = :numeroEnregistrement",
|
||||
Organisation.class);
|
||||
query.setParameter("numeroEnregistrement", numeroEnregistrement);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les organisations actives
|
||||
*
|
||||
* @return liste des organisations actives
|
||||
*/
|
||||
public List<Organisation> findAllActives() {
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true",
|
||||
Organisation.class);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les organisations actives avec pagination
|
||||
*
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations actives
|
||||
*/
|
||||
public List<Organisation> findAllActives(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true" + orderBy,
|
||||
Organisation.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'organisations actives
|
||||
*
|
||||
* @return nombre d'organisations actives
|
||||
*/
|
||||
public long countActives() {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(o) FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true",
|
||||
Long.class);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations par statut
|
||||
*
|
||||
* @param statut le statut recherché
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations avec le statut spécifié
|
||||
*/
|
||||
public List<Organisation> findByStatut(String statut, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.statut = :statut" + orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("statut", statut);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations par type
|
||||
*
|
||||
* @param typeOrganisation le type d'organisation
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations du type spécifié
|
||||
*/
|
||||
public List<Organisation> findByType(String typeOrganisation, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation" + orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("typeOrganisation", typeOrganisation);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations par ville
|
||||
*
|
||||
* @param ville la ville
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations de la ville spécifiée
|
||||
*/
|
||||
public List<Organisation> findByVille(String ville, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.ville = :ville" + orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("ville", ville);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations par pays
|
||||
*
|
||||
* @param pays le pays
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations du pays spécifié
|
||||
*/
|
||||
public List<Organisation> findByPays(String pays, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.pays = :pays" + orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("pays", pays);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations par région
|
||||
*
|
||||
* @param region la région
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations de la région spécifiée
|
||||
*/
|
||||
public List<Organisation> findByRegion(String region, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.region = :region" + orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("region", region);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations filles d'une organisation parente
|
||||
*
|
||||
* @param organisationParenteId l'UUID de l'organisation parente
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations filles
|
||||
*/
|
||||
public List<Organisation> findByOrganisationParente(
|
||||
UUID organisationParenteId, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.organisationParenteId = :organisationParenteId"
|
||||
+ orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("organisationParenteId", organisationParenteId);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations racines (sans parent)
|
||||
*
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations racines
|
||||
*/
|
||||
public List<Organisation> findOrganisationsRacines(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.organisationParenteId IS NULL" + orderBy,
|
||||
Organisation.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche d'organisations par nom ou nom court
|
||||
*
|
||||
* @param recherche terme de recherche
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations correspondantes
|
||||
*/
|
||||
public List<Organisation> findByNomOrNomCourt(String recherche, Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE LOWER(o.nom) LIKE LOWER(:recherche) OR LOWER(o.nomCourt) LIKE LOWER(:recherche)"
|
||||
+ orderBy,
|
||||
Organisation.class);
|
||||
query.setParameter("recherche", "%" + recherche + "%");
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée d'organisations
|
||||
*
|
||||
* @param nom nom (optionnel)
|
||||
* @param typeOrganisation type (optionnel)
|
||||
* @param statut statut (optionnel)
|
||||
* @param ville ville (optionnel)
|
||||
* @param region région (optionnel)
|
||||
* @param pays pays (optionnel)
|
||||
* @param page pagination
|
||||
* @return liste filtrée des organisations
|
||||
*/
|
||||
public List<Organisation> rechercheAvancee(
|
||||
String nom,
|
||||
String typeOrganisation,
|
||||
String statut,
|
||||
String ville,
|
||||
String region,
|
||||
String pays,
|
||||
Page page) {
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT o FROM Organisation o WHERE 1=1");
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
if (nom != null && !nom.isEmpty()) {
|
||||
queryBuilder.append(" AND (LOWER(o.nom) LIKE LOWER(:nom) OR LOWER(o.nomCourt) LIKE LOWER(:nom))");
|
||||
parameters.put("nom", "%" + nom.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
if (typeOrganisation != null && !typeOrganisation.isEmpty()) {
|
||||
queryBuilder.append(" AND o.typeOrganisation = :typeOrganisation");
|
||||
parameters.put("typeOrganisation", typeOrganisation);
|
||||
}
|
||||
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
queryBuilder.append(" AND o.statut = :statut");
|
||||
parameters.put("statut", statut);
|
||||
}
|
||||
|
||||
if (ville != null && !ville.isEmpty()) {
|
||||
queryBuilder.append(" AND LOWER(o.ville) LIKE LOWER(:ville)");
|
||||
parameters.put("ville", "%" + ville.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
if (region != null && !region.isEmpty()) {
|
||||
queryBuilder.append(" AND LOWER(o.region) LIKE LOWER(:region)");
|
||||
parameters.put("region", "%" + region.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
if (pays != null && !pays.isEmpty()) {
|
||||
queryBuilder.append(" AND LOWER(o.pays) LIKE LOWER(:pays)");
|
||||
parameters.put("pays", "%" + pays.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
queryBuilder.append(" ORDER BY o.nom ASC");
|
||||
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
queryBuilder.toString(), Organisation.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
query.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les nouvelles organisations depuis une date donnée
|
||||
*
|
||||
* @param depuis date de référence
|
||||
* @return nombre de nouvelles organisations
|
||||
*/
|
||||
public long countNouvellesOrganisations(LocalDate depuis) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(o) FROM Organisation o WHERE o.dateCreation >= :depuis", Long.class);
|
||||
query.setParameter("depuis", depuis.atStartOfDay());
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations publiques (visibles dans l'annuaire)
|
||||
*
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations publiques
|
||||
*/
|
||||
public List<Organisation> findOrganisationsPubliques(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.organisationPublique = true AND o.statut = 'ACTIVE' AND o.actif = true"
|
||||
+ orderBy,
|
||||
Organisation.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les organisations acceptant de nouveaux membres
|
||||
*
|
||||
* @param page pagination
|
||||
* @param sort tri
|
||||
* @return liste paginée des organisations acceptant de nouveaux membres
|
||||
*/
|
||||
public List<Organisation> findOrganisationsOuvertes(Page page, Sort sort) {
|
||||
String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
|
||||
TypedQuery<Organisation> query = entityManager.createQuery(
|
||||
"SELECT o FROM Organisation o WHERE o.accepteNouveauxMembres = true AND o.statut = 'ACTIVE' AND o.actif = true"
|
||||
+ orderBy,
|
||||
Organisation.class);
|
||||
query.setFirstResult(page.index * page.size);
|
||||
query.setMaxResults(page.size);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les organisations par statut
|
||||
*
|
||||
* @param statut le statut
|
||||
* @return nombre d'organisations avec ce statut
|
||||
*/
|
||||
public long countByStatut(String statut) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(o) FROM Organisation o WHERE o.statut = :statut", Long.class);
|
||||
query.setParameter("statut", statut);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte les organisations par type
|
||||
*
|
||||
* @param typeOrganisation le type d'organisation
|
||||
* @return nombre d'organisations de ce type
|
||||
*/
|
||||
public long countByType(String typeOrganisation) {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(o) FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation",
|
||||
Long.class);
|
||||
query.setParameter("typeOrganisation", typeOrganisation);
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
return "o.id";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
for (int i = 0; i < sort.getColumns().size(); i++) {
|
||||
if (i > 0) {
|
||||
orderBy.append(", ");
|
||||
}
|
||||
Sort.Column column = sort.getColumns().get(i);
|
||||
orderBy.append("o.").append(column.getName());
|
||||
if (column.getDirection() == Sort.Direction.Descending) {
|
||||
orderBy.append(" DESC");
|
||||
} else {
|
||||
orderBy.append(" ASC");
|
||||
}
|
||||
}
|
||||
return orderBy.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
|
||||
import dev.lions.unionflow.server.entity.Paiement;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Paiement
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PaiementRepository implements PanacheRepository<Paiement> {
|
||||
|
||||
/**
|
||||
* Trouve un paiement par son UUID
|
||||
*
|
||||
* @param id UUID du paiement
|
||||
* @return Paiement ou Optional.empty()
|
||||
*/
|
||||
public Optional<Paiement> findPaiementById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un paiement par son numéro de référence
|
||||
*
|
||||
* @param numeroReference Numéro de référence
|
||||
* @return Paiement ou Optional.empty()
|
||||
*/
|
||||
public Optional<Paiement> findByNumeroReference(String numeroReference) {
|
||||
return find("numeroReference", numeroReference).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les paiements d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), membreId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les paiements par statut
|
||||
*
|
||||
* @param statut Statut du paiement
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findByStatut(StatutPaiement statut) {
|
||||
return find("statutPaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), statut)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les paiements par méthode
|
||||
*
|
||||
* @param methode Méthode de paiement
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findByMethode(MethodePaiement methode) {
|
||||
return find("methodePaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), methode)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les paiements validés dans une période
|
||||
*
|
||||
* @param dateDebut Date de début
|
||||
* @param dateFin Date de fin
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findValidesParPeriode(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
return find(
|
||||
"statutPaiement = ?1 AND dateValidation >= ?2 AND dateValidation <= ?3 AND actif = true",
|
||||
Sort.by("dateValidation", Sort.Direction.Descending),
|
||||
StatutPaiement.VALIDE,
|
||||
dateDebut,
|
||||
dateFin)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le montant total des paiements validés dans une période
|
||||
*
|
||||
* @param dateDebut Date de début
|
||||
* @param dateFin Date de fin
|
||||
* @return Montant total
|
||||
*/
|
||||
public BigDecimal calculerMontantTotalValides(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
List<Paiement> paiements = findValidesParPeriode(dateDebut, dateFin);
|
||||
return paiements.stream()
|
||||
.map(Paiement::getMontant)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Permission;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Permission
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PermissionRepository implements PanacheRepository<Permission> {
|
||||
|
||||
/**
|
||||
* Trouve une permission par son UUID
|
||||
*
|
||||
* @param id UUID de la permission
|
||||
* @return Permission ou Optional.empty()
|
||||
*/
|
||||
public Optional<Permission> findPermissionById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une permission par son code
|
||||
*
|
||||
* @param code Code de la permission
|
||||
* @return Permission ou Optional.empty()
|
||||
*/
|
||||
public Optional<Permission> findByCode(String code) {
|
||||
return find("code", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les permissions par module
|
||||
*
|
||||
* @param module Nom du module
|
||||
* @return Liste des permissions
|
||||
*/
|
||||
public List<Permission> findByModule(String module) {
|
||||
return find("LOWER(module) = LOWER(?1) AND actif = true", module).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les permissions par ressource
|
||||
*
|
||||
* @param ressource Nom de la ressource
|
||||
* @return Liste des permissions
|
||||
*/
|
||||
public List<Permission> findByRessource(String ressource) {
|
||||
return find("LOWER(ressource) = LOWER(?1) AND actif = true", ressource).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les permissions par module et ressource
|
||||
*
|
||||
* @param module Nom du module
|
||||
* @param ressource Nom de la ressource
|
||||
* @return Liste des permissions
|
||||
*/
|
||||
public List<Permission> findByModuleAndRessource(String module, String ressource) {
|
||||
return find(
|
||||
"LOWER(module) = LOWER(?1) AND LOWER(ressource) = LOWER(?2) AND actif = true",
|
||||
module,
|
||||
ressource)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les permissions actives
|
||||
*
|
||||
* @return Liste des permissions actives
|
||||
*/
|
||||
public List<Permission> findAllActives() {
|
||||
return find("actif = true", Sort.by("module", Sort.Direction.Ascending)
|
||||
.and("ressource", Sort.Direction.Ascending)
|
||||
.and("action", Sort.Direction.Ascending)).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.PieceJointe;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité PieceJointe
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PieceJointeRepository implements PanacheRepository<PieceJointe> {
|
||||
|
||||
/**
|
||||
* Trouve une pièce jointe par son UUID
|
||||
*
|
||||
* @param id UUID de la pièce jointe
|
||||
* @return Pièce jointe ou Optional.empty()
|
||||
*/
|
||||
public Optional<PieceJointe> findPieceJointeById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'un document
|
||||
*
|
||||
* @param documentId ID du document
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByDocumentId(UUID documentId) {
|
||||
return find("document.id = ?1 ORDER BY ordre ASC", documentId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByMembreId(UUID membreId) {
|
||||
return find("membre.id = ?1 ORDER BY ordre ASC", membreId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id = ?1 ORDER BY ordre ASC", organisationId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'une cotisation
|
||||
*
|
||||
* @param cotisationId ID de la cotisation
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByCotisationId(UUID cotisationId) {
|
||||
return find("cotisation.id = ?1 ORDER BY ordre ASC", cotisationId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'une adhésion
|
||||
*
|
||||
* @param adhesionId ID de l'adhésion
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByAdhesionId(UUID adhesionId) {
|
||||
return find("adhesion.id = ?1 ORDER BY ordre ASC", adhesionId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'une demande d'aide
|
||||
*
|
||||
* @param demandeAideId ID de la demande d'aide
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByDemandeAideId(UUID demandeAideId) {
|
||||
return find("demandeAide.id = ?1 ORDER BY ordre ASC", demandeAideId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les pièces jointes d'une transaction Wave
|
||||
*
|
||||
* @param transactionWaveId ID de la transaction Wave
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointe> findByTransactionWaveId(UUID transactionWaveId) {
|
||||
return find("transactionWave.id = ?1 ORDER BY ordre ASC", transactionWaveId).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.RolePermission;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité RolePermission
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class RolePermissionRepository implements PanacheRepository<RolePermission> {
|
||||
|
||||
/**
|
||||
* Trouve une association rôle-permission par son UUID
|
||||
*
|
||||
* @param id UUID de l'association
|
||||
* @return Association ou Optional.empty()
|
||||
*/
|
||||
public Optional<RolePermission> findRolePermissionById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les permissions d'un rôle
|
||||
*
|
||||
* @param roleId ID du rôle
|
||||
* @return Liste des associations rôle-permission
|
||||
*/
|
||||
public List<RolePermission> findByRoleId(UUID roleId) {
|
||||
return find("role.id = ?1 AND actif = true", roleId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles ayant une permission spécifique
|
||||
*
|
||||
* @param permissionId ID de la permission
|
||||
* @return Liste des associations rôle-permission
|
||||
*/
|
||||
public List<RolePermission> findByPermissionId(UUID permissionId) {
|
||||
return find("permission.id = ?1 AND actif = true", permissionId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une association spécifique rôle-permission
|
||||
*
|
||||
* @param roleId ID du rôle
|
||||
* @param permissionId ID de la permission
|
||||
* @return Association ou null
|
||||
*/
|
||||
public RolePermission findByRoleAndPermission(UUID roleId, UUID permissionId) {
|
||||
return find("role.id = ?1 AND permission.id = ?2", roleId, permissionId).firstResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Role;
|
||||
import dev.lions.unionflow.server.entity.Role.TypeRole;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité Role
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class RoleRepository implements PanacheRepository<Role> {
|
||||
|
||||
/**
|
||||
* Trouve un rôle par son UUID
|
||||
*
|
||||
* @param id UUID du rôle
|
||||
* @return Rôle ou Optional.empty()
|
||||
*/
|
||||
public Optional<Role> findRoleById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un rôle par son code
|
||||
*
|
||||
* @param code Code du rôle
|
||||
* @return Rôle ou Optional.empty()
|
||||
*/
|
||||
public Optional<Role> findByCode(String code) {
|
||||
return find("code", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles système
|
||||
*
|
||||
* @return Liste des rôles système
|
||||
*/
|
||||
public List<Role> findRolesSysteme() {
|
||||
return find("typeRole = ?1 AND actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending), TypeRole.SYSTEME)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des rôles
|
||||
*/
|
||||
public List<Role> findByOrganisationId(UUID organisationId) {
|
||||
return find("organisation.id = ?1 AND actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending), organisationId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles actifs
|
||||
*
|
||||
* @return Liste des rôles actifs
|
||||
*/
|
||||
public List<Role> findAllActifs() {
|
||||
return find("actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending)).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les rôles par type
|
||||
*
|
||||
* @param typeRole Type de rôle
|
||||
* @return Liste des rôles
|
||||
*/
|
||||
public List<Role> findByType(TypeRole typeRole) {
|
||||
return find("typeRole = ?1 AND actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending), typeRole)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.TemplateNotification;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité TemplateNotification
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TemplateNotificationRepository implements PanacheRepository<TemplateNotification> {
|
||||
|
||||
/**
|
||||
* Trouve un template par son UUID
|
||||
*
|
||||
* @param id UUID du template
|
||||
* @return Template ou Optional.empty()
|
||||
*/
|
||||
public Optional<TemplateNotification> findTemplateNotificationById(UUID id) {
|
||||
return find("id = ?1 AND actif = true", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un template par son code
|
||||
*
|
||||
* @param code Code du template
|
||||
* @return Template ou Optional.empty()
|
||||
*/
|
||||
public Optional<TemplateNotification> findByCode(String code) {
|
||||
return find("code = ?1 AND actif = true", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les templates actifs
|
||||
*
|
||||
* @return Liste des templates actifs
|
||||
*/
|
||||
public List<TemplateNotification> findAllActifs() {
|
||||
return find("actif = true ORDER BY code ASC").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les templates par langue
|
||||
*
|
||||
* @param langue Code langue (ex: fr, en)
|
||||
* @return Liste des templates
|
||||
*/
|
||||
public List<TemplateNotification> findByLangue(String langue) {
|
||||
return find("langue = ?1 AND actif = true ORDER BY code ASC", langue).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
|
||||
import dev.lions.unionflow.server.entity.TransactionWave;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité TransactionWave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TransactionWaveRepository implements PanacheRepository<TransactionWave> {
|
||||
|
||||
/**
|
||||
* Trouve une transaction par son UUID
|
||||
*
|
||||
* @param id UUID de la transaction
|
||||
* @return Transaction ou Optional.empty()
|
||||
*/
|
||||
public Optional<TransactionWave> findTransactionWaveById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une transaction par son identifiant Wave
|
||||
*
|
||||
* @param waveTransactionId Identifiant Wave
|
||||
* @return Transaction ou Optional.empty()
|
||||
*/
|
||||
public Optional<TransactionWave> findByWaveTransactionId(String waveTransactionId) {
|
||||
return find("waveTransactionId = ?1", waveTransactionId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une transaction par son identifiant de requête
|
||||
*
|
||||
* @param waveRequestId Identifiant de requête
|
||||
* @return Transaction ou Optional.empty()
|
||||
*/
|
||||
public Optional<TransactionWave> findByWaveRequestId(String waveRequestId) {
|
||||
return find("waveRequestId = ?1", waveRequestId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les transactions d'un compte Wave
|
||||
*
|
||||
* @param compteWaveId ID du compte Wave
|
||||
* @return Liste des transactions
|
||||
*/
|
||||
public List<TransactionWave> findByCompteWaveId(UUID compteWaveId) {
|
||||
return find("compteWave.id = ?1 ORDER BY dateCreation DESC", compteWaveId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les transactions par statut
|
||||
*
|
||||
* @param statut Statut de la transaction
|
||||
* @return Liste des transactions
|
||||
*/
|
||||
public List<TransactionWave> findByStatut(StatutTransactionWave statut) {
|
||||
return find("statutTransaction = ?1 ORDER BY dateCreation DESC", statut).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les transactions par type
|
||||
*
|
||||
* @param type Type de transaction
|
||||
* @return Liste des transactions
|
||||
*/
|
||||
public List<TransactionWave> findByType(TypeTransactionWave type) {
|
||||
return find("typeTransaction = ?1 ORDER BY dateCreation DESC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les transactions réussies dans une période
|
||||
*
|
||||
* @param compteWaveId ID du compte Wave
|
||||
* @return Liste des transactions réussies
|
||||
*/
|
||||
public List<TransactionWave> findReussiesByCompteWave(UUID compteWaveId) {
|
||||
return find(
|
||||
"compteWave.id = ?1 AND statutTransaction = ?2 ORDER BY dateCreation DESC",
|
||||
compteWaveId,
|
||||
StatutTransactionWave.REUSSIE)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les transactions échouées pouvant être retentées
|
||||
*
|
||||
* @return Liste des transactions échouées
|
||||
*/
|
||||
public List<TransactionWave> findEchoueesRetentables() {
|
||||
return find(
|
||||
"statutTransaction IN (?1, ?2) AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateCreation ASC",
|
||||
StatutTransactionWave.ECHOUE,
|
||||
StatutTransactionWave.EXPIRED)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.TypeOrganisationEntity;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité {@link TypeOrganisationEntity}.
|
||||
*
|
||||
* <p>Permet de gérer le catalogue des types d'organisations.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TypeOrganisationRepository extends BaseRepository<TypeOrganisationEntity> {
|
||||
|
||||
public TypeOrganisationRepository() {
|
||||
super(TypeOrganisationEntity.class);
|
||||
}
|
||||
|
||||
/** Recherche un type par son code fonctionnel. */
|
||||
public Optional<TypeOrganisationEntity> findByCode(String code) {
|
||||
TypedQuery<TypeOrganisationEntity> query =
|
||||
entityManager.createQuery(
|
||||
"SELECT t FROM TypeOrganisationEntity t WHERE UPPER(t.code) = UPPER(:code)",
|
||||
TypeOrganisationEntity.class);
|
||||
query.setParameter("code", code);
|
||||
return query.getResultStream().findFirst();
|
||||
}
|
||||
|
||||
/** Liste les types actifs, triés par ordreAffichage puis libellé. */
|
||||
public List<TypeOrganisationEntity> listActifsOrdennes() {
|
||||
return entityManager
|
||||
.createQuery(
|
||||
"SELECT t FROM TypeOrganisationEntity t "
|
||||
+ "WHERE t.actif = true "
|
||||
+ "ORDER BY COALESCE(t.ordreAffichage, 9999), t.libelle",
|
||||
TypeOrganisationEntity.class)
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
|
||||
import dev.lions.unionflow.server.entity.WebhookWave;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour l'entité WebhookWave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class WebhookWaveRepository implements PanacheRepository<WebhookWave> {
|
||||
|
||||
/**
|
||||
* Trouve un webhook Wave par son UUID
|
||||
*
|
||||
* @param id UUID du webhook
|
||||
* @return Webhook ou Optional.empty()
|
||||
*/
|
||||
public Optional<WebhookWave> findWebhookWaveById(UUID id) {
|
||||
return find("id = ?1", id).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un webhook par son identifiant d'événement Wave
|
||||
*
|
||||
* @param waveEventId Identifiant d'événement
|
||||
* @return Webhook ou Optional.empty()
|
||||
*/
|
||||
public Optional<WebhookWave> findByWaveEventId(String waveEventId) {
|
||||
return find("waveEventId = ?1", waveEventId).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les webhooks d'une transaction
|
||||
*
|
||||
* @param transactionWaveId ID de la transaction
|
||||
* @return Liste des webhooks
|
||||
*/
|
||||
public List<WebhookWave> findByTransactionWaveId(UUID transactionWaveId) {
|
||||
return find("transactionWave.id = ?1 ORDER BY dateReception DESC", transactionWaveId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les webhooks d'un paiement
|
||||
*
|
||||
* @param paiementId ID du paiement
|
||||
* @return Liste des webhooks
|
||||
*/
|
||||
public List<WebhookWave> findByPaiementId(UUID paiementId) {
|
||||
return find("paiement.id = ?1 ORDER BY dateReception DESC", paiementId).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les webhooks par statut
|
||||
*
|
||||
* @param statut Statut de traitement
|
||||
* @return Liste des webhooks
|
||||
*/
|
||||
public List<WebhookWave> findByStatut(StatutWebhook statut) {
|
||||
return find("statutTraitement = ?1 ORDER BY dateReception DESC", statut).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les webhooks par type d'événement
|
||||
*
|
||||
* @param type Type d'événement
|
||||
* @return Liste des webhooks
|
||||
*/
|
||||
public List<WebhookWave> findByType(TypeEvenementWebhook type) {
|
||||
return find("typeEvenement = ?1 ORDER BY dateReception DESC", type).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les webhooks en attente de traitement
|
||||
*
|
||||
* @return Liste des webhooks en attente
|
||||
*/
|
||||
public List<WebhookWave> findEnAttente() {
|
||||
return find("statutTraitement = ?1 ORDER BY dateReception ASC", StatutWebhook.EN_ATTENTE)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les webhooks échoués pouvant être retentés
|
||||
*
|
||||
* @return Liste des webhooks échoués
|
||||
*/
|
||||
public List<WebhookWave> findEchouesRetentables() {
|
||||
return find(
|
||||
"statutTraitement = ?1 AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateReception ASC",
|
||||
StatutWebhook.ECHOUE)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,705 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO;
|
||||
import dev.lions.unionflow.server.service.AdhesionService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des adhésions
|
||||
* Expose les endpoints API pour les opérations CRUD sur les adhésions
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Path("/api/adhesions")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Adhésions", description = "Gestion des demandes d'adhésion des membres")
|
||||
@Slf4j
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class AdhesionResource {
|
||||
|
||||
@Inject AdhesionService adhesionService;
|
||||
|
||||
/** Récupère toutes les adhésions avec pagination */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les adhésions",
|
||||
description = "Récupère la liste paginée de toutes les adhésions")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des adhésions récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAllAdhesions(
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions - page: {}, size: {}", page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAllAdhesions(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions", adhesions.size());
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une adhésion par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une adhésion par ID",
|
||||
description = "Récupère les détails d'une adhésion spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Adhésion trouvée",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionById(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/{}", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.getAdhesionById(id);
|
||||
|
||||
log.info("Adhésion récupérée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une adhésion par son numéro de référence */
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
@Operation(
|
||||
summary = "Récupérer une adhésion par référence",
|
||||
description = "Récupère une adhésion par son numéro de référence unique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion trouvée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionByReference(
|
||||
@Parameter(description = "Numéro de référence de l'adhésion", required = true)
|
||||
@PathParam("numeroReference")
|
||||
@NotNull
|
||||
String numeroReference) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/reference/{}", numeroReference);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.getAdhesionByReference(numeroReference);
|
||||
|
||||
log.info("Adhésion récupérée avec succès - Référence: {}", numeroReference);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée - Référence: {}", numeroReference);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "reference", numeroReference))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Erreur lors de la récupération de l'adhésion - Référence: " + numeroReference, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Crée une nouvelle adhésion */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle adhésion",
|
||||
description = "Crée une nouvelle demande d'adhésion pour un membre")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Adhésion créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = AdhesionDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Membre ou organisation non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response createAdhesion(
|
||||
@Parameter(description = "Données de l'adhésion à créer", required = true) @Valid
|
||||
AdhesionDTO adhesionDTO) {
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"POST /api/adhesions - Création adhésion pour membre: {} et organisation: {}",
|
||||
adhesionDTO.getMembreId(),
|
||||
adhesionDTO.getOrganisationId());
|
||||
|
||||
AdhesionDTO nouvelleAdhesion = adhesionService.createAdhesion(adhesionDTO);
|
||||
|
||||
log.info(
|
||||
"Adhésion créée avec succès - ID: {}, Référence: {}",
|
||||
nouvelleAdhesion.getId(),
|
||||
nouvelleAdhesion.getNumeroReference());
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(nouvelleAdhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre ou organisation non trouvé lors de la création d'adhésion");
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre ou organisation non trouvé", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides pour la création d'adhésion: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la création de l'adhésion", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la création de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour une adhésion existante */
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour une adhésion",
|
||||
description = "Met à jour les données d'une adhésion existante")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response updateAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Nouvelles données de l'adhésion", required = true) @Valid
|
||||
AdhesionDTO adhesionDTO) {
|
||||
|
||||
try {
|
||||
log.info("PUT /api/adhesions/{}", id);
|
||||
|
||||
AdhesionDTO adhesionMiseAJour = adhesionService.updateAdhesion(id, adhesionDTO);
|
||||
|
||||
log.info("Adhésion mise à jour avec succès - ID: {}", id);
|
||||
return Response.ok(adhesionMiseAJour).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour mise à jour - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn(
|
||||
"Données invalides pour la mise à jour d'adhésion - ID: {}, Erreur: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la mise à jour de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la mise à jour de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime une adhésion */
|
||||
@DELETE
|
||||
@RolesAllowed({"ADMIN"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une adhésion",
|
||||
description = "Supprime (annule) une adhésion")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Adhésion supprimée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(
|
||||
responseCode = "409",
|
||||
description = "Impossible de supprimer une adhésion payée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response deleteAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("DELETE /api/adhesions/{}", id);
|
||||
|
||||
adhesionService.deleteAdhesion(id);
|
||||
|
||||
log.info("Adhésion supprimée avec succès - ID: {}", id);
|
||||
return Response.noContent().build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour suppression - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de supprimer l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", "Impossible de supprimer l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la suppression de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Approuve une adhésion */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/approuver")
|
||||
@Operation(
|
||||
summary = "Approuver une adhésion",
|
||||
description = "Approuve une demande d'adhésion en attente")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion approuvée avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être approuvée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response approuverAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Nom de l'utilisateur qui approuve")
|
||||
@QueryParam("approuvePar")
|
||||
String approuvePar) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/approuver", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.approuverAdhesion(id, approuvePar);
|
||||
|
||||
log.info("Adhésion approuvée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour approbation - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible d'approuver l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Impossible d'approuver l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'approbation de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de l'approbation de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Rejette une adhésion */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/rejeter")
|
||||
@Operation(
|
||||
summary = "Rejeter une adhésion",
|
||||
description = "Rejette une demande d'adhésion en attente")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Adhésion rejetée avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être rejetée"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response rejeterAdhesion(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Motif du rejet", required = true) @QueryParam("motifRejet")
|
||||
@NotNull
|
||||
String motifRejet) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/rejeter", id);
|
||||
|
||||
AdhesionDTO adhesion = adhesionService.rejeterAdhesion(id, motifRejet);
|
||||
|
||||
log.info("Adhésion rejetée avec succès - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour rejet - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de rejeter l'adhésion - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Impossible de rejeter l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors du rejet de l'adhésion - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors du rejet de l'adhésion", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Enregistre un paiement pour une adhésion */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/paiement")
|
||||
@Operation(
|
||||
summary = "Enregistrer un paiement",
|
||||
description = "Enregistre un paiement pour une adhésion approuvée")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Paiement enregistré avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "L'adhésion ne peut pas recevoir de paiement"),
|
||||
@APIResponse(responseCode = "404", description = "Adhésion non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response enregistrerPaiement(
|
||||
@Parameter(description = "Identifiant de l'adhésion", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Montant payé", required = true) @QueryParam("montantPaye")
|
||||
@NotNull
|
||||
BigDecimal montantPaye,
|
||||
@Parameter(description = "Méthode de paiement") @QueryParam("methodePaiement")
|
||||
String methodePaiement,
|
||||
@Parameter(description = "Référence du paiement") @QueryParam("referencePaiement")
|
||||
String referencePaiement) {
|
||||
|
||||
try {
|
||||
log.info("POST /api/adhesions/{}/paiement", id);
|
||||
|
||||
AdhesionDTO adhesion =
|
||||
adhesionService.enregistrerPaiement(id, montantPaye, methodePaiement, referencePaiement);
|
||||
|
||||
log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
|
||||
return Response.ok(adhesion).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Adhésion non trouvée pour paiement - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Adhésion non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible d'enregistrer le paiement - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
Map.of("error", "Impossible d'enregistrer le paiement", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'enregistrement du paiement - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de l'enregistrement du paiement", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions d'un membre */
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions d'un membre",
|
||||
description = "Récupère toutes les adhésions d'un membre spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions du membre"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByMembre(
|
||||
@Parameter(description = "Identifiant du membre", required = true)
|
||||
@PathParam("membreId")
|
||||
@NotNull
|
||||
UUID membreId,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/membre/{} - page: {}, size: {}", membreId, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsByMembre(membreId, page, size);
|
||||
|
||||
log.info(
|
||||
"Récupération réussie de {} adhésions pour le membre {}", adhesions.size(), membreId);
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre non trouvé - ID: {}", membreId);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre non trouvé", "membreId", membreId))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions du membre - ID: " + membreId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions d'une organisation */
|
||||
@GET
|
||||
@Path("/organisation/{organisationId}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions d'une organisation",
|
||||
description = "Récupère toutes les adhésions d'une organisation spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions de l'organisation"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByOrganisation(
|
||||
@Parameter(description = "Identifiant de l'organisation", required = true)
|
||||
@PathParam("organisationId")
|
||||
@NotNull
|
||||
UUID organisationId,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"GET /api/adhesions/organisation/{} - page: {}, size: {}", organisationId, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions =
|
||||
adhesionService.getAdhesionsByOrganisation(organisationId, page, size);
|
||||
|
||||
log.info(
|
||||
"Récupération réussie de {} adhésions pour l'organisation {}",
|
||||
adhesions.size(),
|
||||
organisationId);
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Organisation non trouvée - ID: {}", organisationId);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Organisation non trouvée", "organisationId", organisationId))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Erreur lors de la récupération des adhésions de l'organisation - ID: " + organisationId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions par statut */
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions par statut",
|
||||
description = "Récupère toutes les adhésions ayant un statut spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des adhésions avec le statut spécifié"),
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsByStatut(
|
||||
@Parameter(description = "Statut des adhésions", required = true, example = "EN_ATTENTE")
|
||||
@PathParam("statut")
|
||||
@NotNull
|
||||
String statut,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/statut/{} - page: {}, size: {}", statut, page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsByStatut(statut, page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions avec statut {}", adhesions.size(), statut);
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions par statut - Statut: " + statut, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les adhésions en attente */
|
||||
@GET
|
||||
@Path("/en-attente")
|
||||
@Operation(
|
||||
summary = "Lister les adhésions en attente",
|
||||
description = "Récupère toutes les adhésions en attente d'approbation")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des adhésions en attente"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAdhesionsEnAttente(
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/adhesions/en-attente - page: {}, size: {}", page, size);
|
||||
|
||||
List<AdhesionDTO> adhesions = adhesionService.getAdhesionsEnAttente(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} adhésions en attente", adhesions.size());
|
||||
return Response.ok(adhesions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des adhésions en attente", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des adhésions en attente", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les statistiques des adhésions */
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(
|
||||
summary = "Statistiques des adhésions",
|
||||
description = "Récupère les statistiques globales des adhésions")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getStatistiquesAdhesions() {
|
||||
try {
|
||||
log.info("GET /api/adhesions/stats");
|
||||
|
||||
Map<String, Object> statistiques = adhesionService.getStatistiquesAdhesions();
|
||||
|
||||
log.info("Statistiques récupérées avec succès");
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des statistiques", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetDTO;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO;
|
||||
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
|
||||
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
|
||||
import dev.lions.unionflow.server.service.AnalyticsService;
|
||||
import dev.lions.unionflow.server.service.KPICalculatorService;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Ressource REST pour les analytics et métriques UnionFlow
|
||||
*
|
||||
* <p>Cette ressource expose les APIs pour accéder aux données analytics, KPI, tendances et widgets
|
||||
* de tableau de bord.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Path("/api/v1/analytics")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Authenticated
|
||||
@Tag(name = "Analytics", description = "APIs pour les analytics et métriques")
|
||||
public class AnalyticsResource {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AnalyticsResource.class);
|
||||
|
||||
@Inject AnalyticsService analyticsService;
|
||||
|
||||
@Inject KPICalculatorService kpiCalculatorService;
|
||||
|
||||
/** Calcule une métrique analytics pour une période donnée */
|
||||
@GET
|
||||
@Path("/metriques/{typeMetrique}")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Calculer une métrique analytics",
|
||||
description = "Calcule une métrique spécifique pour une période et organisation données")
|
||||
@APIResponse(responseCode = "200", description = "Métrique calculée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response calculerMetrique(
|
||||
@Parameter(description = "Type de métrique à calculer", required = true)
|
||||
@PathParam("typeMetrique")
|
||||
TypeMetrique typeMetrique,
|
||||
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Calcul de la métrique %s pour la période %s et l'organisation %s",
|
||||
typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
AnalyticsDataDTO result =
|
||||
analyticsService.calculerMetrique(typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors du calcul de la métrique", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Calcule les tendances d'un KPI sur une période */
|
||||
@GET
|
||||
@Path("/tendances/{typeMetrique}")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Calculer la tendance d'un KPI",
|
||||
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
|
||||
@APIResponse(responseCode = "200", description = "Tendance calculée avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response calculerTendanceKPI(
|
||||
@Parameter(description = "Type de métrique pour la tendance", required = true)
|
||||
@PathParam("typeMetrique")
|
||||
TypeMetrique typeMetrique,
|
||||
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Calcul de la tendance KPI %s pour la période %s et l'organisation %s",
|
||||
typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
KPITrendDTO result =
|
||||
analyticsService.calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.errorf(
|
||||
e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors du calcul de la tendance", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient tous les KPI pour une organisation */
|
||||
@GET
|
||||
@Path("/kpis")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Obtenir tous les KPI",
|
||||
description = "Récupère tous les KPI calculés pour une organisation et période données")
|
||||
@APIResponse(responseCode = "200", description = "KPI récupérés avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response obtenirTousLesKPI(
|
||||
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Récupération de tous les KPI pour la période %s et l'organisation %s",
|
||||
periodeAnalyse, organisationId);
|
||||
|
||||
Map<TypeMetrique, BigDecimal> kpis =
|
||||
kpiCalculatorService.calculerTousLesKPI(
|
||||
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
|
||||
|
||||
return Response.ok(kpis).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des KPI: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of("error", "Erreur lors de la récupération des KPI", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Calcule le KPI de performance globale */
|
||||
@GET
|
||||
@Path("/performance-globale")
|
||||
@RolesAllowed({"ADMIN", "MANAGER"})
|
||||
@Operation(
|
||||
summary = "Calculer la performance globale",
|
||||
description = "Calcule le score de performance globale de l'organisation")
|
||||
@APIResponse(responseCode = "200", description = "Performance globale calculée avec succès")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response calculerPerformanceGlobale(
|
||||
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Calcul de la performance globale pour la période %s et l'organisation %s",
|
||||
periodeAnalyse, organisationId);
|
||||
|
||||
BigDecimal performanceGlobale =
|
||||
kpiCalculatorService.calculerKPIPerformanceGlobale(
|
||||
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"performanceGlobale", performanceGlobale,
|
||||
"periode", periodeAnalyse,
|
||||
"organisationId", organisationId,
|
||||
"dateCalcul", java.time.LocalDateTime.now()))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors du calcul de la performance globale: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors du calcul de la performance globale",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient les évolutions des KPI par rapport à la période précédente */
|
||||
@GET
|
||||
@Path("/evolutions")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Obtenir les évolutions des KPI",
|
||||
description = "Récupère les évolutions des KPI par rapport à la période précédente")
|
||||
@APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response obtenirEvolutionsKPI(
|
||||
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Récupération des évolutions KPI pour la période %s et l'organisation %s",
|
||||
periodeAnalyse, organisationId);
|
||||
|
||||
Map<TypeMetrique, BigDecimal> evolutions =
|
||||
kpiCalculatorService.calculerEvolutionsKPI(
|
||||
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
|
||||
|
||||
return Response.ok(evolutions).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des évolutions KPI: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des évolutions",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient les widgets du tableau de bord pour un utilisateur */
|
||||
@GET
|
||||
@Path("/dashboard/widgets")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Obtenir les widgets du tableau de bord",
|
||||
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Widgets récupérés avec succès")
|
||||
@APIResponse(responseCode = "403", description = "Accès non autorisé")
|
||||
public Response obtenirWidgetsTableauBord(
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
|
||||
UUID organisationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("utilisateurId")
|
||||
@NotNull
|
||||
UUID utilisateurId) {
|
||||
|
||||
try {
|
||||
log.infof(
|
||||
"Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s",
|
||||
organisationId, utilisateurId);
|
||||
|
||||
List<DashboardWidgetDTO> widgets =
|
||||
analyticsService.obtenirMetriquesTableauBord(organisationId, utilisateurId);
|
||||
|
||||
return Response.ok(widgets).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des widgets: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la récupération des widgets", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient les types de métriques disponibles */
|
||||
@GET
|
||||
@Path("/types-metriques")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Obtenir les types de métriques disponibles",
|
||||
description = "Récupère la liste de tous les types de métriques disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
|
||||
public Response obtenirTypesMetriques() {
|
||||
try {
|
||||
log.info("Récupération des types de métriques disponibles");
|
||||
|
||||
TypeMetrique[] typesMetriques = TypeMetrique.values();
|
||||
|
||||
return Response.ok(Map.of("typesMetriques", typesMetriques, "total", typesMetriques.length))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des types de métriques: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des types de métriques",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient les périodes d'analyse disponibles */
|
||||
@GET
|
||||
@Path("/periodes-analyse")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@Operation(
|
||||
summary = "Obtenir les périodes d'analyse disponibles",
|
||||
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
|
||||
@APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
|
||||
public Response obtenirPeriodesAnalyse() {
|
||||
try {
|
||||
log.info("Récupération des périodes d'analyse disponibles");
|
||||
|
||||
PeriodeAnalyse[] periodesAnalyse = PeriodeAnalyse.values();
|
||||
|
||||
return Response.ok(
|
||||
Map.of("periodesAnalyse", periodesAnalyse, "total", periodesAnalyse.length))
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des périodes d'analyse: {}", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des périodes d'analyse",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO;
|
||||
import dev.lions.unionflow.server.service.AuditService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Path("/api/audit")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Audit", description = "Gestion des logs d'audit")
|
||||
@Slf4j
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class AuditResource {
|
||||
|
||||
@Inject
|
||||
AuditService auditService;
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Liste tous les logs d'audit", description = "Récupère tous les logs avec pagination")
|
||||
public Response listerTous(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size,
|
||||
@QueryParam("sortBy") @DefaultValue("dateHeure") String sortBy,
|
||||
@QueryParam("sortOrder") @DefaultValue("desc") String sortOrder) {
|
||||
|
||||
try {
|
||||
Map<String, Object> result = auditService.listerTous(page, size, sortBy, sortOrder);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des logs d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des logs: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/rechercher")
|
||||
@Operation(summary = "Recherche des logs avec filtres", description = "Recherche avancée avec filtres multiples")
|
||||
public Response rechercher(
|
||||
@QueryParam("dateDebut") String dateDebutStr,
|
||||
@QueryParam("dateFin") String dateFinStr,
|
||||
@QueryParam("typeAction") String typeAction,
|
||||
@QueryParam("severite") String severite,
|
||||
@QueryParam("utilisateur") String utilisateur,
|
||||
@QueryParam("module") String module,
|
||||
@QueryParam("ipAddress") String ipAddress,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
|
||||
try {
|
||||
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
|
||||
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
|
||||
|
||||
Map<String, Object> result = auditService.rechercher(
|
||||
dateDebut, dateFin, typeAction, severite, utilisateur, module, ipAddress, page, size);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la recherche des logs d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la recherche: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Enregistre un nouveau log d'audit", description = "Crée une nouvelle entrée dans le journal d'audit")
|
||||
public Response enregistrerLog(@Valid AuditLogDTO dto) {
|
||||
try {
|
||||
AuditLogDTO result = auditService.enregistrerLog(dto);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'enregistrement du log d'audit", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de l'enregistrement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Récupère les statistiques d'audit", description = "Retourne les statistiques globales des logs")
|
||||
public Response getStatistiques() {
|
||||
try {
|
||||
Map<String, Object> stats = auditService.getStatistiques();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.serverError()
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des statistiques: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.*;
|
||||
import dev.lions.unionflow.server.service.ComptabiliteService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion comptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/comptabilite")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class ComptabiliteResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ComptabiliteResource.class);
|
||||
|
||||
@Inject ComptabiliteService comptabiliteService;
|
||||
|
||||
// ========================================
|
||||
// COMPTES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau compte comptable
|
||||
*
|
||||
* @param compteDTO DTO du compte à créer
|
||||
* @return Compte créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/comptes")
|
||||
public Response creerCompteComptable(@Valid CompteComptableDTO compteDTO) {
|
||||
try {
|
||||
CompteComptableDTO result = comptabiliteService.creerCompteComptable(compteDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du compte comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte comptable par son ID
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return Compte comptable
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/{id}")
|
||||
public Response trouverCompteParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
CompteComptableDTO result = comptabiliteService.trouverCompteParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte comptable non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les comptes comptables actifs
|
||||
*
|
||||
* @return Liste des comptes
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes")
|
||||
public Response listerTousLesComptes() {
|
||||
try {
|
||||
List<CompteComptableDTO> result = comptabiliteService.listerTousLesComptes();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des comptes comptables");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// JOURNAUX COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau journal comptable
|
||||
*
|
||||
* @param journalDTO DTO du journal à créer
|
||||
* @return Journal créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/journaux")
|
||||
public Response creerJournalComptable(@Valid JournalComptableDTO journalDTO) {
|
||||
try {
|
||||
JournalComptableDTO result = comptabiliteService.creerJournalComptable(journalDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du journal comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un journal comptable par son ID
|
||||
*
|
||||
* @param id ID du journal
|
||||
* @return Journal comptable
|
||||
*/
|
||||
@GET
|
||||
@Path("/journaux/{id}")
|
||||
public Response trouverJournalParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
JournalComptableDTO result = comptabiliteService.trouverJournalParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Journal comptable non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du journal comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les journaux comptables actifs
|
||||
*
|
||||
* @return Liste des journaux
|
||||
*/
|
||||
@GET
|
||||
@Path("/journaux")
|
||||
public Response listerTousLesJournaux() {
|
||||
try {
|
||||
List<JournalComptableDTO> result = comptabiliteService.listerTousLesJournaux();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des journaux comptables");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ÉCRITURES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle écriture comptable
|
||||
*
|
||||
* @param ecritureDTO DTO de l'écriture à créer
|
||||
* @return Écriture créée
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/ecritures")
|
||||
public Response creerEcritureComptable(@Valid EcritureComptableDTO ecritureDTO) {
|
||||
try {
|
||||
EcritureComptableDTO result = comptabiliteService.creerEcritureComptable(ecritureDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de l'écriture comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une écriture comptable par son ID
|
||||
*
|
||||
* @param id ID de l'écriture
|
||||
* @return Écriture comptable
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/{id}")
|
||||
public Response trouverEcritureParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
EcritureComptableDTO result = comptabiliteService.trouverEcritureParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Écriture comptable non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les écritures d'un journal
|
||||
*
|
||||
* @param journalId ID du journal
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/journal/{journalId}")
|
||||
public Response listerEcrituresParJournal(@PathParam("journalId") UUID journalId) {
|
||||
try {
|
||||
List<EcritureComptableDTO> result = comptabiliteService.listerEcrituresParJournal(journalId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des écritures");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les écritures d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/organisation/{organisationId}")
|
||||
public Response listerEcrituresParOrganisation(@PathParam("organisationId") UUID organisationId) {
|
||||
try {
|
||||
List<EcritureComptableDTO> result = comptabiliteService.listerEcrituresParOrganisation(organisationId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des écritures");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.service.CotisationService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des cotisations Expose les endpoints API pour les opérations CRUD
|
||||
* sur les cotisations
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Path("/api/cotisations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Cotisations", description = "Gestion des cotisations des membres")
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
@Slf4j
|
||||
public class CotisationResource {
|
||||
|
||||
@Inject CotisationService cotisationService;
|
||||
|
||||
/** Endpoint public pour les cotisations (test) */
|
||||
@GET
|
||||
@Path("/public")
|
||||
@Operation(
|
||||
summary = "Cotisations publiques",
|
||||
description = "Liste des cotisations sans authentification")
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations")
|
||||
public Response getCotisationsPublic(
|
||||
@QueryParam("page") @DefaultValue("0") @Min(0) int page,
|
||||
@QueryParam("size") @DefaultValue("20") @Min(1) int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/public - page: {}, size: {}", page, size);
|
||||
|
||||
// Récupérer les cotisations depuis la base de données
|
||||
List<CotisationDTO> cotisationsDTO = cotisationService.getAllCotisations(page, size);
|
||||
|
||||
// Convertir en format pour l'application mobile
|
||||
List<Map<String, Object>> cotisations = cotisationsDTO.stream()
|
||||
.map(c -> {
|
||||
Map<String, Object> map = new java.util.HashMap<>();
|
||||
map.put("id", c.getId() != null ? c.getId().toString() : "");
|
||||
map.put("nom", c.getDescription() != null ? c.getDescription() : "Cotisation");
|
||||
map.put("description", c.getDescription() != null ? c.getDescription() : "");
|
||||
map.put("montant", c.getMontantDu() != null ? c.getMontantDu().doubleValue() : 0.0);
|
||||
map.put("devise", c.getCodeDevise() != null ? c.getCodeDevise() : "XOF");
|
||||
map.put("dateEcheance", c.getDateEcheance() != null ? c.getDateEcheance().toString() : "");
|
||||
map.put("statut", c.getStatut() != null ? c.getStatut() : "EN_ATTENTE");
|
||||
map.put("type", c.getTypeCotisation() != null ? c.getTypeCotisation() : "MENSUELLE");
|
||||
return map;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long totalElements = cotisationService.getStatistiquesCotisations().get("totalCotisations") != null
|
||||
? ((Number) cotisationService.getStatistiquesCotisations().get("totalCotisations")).longValue()
|
||||
: cotisations.size();
|
||||
int totalPages = (int) Math.ceil((double) totalElements / size);
|
||||
|
||||
Map<String, Object> response =
|
||||
Map.of(
|
||||
"content", cotisations,
|
||||
"totalElements", totalElements,
|
||||
"totalPages", totalPages,
|
||||
"size", size,
|
||||
"number", page);
|
||||
|
||||
return Response.ok(response).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations publiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des cotisations"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère toutes les cotisations avec pagination */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister toutes les cotisations",
|
||||
description = "Récupère la liste paginée de toutes les cotisations")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des cotisations récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getAllCotisations(
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations - page: {}, size: {}", page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des cotisations",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une cotisation par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Récupérer une cotisation par ID",
|
||||
description = "Récupère les détails d'une cotisation spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Cotisation trouvée",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationById(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/{}", id);
|
||||
|
||||
CotisationDTO cotisation = cotisationService.getCotisationById(id);
|
||||
|
||||
log.info("Cotisation récupérée avec succès - ID: {}", id);
|
||||
return Response.ok(cotisation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération de la cotisation",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une cotisation par son numéro de référence */
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
@Operation(
|
||||
summary = "Récupérer une cotisation par référence",
|
||||
description = "Récupère une cotisation par son numéro de référence unique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Cotisation trouvée"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationByReference(
|
||||
@Parameter(description = "Numéro de référence de la cotisation", required = true)
|
||||
@PathParam("numeroReference")
|
||||
@NotNull
|
||||
String numeroReference) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/reference/{}", numeroReference);
|
||||
|
||||
CotisationDTO cotisation = cotisationService.getCotisationByReference(numeroReference);
|
||||
|
||||
log.info("Cotisation récupérée avec succès - Référence: {}", numeroReference);
|
||||
return Response.ok(cotisation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée - Référence: {}", numeroReference);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "reference", numeroReference))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Erreur lors de la récupération de la cotisation - Référence: " + numeroReference, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération de la cotisation",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Crée une nouvelle cotisation */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle cotisation",
|
||||
description = "Crée une nouvelle cotisation pour un membre")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Cotisation créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CotisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response createCotisation(
|
||||
@Parameter(description = "Données de la cotisation à créer", required = true) @Valid
|
||||
CotisationDTO cotisationDTO) {
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"POST /api/cotisations - Création cotisation pour membre: {}",
|
||||
cotisationDTO.getMembreId());
|
||||
|
||||
CotisationDTO nouvelleCotisation = cotisationService.createCotisation(cotisationDTO);
|
||||
|
||||
log.info(
|
||||
"Cotisation créée avec succès - ID: {}, Référence: {}",
|
||||
nouvelleCotisation.getId(),
|
||||
nouvelleCotisation.getNumeroReference());
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(nouvelleCotisation).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn(
|
||||
"Membre non trouvé lors de la création de cotisation: {}", cotisationDTO.getMembreId());
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre non trouvé", "membreId", cotisationDTO.getMembreId()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides pour la création de cotisation: {}", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la création de la cotisation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la création de la cotisation",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour une cotisation existante */
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour une cotisation",
|
||||
description = "Met à jour les données d'une cotisation existante")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Cotisation mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response updateCotisation(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id,
|
||||
@Parameter(description = "Nouvelles données de la cotisation", required = true) @Valid
|
||||
CotisationDTO cotisationDTO) {
|
||||
|
||||
try {
|
||||
log.info("PUT /api/cotisations/{}", id);
|
||||
|
||||
CotisationDTO cotisationMiseAJour = cotisationService.updateCotisation(id, cotisationDTO);
|
||||
|
||||
log.info("Cotisation mise à jour avec succès - ID: {}", id);
|
||||
return Response.ok(cotisationMiseAJour).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée pour mise à jour - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn(
|
||||
"Données invalides pour la mise à jour de cotisation - ID: {}, Erreur: {}",
|
||||
id,
|
||||
e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Données invalides", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la mise à jour de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la mise à jour de la cotisation",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime une cotisation */
|
||||
@DELETE
|
||||
@RolesAllowed({"ADMIN"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Supprimer une cotisation",
|
||||
description = "Supprime (désactive) une cotisation")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Cotisation supprimée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Cotisation non trouvée"),
|
||||
@APIResponse(
|
||||
responseCode = "409",
|
||||
description = "Impossible de supprimer une cotisation payée"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response deleteCotisation(
|
||||
@Parameter(description = "Identifiant de la cotisation", required = true)
|
||||
@PathParam("id")
|
||||
@NotNull
|
||||
UUID id) {
|
||||
|
||||
try {
|
||||
log.info("DELETE /api/cotisations/{}", id);
|
||||
|
||||
cotisationService.deleteCotisation(id);
|
||||
|
||||
log.info("Cotisation supprimée avec succès - ID: {}", id);
|
||||
return Response.noContent().build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Cotisation non trouvée pour suppression - ID: {}", id);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Cotisation non trouvée", "id", id))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Impossible de supprimer la cotisation - ID: {}, Raison: {}", id, e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(
|
||||
Map.of("error", "Impossible de supprimer la cotisation", "message", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression de la cotisation - ID: " + id, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la suppression de la cotisation",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les cotisations d'un membre */
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
@Operation(
|
||||
summary = "Lister les cotisations d'un membre",
|
||||
description = "Récupère toutes les cotisations d'un membre spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations du membre"),
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsByMembre(
|
||||
@Parameter(description = "Identifiant du membre", required = true)
|
||||
@PathParam("membreId")
|
||||
@NotNull
|
||||
UUID membreId,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/membre/{} - page: {}, size: {}", membreId, page, size);
|
||||
|
||||
List<CotisationDTO> cotisations =
|
||||
cotisationService.getCotisationsByMembre(membreId, page, size);
|
||||
|
||||
log.info(
|
||||
"Récupération réussie de {} cotisations pour le membre {}", cotisations.size(), membreId);
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Membre non trouvé - ID: {}", membreId);
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Membre non trouvé", "membreId", membreId))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations du membre - ID: " + membreId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des cotisations",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les cotisations par statut */
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
@Operation(
|
||||
summary = "Lister les cotisations par statut",
|
||||
description = "Récupère toutes les cotisations ayant un statut spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des cotisations avec le statut spécifié"),
|
||||
@APIResponse(responseCode = "400", description = "Statut invalide"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsByStatut(
|
||||
@Parameter(description = "Statut des cotisations", required = true, example = "EN_ATTENTE")
|
||||
@PathParam("statut")
|
||||
@NotNull
|
||||
String statut,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/statut/{} - page: {}, size: {}", statut, page, size);
|
||||
|
||||
List<CotisationDTO> cotisations =
|
||||
cotisationService.getCotisationsByStatut(statut, page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations avec statut {}", cotisations.size(), statut);
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations par statut - Statut: " + statut, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des cotisations",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les cotisations en retard */
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
@Operation(
|
||||
summary = "Lister les cotisations en retard",
|
||||
description = "Récupère toutes les cotisations dont la date d'échéance est dépassée")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des cotisations en retard"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getCotisationsEnRetard(
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations/en-retard - page: {}, size: {}", page, size);
|
||||
|
||||
List<CotisationDTO> cotisations = cotisationService.getCotisationsEnRetard(page, size);
|
||||
|
||||
log.info("Récupération réussie de {} cotisations en retard", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des cotisations en retard", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des cotisations en retard",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Recherche avancée de cotisations */
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(
|
||||
summary = "Recherche avancée de cotisations",
|
||||
description = "Recherche de cotisations avec filtres multiples")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres de recherche invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response rechercherCotisations(
|
||||
@Parameter(description = "Identifiant du membre") @QueryParam("membreId") UUID membreId,
|
||||
@Parameter(description = "Statut de la cotisation") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type de cotisation") @QueryParam("typeCotisation")
|
||||
String typeCotisation,
|
||||
@Parameter(description = "Année") @QueryParam("annee") Integer annee,
|
||||
@Parameter(description = "Mois") @QueryParam("mois") Integer mois,
|
||||
@Parameter(description = "Numéro de page", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size) {
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"GET /api/cotisations/recherche - Filtres: membreId={}, statut={}, type={}, annee={},"
|
||||
+ " mois={}",
|
||||
membreId,
|
||||
statut,
|
||||
typeCotisation,
|
||||
annee,
|
||||
mois);
|
||||
|
||||
List<CotisationDTO> cotisations =
|
||||
cotisationService.rechercherCotisations(
|
||||
membreId, statut, typeCotisation, annee, mois, page, size);
|
||||
|
||||
log.info("Recherche réussie - {} cotisations trouvées", cotisations.size());
|
||||
return Response.ok(cotisations).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la recherche de cotisations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error", "Erreur lors de la recherche de cotisations", "message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère les statistiques des cotisations */
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(
|
||||
summary = "Statistiques des cotisations",
|
||||
description = "Récupère les statistiques globales des cotisations")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Response getStatistiquesCotisations() {
|
||||
try {
|
||||
log.info("GET /api/cotisations/stats");
|
||||
|
||||
Map<String, Object> statistiques = cotisationService.getStatistiquesCotisations();
|
||||
|
||||
log.info("Statistiques récupérées avec succès");
|
||||
return Response.ok(statistiques).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des statistiques", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(
|
||||
Map.of(
|
||||
"error",
|
||||
"Erreur lors de la récupération des statistiques",
|
||||
"message",
|
||||
e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY)
|
||||
*
|
||||
* @param membreIds Liste des IDs des membres destinataires
|
||||
* @return Nombre de rappels envoyés
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/rappels/groupes")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Envoyer des rappels de cotisations groupés")
|
||||
@APIResponse(responseCode = "200", description = "Rappels envoyés avec succès")
|
||||
public Response envoyerRappelsGroupes(List<UUID> membreIds) {
|
||||
try {
|
||||
int rappelsEnvoyes = cotisationService.envoyerRappelsCotisationsGroupes(membreIds);
|
||||
return Response.ok(Map.of("rappelsEnvoyes", rappelsEnvoyes)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'envoi des rappels groupés", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'envoi des rappels: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO;
|
||||
import dev.lions.unionflow.server.api.service.dashboard.DashboardService;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resource REST pour les APIs du dashboard
|
||||
*
|
||||
* <p>Cette ressource expose les endpoints pour récupérer les données du dashboard,
|
||||
* incluant les statistiques, activités récentes et événements à venir.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Path("/api/v1/dashboard")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Dashboard", description = "APIs pour la gestion du dashboard")
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class DashboardResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DashboardResource.class);
|
||||
|
||||
@Inject
|
||||
DashboardService dashboardService;
|
||||
|
||||
/**
|
||||
* Récupère toutes les données du dashboard
|
||||
*/
|
||||
@GET
|
||||
@Path("/data")
|
||||
@Operation(
|
||||
summary = "Récupérer toutes les données du dashboard",
|
||||
description = "Retourne les statistiques, activités récentes et événements à venir"
|
||||
)
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Données récupérées avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
public Response getDashboardData(
|
||||
@Parameter(description = "ID de l'organisation", required = true)
|
||||
@QueryParam("organizationId") @NotNull String organizationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("userId") @NotNull String userId) {
|
||||
|
||||
LOG.infof("GET /api/v1/dashboard/data - org: %s, user: %s", organizationId, userId);
|
||||
|
||||
try {
|
||||
DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId);
|
||||
return Response.ok(dashboardData).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des données dashboard");
|
||||
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère uniquement les statistiques du dashboard
|
||||
*/
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(
|
||||
summary = "Récupérer les statistiques du dashboard",
|
||||
description = "Retourne uniquement les statistiques principales"
|
||||
)
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
public Response getDashboardStats(
|
||||
@Parameter(description = "ID de l'organisation", required = true)
|
||||
@QueryParam("organizationId") @NotNull String organizationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("userId") @NotNull String userId) {
|
||||
|
||||
LOG.infof("GET /api/v1/dashboard/stats - org: %s, user: %s", organizationId, userId);
|
||||
|
||||
try {
|
||||
DashboardStatsDTO stats = dashboardService.getDashboardStats(organizationId, userId);
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des statistiques dashboard");
|
||||
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les activités récentes
|
||||
*/
|
||||
@GET
|
||||
@Path("/activities")
|
||||
@Operation(
|
||||
summary = "Récupérer les activités récentes",
|
||||
description = "Retourne la liste des activités récentes avec pagination"
|
||||
)
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Activités récupérées avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
public Response getRecentActivities(
|
||||
@Parameter(description = "ID de l'organisation", required = true)
|
||||
@QueryParam("organizationId") @NotNull String organizationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("userId") @NotNull String userId,
|
||||
@Parameter(description = "Nombre maximum d'activités à retourner", required = false)
|
||||
@QueryParam("limit") @DefaultValue("10") int limit) {
|
||||
|
||||
LOG.infof("GET /api/v1/dashboard/activities - org: %s, user: %s, limit: %d",
|
||||
organizationId, userId, limit);
|
||||
|
||||
try {
|
||||
List<RecentActivityDTO> activities = dashboardService.getRecentActivities(
|
||||
organizationId, userId, limit);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("activities", activities);
|
||||
response.put("total", activities.size());
|
||||
response.put("limit", limit);
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des activités récentes");
|
||||
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les événements à venir
|
||||
*/
|
||||
@GET
|
||||
@Path("/events/upcoming")
|
||||
@Operation(
|
||||
summary = "Récupérer les événements à venir",
|
||||
description = "Retourne la liste des événements à venir avec pagination"
|
||||
)
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Événements récupérés avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
public Response getUpcomingEvents(
|
||||
@Parameter(description = "ID de l'organisation", required = true)
|
||||
@QueryParam("organizationId") @NotNull String organizationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("userId") @NotNull String userId,
|
||||
@Parameter(description = "Nombre maximum d'événements à retourner", required = false)
|
||||
@QueryParam("limit") @DefaultValue("5") int limit) {
|
||||
|
||||
LOG.infof("GET /api/v1/dashboard/events/upcoming - org: %s, user: %s, limit: %d",
|
||||
organizationId, userId, limit);
|
||||
|
||||
try {
|
||||
List<UpcomingEventDTO> events = dashboardService.getUpcomingEvents(
|
||||
organizationId, userId, limit);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("events", events);
|
||||
response.put("total", events.size());
|
||||
response.put("limit", limit);
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des événements à venir");
|
||||
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint de santé pour vérifier le statut du dashboard
|
||||
*/
|
||||
@GET
|
||||
@Path("/health")
|
||||
@Operation(
|
||||
summary = "Vérifier la santé du service dashboard",
|
||||
description = "Retourne le statut de santé du service dashboard"
|
||||
)
|
||||
@APIResponse(responseCode = "200", description = "Service en bonne santé")
|
||||
public Response healthCheck() {
|
||||
LOG.debug("GET /api/v1/dashboard/health");
|
||||
|
||||
Map<String, Object> health = new HashMap<>();
|
||||
health.put("status", "UP");
|
||||
health.put("service", "dashboard");
|
||||
health.put("timestamp", System.currentTimeMillis());
|
||||
health.put("version", "1.0.0");
|
||||
|
||||
return Response.ok(health).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint pour rafraîchir les données du dashboard
|
||||
*/
|
||||
@POST
|
||||
@Path("/refresh")
|
||||
@Operation(
|
||||
summary = "Rafraîchir les données du dashboard",
|
||||
description = "Force la mise à jour des données du dashboard"
|
||||
)
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Données rafraîchies avec succès"),
|
||||
@APIResponse(responseCode = "400", description = "Paramètres invalides"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
public Response refreshDashboard(
|
||||
@Parameter(description = "ID de l'organisation", required = true)
|
||||
@QueryParam("organizationId") @NotNull String organizationId,
|
||||
@Parameter(description = "ID de l'utilisateur", required = true)
|
||||
@QueryParam("userId") @NotNull String userId) {
|
||||
|
||||
LOG.infof("POST /api/v1/dashboard/refresh - org: %s, user: %s", organizationId, userId);
|
||||
|
||||
try {
|
||||
// Simuler un rafraîchissement (dans un vrai système, cela pourrait vider le cache)
|
||||
DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("status", "refreshed");
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
response.put("data", dashboardData);
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du rafraîchissement du dashboard");
|
||||
return Response.serverError().entity(Map.of("error", "Erreur serveur")).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.document.DocumentDTO;
|
||||
import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO;
|
||||
import dev.lions.unionflow.server.service.DocumentService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion documentaire
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/documents")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class DocumentResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DocumentResource.class);
|
||||
|
||||
@Inject DocumentService documentService;
|
||||
|
||||
/**
|
||||
* Crée un nouveau document
|
||||
*
|
||||
* @param documentDTO DTO du document à créer
|
||||
* @return Document créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
public Response creerDocument(@Valid DocumentDTO documentDTO) {
|
||||
try {
|
||||
DocumentDTO result = documentService.creerDocument(documentDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du document");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un document par son ID
|
||||
*
|
||||
* @param id ID du document
|
||||
* @return Document
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response trouverParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
DocumentDTO result = documentService.trouverParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Document non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du document");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un téléchargement de document
|
||||
*
|
||||
* @param id ID du document
|
||||
* @return Succès
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/telechargement")
|
||||
public Response enregistrerTelechargement(@PathParam("id") UUID id) {
|
||||
try {
|
||||
documentService.enregistrerTelechargement(id);
|
||||
return Response.ok().build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Document non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de l'enregistrement du téléchargement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une pièce jointe
|
||||
*
|
||||
* @param pieceJointeDTO DTO de la pièce jointe à créer
|
||||
* @return Pièce jointe créée
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/pieces-jointes")
|
||||
public Response creerPieceJointe(@Valid PieceJointeDTO pieceJointeDTO) {
|
||||
try {
|
||||
PieceJointeDTO result = documentService.creerPieceJointe(pieceJointeDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la pièce jointe");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les pièces jointes d'un document
|
||||
*
|
||||
* @param documentId ID du document
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
@GET
|
||||
@Path("/{documentId}/pieces-jointes")
|
||||
public Response listerPiecesJointesParDocument(@PathParam("documentId") UUID documentId) {
|
||||
try {
|
||||
List<PieceJointeDTO> result = documentService.listerPiecesJointesParDocument(documentId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des pièces jointes");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.dto.EvenementMobileDTO;
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
|
||||
import dev.lions.unionflow.server.service.EvenementService;
|
||||
import java.util.stream.Collectors;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des événements
|
||||
*
|
||||
* <p>Fournit les endpoints API pour les opérations CRUD sur les événements, optimisé pour
|
||||
* l'intégration avec l'application mobile UnionFlow.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Path("/api/evenements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Événements", description = "Gestion des événements de l'union")
|
||||
public class EvenementResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(EvenementResource.class);
|
||||
|
||||
@Inject EvenementService evenementService;
|
||||
|
||||
/** Endpoint de test public pour vérifier la connectivité */
|
||||
@GET
|
||||
@Path("/test")
|
||||
@Operation(
|
||||
summary = "Test de connectivité",
|
||||
description = "Endpoint public pour tester la connectivité")
|
||||
@APIResponse(responseCode = "200", description = "Test réussi")
|
||||
public Response testConnectivity() {
|
||||
LOG.info("Test de connectivité appelé depuis l'application mobile");
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"status", "success",
|
||||
"message", "Serveur UnionFlow opérationnel",
|
||||
"timestamp", System.currentTimeMillis(),
|
||||
"version", "1.0.0"))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Endpoint de debug pour vérifier le chargement des données */
|
||||
@GET
|
||||
@Path("/count")
|
||||
@Operation(summary = "Compter les événements", description = "Compte le nombre d'événements dans la base")
|
||||
@APIResponse(responseCode = "200", description = "Nombre d'événements")
|
||||
public Response countEvenements() {
|
||||
try {
|
||||
long count = evenementService.countEvenements();
|
||||
return Response.ok(Map.of("count", count, "status", "success")).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur count: %s", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Liste tous les événements actifs avec pagination */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister tous les événements actifs",
|
||||
description = "Récupère la liste paginée des événements actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des événements actifs")
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié")
|
||||
// @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) // Temporairement désactivé
|
||||
public Response listerEvenements(
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@Min(0)
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
@Min(1)
|
||||
int size,
|
||||
@Parameter(description = "Champ de tri", example = "dateDebut")
|
||||
@QueryParam("sort")
|
||||
@DefaultValue("dateDebut")
|
||||
String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)", example = "asc")
|
||||
@QueryParam("direction")
|
||||
@DefaultValue("asc")
|
||||
String sortDirection) {
|
||||
|
||||
try {
|
||||
LOG.infof("GET /api/evenements - page: %d, size: %d", page, size);
|
||||
|
||||
Sort sort =
|
||||
sortDirection.equalsIgnoreCase("desc")
|
||||
? Sort.by(sortField).descending()
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
List<Evenement> evenements =
|
||||
evenementService.listerEvenementsActifs(Page.of(page, size), sort);
|
||||
|
||||
LOG.infof("Nombre d'événements récupérés: %d", evenements.size());
|
||||
|
||||
// Convertir en DTO mobile
|
||||
List<EvenementMobileDTO> evenementsDTOs = new ArrayList<>();
|
||||
for (Evenement evenement : evenements) {
|
||||
try {
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(evenement);
|
||||
evenementsDTOs.add(dto);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la conversion de l'événement %s: %s", evenement.getId(), e.getMessage());
|
||||
// Continuer avec les autres événements
|
||||
}
|
||||
}
|
||||
|
||||
LOG.infof("Nombre de DTOs créés: %d", evenementsDTOs.size());
|
||||
|
||||
// Compter le total d'événements actifs
|
||||
long total = evenementService.countEvenementsActifs();
|
||||
int totalPages = total > 0 ? (int) Math.ceil((double) total / size) : 0;
|
||||
|
||||
// Retourner la structure paginée attendue par le mobile
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("data", evenementsDTOs);
|
||||
response.put("total", total);
|
||||
response.put("page", page);
|
||||
response.put("size", size);
|
||||
response.put("totalPages", totalPages);
|
||||
|
||||
LOG.infof("Réponse prête: %d événements, total=%d, pages=%d", evenementsDTOs.size(), total, totalPages);
|
||||
|
||||
return Response.ok(response)
|
||||
.header("Content-Type", "application/json;charset=UTF-8")
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la récupération des événements: %s", e.getMessage(), e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des événements: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère un événement par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un événement par ID")
|
||||
@APIResponse(responseCode = "200", description = "Événement trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
|
||||
public Response obtenirEvenement(
|
||||
@Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
LOG.infof("GET /api/evenements/%s", id);
|
||||
|
||||
Optional<Evenement> evenement = evenementService.trouverParId(id);
|
||||
|
||||
if (evenement.isPresent()) {
|
||||
return Response.ok(evenement.get()).build();
|
||||
} else {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Événement non trouvé"))
|
||||
.build();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la récupération de l'événement %d: %s", id, e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération de l'événement"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Crée un nouvel événement */
|
||||
@POST
|
||||
@Operation(summary = "Créer un nouvel événement")
|
||||
@APIResponse(responseCode = "201", description = "Événement créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
|
||||
public Response creerEvenement(
|
||||
@Parameter(description = "Données de l'événement à créer", required = true) @Valid
|
||||
Evenement evenement) {
|
||||
|
||||
try {
|
||||
LOG.infof("POST /api/evenements - Création événement: %s", evenement.getTitre());
|
||||
|
||||
Evenement evenementCree = evenementService.creerEvenement(evenement);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(evenementCree).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Données invalides: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (SecurityException e) {
|
||||
LOG.warnf("Permissions insuffisantes: %s", e.getMessage());
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la création: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la création de l'événement"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour un événement existant */
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un événement")
|
||||
@APIResponse(responseCode = "200", description = "Événement mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
|
||||
public Response mettreAJourEvenement(@PathParam("id") UUID id, @Valid Evenement evenement) {
|
||||
|
||||
try {
|
||||
LOG.infof("PUT /api/evenements/%s", id);
|
||||
|
||||
Evenement evenementMisAJour = evenementService.mettreAJourEvenement(id, evenement);
|
||||
|
||||
return Response.ok(evenementMisAJour).build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (SecurityException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la mise à jour: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la mise à jour"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime un événement */
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprimer un événement")
|
||||
@APIResponse(responseCode = "204", description = "Événement supprimé avec succès")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"})
|
||||
public Response supprimerEvenement(@PathParam("id") UUID id) {
|
||||
|
||||
try {
|
||||
LOG.infof("DELETE /api/evenements/%s", id);
|
||||
|
||||
evenementService.supprimerEvenement(id);
|
||||
|
||||
return Response.noContent().build();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (SecurityException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la suppression: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la suppression"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Endpoints spécialisés pour l'application mobile */
|
||||
|
||||
/** Liste les événements à venir */
|
||||
@GET
|
||||
@Path("/a-venir")
|
||||
@Operation(summary = "Événements à venir")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
|
||||
public Response evenementsAVenir(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("10") int size) {
|
||||
|
||||
try {
|
||||
List<Evenement> evenements =
|
||||
evenementService.listerEvenementsAVenir(
|
||||
Page.of(page, size), Sort.by("dateDebut").ascending());
|
||||
|
||||
return Response.ok(evenements).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur événements à venir: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Liste les événements publics */
|
||||
@GET
|
||||
@Path("/publics")
|
||||
@Operation(summary = "Événements publics")
|
||||
public Response evenementsPublics(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
|
||||
try {
|
||||
List<Evenement> evenements =
|
||||
evenementService.listerEvenementsPublics(
|
||||
Page.of(page, size), Sort.by("dateDebut").ascending());
|
||||
|
||||
return Response.ok(evenements).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur événements publics: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Recherche d'événements */
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(summary = "Rechercher des événements")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
|
||||
public Response rechercherEvenements(
|
||||
@QueryParam("q") String recherche,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
|
||||
try {
|
||||
if (recherche == null || recherche.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Le terme de recherche est obligatoire"))
|
||||
.build();
|
||||
}
|
||||
|
||||
List<Evenement> evenements =
|
||||
evenementService.rechercherEvenements(
|
||||
recherche, Page.of(page, size), Sort.by("dateDebut").ascending());
|
||||
|
||||
return Response.ok(evenements).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur recherche: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Événements par type */
|
||||
@GET
|
||||
@Path("/type/{type}")
|
||||
@Operation(summary = "Événements par type")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"})
|
||||
public Response evenementsParType(
|
||||
@PathParam("type") TypeEvenement type,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
|
||||
try {
|
||||
List<Evenement> evenements =
|
||||
evenementService.listerParType(
|
||||
type, Page.of(page, size), Sort.by("dateDebut").ascending());
|
||||
|
||||
return Response.ok(evenements).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur événements par type: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Change le statut d'un événement */
|
||||
@PATCH
|
||||
@Path("/{id}/statut")
|
||||
@Operation(summary = "Changer le statut d'un événement")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"})
|
||||
public Response changerStatut(
|
||||
@PathParam("id") UUID id, @QueryParam("statut") StatutEvenement nouveauStatut) {
|
||||
|
||||
try {
|
||||
if (nouveauStatut == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Le nouveau statut est obligatoire"))
|
||||
.build();
|
||||
}
|
||||
|
||||
Evenement evenement = evenementService.changerStatut(id, nouveauStatut);
|
||||
|
||||
return Response.ok(evenement).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (SecurityException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur changement statut: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du changement de statut"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Statistiques des événements */
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Operation(summary = "Statistiques des événements")
|
||||
@RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"})
|
||||
public Response obtenirStatistiques() {
|
||||
|
||||
try {
|
||||
Map<String, Object> statistiques = evenementService.obtenirStatistiques();
|
||||
|
||||
return Response.ok(statistiques).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur statistiques: %s", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du calcul des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.ExportService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour l'export des données */
|
||||
@Path("/api/export")
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Export", description = "API d'export des données")
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class ExportResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ExportResource.class);
|
||||
|
||||
@Inject ExportService exportService;
|
||||
|
||||
@GET
|
||||
@Path("/cotisations/csv")
|
||||
@Produces("text/csv")
|
||||
@Operation(summary = "Exporter les cotisations en CSV")
|
||||
@APIResponse(responseCode = "200", description = "Fichier CSV généré")
|
||||
public Response exporterCotisationsCSV(
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("associationId") UUID associationId) {
|
||||
LOG.info("Export CSV des cotisations");
|
||||
|
||||
byte[] csv = exportService.exporterToutesCotisationsCSV(statut, type, associationId);
|
||||
|
||||
return Response.ok(csv)
|
||||
.header("Content-Disposition", "attachment; filename=\"cotisations.csv\"")
|
||||
.header("Content-Type", "text/csv; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/cotisations/csv")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("text/csv")
|
||||
@Operation(summary = "Exporter des cotisations spécifiques en CSV")
|
||||
@APIResponse(responseCode = "200", description = "Fichier CSV généré")
|
||||
public Response exporterCotisationsSelectionneesCSV(List<UUID> cotisationIds) {
|
||||
LOG.infof("Export CSV de %d cotisations", cotisationIds.size());
|
||||
|
||||
byte[] csv = exportService.exporterCotisationsCSV(cotisationIds);
|
||||
|
||||
return Response.ok(csv)
|
||||
.header("Content-Disposition", "attachment; filename=\"cotisations.csv\"")
|
||||
.header("Content-Type", "text/csv; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/cotisations/{cotisationId}/recu")
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer un reçu de paiement")
|
||||
@APIResponse(responseCode = "200", description = "Reçu généré")
|
||||
public Response genererRecu(@PathParam("cotisationId") UUID cotisationId) {
|
||||
LOG.infof("Génération reçu pour: %s", cotisationId);
|
||||
|
||||
byte[] recu = exportService.genererRecuPaiement(cotisationId);
|
||||
|
||||
return Response.ok(recu)
|
||||
.header("Content-Disposition", "attachment; filename=\"recu-" + cotisationId + ".txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/cotisations/recus")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer des reçus groupés")
|
||||
@APIResponse(responseCode = "200", description = "Reçus générés")
|
||||
public Response genererRecusGroupes(List<UUID> cotisationIds) {
|
||||
LOG.infof("Génération de %d reçus", cotisationIds.size());
|
||||
|
||||
byte[] recus = exportService.genererRecusGroupes(cotisationIds);
|
||||
|
||||
return Response.ok(recus)
|
||||
.header("Content-Disposition", "attachment; filename=\"recus-groupes.txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/rapport/mensuel")
|
||||
@Produces("text/plain")
|
||||
@Operation(summary = "Générer un rapport mensuel")
|
||||
@APIResponse(responseCode = "200", description = "Rapport généré")
|
||||
public Response genererRapportMensuel(
|
||||
@QueryParam("annee") int annee,
|
||||
@QueryParam("mois") int mois,
|
||||
@QueryParam("associationId") UUID associationId) {
|
||||
LOG.infof("Génération rapport mensuel: %d/%d", mois, annee);
|
||||
|
||||
byte[] rapport = exportService.genererRapportMensuel(annee, mois, associationId);
|
||||
|
||||
return Response.ok(rapport)
|
||||
.header("Content-Disposition",
|
||||
"attachment; filename=\"rapport-" + annee + "-" + String.format("%02d", mois) + ".txt\"")
|
||||
.header("Content-Type", "text/plain; charset=UTF-8")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
/** Resource de santé pour UnionFlow Server */
|
||||
@Path("/api/status")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Status", description = "API de statut du serveur")
|
||||
public class HealthResource {
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Vérifier le statut du serveur")
|
||||
public Response getStatus() {
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"status", "UP",
|
||||
"service", "UnionFlow Server",
|
||||
"version", "1.0.0",
|
||||
"timestamp", LocalDateTime.now().toString(),
|
||||
"message", "Serveur opérationnel"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.service.MembreService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour la gestion des membres */
|
||||
@Path("/api/membres")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Membres", description = "API de gestion des membres")
|
||||
public class MembreResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MembreResource.class);
|
||||
|
||||
@Inject MembreService membreService;
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Lister tous les membres actifs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
|
||||
public Response listerMembres(
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
|
||||
String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)")
|
||||
@QueryParam("direction")
|
||||
@DefaultValue("asc")
|
||||
String sortDirection) {
|
||||
|
||||
LOG.infof("Récupération de la liste des membres actifs - page: %d, size: %d", page, size);
|
||||
|
||||
Sort sort =
|
||||
"desc".equalsIgnoreCase(sortDirection)
|
||||
? Sort.by(sortField).descending()
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
List<Membre> membres = membreService.listerMembresActifs(Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupérer un membre par son ID")
|
||||
@APIResponse(responseCode = "200", description = "Membre trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
public Response obtenirMembre(@Parameter(description = "UUID du membre") @PathParam("id") UUID id) {
|
||||
LOG.infof("Récupération du membre ID: %s", id);
|
||||
return membreService
|
||||
.trouverParId(id)
|
||||
.map(
|
||||
membre -> {
|
||||
MembreDTO membreDTO = membreService.convertToDTO(membre);
|
||||
return Response.ok(membreDTO).build();
|
||||
})
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("message", "Membre non trouvé"))
|
||||
.build());
|
||||
}
|
||||
|
||||
@POST
|
||||
@PermitAll
|
||||
@Operation(summary = "Créer un nouveau membre")
|
||||
@APIResponse(responseCode = "201", description = "Membre créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response creerMembre(@Valid MembreDTO membreDTO) {
|
||||
LOG.infof("Création d'un nouveau membre: %s", membreDTO.getEmail());
|
||||
try {
|
||||
// Conversion DTO vers entité
|
||||
Membre membre = membreService.convertFromDTO(membreDTO);
|
||||
|
||||
// Création du membre
|
||||
Membre nouveauMembre = membreService.creerMembre(membre);
|
||||
|
||||
// Conversion de retour vers DTO
|
||||
MembreDTO nouveauMembreDTO = membreService.convertToDTO(nouveauMembre);
|
||||
|
||||
return Response.status(Response.Status.CREATED).entity(nouveauMembreDTO).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Mettre à jour un membre existant")
|
||||
@APIResponse(responseCode = "200", description = "Membre mis à jour avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
public Response mettreAJourMembre(
|
||||
@Parameter(description = "UUID du membre") @PathParam("id") UUID id,
|
||||
@Valid MembreDTO membreDTO) {
|
||||
LOG.infof("Mise à jour du membre ID: %s", id);
|
||||
try {
|
||||
// Conversion DTO vers entité
|
||||
Membre membre = membreService.convertFromDTO(membreDTO);
|
||||
|
||||
// Mise à jour du membre
|
||||
Membre membreMisAJour = membreService.mettreAJourMembre(id, membre);
|
||||
|
||||
// Conversion de retour vers DTO
|
||||
MembreDTO membreMisAJourDTO = membreService.convertToDTO(membreMisAJour);
|
||||
|
||||
return Response.ok(membreMisAJourDTO).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Désactiver un membre")
|
||||
@APIResponse(responseCode = "204", description = "Membre désactivé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
public Response desactiverMembre(
|
||||
@Parameter(description = "UUID du membre") @PathParam("id") UUID id) {
|
||||
LOG.infof("Désactivation du membre ID: %s", id);
|
||||
try {
|
||||
membreService.desactiverMembre(id);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(summary = "Rechercher des membres par nom ou prénom")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
|
||||
public Response rechercherMembres(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
|
||||
String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)")
|
||||
@QueryParam("direction")
|
||||
@DefaultValue("asc")
|
||||
String sortDirection) {
|
||||
|
||||
LOG.infof("Recherche de membres avec le terme: %s", recherche);
|
||||
if (recherche == null || recherche.trim().isEmpty()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Le terme de recherche est requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
Sort sort =
|
||||
"desc".equalsIgnoreCase(sortDirection)
|
||||
? Sort.by(sortField).descending()
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
List<Membre> membres =
|
||||
membreService.rechercherMembres(recherche.trim(), Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Obtenir les statistiques avancées des membres")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques complètes des membres")
|
||||
public Response obtenirStatistiques() {
|
||||
LOG.info("Récupération des statistiques avancées des membres");
|
||||
Map<String, Object> statistiques = membreService.obtenirStatistiquesAvancees();
|
||||
return Response.ok(statistiques).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/autocomplete/villes")
|
||||
@Operation(summary = "Obtenir la liste des villes pour autocomplétion")
|
||||
@APIResponse(responseCode = "200", description = "Liste des villes distinctes")
|
||||
public Response obtenirVilles(
|
||||
@Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) {
|
||||
LOG.infof("Récupération des villes pour autocomplétion - query: %s", query);
|
||||
List<String> villes = membreService.obtenirVillesDistinctes(query);
|
||||
return Response.ok(villes).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/autocomplete/professions")
|
||||
@Operation(summary = "Obtenir la liste des professions pour autocomplétion")
|
||||
@APIResponse(responseCode = "200", description = "Liste des professions distinctes")
|
||||
public Response obtenirProfessions(
|
||||
@Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) {
|
||||
LOG.infof("Récupération des professions pour autocomplétion - query: %s", query);
|
||||
List<String> professions = membreService.obtenirProfessionsDistinctes(query);
|
||||
return Response.ok(professions).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recherche-avancee")
|
||||
@Operation(summary = "Recherche avancée de membres avec filtres multiples (DEPRECATED)")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche avancée")
|
||||
@Deprecated
|
||||
public Response rechercheAvancee(
|
||||
@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche,
|
||||
@Parameter(description = "Statut actif (true/false)") @QueryParam("actif") Boolean actif,
|
||||
@Parameter(description = "Date d'adhésion minimum (YYYY-MM-DD)")
|
||||
@QueryParam("dateAdhesionMin")
|
||||
String dateAdhesionMin,
|
||||
@Parameter(description = "Date d'adhésion maximum (YYYY-MM-DD)")
|
||||
@QueryParam("dateAdhesionMax")
|
||||
String dateAdhesionMax,
|
||||
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom")
|
||||
String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)")
|
||||
@QueryParam("direction")
|
||||
@DefaultValue("asc")
|
||||
String sortDirection) {
|
||||
|
||||
LOG.infof(
|
||||
"Recherche avancée de membres (DEPRECATED) - recherche: %s, actif: %s", recherche, actif);
|
||||
|
||||
try {
|
||||
Sort sort =
|
||||
"desc".equalsIgnoreCase(sortDirection)
|
||||
? Sort.by(sortField).descending()
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
// Conversion des dates si fournies
|
||||
java.time.LocalDate dateMin =
|
||||
dateAdhesionMin != null ? java.time.LocalDate.parse(dateAdhesionMin) : null;
|
||||
java.time.LocalDate dateMax =
|
||||
dateAdhesionMax != null ? java.time.LocalDate.parse(dateAdhesionMax) : null;
|
||||
|
||||
List<Membre> membres =
|
||||
membreService.rechercheAvancee(
|
||||
recherche, actif, dateMin, dateMax, Page.of(page, size), sort);
|
||||
List<MembreDTO> membresDTO = membreService.convertToDTOList(membres);
|
||||
|
||||
return Response.ok(membresDTO).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Erreur dans les paramètres de recherche: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nouvelle recherche avancée avec critères complets et résultats enrichis Réservée aux super
|
||||
* administrateurs pour des recherches sophistiquées
|
||||
*/
|
||||
@POST
|
||||
@Path("/search/advanced")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
|
||||
@Operation(
|
||||
summary = "Recherche avancée de membres avec critères multiples",
|
||||
description =
|
||||
"""
|
||||
Recherche sophistiquée de membres avec de nombreux critères de filtrage :
|
||||
- Recherche textuelle dans nom, prénom, email
|
||||
- Filtres par organisation, rôles, statut
|
||||
- Filtres par âge, région, profession
|
||||
- Filtres par dates d'adhésion
|
||||
- Résultats paginés avec statistiques
|
||||
|
||||
Réservée aux super administrateurs et administrateurs.
|
||||
""")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Recherche effectuée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = MembreSearchResultDTO.class),
|
||||
examples =
|
||||
@ExampleObject(
|
||||
name = "Exemple de résultats",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"membres": [...],
|
||||
"totalElements": 247,
|
||||
"totalPages": 13,
|
||||
"currentPage": 0,
|
||||
"pageSize": 20,
|
||||
"hasNext": true,
|
||||
"hasPrevious": false,
|
||||
"executionTimeMs": 45,
|
||||
"statistics": {
|
||||
"membresActifs": 230,
|
||||
"membresInactifs": 17,
|
||||
"ageMoyen": 34.5,
|
||||
"nombreOrganisations": 12
|
||||
}
|
||||
}
|
||||
"""))),
|
||||
@APIResponse(
|
||||
responseCode = "400",
|
||||
description = "Critères de recherche invalides",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
examples =
|
||||
@ExampleObject(
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"message": "Critères de recherche invalides",
|
||||
"details": "La date minimum ne peut pas être postérieure à la date maximum"
|
||||
}
|
||||
"""))),
|
||||
@APIResponse(
|
||||
responseCode = "403",
|
||||
description = "Accès non autorisé - Rôle SUPER_ADMIN ou ADMIN requis"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
@SecurityRequirement(name = "keycloak")
|
||||
public Response searchMembresAdvanced(
|
||||
@RequestBody(
|
||||
description = "Critères de recherche avancée",
|
||||
required = false,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = MembreSearchCriteria.class),
|
||||
examples =
|
||||
@ExampleObject(
|
||||
name = "Exemple de critères",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"query": "marie",
|
||||
"statut": "ACTIF",
|
||||
"ageMin": 25,
|
||||
"ageMax": 45,
|
||||
"region": "Dakar",
|
||||
"roles": ["PRESIDENT", "SECRETAIRE"],
|
||||
"dateAdhesionMin": "2020-01-01",
|
||||
"includeInactifs": false
|
||||
}
|
||||
""")))
|
||||
MembreSearchCriteria criteria,
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Champ de tri", example = "nom")
|
||||
@QueryParam("sort")
|
||||
@DefaultValue("nom")
|
||||
String sortField,
|
||||
@Parameter(description = "Direction du tri (asc/desc)", example = "asc")
|
||||
@QueryParam("direction")
|
||||
@DefaultValue("asc")
|
||||
String sortDirection) {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// Validation des critères
|
||||
if (criteria == null) {
|
||||
LOG.warn("Recherche avancée de membres - critères null rejetés");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Les critères de recherche sont requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
LOG.infof(
|
||||
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
|
||||
criteria.getDescription(), page, size);
|
||||
|
||||
// Nettoyage et validation des critères
|
||||
criteria.sanitize();
|
||||
|
||||
if (!criteria.hasAnyCriteria()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Au moins un critère de recherche doit être spécifié"))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (!criteria.isValid()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
Map.of(
|
||||
"message", "Critères de recherche invalides",
|
||||
"details", "Vérifiez la cohérence des dates et des âges"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Construction du tri
|
||||
Sort sort =
|
||||
"desc".equalsIgnoreCase(sortDirection)
|
||||
? Sort.by(sortField).descending()
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
// Exécution de la recherche
|
||||
MembreSearchResultDTO result =
|
||||
membreService.searchMembresAdvanced(criteria, Page.of(page, size), sort);
|
||||
|
||||
// Calcul du temps d'exécution
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
result.setExecutionTimeMs(executionTime);
|
||||
|
||||
LOG.infof(
|
||||
"Recherche avancée terminée - %d résultats trouvés en %d ms",
|
||||
result.getTotalElements(), executionTime);
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (jakarta.validation.ConstraintViolationException e) {
|
||||
LOG.warnf("Erreur de validation Jakarta dans la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Critères de recherche invalides", "details", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur de validation dans la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Paramètres de recherche invalides", "details", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche avancée de membres");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("message", "Erreur interne lors de la recherche", "error", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/export/selection")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Operation(summary = "Exporter une sélection de membres en Excel")
|
||||
@APIResponse(responseCode = "200", description = "Fichier Excel généré")
|
||||
public Response exporterSelectionMembres(
|
||||
@Parameter(description = "Liste des IDs des membres à exporter") List<UUID> membreIds,
|
||||
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format) {
|
||||
LOG.infof("Export de %d membres sélectionnés", membreIds.size());
|
||||
try {
|
||||
byte[] excelData = membreService.exporterMembresSelectionnes(membreIds, format);
|
||||
return Response.ok(excelData)
|
||||
.header("Content-Disposition", "attachment; filename=\"membres_selection_" +
|
||||
java.time.LocalDate.now() + "." + (format != null ? format.toLowerCase() : "xlsx") + "\"")
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export de la sélection");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Importer des membres depuis un fichier Excel ou CSV")
|
||||
@APIResponse(responseCode = "200", description = "Import terminé")
|
||||
public Response importerMembres(
|
||||
@Parameter(description = "Contenu du fichier à importer") @FormParam("file") byte[] fileContent,
|
||||
@Parameter(description = "Nom du fichier") @FormParam("fileName") String fileName,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @FormParam("organisationId") UUID organisationId,
|
||||
@Parameter(description = "Type de membre par défaut") @FormParam("typeMembreDefaut") String typeMembreDefaut,
|
||||
@Parameter(description = "Mettre à jour les membres existants") @FormParam("mettreAJourExistants") boolean mettreAJourExistants,
|
||||
@Parameter(description = "Ignorer les erreurs") @FormParam("ignorerErreurs") boolean ignorerErreurs) {
|
||||
|
||||
try {
|
||||
if (fileContent == null || fileContent.length == 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Aucun fichier fourni"))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
fileName = "import.xlsx";
|
||||
}
|
||||
|
||||
if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) {
|
||||
typeMembreDefaut = "ACTIF";
|
||||
}
|
||||
|
||||
InputStream fileInputStream = new java.io.ByteArrayInputStream(fileContent);
|
||||
dev.lions.unionflow.server.service.MembreImportExportService.ResultatImport resultat = membreService.importerMembres(
|
||||
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("totalLignes", resultat.totalLignes);
|
||||
response.put("lignesTraitees", resultat.lignesTraitees);
|
||||
response.put("lignesErreur", resultat.lignesErreur);
|
||||
response.put("erreurs", resultat.erreurs);
|
||||
response.put("membresImportes", resultat.membresImportes);
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'import");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'import: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Operation(summary = "Exporter des membres en Excel, CSV ou PDF")
|
||||
@APIResponse(responseCode = "200", description = "Fichier exporté")
|
||||
public Response exporterMembres(
|
||||
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
|
||||
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type de membre") @QueryParam("type") String type,
|
||||
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin,
|
||||
@Parameter(description = "Colonnes à exporter") @QueryParam("colonnes") List<String> colonnesExportList,
|
||||
@Parameter(description = "Inclure les en-têtes") @QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
|
||||
@Parameter(description = "Formater les dates") @QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
|
||||
@Parameter(description = "Inclure un onglet statistiques (Excel uniquement)") @QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
|
||||
@Parameter(description = "Mot de passe pour chiffrer le fichier (optionnel)") @QueryParam("motDePasse") String motDePasse) {
|
||||
|
||||
try {
|
||||
// Récupérer les membres selon les filtres
|
||||
List<MembreDTO> membres = membreService.listerMembresPourExport(
|
||||
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
|
||||
|
||||
byte[] exportData;
|
||||
String contentType;
|
||||
String extension;
|
||||
|
||||
List<String> colonnesExport = colonnesExportList != null ? colonnesExportList : new ArrayList<>();
|
||||
|
||||
if ("CSV".equalsIgnoreCase(format)) {
|
||||
exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
|
||||
contentType = "text/csv";
|
||||
extension = "csv";
|
||||
} else {
|
||||
// Pour Excel, inclure les statistiques uniquement si demandé et si format Excel
|
||||
boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format);
|
||||
exportData = membreService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, stats, motDePasse);
|
||||
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
extension = "xlsx";
|
||||
}
|
||||
|
||||
return Response.ok(exportData)
|
||||
.type(contentType)
|
||||
.header("Content-Disposition", "attachment; filename=\"membres_export_" +
|
||||
java.time.LocalDate.now() + "." + extension + "\"")
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/import/modele")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Operation(summary = "Télécharger le modèle Excel pour l'import")
|
||||
@APIResponse(responseCode = "200", description = "Modèle Excel généré")
|
||||
public Response telechargerModeleImport() {
|
||||
try {
|
||||
byte[] modele = membreService.genererModeleImport();
|
||||
return Response.ok(modele)
|
||||
.header("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"")
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la génération du modèle");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la génération du modèle: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Compter les membres selon les filtres pour l'export")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de membres correspondant aux critères")
|
||||
public Response compterMembresPourExport(
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
|
||||
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type de membre") @QueryParam("type") String type,
|
||||
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin) {
|
||||
|
||||
try {
|
||||
List<MembreDTO> membres = membreService.listerMembresPourExport(
|
||||
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
|
||||
|
||||
return Response.ok(Map.of("count", membres.size())).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du comptage des membres");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du comptage: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.notification.NotificationDTO;
|
||||
import dev.lions.unionflow.server.api.dto.notification.TemplateNotificationDTO;
|
||||
import dev.lions.unionflow.server.service.NotificationService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des notifications
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/notifications")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class NotificationResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(NotificationResource.class);
|
||||
|
||||
@Inject NotificationService notificationService;
|
||||
|
||||
// ========================================
|
||||
// TEMPLATES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau template de notification
|
||||
*
|
||||
* @param templateDTO DTO du template à créer
|
||||
* @return Template créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/templates")
|
||||
public Response creerTemplate(@Valid TemplateNotificationDTO templateDTO) {
|
||||
try {
|
||||
TemplateNotificationDTO result = notificationService.creerTemplate(templateDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du template");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du template: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NOTIFICATIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle notification
|
||||
*
|
||||
* @param notificationDTO DTO de la notification à créer
|
||||
* @return Notification créée
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
public Response creerNotification(@Valid NotificationDTO notificationDTO) {
|
||||
try {
|
||||
NotificationDTO result = notificationService.creerNotification(notificationDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marque une notification comme lue
|
||||
*
|
||||
* @param id ID de la notification
|
||||
* @return Notification mise à jour
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/marquer-lue")
|
||||
public Response marquerCommeLue(@PathParam("id") UUID id) {
|
||||
try {
|
||||
NotificationDTO result = notificationService.marquerCommeLue(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Notification non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du marquage de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors du marquage de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une notification par son ID
|
||||
*
|
||||
* @param id ID de la notification
|
||||
* @return Notification
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response trouverNotificationParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
NotificationDTO result = notificationService.trouverNotificationParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Notification non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les notifications d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des notifications
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
public Response listerNotificationsParMembre(@PathParam("membreId") UUID membreId) {
|
||||
try {
|
||||
List<NotificationDTO> result = notificationService.listerNotificationsParMembre(membreId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des notifications: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les notifications non lues d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des notifications non lues
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}/non-lues")
|
||||
public Response listerNotificationsNonLuesParMembre(@PathParam("membreId") UUID membreId) {
|
||||
try {
|
||||
List<NotificationDTO> result = notificationService.listerNotificationsNonLuesParMembre(membreId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications non lues");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la liste des notifications non lues: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les notifications en attente d'envoi
|
||||
*
|
||||
* @return Liste des notifications en attente
|
||||
*/
|
||||
@GET
|
||||
@Path("/en-attente-envoi")
|
||||
public Response listerNotificationsEnAttenteEnvoi() {
|
||||
try {
|
||||
List<NotificationDTO> result = notificationService.listerNotificationsEnAttenteEnvoi();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications en attente");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la liste des notifications en attente: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie des notifications groupées à plusieurs membres (WOU/DRY)
|
||||
*
|
||||
* @param request DTO contenant les IDs des membres, sujet, corps et canaux
|
||||
* @return Nombre de notifications créées
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/groupees")
|
||||
public Response envoyerNotificationsGroupees(NotificationGroupeeRequest request) {
|
||||
try {
|
||||
int notificationsCreees =
|
||||
notificationService.envoyerNotificationsGroupees(
|
||||
request.membreIds, request.sujet, request.corps, request.canaux);
|
||||
return Response.ok(Map.of("notificationsCreees", notificationsCreees)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'envoi des notifications groupées");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de l'envoi des notifications groupées: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les requêtes de notifications groupées (WOU/DRY) */
|
||||
public static class NotificationGroupeeRequest {
|
||||
public List<UUID> membreIds;
|
||||
public String sujet;
|
||||
public String corps;
|
||||
public List<String> canaux;
|
||||
|
||||
public NotificationGroupeeRequest() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import dev.lions.unionflow.server.service.OrganisationService;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des organisations
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@Path("/api/organisations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Organisations", description = "Gestion des organisations")
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class OrganisationResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(OrganisationResource.class);
|
||||
|
||||
@Inject OrganisationService organisationService;
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/** Crée une nouvelle organisation */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle organisation",
|
||||
description = "Crée une nouvelle organisation dans le système")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Organisation créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = OrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "409", description = "Organisation déjà existante"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response creerOrganisation(@Valid OrganisationDTO organisationDTO) {
|
||||
LOG.infof("Création d'une nouvelle organisation: %s", organisationDTO.getNom());
|
||||
|
||||
try {
|
||||
Organisation organisation = organisationService.convertFromDTO(organisationDTO);
|
||||
Organisation organisationCreee = organisationService.creerOrganisation(organisation);
|
||||
OrganisationDTO dto = organisationService.convertToDTO(organisationCreee);
|
||||
|
||||
return Response.created(URI.create("/api/organisations/" + organisationCreee.getId()))
|
||||
.entity(dto)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la création de l'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la création de l'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère toutes les organisations actives */
|
||||
@GET
|
||||
@jakarta.annotation.security.PermitAll // ✅ Accès public pour inscription
|
||||
@Operation(
|
||||
summary = "Lister les organisations",
|
||||
description = "Récupère la liste des organisations actives avec pagination")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des organisations récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response listerOrganisations(
|
||||
@Parameter(description = "Numéro de page (commence à 0)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
int page,
|
||||
@Parameter(description = "Taille de la page", example = "20")
|
||||
@QueryParam("size")
|
||||
@DefaultValue("20")
|
||||
int size,
|
||||
@Parameter(description = "Terme de recherche (nom ou nom court)") @QueryParam("recherche")
|
||||
String recherche) {
|
||||
|
||||
LOG.infof(
|
||||
"Récupération des organisations - page: %d, size: %d, recherche: %s",
|
||||
page, size, recherche);
|
||||
|
||||
try {
|
||||
List<Organisation> organisations;
|
||||
|
||||
if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size);
|
||||
} else {
|
||||
organisations = organisationService.listerOrganisationsActives(page, size);
|
||||
}
|
||||
|
||||
List<OrganisationDTO> dtos =
|
||||
organisations.stream()
|
||||
.map(organisationService::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(dtos).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des organisations");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Récupère une organisation par son ID */
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
|
||||
@Operation(
|
||||
summary = "Récupérer une organisation",
|
||||
description = "Récupère une organisation par son ID")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Organisation trouvée",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = OrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response obtenirOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Récupération de l'organisation ID: %d", id);
|
||||
|
||||
return organisationService
|
||||
.trouverParId(id)
|
||||
.map(
|
||||
organisation -> {
|
||||
OrganisationDTO dto = organisationService.convertToDTO(organisation);
|
||||
return Response.ok(dto).build();
|
||||
})
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Organisation non trouvée"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/** Met à jour une organisation */
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}")
|
||||
|
||||
@Operation(
|
||||
summary = "Mettre à jour une organisation",
|
||||
description = "Met à jour les informations d'une organisation")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Organisation mise à jour avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = OrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "409", description = "Conflit de données"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response mettreAJourOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id,
|
||||
@Valid OrganisationDTO organisationDTO) {
|
||||
|
||||
LOG.infof("Mise à jour de l'organisation ID: %s", id);
|
||||
|
||||
try {
|
||||
Organisation organisationMiseAJour = organisationService.convertFromDTO(organisationDTO);
|
||||
Organisation organisation =
|
||||
organisationService.mettreAJourOrganisation(id, organisationMiseAJour, "system");
|
||||
OrganisationDTO dto = organisationService.convertToDTO(organisation);
|
||||
|
||||
return Response.ok(dto).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la mise à jour de l'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la mise à jour de l'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Supprime une organisation */
|
||||
@DELETE
|
||||
@RolesAllowed({"ADMIN"})
|
||||
@Path("/{id}")
|
||||
|
||||
@Operation(
|
||||
summary = "Supprimer une organisation",
|
||||
description = "Supprime une organisation (soft delete)")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Organisation supprimée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "409", description = "Impossible de supprimer l'organisation"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response supprimerOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Suppression de l'organisation ID: %d", id);
|
||||
|
||||
try {
|
||||
organisationService.supprimerOrganisation(id, "system");
|
||||
return Response.noContent().build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
LOG.warnf("Erreur lors de la suppression de l'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la suppression de l'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Recherche avancée d'organisations */
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
|
||||
@Operation(
|
||||
summary = "Recherche avancée",
|
||||
description = "Recherche d'organisations avec critères multiples")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Résultats de recherche",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response rechercheAvancee(
|
||||
@Parameter(description = "Nom de l'organisation") @QueryParam("nom") String nom,
|
||||
@Parameter(description = "Type d'organisation") @QueryParam("type") String typeOrganisation,
|
||||
@Parameter(description = "Statut") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Ville") @QueryParam("ville") String ville,
|
||||
@Parameter(description = "Région") @QueryParam("region") String region,
|
||||
@Parameter(description = "Pays") @QueryParam("pays") String pays,
|
||||
@Parameter(description = "Numéro de page") @QueryParam("page") @DefaultValue("0") int page,
|
||||
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20")
|
||||
int size) {
|
||||
|
||||
LOG.infof("Recherche avancée d'organisations avec critères multiples");
|
||||
|
||||
try {
|
||||
List<Organisation> organisations =
|
||||
organisationService.rechercheAvancee(
|
||||
nom, typeOrganisation, statut, ville, region, pays, page, size);
|
||||
|
||||
List<OrganisationDTO> dtos =
|
||||
organisations.stream()
|
||||
.map(organisationService::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response.ok(dtos).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche avancée");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Active une organisation */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/activer")
|
||||
|
||||
@Operation(
|
||||
summary = "Activer une organisation",
|
||||
description = "Active une organisation suspendue")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Organisation activée avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response activerOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Activation de l'organisation ID: %d", id);
|
||||
|
||||
try {
|
||||
Organisation organisation = organisationService.activerOrganisation(id, "system");
|
||||
OrganisationDTO dto = organisationService.convertToDTO(organisation);
|
||||
return Response.ok(dto).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'activation de l'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Suspend une organisation */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/suspendre")
|
||||
|
||||
@Operation(
|
||||
summary = "Suspendre une organisation",
|
||||
description = "Suspend une organisation active")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Organisation suspendue avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Organisation non trouvée"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response suspendreOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Suspension de l'organisation ID: %d", id);
|
||||
|
||||
try {
|
||||
Organisation organisation = organisationService.suspendreOrganisation(id, "system");
|
||||
OrganisationDTO dto = organisationService.convertToDTO(organisation);
|
||||
return Response.ok(dto).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la suspension de l'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtient les statistiques des organisations */
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
|
||||
@Operation(
|
||||
summary = "Statistiques des organisations",
|
||||
description = "Récupère les statistiques globales des organisations")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response obtenirStatistiques() {
|
||||
LOG.info("Récupération des statistiques des organisations");
|
||||
|
||||
try {
|
||||
Map<String, Object> statistiques = organisationService.obtenirStatistiques();
|
||||
return Response.ok(statistiques).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération des statistiques");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO;
|
||||
import dev.lions.unionflow.server.service.PaiementService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des paiements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/paiements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class PaiementResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PaiementResource.class);
|
||||
|
||||
@Inject PaiementService paiementService;
|
||||
|
||||
/**
|
||||
* Crée un nouveau paiement
|
||||
*
|
||||
* @param paiementDTO DTO du paiement à créer
|
||||
* @return Paiement créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
public Response creerPaiement(@Valid PaiementDTO paiementDTO) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.creerPaiement(paiementDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @param paiementDTO DTO avec les modifications
|
||||
* @return Paiement mis à jour
|
||||
*/
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}")
|
||||
public Response mettreAJourPaiement(@PathParam("id") UUID id, @Valid PaiementDTO paiementDTO) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.mettreAJourPaiement(id, paiementDTO);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la mise à jour du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement validé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/valider")
|
||||
public Response validerPaiement(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.validerPaiement(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la validation du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la validation du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annule un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement annulé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}/annuler")
|
||||
public Response annulerPaiement(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.annulerPaiement(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'annulation du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de l'annulation du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un paiement par son ID
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response trouverParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.trouverParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un paiement par son numéro de référence
|
||||
*
|
||||
* @param numeroReference Numéro de référence
|
||||
* @return Paiement
|
||||
*/
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
public Response trouverParNumeroReference(@PathParam("numeroReference") String numeroReference) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.trouverParNumeroReference(numeroReference);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les paiements d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
public Response listerParMembre(@PathParam("membreId") UUID membreId) {
|
||||
try {
|
||||
List<PaiementDTO> result = paiementService.listerParMembre(membreId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des paiements");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des paiements: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.PreferencesNotificationService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/** Resource REST pour la gestion des préférences utilisateur */
|
||||
@Path("/api/preferences")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@Tag(name = "Préférences", description = "API de gestion des préférences utilisateur")
|
||||
public class PreferencesResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PreferencesResource.class);
|
||||
|
||||
@Inject PreferencesNotificationService preferencesService;
|
||||
|
||||
@GET
|
||||
@Path("/{utilisateurId}")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Obtenir les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Préférences récupérées avec succès")
|
||||
public Response obtenirPreferences(
|
||||
@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Récupération des préférences pour l'utilisateur %s", utilisateurId);
|
||||
Map<String, Boolean> preferences = preferencesService.obtenirPreferences(utilisateurId);
|
||||
return Response.ok(preferences).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{utilisateurId}")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Mettre à jour les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "204", description = "Préférences mises à jour avec succès")
|
||||
public Response mettreAJourPreferences(
|
||||
@PathParam("utilisateurId") UUID utilisateurId, Map<String, Boolean> preferences) {
|
||||
LOG.infof("Mise à jour des préférences pour l'utilisateur %s", utilisateurId);
|
||||
preferencesService.mettreAJourPreferences(utilisateurId, preferences);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{utilisateurId}/reinitialiser")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Réinitialiser les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "204", description = "Préférences réinitialisées avec succès")
|
||||
public Response reinitialiserPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId);
|
||||
preferencesService.reinitialiserPreferences(utilisateurId);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{utilisateurId}/export")
|
||||
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
|
||||
@Operation(summary = "Exporter les préférences d'un utilisateur")
|
||||
@APIResponse(responseCode = "200", description = "Préférences exportées avec succès")
|
||||
public Response exporterPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
|
||||
LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId);
|
||||
Map<String, Object> export = preferencesService.exporterPreferences(utilisateurId);
|
||||
return Response.ok(export).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.organisation.TypeOrganisationDTO;
|
||||
import dev.lions.unionflow.server.service.TypeOrganisationService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Ressource REST pour la gestion du catalogue des types d'organisation.
|
||||
*/
|
||||
@Path("/api/types-organisations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Types d'organisation", description = "Catalogue des types d'organisation")
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class TypeOrganisationResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TypeOrganisationResource.class);
|
||||
|
||||
@Inject TypeOrganisationService service;
|
||||
|
||||
/** Liste les types d'organisation. */
|
||||
@GET
|
||||
@Operation(
|
||||
summary = "Lister les types d'organisation",
|
||||
description = "Récupère la liste des types d'organisation, optionnellement seulement actifs")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Liste des types récupérée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response listTypes(
|
||||
@Parameter(description = "Limiter aux types actifs", example = "true")
|
||||
@QueryParam("onlyActifs")
|
||||
@DefaultValue("true")
|
||||
String onlyActifs) {
|
||||
// Parsing manuel pour éviter toute erreur de conversion JAX-RS (qui peut renvoyer une 400)
|
||||
boolean actifsSeulement = !"false".equalsIgnoreCase(onlyActifs);
|
||||
List<TypeOrganisationDTO> types = service.listAll(actifsSeulement);
|
||||
return Response.ok(types).build();
|
||||
}
|
||||
|
||||
/** Crée un nouveau type d'organisation (réservé à l'administration). */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Operation(
|
||||
summary = "Créer un type d'organisation",
|
||||
description = "Crée un nouveau type dans le catalogue (code doit exister dans l'enum)")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "201",
|
||||
description = "Type créé avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response create(TypeOrganisationDTO dto) {
|
||||
try {
|
||||
TypeOrganisationDTO created = service.create(dto);
|
||||
return Response.status(Response.Status.CREATED).entity(created).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la création du type d'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la création du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Met à jour un type. */
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Mettre à jour un type d'organisation",
|
||||
description = "Met à jour un type existant (libellé, description, ordre, actif, code)")
|
||||
@APIResponses({
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Type mis à jour avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = TypeOrganisationDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "404", description = "Type non trouvé"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response update(@PathParam("id") UUID id, TypeOrganisationDTO dto) {
|
||||
try {
|
||||
TypeOrganisationDTO updated = service.update(id, dto);
|
||||
return Response.ok(updated).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur lors de la mise à jour du type d'organisation: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la mise à jour du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Désactive un type (soft delete). */
|
||||
@DELETE
|
||||
@RolesAllowed({"ADMIN"})
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
summary = "Désactiver un type d'organisation",
|
||||
description = "Désactive un type dans le catalogue (soft delete)")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Type désactivé avec succès"),
|
||||
@APIResponse(responseCode = "404", description = "Type non trouvé"),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response disable(@PathParam("id") UUID id) {
|
||||
try {
|
||||
service.disable(id);
|
||||
return Response.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur inattendue lors de la désactivation du type d'organisation");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur interne du serveur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
|
||||
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.service.WaveService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour l'intégration Wave Mobile Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/wave")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed({"ADMIN", "MEMBRE", "USER"})
|
||||
public class WaveResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WaveResource.class);
|
||||
|
||||
@Inject WaveService waveService;
|
||||
|
||||
// ========================================
|
||||
// COMPTES WAVE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau compte Wave
|
||||
*
|
||||
* @param compteWaveDTO DTO du compte à créer
|
||||
* @return Compte créé
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/comptes")
|
||||
public Response creerCompteWave(@Valid CompteWaveDTO compteWaveDTO) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.creerCompteWave(compteWaveDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @param compteWaveDTO DTO avec les modifications
|
||||
* @return Compte mis à jour
|
||||
*/
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/comptes/{id}")
|
||||
public Response mettreAJourCompteWave(@PathParam("id") UUID id, @Valid CompteWaveDTO compteWaveDTO) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.mettreAJourCompteWave(id, compteWaveDTO);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la mise à jour du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return Compte vérifié
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/comptes/{id}/verifier")
|
||||
public Response verifierCompteWave(@PathParam("id") UUID id) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.verifierCompteWave(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la vérification du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par son ID
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return Compte Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/{id}")
|
||||
public Response trouverCompteWaveParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.trouverCompteWaveParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par numéro de téléphone
|
||||
*
|
||||
* @param numeroTelephone Numéro de téléphone
|
||||
* @return Compte Wave ou null
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/telephone/{numeroTelephone}")
|
||||
public Response trouverCompteWaveParTelephone(@PathParam("numeroTelephone") String numeroTelephone) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.trouverCompteWaveParTelephone(numeroTelephone);
|
||||
if (result == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les comptes Wave d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des comptes Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/organisation/{organisationId}")
|
||||
public Response listerComptesWaveParOrganisation(@PathParam("organisationId") UUID organisationId) {
|
||||
try {
|
||||
List<CompteWaveDTO> result = waveService.listerComptesWaveParOrganisation(organisationId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des comptes Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des comptes Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TRANSACTIONS WAVE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle transaction Wave
|
||||
*
|
||||
* @param transactionWaveDTO DTO de la transaction à créer
|
||||
* @return Transaction créée
|
||||
*/
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/transactions")
|
||||
public Response creerTransactionWave(@Valid TransactionWaveDTO transactionWaveDTO) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.creerTransactionWave(transactionWaveDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le statut d'une transaction Wave
|
||||
*
|
||||
* @param waveTransactionId Identifiant Wave de la transaction
|
||||
* @param statut Nouveau statut
|
||||
* @return Transaction mise à jour
|
||||
*/
|
||||
@PUT
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@Path("/transactions/{waveTransactionId}/statut")
|
||||
public Response mettreAJourStatutTransaction(
|
||||
@PathParam("waveTransactionId") String waveTransactionId, StatutTransactionWave statut) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.mettreAJourStatutTransaction(waveTransactionId, statut);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du statut de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une transaction Wave par son identifiant Wave
|
||||
*
|
||||
* @param waveTransactionId Identifiant Wave
|
||||
* @return Transaction Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/transactions/{waveTransactionId}")
|
||||
public Response trouverTransactionWaveParId(@PathParam("waveTransactionId") String waveTransactionId) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.trouverTransactionWaveParId(waveTransactionId);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package dev.lions.unionflow.server.security;
|
||||
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Configuration et utilitaires de sécurité avec Keycloak
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class SecurityConfig {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SecurityConfig.class);
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/** Rôles disponibles dans l'application */
|
||||
public static class Roles {
|
||||
public static final String ADMIN = "ADMIN";
|
||||
public static final String GESTIONNAIRE_MEMBRE = "GESTIONNAIRE_MEMBRE";
|
||||
public static final String TRESORIER = "TRESORIER";
|
||||
public static final String SECRETAIRE = "SECRETAIRE";
|
||||
public static final String MEMBRE = "MEMBRE";
|
||||
public static final String PRESIDENT = "PRESIDENT";
|
||||
public static final String VICE_PRESIDENT = "VICE_PRESIDENT";
|
||||
public static final String ORGANISATEUR_EVENEMENT = "ORGANISATEUR_EVENEMENT";
|
||||
public static final String GESTIONNAIRE_SOLIDARITE = "GESTIONNAIRE_SOLIDARITE";
|
||||
public static final String AUDITEUR = "AUDITEUR";
|
||||
}
|
||||
|
||||
/** Permissions disponibles dans l'application */
|
||||
public static class Permissions {
|
||||
// Permissions membres
|
||||
public static final String CREATE_MEMBRE = "CREATE_MEMBRE";
|
||||
public static final String READ_MEMBRE = "READ_MEMBRE";
|
||||
public static final String UPDATE_MEMBRE = "UPDATE_MEMBRE";
|
||||
public static final String DELETE_MEMBRE = "DELETE_MEMBRE";
|
||||
|
||||
// Permissions organisations
|
||||
public static final String CREATE_ORGANISATION = "CREATE_ORGANISATION";
|
||||
public static final String READ_ORGANISATION = "READ_ORGANISATION";
|
||||
public static final String UPDATE_ORGANISATION = "UPDATE_ORGANISATION";
|
||||
public static final String DELETE_ORGANISATION = "DELETE_ORGANISATION";
|
||||
|
||||
// Permissions événements
|
||||
public static final String CREATE_EVENEMENT = "CREATE_EVENEMENT";
|
||||
public static final String READ_EVENEMENT = "READ_EVENEMENT";
|
||||
public static final String UPDATE_EVENEMENT = "UPDATE_EVENEMENT";
|
||||
public static final String DELETE_EVENEMENT = "DELETE_EVENEMENT";
|
||||
|
||||
// Permissions finances
|
||||
public static final String CREATE_COTISATION = "CREATE_COTISATION";
|
||||
public static final String READ_COTISATION = "READ_COTISATION";
|
||||
public static final String UPDATE_COTISATION = "UPDATE_COTISATION";
|
||||
public static final String DELETE_COTISATION = "DELETE_COTISATION";
|
||||
|
||||
// Permissions solidarité
|
||||
public static final String CREATE_SOLIDARITE = "CREATE_SOLIDARITE";
|
||||
public static final String READ_SOLIDARITE = "READ_SOLIDARITE";
|
||||
public static final String UPDATE_SOLIDARITE = "UPDATE_SOLIDARITE";
|
||||
public static final String DELETE_SOLIDARITE = "DELETE_SOLIDARITE";
|
||||
|
||||
// Permissions administration
|
||||
public static final String ADMIN_USERS = "ADMIN_USERS";
|
||||
public static final String ADMIN_SYSTEM = "ADMIN_SYSTEM";
|
||||
public static final String VIEW_REPORTS = "VIEW_REPORTS";
|
||||
public static final String EXPORT_DATA = "EXPORT_DATA";
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a un rôle spécifique
|
||||
*
|
||||
* @param role le rôle à vérifier
|
||||
* @return true si l'utilisateur a le rôle
|
||||
*/
|
||||
public boolean hasRole(String role) {
|
||||
return keycloakService.hasRole(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a au moins un des rôles spécifiés
|
||||
*
|
||||
* @param roles les rôles à vérifier
|
||||
* @return true si l'utilisateur a au moins un des rôles
|
||||
*/
|
||||
public boolean hasAnyRole(String... roles) {
|
||||
return keycloakService.hasAnyRole(roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a tous les rôles spécifiés
|
||||
*
|
||||
* @param roles les rôles à vérifier
|
||||
* @return true si l'utilisateur a tous les rôles
|
||||
*/
|
||||
public boolean hasAllRoles(String... roles) {
|
||||
return keycloakService.hasAllRoles(roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient l'ID de l'utilisateur actuel
|
||||
*
|
||||
* @return l'ID de l'utilisateur ou null si non authentifié
|
||||
*/
|
||||
public String getCurrentUserId() {
|
||||
return keycloakService.getCurrentUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient l'email de l'utilisateur actuel
|
||||
*
|
||||
* @return l'email de l'utilisateur ou null si non authentifié
|
||||
*/
|
||||
public String getCurrentUserEmail() {
|
||||
return keycloakService.getCurrentUserEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient tous les rôles de l'utilisateur actuel
|
||||
*
|
||||
* @return les rôles de l'utilisateur
|
||||
*/
|
||||
public Set<String> getCurrentUserRoles() {
|
||||
return keycloakService.getCurrentUserRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel est authentifié
|
||||
*
|
||||
* @return true si l'utilisateur est authentifié
|
||||
*/
|
||||
public boolean isAuthenticated() {
|
||||
return keycloakService.isAuthenticated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel est un administrateur
|
||||
*
|
||||
* @return true si l'utilisateur est administrateur
|
||||
*/
|
||||
public boolean isAdmin() {
|
||||
return hasRole(Roles.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les membres
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les membres
|
||||
*/
|
||||
public boolean canManageMembers() {
|
||||
return hasAnyRole(Roles.ADMIN, Roles.GESTIONNAIRE_MEMBRE, Roles.PRESIDENT, Roles.SECRETAIRE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les finances
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les finances
|
||||
*/
|
||||
public boolean canManageFinances() {
|
||||
return hasAnyRole(Roles.ADMIN, Roles.TRESORIER, Roles.PRESIDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les événements
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les événements
|
||||
*/
|
||||
public boolean canManageEvents() {
|
||||
return hasAnyRole(Roles.ADMIN, Roles.ORGANISATEUR_EVENEMENT, Roles.PRESIDENT, Roles.SECRETAIRE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les organisations
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les organisations
|
||||
*/
|
||||
public boolean canManageOrganizations() {
|
||||
return hasAnyRole(Roles.ADMIN, Roles.PRESIDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut accéder aux données d'un membre spécifique
|
||||
*
|
||||
* @param membreId l'ID du membre
|
||||
* @return true si l'utilisateur peut accéder aux données
|
||||
*/
|
||||
public boolean canAccessMemberData(String membreId) {
|
||||
// Un utilisateur peut toujours accéder à ses propres données
|
||||
if (membreId.equals(getCurrentUserId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les gestionnaires peuvent accéder aux données de tous les membres
|
||||
return canManageMembers();
|
||||
}
|
||||
|
||||
/** Log les informations de sécurité pour debug */
|
||||
public void logSecurityInfo() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
if (isAuthenticated()) {
|
||||
LOG.debugf(
|
||||
"Utilisateur authentifié: %s, Rôles: %s", getCurrentUserEmail(), getCurrentUserRoles());
|
||||
} else {
|
||||
LOG.debug("Utilisateur non authentifié");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO;
|
||||
import dev.lions.unionflow.server.entity.Adhesion;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.AdhesionRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des adhésions
|
||||
* Contient la logique métier et les règles de validation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AdhesionService {
|
||||
|
||||
@Inject AdhesionRepository adhesionRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
/**
|
||||
* Récupère toutes les adhésions avec pagination
|
||||
*
|
||||
* @param page numéro de page (0-based)
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions converties en DTO
|
||||
*/
|
||||
public List<AdhesionDTO> getAllAdhesions(int page, int size) {
|
||||
log.debug("Récupération des adhésions - page: {}, size: {}", page, size);
|
||||
|
||||
jakarta.persistence.TypedQuery<Adhesion> query =
|
||||
adhesionRepository
|
||||
.getEntityManager()
|
||||
.createQuery(
|
||||
"SELECT a FROM Adhesion a ORDER BY a.dateDemande DESC", Adhesion.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
List<Adhesion> adhesions = query.getResultList();
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une adhésion par son ID
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @return DTO de l'adhésion
|
||||
* @throws NotFoundException si l'adhésion n'existe pas
|
||||
*/
|
||||
public AdhesionDTO getAdhesionById(@NotNull UUID id) {
|
||||
log.debug("Récupération de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une adhésion par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return DTO de l'adhésion
|
||||
* @throws NotFoundException si l'adhésion n'existe pas
|
||||
*/
|
||||
public AdhesionDTO getAdhesionByReference(@NotNull String numeroReference) {
|
||||
log.debug("Récupération de l'adhésion avec référence: {}", numeroReference);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByNumeroReference(numeroReference)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Adhésion non trouvée avec la référence: " + numeroReference));
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle adhésion
|
||||
*
|
||||
* @param adhesionDTO données de l'adhésion à créer
|
||||
* @return DTO de l'adhésion créée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO createAdhesion(@Valid AdhesionDTO adhesionDTO) {
|
||||
log.info(
|
||||
"Création d'une nouvelle adhésion pour le membre: {} et l'organisation: {}",
|
||||
adhesionDTO.getMembreId(),
|
||||
adhesionDTO.getOrganisationId());
|
||||
|
||||
// Validation du membre
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(adhesionDTO.getMembreId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Membre non trouvé avec l'ID: " + adhesionDTO.getMembreId()));
|
||||
|
||||
// Validation de l'organisation
|
||||
Organisation organisation =
|
||||
organisationRepository
|
||||
.findByIdOptional(adhesionDTO.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + adhesionDTO.getOrganisationId()));
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Adhesion adhesion = convertToEntity(adhesionDTO);
|
||||
adhesion.setMembre(membre);
|
||||
adhesion.setOrganisation(organisation);
|
||||
|
||||
// Génération automatique du numéro de référence si absent
|
||||
if (adhesion.getNumeroReference() == null || adhesion.getNumeroReference().isEmpty()) {
|
||||
adhesion.setNumeroReference(genererNumeroReference());
|
||||
}
|
||||
|
||||
// Initialisation par défaut
|
||||
if (adhesion.getDateDemande() == null) {
|
||||
adhesion.setDateDemande(LocalDate.now());
|
||||
}
|
||||
if (adhesion.getStatut() == null || adhesion.getStatut().isEmpty()) {
|
||||
adhesion.setStatut("EN_ATTENTE");
|
||||
}
|
||||
if (adhesion.getMontantPaye() == null) {
|
||||
adhesion.setMontantPaye(BigDecimal.ZERO);
|
||||
}
|
||||
if (adhesion.getCodeDevise() == null || adhesion.getCodeDevise().isEmpty()) {
|
||||
adhesion.setCodeDevise("XOF");
|
||||
}
|
||||
|
||||
// Persistance
|
||||
adhesionRepository.persist(adhesion);
|
||||
|
||||
log.info(
|
||||
"Adhésion créée avec succès - ID: {}, Référence: {}",
|
||||
adhesion.getId(),
|
||||
adhesion.getNumeroReference());
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une adhésion existante
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param adhesionDTO nouvelles données
|
||||
* @return DTO de l'adhésion mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO updateAdhesion(@NotNull UUID id, @Valid AdhesionDTO adhesionDTO) {
|
||||
log.info("Mise à jour de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesionExistante =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
updateAdhesionFields(adhesionExistante, adhesionDTO);
|
||||
|
||||
log.info("Adhésion mise à jour avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesionExistante);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime (désactive) une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteAdhesion(@NotNull UUID id) {
|
||||
log.info("Suppression de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
// Vérification si l'adhésion peut être supprimée
|
||||
if ("PAYEE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException("Impossible de supprimer une adhésion déjà payée");
|
||||
}
|
||||
|
||||
adhesion.setStatut("ANNULEE");
|
||||
|
||||
log.info("Adhésion supprimée avec succès - ID: {}", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approuve une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param approuvePar nom de l'utilisateur qui approuve
|
||||
* @return DTO de l'adhésion approuvée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO approuverAdhesion(@NotNull UUID id, String approuvePar) {
|
||||
log.info("Approbation de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"EN_ATTENTE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException(
|
||||
"Seules les adhésions en attente peuvent être approuvées");
|
||||
}
|
||||
|
||||
adhesion.setStatut("APPROUVEE");
|
||||
adhesion.setDateApprobation(LocalDate.now());
|
||||
adhesion.setApprouvePar(approuvePar);
|
||||
adhesion.setDateValidation(LocalDate.now());
|
||||
|
||||
log.info("Adhésion approuvée avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejette une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param motifRejet motif du rejet
|
||||
* @return DTO de l'adhésion rejetée
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO rejeterAdhesion(@NotNull UUID id, String motifRejet) {
|
||||
log.info("Rejet de l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"EN_ATTENTE".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées");
|
||||
}
|
||||
|
||||
adhesion.setStatut("REJETEE");
|
||||
adhesion.setMotifRejet(motifRejet);
|
||||
|
||||
log.info("Adhésion rejetée avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un paiement pour une adhésion
|
||||
*
|
||||
* @param id identifiant UUID de l'adhésion
|
||||
* @param montantPaye montant payé
|
||||
* @param methodePaiement méthode de paiement
|
||||
* @param referencePaiement référence du paiement
|
||||
* @return DTO de l'adhésion mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public AdhesionDTO enregistrerPaiement(
|
||||
@NotNull UUID id,
|
||||
BigDecimal montantPaye,
|
||||
String methodePaiement,
|
||||
String referencePaiement) {
|
||||
log.info("Enregistrement du paiement pour l'adhésion avec ID: {}", id);
|
||||
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
|
||||
|
||||
if (!"APPROUVEE".equals(adhesion.getStatut()) && !"EN_PAIEMENT".equals(adhesion.getStatut())) {
|
||||
throw new IllegalStateException(
|
||||
"Seules les adhésions approuvées peuvent recevoir un paiement");
|
||||
}
|
||||
|
||||
BigDecimal nouveauMontantPaye =
|
||||
adhesion.getMontantPaye() != null
|
||||
? adhesion.getMontantPaye().add(montantPaye)
|
||||
: montantPaye;
|
||||
|
||||
adhesion.setMontantPaye(nouveauMontantPaye);
|
||||
adhesion.setMethodePaiement(methodePaiement);
|
||||
adhesion.setReferencePaiement(referencePaiement);
|
||||
adhesion.setDatePaiement(java.time.LocalDateTime.now());
|
||||
|
||||
// Mise à jour du statut si payée intégralement
|
||||
if (adhesion.isPayeeIntegralement()) {
|
||||
adhesion.setStatut("PAYEE");
|
||||
} else {
|
||||
adhesion.setStatut("EN_PAIEMENT");
|
||||
}
|
||||
|
||||
log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
|
||||
|
||||
return convertToDTO(adhesion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions d'un membre
|
||||
*
|
||||
* @param membreId identifiant UUID du membre
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions du membre
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByMembre(@NotNull UUID membreId, int page, int size) {
|
||||
log.debug("Récupération des adhésions du membre: {}", membreId);
|
||||
|
||||
if (!membreRepository.findByIdOptional(membreId).isPresent()) {
|
||||
throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
|
||||
}
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByMembreId(membreId).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions d'une organisation
|
||||
*
|
||||
* @param organisationId identifiant UUID de l'organisation
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions de l'organisation
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByOrganisation(
|
||||
@NotNull UUID organisationId, int page, int size) {
|
||||
log.debug("Récupération des adhésions de l'organisation: {}", organisationId);
|
||||
|
||||
if (!organisationRepository.findByIdOptional(organisationId).isPresent()) {
|
||||
throw new NotFoundException("Organisation non trouvée avec l'ID: " + organisationId);
|
||||
}
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByOrganisationId(organisationId).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions par statut
|
||||
*
|
||||
* @param statut statut recherché
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions avec le statut spécifié
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsByStatut(@NotNull String statut, int page, int size) {
|
||||
log.debug("Récupération des adhésions avec statut: {}", statut);
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findByStatut(statut).stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adhésions en attente
|
||||
*
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des adhésions en attente
|
||||
*/
|
||||
public List<AdhesionDTO> getAdhesionsEnAttente(int page, int size) {
|
||||
log.debug("Récupération des adhésions en attente");
|
||||
|
||||
List<Adhesion> adhesions =
|
||||
adhesionRepository.findEnAttente().stream()
|
||||
.skip(page * size)
|
||||
.limit(size)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des adhésions
|
||||
*
|
||||
* @return map contenant les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesAdhesions() {
|
||||
log.debug("Calcul des statistiques des adhésions");
|
||||
|
||||
long totalAdhesions = adhesionRepository.count();
|
||||
long adhesionsApprouvees = adhesionRepository.findByStatut("APPROUVEE").size();
|
||||
long adhesionsEnAttente = adhesionRepository.findEnAttente().size();
|
||||
long adhesionsPayees = adhesionRepository.findByStatut("PAYEE").size();
|
||||
|
||||
return Map.of(
|
||||
"totalAdhesions", totalAdhesions,
|
||||
"adhesionsApprouvees", adhesionsApprouvees,
|
||||
"adhesionsEnAttente", adhesionsEnAttente,
|
||||
"adhesionsPayees", adhesionsPayees,
|
||||
"tauxApprobation",
|
||||
totalAdhesions > 0 ? (adhesionsApprouvees * 100.0 / totalAdhesions) : 0.0,
|
||||
"tauxPaiement",
|
||||
adhesionsApprouvees > 0
|
||||
? (adhesionsPayees * 100.0 / adhesionsApprouvees)
|
||||
: 0.0);
|
||||
}
|
||||
|
||||
/** Génère un numéro de référence unique pour une adhésion */
|
||||
private String genererNumeroReference() {
|
||||
return "ADH-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||
}
|
||||
|
||||
/** Convertit une entité Adhesion en DTO */
|
||||
private AdhesionDTO convertToDTO(Adhesion adhesion) {
|
||||
if (adhesion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AdhesionDTO dto = new AdhesionDTO();
|
||||
|
||||
dto.setId(adhesion.getId());
|
||||
dto.setNumeroReference(adhesion.getNumeroReference());
|
||||
|
||||
// Conversion du membre associé
|
||||
if (adhesion.getMembre() != null) {
|
||||
dto.setMembreId(adhesion.getMembre().getId());
|
||||
dto.setNomMembre(adhesion.getMembre().getNomComplet());
|
||||
dto.setNumeroMembre(adhesion.getMembre().getNumeroMembre());
|
||||
dto.setEmailMembre(adhesion.getMembre().getEmail());
|
||||
}
|
||||
|
||||
// Conversion de l'organisation
|
||||
if (adhesion.getOrganisation() != null) {
|
||||
dto.setOrganisationId(adhesion.getOrganisation().getId());
|
||||
dto.setNomOrganisation(adhesion.getOrganisation().getNom());
|
||||
}
|
||||
|
||||
// Propriétés de l'adhésion
|
||||
dto.setDateDemande(adhesion.getDateDemande());
|
||||
dto.setFraisAdhesion(adhesion.getFraisAdhesion());
|
||||
dto.setMontantPaye(adhesion.getMontantPaye());
|
||||
dto.setCodeDevise(adhesion.getCodeDevise());
|
||||
dto.setStatut(adhesion.getStatut());
|
||||
dto.setDateApprobation(adhesion.getDateApprobation());
|
||||
dto.setDatePaiement(adhesion.getDatePaiement());
|
||||
dto.setMethodePaiement(adhesion.getMethodePaiement());
|
||||
dto.setReferencePaiement(adhesion.getReferencePaiement());
|
||||
dto.setMotifRejet(adhesion.getMotifRejet());
|
||||
dto.setObservations(adhesion.getObservations());
|
||||
dto.setApprouvePar(adhesion.getApprouvePar());
|
||||
dto.setDateValidation(adhesion.getDateValidation());
|
||||
|
||||
// Métadonnées de BaseEntity
|
||||
dto.setDateCreation(adhesion.getDateCreation());
|
||||
dto.setDateModification(adhesion.getDateModification());
|
||||
dto.setCreePar(adhesion.getCreePar());
|
||||
dto.setModifiePar(adhesion.getModifiePar());
|
||||
dto.setActif(adhesion.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité Adhesion */
|
||||
private Adhesion convertToEntity(AdhesionDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Adhesion adhesion = new Adhesion();
|
||||
|
||||
adhesion.setNumeroReference(dto.getNumeroReference());
|
||||
adhesion.setDateDemande(dto.getDateDemande());
|
||||
adhesion.setFraisAdhesion(dto.getFraisAdhesion());
|
||||
adhesion.setMontantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO);
|
||||
adhesion.setCodeDevise(dto.getCodeDevise());
|
||||
adhesion.setStatut(dto.getStatut());
|
||||
adhesion.setDateApprobation(dto.getDateApprobation());
|
||||
adhesion.setDatePaiement(dto.getDatePaiement());
|
||||
adhesion.setMethodePaiement(dto.getMethodePaiement());
|
||||
adhesion.setReferencePaiement(dto.getReferencePaiement());
|
||||
adhesion.setMotifRejet(dto.getMotifRejet());
|
||||
adhesion.setObservations(dto.getObservations());
|
||||
adhesion.setApprouvePar(dto.getApprouvePar());
|
||||
adhesion.setDateValidation(dto.getDateValidation());
|
||||
|
||||
return adhesion;
|
||||
}
|
||||
|
||||
/** Met à jour les champs modifiables d'une adhésion existante */
|
||||
private void updateAdhesionFields(Adhesion adhesion, AdhesionDTO dto) {
|
||||
if (dto.getFraisAdhesion() != null) {
|
||||
adhesion.setFraisAdhesion(dto.getFraisAdhesion());
|
||||
}
|
||||
if (dto.getMontantPaye() != null) {
|
||||
adhesion.setMontantPaye(dto.getMontantPaye());
|
||||
}
|
||||
if (dto.getStatut() != null) {
|
||||
adhesion.setStatut(dto.getStatut());
|
||||
}
|
||||
if (dto.getDateApprobation() != null) {
|
||||
adhesion.setDateApprobation(dto.getDateApprobation());
|
||||
}
|
||||
if (dto.getDatePaiement() != null) {
|
||||
adhesion.setDatePaiement(dto.getDatePaiement());
|
||||
}
|
||||
if (dto.getMethodePaiement() != null) {
|
||||
adhesion.setMethodePaiement(dto.getMethodePaiement());
|
||||
}
|
||||
if (dto.getReferencePaiement() != null) {
|
||||
adhesion.setReferencePaiement(dto.getReferencePaiement());
|
||||
}
|
||||
if (dto.getMotifRejet() != null) {
|
||||
adhesion.setMotifRejet(dto.getMotifRejet());
|
||||
}
|
||||
if (dto.getObservations() != null) {
|
||||
adhesion.setObservations(dto.getObservations());
|
||||
}
|
||||
if (dto.getApprouvePar() != null) {
|
||||
adhesion.setApprouvePar(dto.getApprouvePar());
|
||||
}
|
||||
if (dto.getDateValidation() != null) {
|
||||
adhesion.setDateValidation(dto.getDateValidation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.adresse.AdresseDTO;
|
||||
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
|
||||
import dev.lions.unionflow.server.entity.Adresse;
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.AdresseRepository;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des adresses
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AdresseService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AdresseService.class);
|
||||
|
||||
@Inject AdresseRepository adresseRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject EvenementRepository evenementRepository;
|
||||
|
||||
/**
|
||||
* Crée une nouvelle adresse
|
||||
*
|
||||
* @param adresseDTO DTO de l'adresse à créer
|
||||
* @return DTO de l'adresse créée
|
||||
*/
|
||||
@Transactional
|
||||
public AdresseDTO creerAdresse(AdresseDTO adresseDTO) {
|
||||
LOG.infof("Création d'une nouvelle adresse de type: %s", adresseDTO.getTypeAdresse());
|
||||
|
||||
Adresse adresse = convertToEntity(adresseDTO);
|
||||
|
||||
// Gestion de l'adresse principale
|
||||
if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
|
||||
desactiverAutresPrincipales(adresseDTO);
|
||||
}
|
||||
|
||||
adresseRepository.persist(adresse);
|
||||
LOG.infof("Adresse créée avec succès: ID=%s", adresse.getId());
|
||||
|
||||
return convertToDTO(adresse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une adresse existante
|
||||
*
|
||||
* @param id ID de l'adresse
|
||||
* @param adresseDTO DTO avec les nouvelles données
|
||||
* @return DTO de l'adresse mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public AdresseDTO mettreAJourAdresse(UUID id, AdresseDTO adresseDTO) {
|
||||
LOG.infof("Mise à jour de l'adresse ID: %s", id);
|
||||
|
||||
Adresse adresse =
|
||||
adresseRepository
|
||||
.findAdresseById(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs
|
||||
updateFromDTO(adresse, adresseDTO);
|
||||
|
||||
// Gestion de l'adresse principale
|
||||
if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
|
||||
desactiverAutresPrincipales(adresseDTO);
|
||||
}
|
||||
|
||||
adresseRepository.persist(adresse);
|
||||
LOG.infof("Adresse mise à jour avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(adresse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une adresse
|
||||
*
|
||||
* @param id ID de l'adresse
|
||||
*/
|
||||
@Transactional
|
||||
public void supprimerAdresse(UUID id) {
|
||||
LOG.infof("Suppression de l'adresse ID: %s", id);
|
||||
|
||||
Adresse adresse =
|
||||
adresseRepository
|
||||
.findAdresseById(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
|
||||
|
||||
adresseRepository.delete(adresse);
|
||||
LOG.infof("Adresse supprimée avec succès: ID=%s", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une adresse par son ID
|
||||
*
|
||||
* @param id ID de l'adresse
|
||||
* @return DTO de l'adresse
|
||||
*/
|
||||
public AdresseDTO trouverParId(UUID id) {
|
||||
return adresseRepository
|
||||
.findAdresseById(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adresses d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<AdresseDTO> trouverParOrganisation(UUID organisationId) {
|
||||
return adresseRepository.findByOrganisationId(organisationId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve toutes les adresses d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<AdresseDTO> trouverParMembre(UUID membreId) {
|
||||
return adresseRepository.findByMembreId(membreId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse d'un événement
|
||||
*
|
||||
* @param evenementId ID de l'événement
|
||||
* @return DTO de l'adresse ou null
|
||||
*/
|
||||
public AdresseDTO trouverParEvenement(UUID evenementId) {
|
||||
return adresseRepository
|
||||
.findByEvenementId(evenementId)
|
||||
.map(this::convertToDTO)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse principale d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return DTO de l'adresse principale ou null
|
||||
*/
|
||||
public AdresseDTO trouverPrincipaleParOrganisation(UUID organisationId) {
|
||||
return adresseRepository
|
||||
.findPrincipaleByOrganisationId(organisationId)
|
||||
.map(this::convertToDTO)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve l'adresse principale d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return DTO de l'adresse principale ou null
|
||||
*/
|
||||
public AdresseDTO trouverPrincipaleParMembre(UUID membreId) {
|
||||
return adresseRepository
|
||||
.findPrincipaleByMembreId(membreId)
|
||||
.map(this::convertToDTO)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES PRIVÉES
|
||||
// ========================================
|
||||
|
||||
/** Désactive les autres adresses principales pour la même entité */
|
||||
private void desactiverAutresPrincipales(AdresseDTO adresseDTO) {
|
||||
List<Adresse> autresPrincipales;
|
||||
|
||||
if (adresseDTO.getOrganisationId() != null) {
|
||||
autresPrincipales =
|
||||
adresseRepository
|
||||
.find("organisation.id = ?1 AND principale = true", adresseDTO.getOrganisationId())
|
||||
.list();
|
||||
} else if (adresseDTO.getMembreId() != null) {
|
||||
autresPrincipales =
|
||||
adresseRepository
|
||||
.find("membre.id = ?1 AND principale = true", adresseDTO.getMembreId())
|
||||
.list();
|
||||
} else {
|
||||
return; // Pas d'entité associée
|
||||
}
|
||||
|
||||
autresPrincipales.forEach(adr -> adr.setPrincipale(false));
|
||||
}
|
||||
|
||||
/** Convertit une entité en DTO */
|
||||
private AdresseDTO convertToDTO(Adresse adresse) {
|
||||
if (adresse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AdresseDTO dto = new AdresseDTO();
|
||||
dto.setId(adresse.getId());
|
||||
dto.setTypeAdresse(convertTypeAdresse(adresse.getTypeAdresse()));
|
||||
dto.setAdresse(adresse.getAdresse());
|
||||
dto.setComplementAdresse(adresse.getComplementAdresse());
|
||||
dto.setCodePostal(adresse.getCodePostal());
|
||||
dto.setVille(adresse.getVille());
|
||||
dto.setRegion(adresse.getRegion());
|
||||
dto.setPays(adresse.getPays());
|
||||
dto.setLatitude(adresse.getLatitude());
|
||||
dto.setLongitude(adresse.getLongitude());
|
||||
dto.setPrincipale(adresse.getPrincipale());
|
||||
dto.setLibelle(adresse.getLibelle());
|
||||
dto.setNotes(adresse.getNotes());
|
||||
|
||||
if (adresse.getOrganisation() != null) {
|
||||
dto.setOrganisationId(adresse.getOrganisation().getId());
|
||||
}
|
||||
if (adresse.getMembre() != null) {
|
||||
dto.setMembreId(adresse.getMembre().getId());
|
||||
}
|
||||
if (adresse.getEvenement() != null) {
|
||||
dto.setEvenementId(adresse.getEvenement().getId());
|
||||
}
|
||||
|
||||
dto.setAdresseComplete(adresse.getAdresseComplete());
|
||||
dto.setDateCreation(adresse.getDateCreation());
|
||||
dto.setDateModification(adresse.getDateModification());
|
||||
dto.setActif(adresse.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité */
|
||||
private Adresse convertToEntity(AdresseDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Adresse adresse = new Adresse();
|
||||
adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
|
||||
adresse.setAdresse(dto.getAdresse());
|
||||
adresse.setComplementAdresse(dto.getComplementAdresse());
|
||||
adresse.setCodePostal(dto.getCodePostal());
|
||||
adresse.setVille(dto.getVille());
|
||||
adresse.setRegion(dto.getRegion());
|
||||
adresse.setPays(dto.getPays());
|
||||
adresse.setLatitude(dto.getLatitude());
|
||||
adresse.setLongitude(dto.getLongitude());
|
||||
adresse.setPrincipale(dto.getPrincipale() != null ? dto.getPrincipale() : false);
|
||||
adresse.setLibelle(dto.getLibelle());
|
||||
adresse.setNotes(dto.getNotes());
|
||||
|
||||
// Relations
|
||||
if (dto.getOrganisationId() != null) {
|
||||
Organisation org =
|
||||
organisationRepository
|
||||
.findByIdOptional(dto.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
|
||||
adresse.setOrganisation(org);
|
||||
}
|
||||
|
||||
if (dto.getMembreId() != null) {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(dto.getMembreId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
|
||||
adresse.setMembre(membre);
|
||||
}
|
||||
|
||||
if (dto.getEvenementId() != null) {
|
||||
Evenement evenement =
|
||||
evenementRepository
|
||||
.findByIdOptional(dto.getEvenementId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Événement non trouvé avec l'ID: " + dto.getEvenementId()));
|
||||
adresse.setEvenement(evenement);
|
||||
}
|
||||
|
||||
return adresse;
|
||||
}
|
||||
|
||||
/** Met à jour une entité à partir d'un DTO */
|
||||
private void updateFromDTO(Adresse adresse, AdresseDTO dto) {
|
||||
if (dto.getTypeAdresse() != null) {
|
||||
adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
|
||||
}
|
||||
if (dto.getAdresse() != null) {
|
||||
adresse.setAdresse(dto.getAdresse());
|
||||
}
|
||||
if (dto.getComplementAdresse() != null) {
|
||||
adresse.setComplementAdresse(dto.getComplementAdresse());
|
||||
}
|
||||
if (dto.getCodePostal() != null) {
|
||||
adresse.setCodePostal(dto.getCodePostal());
|
||||
}
|
||||
if (dto.getVille() != null) {
|
||||
adresse.setVille(dto.getVille());
|
||||
}
|
||||
if (dto.getRegion() != null) {
|
||||
adresse.setRegion(dto.getRegion());
|
||||
}
|
||||
if (dto.getPays() != null) {
|
||||
adresse.setPays(dto.getPays());
|
||||
}
|
||||
if (dto.getLatitude() != null) {
|
||||
adresse.setLatitude(dto.getLatitude());
|
||||
}
|
||||
if (dto.getLongitude() != null) {
|
||||
adresse.setLongitude(dto.getLongitude());
|
||||
}
|
||||
if (dto.getPrincipale() != null) {
|
||||
adresse.setPrincipale(dto.getPrincipale());
|
||||
}
|
||||
if (dto.getLibelle() != null) {
|
||||
adresse.setLibelle(dto.getLibelle());
|
||||
}
|
||||
if (dto.getNotes() != null) {
|
||||
adresse.setNotes(dto.getNotes());
|
||||
}
|
||||
}
|
||||
|
||||
/** Convertit TypeAdresse (entité) vers TypeAdresse (DTO) - même enum, pas de conversion nécessaire */
|
||||
private TypeAdresse convertTypeAdresse(TypeAdresse type) {
|
||||
return type != null ? type : TypeAdresse.AUTRE; // Même enum, valeur par défaut si null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetDTO;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO;
|
||||
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
|
||||
// import dev.lions.unionflow.server.entity.DemandeAide;
|
||||
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import dev.lions.unionflow.server.repository.DemandeAideRepository;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
// import dev.lions.unionflow.server.repository.DemandeAideRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service principal pour les analytics et métriques UnionFlow
|
||||
*
|
||||
* <p>Ce service calcule et fournit toutes les métriques analytics pour les tableaux de bord,
|
||||
* rapports et widgets.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AnalyticsService {
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject DemandeAideRepository demandeAideRepository;
|
||||
|
||||
@Inject EvenementRepository evenementRepository;
|
||||
|
||||
// @Inject
|
||||
// DemandeAideRepository demandeAideRepository;
|
||||
|
||||
@Inject KPICalculatorService kpiCalculatorService;
|
||||
|
||||
@Inject TrendAnalysisService trendAnalysisService;
|
||||
|
||||
/**
|
||||
* Calcule une métrique analytics pour une période donnée
|
||||
*
|
||||
* @param typeMetrique Le type de métrique à calculer
|
||||
* @param periodeAnalyse La période d'analyse
|
||||
* @param organisationId L'ID de l'organisation (optionnel)
|
||||
* @return Les données analytics calculées
|
||||
*/
|
||||
@Transactional
|
||||
public AnalyticsDataDTO calculerMetrique(
|
||||
TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) {
|
||||
log.info(
|
||||
"Calcul de la métrique {} pour la période {} et l'organisation {}",
|
||||
typeMetrique,
|
||||
periodeAnalyse,
|
||||
organisationId);
|
||||
|
||||
LocalDateTime dateDebut = periodeAnalyse.getDateDebut();
|
||||
LocalDateTime dateFin = periodeAnalyse.getDateFin();
|
||||
|
||||
BigDecimal valeur =
|
||||
switch (typeMetrique) {
|
||||
// Métriques membres
|
||||
case NOMBRE_MEMBRES_ACTIFS ->
|
||||
calculerNombreMembresActifs(organisationId, dateDebut, dateFin);
|
||||
case NOMBRE_MEMBRES_INACTIFS ->
|
||||
calculerNombreMembresInactifs(organisationId, dateDebut, dateFin);
|
||||
case TAUX_CROISSANCE_MEMBRES ->
|
||||
calculerTauxCroissanceMembres(organisationId, dateDebut, dateFin);
|
||||
case MOYENNE_AGE_MEMBRES -> calculerMoyenneAgeMembres(organisationId, dateDebut, dateFin);
|
||||
|
||||
// Métriques financières
|
||||
case TOTAL_COTISATIONS_COLLECTEES ->
|
||||
calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin);
|
||||
case COTISATIONS_EN_ATTENTE ->
|
||||
calculerCotisationsEnAttente(organisationId, dateDebut, dateFin);
|
||||
case TAUX_RECOUVREMENT_COTISATIONS ->
|
||||
calculerTauxRecouvrementCotisations(organisationId, dateDebut, dateFin);
|
||||
case MOYENNE_COTISATION_MEMBRE ->
|
||||
calculerMoyenneCotisationMembre(organisationId, dateDebut, dateFin);
|
||||
|
||||
// Métriques événements
|
||||
case NOMBRE_EVENEMENTS_ORGANISES ->
|
||||
calculerNombreEvenementsOrganises(organisationId, dateDebut, dateFin);
|
||||
case TAUX_PARTICIPATION_EVENEMENTS ->
|
||||
calculerTauxParticipationEvenements(organisationId, dateDebut, dateFin);
|
||||
case MOYENNE_PARTICIPANTS_EVENEMENT ->
|
||||
calculerMoyenneParticipantsEvenement(organisationId, dateDebut, dateFin);
|
||||
|
||||
// Métriques solidarité
|
||||
case NOMBRE_DEMANDES_AIDE ->
|
||||
calculerNombreDemandesAide(organisationId, dateDebut, dateFin);
|
||||
case MONTANT_AIDES_ACCORDEES ->
|
||||
calculerMontantAidesAccordees(organisationId, dateDebut, dateFin);
|
||||
case TAUX_APPROBATION_AIDES ->
|
||||
calculerTauxApprobationAides(organisationId, dateDebut, dateFin);
|
||||
|
||||
default -> BigDecimal.ZERO;
|
||||
};
|
||||
|
||||
// Calcul de la valeur précédente pour comparaison
|
||||
BigDecimal valeurPrecedente =
|
||||
calculerValeurPrecedente(typeMetrique, periodeAnalyse, organisationId);
|
||||
BigDecimal pourcentageEvolution = calculerPourcentageEvolution(valeur, valeurPrecedente);
|
||||
|
||||
return AnalyticsDataDTO.builder()
|
||||
.typeMetrique(typeMetrique)
|
||||
.periodeAnalyse(periodeAnalyse)
|
||||
.valeur(valeur)
|
||||
.valeurPrecedente(valeurPrecedente)
|
||||
.pourcentageEvolution(pourcentageEvolution)
|
||||
.dateDebut(dateDebut)
|
||||
.dateFin(dateFin)
|
||||
.dateCalcul(LocalDateTime.now())
|
||||
.organisationId(organisationId)
|
||||
.nomOrganisation(obtenirNomOrganisation(organisationId))
|
||||
.indicateurFiabilite(new BigDecimal("95.0"))
|
||||
.niveauPriorite(3)
|
||||
.tempsReel(false)
|
||||
.necessiteMiseAJour(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les tendances d'un KPI sur une période
|
||||
*
|
||||
* @param typeMetrique Le type de métrique
|
||||
* @param periodeAnalyse La période d'analyse
|
||||
* @param organisationId L'ID de l'organisation (optionnel)
|
||||
* @return Les données de tendance du KPI
|
||||
*/
|
||||
@Transactional
|
||||
public KPITrendDTO calculerTendanceKPI(
|
||||
TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) {
|
||||
log.info(
|
||||
"Calcul de la tendance KPI {} pour la période {} et l'organisation {}",
|
||||
typeMetrique,
|
||||
periodeAnalyse,
|
||||
organisationId);
|
||||
|
||||
return trendAnalysisService.calculerTendance(typeMetrique, periodeAnalyse, organisationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les métriques pour un tableau de bord
|
||||
*
|
||||
* @param organisationId L'ID de l'organisation
|
||||
* @param utilisateurId L'ID de l'utilisateur
|
||||
* @return La liste des widgets du tableau de bord
|
||||
*/
|
||||
@Transactional
|
||||
public List<DashboardWidgetDTO> obtenirMetriquesTableauBord(
|
||||
UUID organisationId, UUID utilisateurId) {
|
||||
log.info(
|
||||
"Obtention des métriques du tableau de bord pour l'organisation {} et l'utilisateur {}",
|
||||
organisationId,
|
||||
utilisateurId);
|
||||
|
||||
List<DashboardWidgetDTO> widgets = new ArrayList<>();
|
||||
|
||||
// Widget KPI Membres Actifs
|
||||
widgets.add(
|
||||
creerWidgetKPI(
|
||||
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
|
||||
PeriodeAnalyse.CE_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
2));
|
||||
|
||||
// Widget KPI Cotisations
|
||||
widgets.add(
|
||||
creerWidgetKPI(
|
||||
TypeMetrique.TOTAL_COTISATIONS_COLLECTEES,
|
||||
PeriodeAnalyse.CE_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
3,
|
||||
0,
|
||||
3,
|
||||
2));
|
||||
|
||||
// Widget KPI Événements
|
||||
widgets.add(
|
||||
creerWidgetKPI(
|
||||
TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES,
|
||||
PeriodeAnalyse.CE_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
6,
|
||||
0,
|
||||
3,
|
||||
2));
|
||||
|
||||
// Widget KPI Solidarité
|
||||
widgets.add(
|
||||
creerWidgetKPI(
|
||||
TypeMetrique.NOMBRE_DEMANDES_AIDE,
|
||||
PeriodeAnalyse.CE_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
9,
|
||||
0,
|
||||
3,
|
||||
2));
|
||||
|
||||
// Widget Graphique Évolution Membres
|
||||
widgets.add(
|
||||
creerWidgetGraphique(
|
||||
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
|
||||
PeriodeAnalyse.SIX_DERNIERS_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
0,
|
||||
2,
|
||||
6,
|
||||
4,
|
||||
"line"));
|
||||
|
||||
// Widget Graphique Évolution Financière
|
||||
widgets.add(
|
||||
creerWidgetGraphique(
|
||||
TypeMetrique.TOTAL_COTISATIONS_COLLECTEES,
|
||||
PeriodeAnalyse.SIX_DERNIERS_MOIS,
|
||||
organisationId,
|
||||
utilisateurId,
|
||||
6,
|
||||
2,
|
||||
6,
|
||||
4,
|
||||
"area"));
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES DE CALCUL ===
|
||||
|
||||
private BigDecimal calculerNombreMembresActifs(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerNombreMembresInactifs(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = membreRepository.countMembresInactifs(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerTauxCroissanceMembres(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long membresActuels = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
Long membresPrecedents =
|
||||
membreRepository.countMembresActifs(
|
||||
organisationId, dateDebut.minusMonths(1), dateFin.minusMonths(1));
|
||||
|
||||
if (membresPrecedents == 0) return BigDecimal.ZERO;
|
||||
|
||||
BigDecimal croissance =
|
||||
new BigDecimal(membresActuels - membresPrecedents)
|
||||
.divide(new BigDecimal(membresPrecedents), 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
|
||||
return croissance;
|
||||
}
|
||||
|
||||
private BigDecimal calculerMoyenneAgeMembres(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Double moyenneAge = membreRepository.calculerMoyenneAge(organisationId, dateDebut, dateFin);
|
||||
return moyenneAge != null
|
||||
? new BigDecimal(moyenneAge).setScale(1, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerTotalCotisationsCollectees(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total = cotisationRepository.sumMontantsPayes(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerCotisationsEnAttente(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total =
|
||||
cotisationRepository.sumMontantsEnAttente(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerTauxRecouvrementCotisations(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal collectees = calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin);
|
||||
BigDecimal enAttente = calculerCotisationsEnAttente(organisationId, dateDebut, dateFin);
|
||||
BigDecimal total = collectees.add(enAttente);
|
||||
|
||||
if (total.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
|
||||
|
||||
return collectees.divide(total, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private BigDecimal calculerMoyenneCotisationMembre(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total = calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin);
|
||||
Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
|
||||
if (nombreMembres == 0) return BigDecimal.ZERO;
|
||||
|
||||
return total.divide(new BigDecimal(nombreMembres), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calculerNombreEvenementsOrganises(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = evenementRepository.countEvenements(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerTauxParticipationEvenements(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
// Implémentation simplifiée - à enrichir selon les besoins
|
||||
return new BigDecimal("75.5"); // Valeur par défaut
|
||||
}
|
||||
|
||||
private BigDecimal calculerMoyenneParticipantsEvenement(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Double moyenne =
|
||||
evenementRepository.calculerMoyenneParticipants(organisationId, dateDebut, dateFin);
|
||||
return moyenne != null
|
||||
? new BigDecimal(moyenne).setScale(1, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerNombreDemandesAide(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerMontantAidesAccordees(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total =
|
||||
demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerTauxApprobationAides(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long totalDemandes = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
|
||||
Long demandesApprouvees =
|
||||
demandeAideRepository.countDemandesApprouvees(organisationId, dateDebut, dateFin);
|
||||
|
||||
if (totalDemandes == 0) return BigDecimal.ZERO;
|
||||
|
||||
return new BigDecimal(demandesApprouvees)
|
||||
.divide(new BigDecimal(totalDemandes), 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private BigDecimal calculerValeurPrecedente(
|
||||
TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) {
|
||||
// Calcul de la période précédente
|
||||
LocalDateTime dateDebutPrecedente =
|
||||
periodeAnalyse.getDateDebut().minus(periodeAnalyse.getDuree(), periodeAnalyse.getUnite());
|
||||
LocalDateTime dateFinPrecedente =
|
||||
periodeAnalyse.getDateFin().minus(periodeAnalyse.getDuree(), periodeAnalyse.getUnite());
|
||||
|
||||
return switch (typeMetrique) {
|
||||
case NOMBRE_MEMBRES_ACTIFS ->
|
||||
calculerNombreMembresActifs(organisationId, dateDebutPrecedente, dateFinPrecedente);
|
||||
case TOTAL_COTISATIONS_COLLECTEES ->
|
||||
calculerTotalCotisationsCollectees(
|
||||
organisationId, dateDebutPrecedente, dateFinPrecedente);
|
||||
case NOMBRE_EVENEMENTS_ORGANISES ->
|
||||
calculerNombreEvenementsOrganises(organisationId, dateDebutPrecedente, dateFinPrecedente);
|
||||
case NOMBRE_DEMANDES_AIDE ->
|
||||
calculerNombreDemandesAide(organisationId, dateDebutPrecedente, dateFinPrecedente);
|
||||
default -> BigDecimal.ZERO;
|
||||
};
|
||||
}
|
||||
|
||||
private BigDecimal calculerPourcentageEvolution(
|
||||
BigDecimal valeurActuelle, BigDecimal valeurPrecedente) {
|
||||
if (valeurPrecedente == null || valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
return valeurActuelle
|
||||
.subtract(valeurPrecedente)
|
||||
.divide(valeurPrecedente, 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private String obtenirNomOrganisation(UUID organisationId) {
|
||||
// Temporairement désactivé pour éviter les erreurs de compilation
|
||||
return "Organisation "
|
||||
+ (organisationId != null ? organisationId.toString().substring(0, 8) : "inconnue");
|
||||
}
|
||||
|
||||
private DashboardWidgetDTO creerWidgetKPI(
|
||||
TypeMetrique typeMetrique,
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
UUID organisationId,
|
||||
UUID utilisateurId,
|
||||
int positionX,
|
||||
int positionY,
|
||||
int largeur,
|
||||
int hauteur) {
|
||||
AnalyticsDataDTO data = calculerMetrique(typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
return DashboardWidgetDTO.builder()
|
||||
.titre(typeMetrique.getLibelle())
|
||||
.typeWidget("kpi")
|
||||
.typeMetrique(typeMetrique)
|
||||
.periodeAnalyse(periodeAnalyse)
|
||||
.organisationId(organisationId)
|
||||
.utilisateurProprietaireId(utilisateurId)
|
||||
.positionX(positionX)
|
||||
.positionY(positionY)
|
||||
.largeur(largeur)
|
||||
.hauteur(hauteur)
|
||||
.couleurPrincipale(typeMetrique.getCouleur())
|
||||
.icone(typeMetrique.getIcone())
|
||||
.donneesWidget(convertirEnJSON(data))
|
||||
.dateDerniereMiseAJour(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DashboardWidgetDTO creerWidgetGraphique(
|
||||
TypeMetrique typeMetrique,
|
||||
PeriodeAnalyse periodeAnalyse,
|
||||
UUID organisationId,
|
||||
UUID utilisateurId,
|
||||
int positionX,
|
||||
int positionY,
|
||||
int largeur,
|
||||
int hauteur,
|
||||
String typeGraphique) {
|
||||
KPITrendDTO trend = calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId);
|
||||
|
||||
return DashboardWidgetDTO.builder()
|
||||
.titre("Évolution " + typeMetrique.getLibelle())
|
||||
.typeWidget("chart")
|
||||
.typeMetrique(typeMetrique)
|
||||
.periodeAnalyse(periodeAnalyse)
|
||||
.organisationId(organisationId)
|
||||
.utilisateurProprietaireId(utilisateurId)
|
||||
.positionX(positionX)
|
||||
.positionY(positionY)
|
||||
.largeur(largeur)
|
||||
.hauteur(hauteur)
|
||||
.couleurPrincipale(typeMetrique.getCouleur())
|
||||
.icone(typeMetrique.getIcone())
|
||||
.donneesWidget(convertirEnJSON(trend))
|
||||
.configurationVisuelle("{\"type\":\"" + typeGraphique + "\",\"responsive\":true}")
|
||||
.dateDerniereMiseAJour(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String convertirEnJSON(Object data) {
|
||||
// Implémentation simplifiée - utiliser Jackson en production
|
||||
return "{}"; // À implémenter avec ObjectMapper
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO;
|
||||
import dev.lions.unionflow.server.entity.AuditLog;
|
||||
import dev.lions.unionflow.server.repository.AuditLogRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service pour la gestion des logs d'audit
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AuditService {
|
||||
|
||||
@Inject
|
||||
AuditLogRepository auditLogRepository;
|
||||
|
||||
/**
|
||||
* Enregistre un nouveau log d'audit
|
||||
*/
|
||||
@Transactional
|
||||
public AuditLogDTO enregistrerLog(AuditLogDTO dto) {
|
||||
log.debug("Enregistrement d'un log d'audit: {}", dto.getTypeAction());
|
||||
|
||||
AuditLog auditLog = convertToEntity(dto);
|
||||
auditLogRepository.persist(auditLog);
|
||||
|
||||
return convertToDTO(auditLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les logs avec pagination
|
||||
*/
|
||||
public Map<String, Object> listerTous(int page, int size, String sortBy, String sortOrder) {
|
||||
log.debug("Récupération des logs d'audit - page: {}, size: {}", page, size);
|
||||
|
||||
String orderBy = sortBy != null ? sortBy : "dateHeure";
|
||||
String order = "desc".equalsIgnoreCase(sortOrder) ? "DESC" : "ASC";
|
||||
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
|
||||
// Compter le total
|
||||
long total = auditLogRepository.count();
|
||||
|
||||
// Récupérer les logs avec pagination
|
||||
var query = entityManager.createQuery(
|
||||
"SELECT a FROM AuditLog a ORDER BY a." + orderBy + " " + order, AuditLog.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
|
||||
List<AuditLog> logs = query.getResultList();
|
||||
List<AuditLogDTO> dtos = logs.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Map.of(
|
||||
"data", dtos,
|
||||
"total", total,
|
||||
"page", page,
|
||||
"size", size,
|
||||
"totalPages", (int) Math.ceil((double) total / size)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche les logs avec filtres
|
||||
*/
|
||||
public Map<String, Object> rechercher(
|
||||
LocalDateTime dateDebut, LocalDateTime dateFin,
|
||||
String typeAction, String severite, String utilisateur,
|
||||
String module, String ipAddress,
|
||||
int page, int size) {
|
||||
|
||||
log.debug("Recherche de logs d'audit avec filtres");
|
||||
|
||||
// Construire la requête dynamique avec Criteria API
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
var cb = entityManager.getCriteriaBuilder();
|
||||
var query = cb.createQuery(AuditLog.class);
|
||||
var root = query.from(AuditLog.class);
|
||||
|
||||
var predicates = new ArrayList<jakarta.persistence.criteria.Predicate>();
|
||||
|
||||
if (dateDebut != null) {
|
||||
predicates.add(cb.greaterThanOrEqualTo(root.get("dateHeure"), dateDebut));
|
||||
}
|
||||
if (dateFin != null) {
|
||||
predicates.add(cb.lessThanOrEqualTo(root.get("dateHeure"), dateFin));
|
||||
}
|
||||
if (typeAction != null && !typeAction.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("typeAction"), typeAction));
|
||||
}
|
||||
if (severite != null && !severite.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("severite"), severite));
|
||||
}
|
||||
if (utilisateur != null && !utilisateur.isEmpty()) {
|
||||
predicates.add(cb.like(cb.lower(root.get("utilisateur")),
|
||||
"%" + utilisateur.toLowerCase() + "%"));
|
||||
}
|
||||
if (module != null && !module.isEmpty()) {
|
||||
predicates.add(cb.equal(root.get("module"), module));
|
||||
}
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||
predicates.add(cb.like(root.get("ipAddress"), "%" + ipAddress + "%"));
|
||||
}
|
||||
|
||||
query.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
query.orderBy(cb.desc(root.get("dateHeure")));
|
||||
|
||||
// Compter le total
|
||||
var countQuery = cb.createQuery(Long.class);
|
||||
countQuery.select(cb.count(countQuery.from(AuditLog.class)));
|
||||
countQuery.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
long total = entityManager.createQuery(countQuery).getSingleResult();
|
||||
|
||||
// Récupérer les résultats avec pagination
|
||||
var typedQuery = entityManager.createQuery(query);
|
||||
typedQuery.setFirstResult(page * size);
|
||||
typedQuery.setMaxResults(size);
|
||||
|
||||
List<AuditLog> logs = typedQuery.getResultList();
|
||||
List<AuditLogDTO> dtos = logs.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Map.of(
|
||||
"data", dtos,
|
||||
"total", total,
|
||||
"page", page,
|
||||
"size", size,
|
||||
"totalPages", (int) Math.ceil((double) total / size)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques d'audit
|
||||
*/
|
||||
public Map<String, Object> getStatistiques() {
|
||||
long total = auditLogRepository.count();
|
||||
|
||||
var entityManager = auditLogRepository.getEntityManager();
|
||||
|
||||
long success = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
|
||||
.setParameter("severite", "SUCCESS")
|
||||
.getSingleResult();
|
||||
|
||||
long errors = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite IN :severites", Long.class)
|
||||
.setParameter("severites", List.of("ERROR", "CRITICAL"))
|
||||
.getSingleResult();
|
||||
|
||||
long warnings = entityManager.createQuery(
|
||||
"SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
|
||||
.setParameter("severite", "WARNING")
|
||||
.getSingleResult();
|
||||
|
||||
return Map.of(
|
||||
"total", total,
|
||||
"success", success,
|
||||
"errors", errors,
|
||||
"warnings", warnings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une entité en DTO
|
||||
*/
|
||||
private AuditLogDTO convertToDTO(AuditLog auditLog) {
|
||||
AuditLogDTO dto = new AuditLogDTO();
|
||||
dto.setId(auditLog.getId());
|
||||
dto.setTypeAction(auditLog.getTypeAction());
|
||||
dto.setSeverite(auditLog.getSeverite());
|
||||
dto.setUtilisateur(auditLog.getUtilisateur());
|
||||
dto.setRole(auditLog.getRole());
|
||||
dto.setModule(auditLog.getModule());
|
||||
dto.setDescription(auditLog.getDescription());
|
||||
dto.setDetails(auditLog.getDetails());
|
||||
dto.setIpAddress(auditLog.getIpAddress());
|
||||
dto.setUserAgent(auditLog.getUserAgent());
|
||||
dto.setSessionId(auditLog.getSessionId());
|
||||
dto.setDateHeure(auditLog.getDateHeure());
|
||||
dto.setDonneesAvant(auditLog.getDonneesAvant());
|
||||
dto.setDonneesApres(auditLog.getDonneesApres());
|
||||
dto.setEntiteId(auditLog.getEntiteId());
|
||||
dto.setEntiteType(auditLog.getEntiteType());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un DTO en entité
|
||||
*/
|
||||
private AuditLog convertToEntity(AuditLogDTO dto) {
|
||||
AuditLog auditLog = new AuditLog();
|
||||
if (dto.getId() != null) {
|
||||
auditLog.setId(dto.getId());
|
||||
}
|
||||
auditLog.setTypeAction(dto.getTypeAction());
|
||||
auditLog.setSeverite(dto.getSeverite());
|
||||
auditLog.setUtilisateur(dto.getUtilisateur());
|
||||
auditLog.setRole(dto.getRole());
|
||||
auditLog.setModule(dto.getModule());
|
||||
auditLog.setDescription(dto.getDescription());
|
||||
auditLog.setDetails(dto.getDetails());
|
||||
auditLog.setIpAddress(dto.getIpAddress());
|
||||
auditLog.setUserAgent(dto.getUserAgent());
|
||||
auditLog.setSessionId(dto.getSessionId());
|
||||
auditLog.setDateHeure(dto.getDateHeure() != null ? dto.getDateHeure() : LocalDateTime.now());
|
||||
auditLog.setDonneesAvant(dto.getDonneesAvant());
|
||||
auditLog.setDonneesApres(dto.getDonneesApres());
|
||||
auditLog.setEntiteId(dto.getEntiteId());
|
||||
auditLog.setEntiteType(dto.getEntiteType());
|
||||
return auditLog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.*;
|
||||
import dev.lions.unionflow.server.entity.*;
|
||||
import dev.lions.unionflow.server.repository.*;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion comptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ComptabiliteService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ComptabiliteService.class);
|
||||
|
||||
@Inject CompteComptableRepository compteComptableRepository;
|
||||
|
||||
@Inject JournalComptableRepository journalComptableRepository;
|
||||
|
||||
@Inject EcritureComptableRepository ecritureComptableRepository;
|
||||
|
||||
@Inject LigneEcritureRepository ligneEcritureRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject PaiementRepository paiementRepository;
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
// ========================================
|
||||
// COMPTES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau compte comptable
|
||||
*
|
||||
* @param compteDTO DTO du compte à créer
|
||||
* @return DTO du compte créé
|
||||
*/
|
||||
@Transactional
|
||||
public CompteComptableDTO creerCompteComptable(CompteComptableDTO compteDTO) {
|
||||
LOG.infof("Création d'un nouveau compte comptable: %s", compteDTO.getNumeroCompte());
|
||||
|
||||
// Vérifier l'unicité du numéro
|
||||
if (compteComptableRepository.findByNumeroCompte(compteDTO.getNumeroCompte()).isPresent()) {
|
||||
throw new IllegalArgumentException("Un compte avec ce numéro existe déjà: " + compteDTO.getNumeroCompte());
|
||||
}
|
||||
|
||||
CompteComptable compte = convertToEntity(compteDTO);
|
||||
compte.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
compteComptableRepository.persist(compte);
|
||||
LOG.infof("Compte comptable créé avec succès: ID=%s, Numéro=%s", compte.getId(), compte.getNumeroCompte());
|
||||
|
||||
return convertToDTO(compte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte comptable par son ID
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return DTO du compte
|
||||
*/
|
||||
public CompteComptableDTO trouverCompteParId(UUID id) {
|
||||
return compteComptableRepository
|
||||
.findCompteComptableById(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Compte comptable non trouvé avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les comptes comptables actifs
|
||||
*
|
||||
* @return Liste des comptes
|
||||
*/
|
||||
public List<CompteComptableDTO> listerTousLesComptes() {
|
||||
return compteComptableRepository.findAllActifs().stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// JOURNAUX COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau journal comptable
|
||||
*
|
||||
* @param journalDTO DTO du journal à créer
|
||||
* @return DTO du journal créé
|
||||
*/
|
||||
@Transactional
|
||||
public JournalComptableDTO creerJournalComptable(JournalComptableDTO journalDTO) {
|
||||
LOG.infof("Création d'un nouveau journal comptable: %s", journalDTO.getCode());
|
||||
|
||||
// Vérifier l'unicité du code
|
||||
if (journalComptableRepository.findByCode(journalDTO.getCode()).isPresent()) {
|
||||
throw new IllegalArgumentException("Un journal avec ce code existe déjà: " + journalDTO.getCode());
|
||||
}
|
||||
|
||||
JournalComptable journal = convertToEntity(journalDTO);
|
||||
journal.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
journalComptableRepository.persist(journal);
|
||||
LOG.infof("Journal comptable créé avec succès: ID=%s, Code=%s", journal.getId(), journal.getCode());
|
||||
|
||||
return convertToDTO(journal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un journal comptable par son ID
|
||||
*
|
||||
* @param id ID du journal
|
||||
* @return DTO du journal
|
||||
*/
|
||||
public JournalComptableDTO trouverJournalParId(UUID id) {
|
||||
return journalComptableRepository
|
||||
.findJournalComptableById(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les journaux comptables actifs
|
||||
*
|
||||
* @return Liste des journaux
|
||||
*/
|
||||
public List<JournalComptableDTO> listerTousLesJournaux() {
|
||||
return journalComptableRepository.findAllActifs().stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ÉCRITURES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle écriture comptable avec validation de l'équilibre
|
||||
*
|
||||
* @param ecritureDTO DTO de l'écriture à créer
|
||||
* @return DTO de l'écriture créée
|
||||
*/
|
||||
@Transactional
|
||||
public EcritureComptableDTO creerEcritureComptable(EcritureComptableDTO ecritureDTO) {
|
||||
LOG.infof("Création d'une nouvelle écriture comptable: %s", ecritureDTO.getNumeroPiece());
|
||||
|
||||
// Vérifier l'équilibre
|
||||
if (!isEcritureEquilibree(ecritureDTO)) {
|
||||
throw new IllegalArgumentException("L'écriture n'est pas équilibrée (Débit ≠ Crédit)");
|
||||
}
|
||||
|
||||
EcritureComptable ecriture = convertToEntity(ecritureDTO);
|
||||
ecriture.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
// Calculer les totaux
|
||||
ecriture.calculerTotaux();
|
||||
|
||||
ecritureComptableRepository.persist(ecriture);
|
||||
LOG.infof("Écriture comptable créée avec succès: ID=%s, Numéro=%s", ecriture.getId(), ecriture.getNumeroPiece());
|
||||
|
||||
return convertToDTO(ecriture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une écriture comptable par son ID
|
||||
*
|
||||
* @param id ID de l'écriture
|
||||
* @return DTO de l'écriture
|
||||
*/
|
||||
public EcritureComptableDTO trouverEcritureParId(UUID id) {
|
||||
return ecritureComptableRepository
|
||||
.findEcritureComptableById(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Écriture comptable non trouvée avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les écritures d'un journal
|
||||
*
|
||||
* @param journalId ID du journal
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptableDTO> listerEcrituresParJournal(UUID journalId) {
|
||||
return ecritureComptableRepository.findByJournalId(journalId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les écritures d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des écritures
|
||||
*/
|
||||
public List<EcritureComptableDTO> listerEcrituresParOrganisation(UUID organisationId) {
|
||||
return ecritureComptableRepository.findByOrganisationId(organisationId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES PRIVÉES - CONVERSIONS
|
||||
// ========================================
|
||||
|
||||
/** Vérifie si une écriture est équilibrée */
|
||||
private boolean isEcritureEquilibree(EcritureComptableDTO ecritureDTO) {
|
||||
if (ecritureDTO.getLignes() == null || ecritureDTO.getLignes().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BigDecimal totalDebit =
|
||||
ecritureDTO.getLignes().stream()
|
||||
.map(LigneEcritureDTO::getMontantDebit)
|
||||
.filter(amount -> amount != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal totalCredit =
|
||||
ecritureDTO.getLignes().stream()
|
||||
.map(LigneEcritureDTO::getMontantCredit)
|
||||
.filter(amount -> amount != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return totalDebit.compareTo(totalCredit) == 0;
|
||||
}
|
||||
|
||||
/** Convertit une entité CompteComptable en DTO */
|
||||
private CompteComptableDTO convertToDTO(CompteComptable compte) {
|
||||
if (compte == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompteComptableDTO dto = new CompteComptableDTO();
|
||||
dto.setId(compte.getId());
|
||||
dto.setNumeroCompte(compte.getNumeroCompte());
|
||||
dto.setLibelle(compte.getLibelle());
|
||||
dto.setTypeCompte(compte.getTypeCompte());
|
||||
dto.setClasseComptable(compte.getClasseComptable());
|
||||
dto.setSoldeInitial(compte.getSoldeInitial());
|
||||
dto.setSoldeActuel(compte.getSoldeActuel());
|
||||
dto.setCompteCollectif(compte.getCompteCollectif());
|
||||
dto.setCompteAnalytique(compte.getCompteAnalytique());
|
||||
dto.setDescription(compte.getDescription());
|
||||
dto.setDateCreation(compte.getDateCreation());
|
||||
dto.setDateModification(compte.getDateModification());
|
||||
dto.setActif(compte.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité CompteComptable */
|
||||
private CompteComptable convertToEntity(CompteComptableDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompteComptable compte = new CompteComptable();
|
||||
compte.setNumeroCompte(dto.getNumeroCompte());
|
||||
compte.setLibelle(dto.getLibelle());
|
||||
compte.setTypeCompte(dto.getTypeCompte());
|
||||
compte.setClasseComptable(dto.getClasseComptable());
|
||||
compte.setSoldeInitial(dto.getSoldeInitial() != null ? dto.getSoldeInitial() : BigDecimal.ZERO);
|
||||
compte.setSoldeActuel(dto.getSoldeActuel() != null ? dto.getSoldeActuel() : dto.getSoldeInitial());
|
||||
compte.setCompteCollectif(dto.getCompteCollectif() != null ? dto.getCompteCollectif() : false);
|
||||
compte.setCompteAnalytique(dto.getCompteAnalytique() != null ? dto.getCompteAnalytique() : false);
|
||||
compte.setDescription(dto.getDescription());
|
||||
|
||||
return compte;
|
||||
}
|
||||
|
||||
/** Convertit une entité JournalComptable en DTO */
|
||||
private JournalComptableDTO convertToDTO(JournalComptable journal) {
|
||||
if (journal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JournalComptableDTO dto = new JournalComptableDTO();
|
||||
dto.setId(journal.getId());
|
||||
dto.setCode(journal.getCode());
|
||||
dto.setLibelle(journal.getLibelle());
|
||||
dto.setTypeJournal(journal.getTypeJournal());
|
||||
dto.setDateDebut(journal.getDateDebut());
|
||||
dto.setDateFin(journal.getDateFin());
|
||||
dto.setStatut(journal.getStatut());
|
||||
dto.setDescription(journal.getDescription());
|
||||
dto.setDateCreation(journal.getDateCreation());
|
||||
dto.setDateModification(journal.getDateModification());
|
||||
dto.setActif(journal.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité JournalComptable */
|
||||
private JournalComptable convertToEntity(JournalComptableDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JournalComptable journal = new JournalComptable();
|
||||
journal.setCode(dto.getCode());
|
||||
journal.setLibelle(dto.getLibelle());
|
||||
journal.setTypeJournal(dto.getTypeJournal());
|
||||
journal.setDateDebut(dto.getDateDebut());
|
||||
journal.setDateFin(dto.getDateFin());
|
||||
journal.setStatut(dto.getStatut() != null ? dto.getStatut() : "OUVERT");
|
||||
journal.setDescription(dto.getDescription());
|
||||
|
||||
return journal;
|
||||
}
|
||||
|
||||
/** Convertit une entité EcritureComptable en DTO */
|
||||
private EcritureComptableDTO convertToDTO(EcritureComptable ecriture) {
|
||||
if (ecriture == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EcritureComptableDTO dto = new EcritureComptableDTO();
|
||||
dto.setId(ecriture.getId());
|
||||
dto.setNumeroPiece(ecriture.getNumeroPiece());
|
||||
dto.setDateEcriture(ecriture.getDateEcriture());
|
||||
dto.setLibelle(ecriture.getLibelle());
|
||||
dto.setReference(ecriture.getReference());
|
||||
dto.setLettrage(ecriture.getLettrage());
|
||||
dto.setPointe(ecriture.getPointe());
|
||||
dto.setMontantDebit(ecriture.getMontantDebit());
|
||||
dto.setMontantCredit(ecriture.getMontantCredit());
|
||||
dto.setCommentaire(ecriture.getCommentaire());
|
||||
|
||||
if (ecriture.getJournal() != null) {
|
||||
dto.setJournalId(ecriture.getJournal().getId());
|
||||
}
|
||||
if (ecriture.getOrganisation() != null) {
|
||||
dto.setOrganisationId(ecriture.getOrganisation().getId());
|
||||
}
|
||||
if (ecriture.getPaiement() != null) {
|
||||
dto.setPaiementId(ecriture.getPaiement().getId());
|
||||
}
|
||||
|
||||
// Convertir les lignes
|
||||
if (ecriture.getLignes() != null) {
|
||||
dto.setLignes(
|
||||
ecriture.getLignes().stream().map(this::convertToDTO).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
dto.setDateCreation(ecriture.getDateCreation());
|
||||
dto.setDateModification(ecriture.getDateModification());
|
||||
dto.setActif(ecriture.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité EcritureComptable */
|
||||
private EcritureComptable convertToEntity(EcritureComptableDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EcritureComptable ecriture = new EcritureComptable();
|
||||
ecriture.setNumeroPiece(dto.getNumeroPiece());
|
||||
ecriture.setDateEcriture(dto.getDateEcriture() != null ? dto.getDateEcriture() : LocalDate.now());
|
||||
ecriture.setLibelle(dto.getLibelle());
|
||||
ecriture.setReference(dto.getReference());
|
||||
ecriture.setLettrage(dto.getLettrage());
|
||||
ecriture.setPointe(dto.getPointe() != null ? dto.getPointe() : false);
|
||||
ecriture.setCommentaire(dto.getCommentaire());
|
||||
|
||||
// Relations
|
||||
if (dto.getJournalId() != null) {
|
||||
JournalComptable journal =
|
||||
journalComptableRepository
|
||||
.findJournalComptableById(dto.getJournalId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + dto.getJournalId()));
|
||||
ecriture.setJournal(journal);
|
||||
}
|
||||
|
||||
if (dto.getOrganisationId() != null) {
|
||||
Organisation org =
|
||||
organisationRepository
|
||||
.findByIdOptional(dto.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
|
||||
ecriture.setOrganisation(org);
|
||||
}
|
||||
|
||||
if (dto.getPaiementId() != null) {
|
||||
Paiement paiement =
|
||||
paiementRepository
|
||||
.findPaiementById(dto.getPaiementId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Paiement non trouvé avec l'ID: " + dto.getPaiementId()));
|
||||
ecriture.setPaiement(paiement);
|
||||
}
|
||||
|
||||
// Convertir les lignes
|
||||
if (dto.getLignes() != null) {
|
||||
for (LigneEcritureDTO ligneDTO : dto.getLignes()) {
|
||||
LigneEcriture ligne = convertToEntity(ligneDTO);
|
||||
ligne.setEcriture(ecriture);
|
||||
ecriture.getLignes().add(ligne);
|
||||
}
|
||||
}
|
||||
|
||||
return ecriture;
|
||||
}
|
||||
|
||||
/** Convertit une entité LigneEcriture en DTO */
|
||||
private LigneEcritureDTO convertToDTO(LigneEcriture ligne) {
|
||||
if (ligne == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LigneEcritureDTO dto = new LigneEcritureDTO();
|
||||
dto.setId(ligne.getId());
|
||||
dto.setNumeroLigne(ligne.getNumeroLigne());
|
||||
dto.setMontantDebit(ligne.getMontantDebit());
|
||||
dto.setMontantCredit(ligne.getMontantCredit());
|
||||
dto.setLibelle(ligne.getLibelle());
|
||||
dto.setReference(ligne.getReference());
|
||||
|
||||
if (ligne.getEcriture() != null) {
|
||||
dto.setEcritureId(ligne.getEcriture().getId());
|
||||
}
|
||||
if (ligne.getCompteComptable() != null) {
|
||||
dto.setCompteComptableId(ligne.getCompteComptable().getId());
|
||||
}
|
||||
|
||||
dto.setDateCreation(ligne.getDateCreation());
|
||||
dto.setDateModification(ligne.getDateModification());
|
||||
dto.setActif(ligne.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité LigneEcriture */
|
||||
private LigneEcriture convertToEntity(LigneEcritureDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LigneEcriture ligne = new LigneEcriture();
|
||||
ligne.setNumeroLigne(dto.getNumeroLigne());
|
||||
ligne.setMontantDebit(dto.getMontantDebit() != null ? dto.getMontantDebit() : BigDecimal.ZERO);
|
||||
ligne.setMontantCredit(dto.getMontantCredit() != null ? dto.getMontantCredit() : BigDecimal.ZERO);
|
||||
ligne.setLibelle(dto.getLibelle());
|
||||
ligne.setReference(dto.getReference());
|
||||
|
||||
// Relation CompteComptable
|
||||
if (dto.getCompteComptableId() != null) {
|
||||
CompteComptable compte =
|
||||
compteComptableRepository
|
||||
.findCompteComptableById(dto.getCompteComptableId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Compte comptable non trouvé avec l'ID: " + dto.getCompteComptableId()));
|
||||
ligne.setCompteComptable(compte);
|
||||
}
|
||||
|
||||
return ligne;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des cotisations Contient la logique métier et les règles de
|
||||
* validation
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class CotisationService {
|
||||
|
||||
@Inject CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
/**
|
||||
* Récupère toutes les cotisations avec pagination
|
||||
*
|
||||
* @param page numéro de page (0-based)
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations converties en DTO
|
||||
*/
|
||||
public List<CotisationDTO> getAllCotisations(int page, int size) {
|
||||
log.debug("Récupération des cotisations - page: {}, size: {}", page, size);
|
||||
|
||||
// Utilisation de EntityManager pour la pagination
|
||||
jakarta.persistence.TypedQuery<Cotisation> query =
|
||||
cotisationRepository.getEntityManager().createQuery(
|
||||
"SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC",
|
||||
Cotisation.class);
|
||||
query.setFirstResult(page * size);
|
||||
query.setMaxResults(size);
|
||||
List<Cotisation> cotisations = query.getResultList();
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son ID
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
* @return DTO de la cotisation
|
||||
* @throws NotFoundException si la cotisation n'existe pas
|
||||
*/
|
||||
public CotisationDTO getCotisationById(@NotNull UUID id) {
|
||||
log.debug("Récupération de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son numéro de référence
|
||||
*
|
||||
* @param numeroReference numéro de référence unique
|
||||
* @return DTO de la cotisation
|
||||
* @throws NotFoundException si la cotisation n'existe pas
|
||||
*/
|
||||
public CotisationDTO getCotisationByReference(@NotNull String numeroReference) {
|
||||
log.debug("Récupération de la cotisation avec référence: {}", numeroReference);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByNumeroReference(numeroReference)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Cotisation non trouvée avec la référence: " + numeroReference));
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle cotisation
|
||||
*
|
||||
* @param cotisationDTO données de la cotisation à créer
|
||||
* @return DTO de la cotisation créée
|
||||
*/
|
||||
@Transactional
|
||||
public CotisationDTO createCotisation(@Valid CotisationDTO cotisationDTO) {
|
||||
log.info("Création d'une nouvelle cotisation pour le membre: {}", cotisationDTO.getMembreId());
|
||||
|
||||
// Validation du membre - UUID direct maintenant
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(cotisationDTO.getMembreId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Membre non trouvé avec l'ID: " + cotisationDTO.getMembreId()));
|
||||
|
||||
// Conversion DTO vers entité
|
||||
Cotisation cotisation = convertToEntity(cotisationDTO);
|
||||
cotisation.setMembre(membre);
|
||||
|
||||
// Génération automatique du numéro de référence si absent
|
||||
if (cotisation.getNumeroReference() == null || cotisation.getNumeroReference().isEmpty()) {
|
||||
cotisation.setNumeroReference(Cotisation.genererNumeroReference());
|
||||
}
|
||||
|
||||
// Validation des règles métier
|
||||
validateCotisationRules(cotisation);
|
||||
|
||||
// Persistance
|
||||
cotisationRepository.persist(cotisation);
|
||||
|
||||
log.info(
|
||||
"Cotisation créée avec succès - ID: {}, Référence: {}",
|
||||
cotisation.getId(),
|
||||
cotisation.getNumeroReference());
|
||||
|
||||
return convertToDTO(cotisation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une cotisation existante
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
* @param cotisationDTO nouvelles données
|
||||
* @return DTO de la cotisation mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public CotisationDTO updateCotisation(@NotNull UUID id, @Valid CotisationDTO cotisationDTO) {
|
||||
log.info("Mise à jour de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisationExistante =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs modifiables
|
||||
updateCotisationFields(cotisationExistante, cotisationDTO);
|
||||
|
||||
// Validation des règles métier
|
||||
validateCotisationRules(cotisationExistante);
|
||||
|
||||
log.info("Cotisation mise à jour avec succès - ID: {}", id);
|
||||
|
||||
return convertToDTO(cotisationExistante);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime (désactive) une cotisation
|
||||
*
|
||||
* @param id identifiant UUID de la cotisation
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteCotisation(@NotNull UUID id) {
|
||||
log.info("Suppression de la cotisation avec ID: {}", id);
|
||||
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));
|
||||
|
||||
// Vérification si la cotisation peut être supprimée
|
||||
if ("PAYEE".equals(cotisation.getStatut())) {
|
||||
throw new IllegalStateException("Impossible de supprimer une cotisation déjà payée");
|
||||
}
|
||||
|
||||
cotisation.setStatut("ANNULEE");
|
||||
|
||||
log.info("Cotisation supprimée avec succès - ID: {}", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations d'un membre
|
||||
*
|
||||
* @param membreId identifiant UUID du membre
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations du membre
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsByMembre(@NotNull UUID membreId, int page, int size) {
|
||||
log.debug("Récupération des cotisations du membre: {}", membreId);
|
||||
|
||||
// Vérification de l'existence du membre
|
||||
if (!membreRepository.findByIdOptional(membreId).isPresent()) {
|
||||
throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
|
||||
}
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.findByMembreId(
|
||||
membreId, Page.of(page, size), Sort.by("dateEcheance").descending());
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations par statut
|
||||
*
|
||||
* @param statut statut recherché
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations avec le statut spécifié
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsByStatut(@NotNull String statut, int page, int size) {
|
||||
log.debug("Récupération des cotisations avec statut: {}", statut);
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.findByStatut(statut, Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les cotisations en retard
|
||||
*
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste des cotisations en retard
|
||||
*/
|
||||
public List<CotisationDTO> getCotisationsEnRetard(int page, int size) {
|
||||
log.debug("Récupération des cotisations en retard");
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.findCotisationsEnRetard(LocalDate.now(), Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche avancée de cotisations
|
||||
*
|
||||
* @param membreId identifiant du membre (optionnel)
|
||||
* @param statut statut (optionnel)
|
||||
* @param typeCotisation type (optionnel)
|
||||
* @param annee année (optionnel)
|
||||
* @param mois mois (optionnel)
|
||||
* @param page numéro de page
|
||||
* @param size taille de la page
|
||||
* @return liste filtrée des cotisations
|
||||
*/
|
||||
public List<CotisationDTO> rechercherCotisations(
|
||||
UUID membreId,
|
||||
String statut,
|
||||
String typeCotisation,
|
||||
Integer annee,
|
||||
Integer mois,
|
||||
int page,
|
||||
int size) {
|
||||
log.debug("Recherche avancée de cotisations avec filtres");
|
||||
|
||||
List<Cotisation> cotisations =
|
||||
cotisationRepository.rechercheAvancee(
|
||||
membreId, statut, typeCotisation, annee, mois, Page.of(page, size));
|
||||
|
||||
return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des cotisations
|
||||
*
|
||||
* @return map contenant les statistiques
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesCotisations() {
|
||||
log.debug("Calcul des statistiques des cotisations");
|
||||
|
||||
long totalCotisations = cotisationRepository.count();
|
||||
long cotisationsPayees = cotisationRepository.compterParStatut("PAYEE");
|
||||
long cotisationsEnRetard =
|
||||
cotisationRepository
|
||||
.findCotisationsEnRetard(LocalDate.now(), Page.of(0, Integer.MAX_VALUE))
|
||||
.size();
|
||||
|
||||
return Map.of(
|
||||
"totalCotisations", totalCotisations,
|
||||
"cotisationsPayees", cotisationsPayees,
|
||||
"cotisationsEnRetard", cotisationsEnRetard,
|
||||
"tauxPaiement",
|
||||
totalCotisations > 0 ? (cotisationsPayees * 100.0 / totalCotisations) : 0.0);
|
||||
}
|
||||
|
||||
/** Convertit une entité Cotisation en DTO */
|
||||
private CotisationDTO convertToDTO(Cotisation cotisation) {
|
||||
if (cotisation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CotisationDTO dto = new CotisationDTO();
|
||||
|
||||
// Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant)
|
||||
dto.setId(cotisation.getId());
|
||||
dto.setNumeroReference(cotisation.getNumeroReference());
|
||||
|
||||
// Conversion du membre associé
|
||||
if (cotisation.getMembre() != null) {
|
||||
dto.setMembreId(cotisation.getMembre().getId());
|
||||
dto.setNomMembre(cotisation.getMembre().getNomComplet());
|
||||
dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre());
|
||||
|
||||
// Conversion de l'organisation du membre (associationId)
|
||||
if (cotisation.getMembre().getOrganisation() != null
|
||||
&& cotisation.getMembre().getOrganisation().getId() != null) {
|
||||
dto.setAssociationId(cotisation.getMembre().getOrganisation().getId());
|
||||
dto.setNomAssociation(cotisation.getMembre().getOrganisation().getNom());
|
||||
}
|
||||
}
|
||||
|
||||
// Propriétés de la cotisation
|
||||
dto.setTypeCotisation(cotisation.getTypeCotisation());
|
||||
dto.setMontantDu(cotisation.getMontantDu());
|
||||
dto.setMontantPaye(cotisation.getMontantPaye());
|
||||
dto.setCodeDevise(cotisation.getCodeDevise());
|
||||
dto.setStatut(cotisation.getStatut());
|
||||
dto.setDateEcheance(cotisation.getDateEcheance());
|
||||
dto.setDatePaiement(cotisation.getDatePaiement());
|
||||
dto.setDescription(cotisation.getDescription());
|
||||
dto.setPeriode(cotisation.getPeriode());
|
||||
dto.setAnnee(cotisation.getAnnee());
|
||||
dto.setMois(cotisation.getMois());
|
||||
dto.setObservations(cotisation.getObservations());
|
||||
dto.setRecurrente(cotisation.getRecurrente());
|
||||
dto.setNombreRappels(cotisation.getNombreRappels());
|
||||
dto.setDateDernierRappel(cotisation.getDateDernierRappel());
|
||||
|
||||
// Conversion du validateur
|
||||
dto.setValidePar(
|
||||
cotisation.getValideParId() != null
|
||||
? cotisation.getValideParId()
|
||||
: null);
|
||||
dto.setNomValidateur(cotisation.getNomValidateur());
|
||||
|
||||
dto.setMethodePaiement(cotisation.getMethodePaiement());
|
||||
dto.setReferencePaiement(cotisation.getReferencePaiement());
|
||||
dto.setDateCreation(cotisation.getDateCreation());
|
||||
dto.setDateModification(cotisation.getDateModification());
|
||||
|
||||
// Propriétés héritées de BaseDTO
|
||||
dto.setActif(true); // Les cotisations sont toujours actives
|
||||
dto.setVersion(0L); // Version par défaut
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité Cotisation */
|
||||
private Cotisation convertToEntity(CotisationDTO dto) {
|
||||
return Cotisation.builder()
|
||||
.numeroReference(dto.getNumeroReference())
|
||||
.typeCotisation(dto.getTypeCotisation())
|
||||
.montantDu(dto.getMontantDu())
|
||||
.montantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO)
|
||||
.codeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF")
|
||||
.statut(dto.getStatut() != null ? dto.getStatut() : "EN_ATTENTE")
|
||||
.dateEcheance(dto.getDateEcheance())
|
||||
.datePaiement(dto.getDatePaiement())
|
||||
.description(dto.getDescription())
|
||||
.periode(dto.getPeriode())
|
||||
.annee(dto.getAnnee())
|
||||
.mois(dto.getMois())
|
||||
.observations(dto.getObservations())
|
||||
.recurrente(dto.getRecurrente() != null ? dto.getRecurrente() : false)
|
||||
.nombreRappels(dto.getNombreRappels() != null ? dto.getNombreRappels() : 0)
|
||||
.dateDernierRappel(dto.getDateDernierRappel())
|
||||
.methodePaiement(dto.getMethodePaiement())
|
||||
.referencePaiement(dto.getReferencePaiement())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Met à jour les champs d'une cotisation existante */
|
||||
private void updateCotisationFields(Cotisation cotisation, CotisationDTO dto) {
|
||||
if (dto.getTypeCotisation() != null) {
|
||||
cotisation.setTypeCotisation(dto.getTypeCotisation());
|
||||
}
|
||||
if (dto.getMontantDu() != null) {
|
||||
cotisation.setMontantDu(dto.getMontantDu());
|
||||
}
|
||||
if (dto.getMontantPaye() != null) {
|
||||
cotisation.setMontantPaye(dto.getMontantPaye());
|
||||
}
|
||||
if (dto.getStatut() != null) {
|
||||
cotisation.setStatut(dto.getStatut());
|
||||
}
|
||||
if (dto.getDateEcheance() != null) {
|
||||
cotisation.setDateEcheance(dto.getDateEcheance());
|
||||
}
|
||||
if (dto.getDatePaiement() != null) {
|
||||
cotisation.setDatePaiement(dto.getDatePaiement());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
cotisation.setDescription(dto.getDescription());
|
||||
}
|
||||
if (dto.getObservations() != null) {
|
||||
cotisation.setObservations(dto.getObservations());
|
||||
}
|
||||
if (dto.getMethodePaiement() != null) {
|
||||
cotisation.setMethodePaiement(dto.getMethodePaiement());
|
||||
}
|
||||
if (dto.getReferencePaiement() != null) {
|
||||
cotisation.setReferencePaiement(dto.getReferencePaiement());
|
||||
}
|
||||
}
|
||||
|
||||
/** Valide les règles métier pour une cotisation */
|
||||
private void validateCotisationRules(Cotisation cotisation) {
|
||||
// Validation du montant
|
||||
if (cotisation.getMontantDu().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("Le montant dû doit être positif");
|
||||
}
|
||||
|
||||
// Validation de la date d'échéance
|
||||
if (cotisation.getDateEcheance().isBefore(LocalDate.now().minusYears(1))) {
|
||||
throw new IllegalArgumentException("La date d'échéance ne peut pas être antérieure à un an");
|
||||
}
|
||||
|
||||
// Validation du montant payé
|
||||
if (cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) > 0) {
|
||||
throw new IllegalArgumentException("Le montant payé ne peut pas dépasser le montant dû");
|
||||
}
|
||||
|
||||
// Validation de la cohérence statut/paiement
|
||||
if ("PAYEE".equals(cotisation.getStatut())
|
||||
&& cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Une cotisation marquée comme payée doit avoir un montant payé égal au montant dû");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY)
|
||||
*
|
||||
* @param membreIds Liste des IDs des membres destinataires
|
||||
* @return Nombre de rappels envoyés
|
||||
*/
|
||||
@Transactional
|
||||
public int envoyerRappelsCotisationsGroupes(List<UUID> membreIds) {
|
||||
log.info("Envoi de rappels de cotisations groupés à {} membres", membreIds.size());
|
||||
|
||||
if (membreIds == null || membreIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("La liste des membres ne peut pas être vide");
|
||||
}
|
||||
|
||||
int rappelsEnvoyes = 0;
|
||||
for (UUID membreId : membreIds) {
|
||||
try {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(membreId)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"Membre non trouvé avec l'ID: " + membreId));
|
||||
|
||||
// Trouver les cotisations en retard pour ce membre
|
||||
List<Cotisation> cotisationsEnRetard =
|
||||
cotisationRepository.findCotisationsAuRappel(7, 3).stream()
|
||||
.filter(c -> c.getMembre() != null && c.getMembre().getId().equals(membreId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Cotisation cotisation : cotisationsEnRetard) {
|
||||
// Incrémenter le nombre de rappels
|
||||
cotisationRepository.incrementerNombreRappels(cotisation.getId());
|
||||
rappelsEnvoyes++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(
|
||||
"Erreur lors de l'envoi du rappel de cotisation pour le membre {}: {}",
|
||||
membreId,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("{} rappels envoyés sur {} membres demandés", rappelsEnvoyes, membreIds.size());
|
||||
return rappelsEnvoyes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import dev.lions.unionflow.server.api.service.dashboard.DashboardService;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.entity.DemandeAide;
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import dev.lions.unionflow.server.repository.DemandeAideRepository;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implémentation du service Dashboard pour Quarkus
|
||||
*
|
||||
* <p>Cette implémentation récupère les données réelles depuis la base de données
|
||||
* via les repositories.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 2.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class DashboardServiceImpl implements DashboardService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DashboardServiceImpl.class);
|
||||
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
EvenementRepository evenementRepository;
|
||||
|
||||
@Inject
|
||||
CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject
|
||||
DemandeAideRepository demandeAideRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
@Override
|
||||
public DashboardDataDTO getDashboardData(String organizationId, String userId) {
|
||||
LOG.infof("Récupération des données dashboard pour org: %s et user: %s", organizationId, userId);
|
||||
|
||||
UUID orgId = UUID.fromString(organizationId);
|
||||
|
||||
return DashboardDataDTO.builder()
|
||||
.stats(getDashboardStats(organizationId, userId))
|
||||
.recentActivities(getRecentActivities(organizationId, userId, 10))
|
||||
.upcomingEvents(getUpcomingEvents(organizationId, userId, 5))
|
||||
.userPreferences(getUserPreferences(userId))
|
||||
.organizationId(organizationId)
|
||||
.userId(userId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DashboardStatsDTO getDashboardStats(String organizationId, String userId) {
|
||||
LOG.infof("Récupération des stats dashboard pour org: %s et user: %s", organizationId, userId);
|
||||
|
||||
UUID orgId = UUID.fromString(organizationId);
|
||||
|
||||
// Compter les membres
|
||||
long totalMembers = membreRepository.count();
|
||||
long activeMembers = membreRepository.countActifs();
|
||||
|
||||
// Compter les événements
|
||||
long totalEvents = evenementRepository.count();
|
||||
long upcomingEvents = evenementRepository.findEvenementsAVenir().size();
|
||||
|
||||
// Compter les cotisations
|
||||
long totalContributions = cotisationRepository.count();
|
||||
BigDecimal totalContributionAmount = calculateTotalContributionAmount(orgId);
|
||||
|
||||
// Compter les demandes en attente
|
||||
List<DemandeAide> pendingRequests = demandeAideRepository.findByStatut(StatutAide.EN_ATTENTE);
|
||||
long pendingRequestsCount = pendingRequests.stream()
|
||||
.filter(d -> d.getOrganisation() != null && d.getOrganisation().getId().equals(orgId))
|
||||
.count();
|
||||
|
||||
// Calculer la croissance mensuelle (membres ajoutés ce mois)
|
||||
LocalDate debutMois = LocalDate.now().withDayOfMonth(1);
|
||||
long nouveauxMembresMois = membreRepository.countNouveauxMembres(debutMois);
|
||||
long totalMembresAvant = totalMembers - nouveauxMembresMois;
|
||||
double monthlyGrowth = totalMembresAvant > 0
|
||||
? (double) nouveauxMembresMois / totalMembresAvant * 100.0
|
||||
: 0.0;
|
||||
|
||||
// Calculer le taux d'engagement (membres actifs / total)
|
||||
double engagementRate = totalMembers > 0
|
||||
? (double) activeMembers / totalMembers
|
||||
: 0.0;
|
||||
|
||||
return DashboardStatsDTO.builder()
|
||||
.totalMembers((int) totalMembers)
|
||||
.activeMembers((int) activeMembers)
|
||||
.totalEvents((int) totalEvents)
|
||||
.upcomingEvents((int) upcomingEvents)
|
||||
.totalContributions((int) totalContributions)
|
||||
.totalContributionAmount(totalContributionAmount.doubleValue())
|
||||
.pendingRequests((int) pendingRequestsCount)
|
||||
.completedProjects(0) // À implémenter si nécessaire
|
||||
.monthlyGrowth(monthlyGrowth)
|
||||
.engagementRate(engagementRate)
|
||||
.lastUpdated(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RecentActivityDTO> getRecentActivities(String organizationId, String userId, int limit) {
|
||||
LOG.infof("Récupération de %d activités récentes pour org: %s et user: %s", limit, organizationId, userId);
|
||||
|
||||
UUID orgId = UUID.fromString(organizationId);
|
||||
List<RecentActivityDTO> activities = new ArrayList<>();
|
||||
|
||||
// Récupérer les membres récemment créés
|
||||
List<Membre> nouveauxMembres = membreRepository.rechercheAvancee(
|
||||
null, true, null, null, Page.of(0, limit), Sort.by("dateCreation", Sort.Direction.Descending));
|
||||
|
||||
for (Membre membre : nouveauxMembres) {
|
||||
if (membre.getOrganisation() != null && membre.getOrganisation().getId().equals(orgId)) {
|
||||
activities.add(RecentActivityDTO.builder()
|
||||
.id(membre.getId().toString())
|
||||
.type("member")
|
||||
.title("Nouveau membre inscrit")
|
||||
.description(membre.getNomComplet() + " a rejoint l'organisation")
|
||||
.userName(membre.getNomComplet())
|
||||
.timestamp(membre.getDateCreation())
|
||||
.userAvatar(null)
|
||||
.actionUrl("/members/" + membre.getId())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer les événements récemment créés
|
||||
List<Evenement> tousEvenements = evenementRepository.listAll();
|
||||
List<Evenement> nouveauxEvenements = tousEvenements.stream()
|
||||
.filter(e -> e.getOrganisation() != null && e.getOrganisation().getId().equals(orgId))
|
||||
.sorted(Comparator.comparing(Evenement::getDateCreation).reversed())
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Evenement evenement : nouveauxEvenements) {
|
||||
activities.add(RecentActivityDTO.builder()
|
||||
.id(evenement.getId().toString())
|
||||
.type("event")
|
||||
.title("Événement créé")
|
||||
.description(evenement.getTitre() + " a été programmé")
|
||||
.userName(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : "Système")
|
||||
.timestamp(evenement.getDateCreation())
|
||||
.userAvatar(null)
|
||||
.actionUrl("/events/" + evenement.getId())
|
||||
.build());
|
||||
}
|
||||
|
||||
// Récupérer les cotisations récentes
|
||||
List<Cotisation> cotisationsRecentes = cotisationRepository.rechercheAvancee(
|
||||
null, "PAYEE", null, null, null, Page.of(0, limit));
|
||||
|
||||
for (Cotisation cotisation : cotisationsRecentes) {
|
||||
if (cotisation.getMembre() != null &&
|
||||
cotisation.getMembre().getOrganisation() != null &&
|
||||
cotisation.getMembre().getOrganisation().getId().equals(orgId)) {
|
||||
activities.add(RecentActivityDTO.builder()
|
||||
.id(cotisation.getId().toString())
|
||||
.type("contribution")
|
||||
.title("Cotisation reçue")
|
||||
.description("Paiement de " + cotisation.getMontantPaye() + " " + cotisation.getCodeDevise() + " reçu")
|
||||
.userName(cotisation.getMembre().getNomComplet())
|
||||
.timestamp(cotisation.getDatePaiement() != null ? cotisation.getDatePaiement() : cotisation.getDateCreation())
|
||||
.userAvatar(null)
|
||||
.actionUrl("/contributions/" + cotisation.getId())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par timestamp décroissant et limiter
|
||||
return activities.stream()
|
||||
.sorted(Comparator.comparing(RecentActivityDTO::getTimestamp).reversed())
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UpcomingEventDTO> getUpcomingEvents(String organizationId, String userId, int limit) {
|
||||
LOG.infof("Récupération de %d événements à venir pour org: %s et user: %s", limit, organizationId, userId);
|
||||
|
||||
UUID orgId = UUID.fromString(organizationId);
|
||||
|
||||
List<Evenement> evenements = evenementRepository.findEvenementsAVenir(
|
||||
Page.of(0, limit), Sort.by("dateDebut", Sort.Direction.Ascending));
|
||||
|
||||
return evenements.stream()
|
||||
.filter(e -> e.getOrganisation() == null || e.getOrganisation().getId().equals(orgId))
|
||||
.map(this::convertToUpcomingEventDTO)
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private UpcomingEventDTO convertToUpcomingEventDTO(Evenement evenement) {
|
||||
return UpcomingEventDTO.builder()
|
||||
.id(evenement.getId().toString())
|
||||
.title(evenement.getTitre())
|
||||
.description(evenement.getDescription())
|
||||
.startDate(evenement.getDateDebut())
|
||||
.endDate(evenement.getDateFin())
|
||||
.location(evenement.getLieu())
|
||||
.maxParticipants(evenement.getCapaciteMax())
|
||||
.currentParticipants(evenement.getNombreInscrits())
|
||||
.status(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE")
|
||||
.imageUrl(null)
|
||||
.tags(Collections.emptyList())
|
||||
.build();
|
||||
}
|
||||
|
||||
private BigDecimal calculateTotalContributionAmount(UUID organisationId) {
|
||||
TypedQuery<BigDecimal> query = cotisationRepository.getEntityManager().createQuery(
|
||||
"SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId",
|
||||
BigDecimal.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private Map<String, Object> getUserPreferences(String userId) {
|
||||
Map<String, Object> preferences = new HashMap<>();
|
||||
preferences.put("theme", "royal_teal");
|
||||
preferences.put("language", "fr");
|
||||
preferences.put("notifications", true);
|
||||
preferences.put("autoRefresh", true);
|
||||
preferences.put("refreshInterval", 300);
|
||||
return preferences;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service spécialisé pour la gestion des demandes d'aide
|
||||
*
|
||||
* <p>Ce service gère le cycle de vie complet des demandes d'aide : création, validation,
|
||||
* changements de statut, recherche et suivi.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class DemandeAideService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DemandeAideService.class);
|
||||
|
||||
// Cache en mémoire pour les demandes fréquemment consultées
|
||||
private final Map<UUID, DemandeAideDTO> cacheDemandesRecentes = new HashMap<>();
|
||||
private final Map<UUID, LocalDateTime> cacheTimestamps = new HashMap<>();
|
||||
private static final long CACHE_DURATION_MINUTES = 15;
|
||||
|
||||
// === OPÉRATIONS CRUD ===
|
||||
|
||||
/**
|
||||
* Crée une nouvelle demande d'aide
|
||||
*
|
||||
* @param demandeDTO La demande à créer
|
||||
* @return La demande créée avec ID généré
|
||||
*/
|
||||
@Transactional
|
||||
public DemandeAideDTO creerDemande(@Valid DemandeAideDTO demandeDTO) {
|
||||
LOG.infof("Création d'une nouvelle demande d'aide: %s", demandeDTO.getTitre());
|
||||
|
||||
// Génération des identifiants
|
||||
demandeDTO.setId(UUID.randomUUID());
|
||||
demandeDTO.setNumeroReference(genererNumeroReference());
|
||||
|
||||
// Initialisation des dates
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
demandeDTO.setDateCreation(maintenant);
|
||||
demandeDTO.setDateModification(maintenant);
|
||||
|
||||
// Statut initial
|
||||
if (demandeDTO.getStatut() == null) {
|
||||
demandeDTO.setStatut(StatutAide.BROUILLON);
|
||||
}
|
||||
|
||||
// Priorité par défaut si non définie
|
||||
if (demandeDTO.getPriorite() == null) {
|
||||
demandeDTO.setPriorite(PrioriteAide.NORMALE);
|
||||
}
|
||||
|
||||
// Initialisation de l'historique
|
||||
HistoriqueStatutDTO historiqueInitial =
|
||||
HistoriqueStatutDTO.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.ancienStatut(null)
|
||||
.nouveauStatut(demandeDTO.getStatut())
|
||||
.dateChangement(maintenant)
|
||||
.auteurId(demandeDTO.getMembreDemandeurId() != null ? demandeDTO.getMembreDemandeurId().toString() : null)
|
||||
.motif("Création de la demande")
|
||||
.estAutomatique(true)
|
||||
.build();
|
||||
|
||||
demandeDTO.setHistoriqueStatuts(List.of(historiqueInitial));
|
||||
|
||||
// Calcul du score de priorité
|
||||
demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO));
|
||||
|
||||
// Sauvegarde en cache
|
||||
ajouterAuCache(demandeDTO);
|
||||
|
||||
LOG.infof("Demande d'aide créée avec succès: %s", demandeDTO.getId());
|
||||
return demandeDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une demande d'aide existante
|
||||
*
|
||||
* @param demandeDTO La demande à mettre à jour
|
||||
* @return La demande mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public DemandeAideDTO mettreAJour(@Valid DemandeAideDTO demandeDTO) {
|
||||
LOG.infof("Mise à jour de la demande d'aide: %s", demandeDTO.getId());
|
||||
|
||||
// Vérification que la demande peut être modifiée
|
||||
if (!demandeDTO.estModifiable()) {
|
||||
throw new IllegalStateException("Cette demande ne peut plus être modifiée");
|
||||
}
|
||||
|
||||
// Mise à jour de la date de modification
|
||||
demandeDTO.setDateModification(LocalDateTime.now());
|
||||
demandeDTO.setVersion(demandeDTO.getVersion() + 1);
|
||||
|
||||
// Recalcul du score de priorité
|
||||
demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO));
|
||||
|
||||
// Mise à jour du cache
|
||||
ajouterAuCache(demandeDTO);
|
||||
|
||||
LOG.infof("Demande d'aide mise à jour avec succès: %s", demandeDTO.getId());
|
||||
return demandeDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient une demande d'aide par son ID
|
||||
*
|
||||
* @param id UUID de la demande
|
||||
* @return La demande trouvée
|
||||
*/
|
||||
public DemandeAideDTO obtenirParId(@NotNull UUID id) {
|
||||
LOG.debugf("Récupération de la demande d'aide: %s", id);
|
||||
|
||||
// Vérification du cache
|
||||
DemandeAideDTO demandeCachee = obtenirDuCache(id);
|
||||
if (demandeCachee != null) {
|
||||
LOG.debugf("Demande trouvée dans le cache: %s", id);
|
||||
return demandeCachee;
|
||||
}
|
||||
|
||||
// Simulation de récupération depuis la base de données
|
||||
// Dans une vraie implémentation, ceci ferait appel au repository
|
||||
DemandeAideDTO demande = simulerRecuperationBDD(id);
|
||||
|
||||
if (demande != null) {
|
||||
ajouterAuCache(demande);
|
||||
}
|
||||
|
||||
return demande;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le statut d'une demande d'aide
|
||||
*
|
||||
* @param demandeId UUID de la demande
|
||||
* @param nouveauStatut Nouveau statut
|
||||
* @param motif Motif du changement
|
||||
* @return La demande avec le nouveau statut
|
||||
*/
|
||||
@Transactional
|
||||
public DemandeAideDTO changerStatut(
|
||||
@NotNull UUID demandeId, @NotNull StatutAide nouveauStatut, String motif) {
|
||||
LOG.infof("Changement de statut pour la demande %s: %s", demandeId, nouveauStatut);
|
||||
|
||||
DemandeAideDTO demande = obtenirParId(demandeId);
|
||||
if (demande == null) {
|
||||
throw new IllegalArgumentException("Demande non trouvée: " + demandeId);
|
||||
}
|
||||
|
||||
StatutAide ancienStatut = demande.getStatut();
|
||||
|
||||
// Validation de la transition
|
||||
if (!ancienStatut.peutTransitionnerVers(nouveauStatut)) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Transition invalide de %s vers %s", ancienStatut, nouveauStatut));
|
||||
}
|
||||
|
||||
// Mise à jour du statut
|
||||
demande.setStatut(nouveauStatut);
|
||||
demande.setDateModification(LocalDateTime.now());
|
||||
|
||||
// Ajout à l'historique
|
||||
HistoriqueStatutDTO nouvelHistorique =
|
||||
HistoriqueStatutDTO.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.ancienStatut(ancienStatut)
|
||||
.nouveauStatut(nouveauStatut)
|
||||
.dateChangement(LocalDateTime.now())
|
||||
.motif(motif)
|
||||
.estAutomatique(false)
|
||||
.build();
|
||||
|
||||
List<HistoriqueStatutDTO> historique = new ArrayList<>(demande.getHistoriqueStatuts());
|
||||
historique.add(nouvelHistorique);
|
||||
demande.setHistoriqueStatuts(historique);
|
||||
|
||||
// Actions spécifiques selon le nouveau statut
|
||||
switch (nouveauStatut) {
|
||||
case SOUMISE -> demande.setDateSoumission(LocalDateTime.now());
|
||||
case APPROUVEE, APPROUVEE_PARTIELLEMENT -> demande.setDateApprobation(LocalDateTime.now());
|
||||
case VERSEE -> demande.setDateVersement(LocalDateTime.now());
|
||||
case CLOTUREE -> demande.setDateCloture(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// Mise à jour du cache
|
||||
ajouterAuCache(demande);
|
||||
|
||||
LOG.infof(
|
||||
"Statut changé avec succès pour la demande %s: %s -> %s",
|
||||
demandeId, ancienStatut, nouveauStatut);
|
||||
return demande;
|
||||
}
|
||||
|
||||
// === RECHERCHE ET FILTRAGE ===
|
||||
|
||||
/**
|
||||
* Recherche des demandes avec filtres
|
||||
*
|
||||
* @param filtres Map des critères de recherche
|
||||
* @return Liste des demandes correspondantes
|
||||
*/
|
||||
public List<DemandeAideDTO> rechercherAvecFiltres(Map<String, Object> filtres) {
|
||||
LOG.debugf("Recherche de demandes avec filtres: %s", filtres);
|
||||
|
||||
// Simulation de recherche - dans une vraie implémentation,
|
||||
// ceci utiliserait des requêtes de base de données optimisées
|
||||
List<DemandeAideDTO> toutesLesDemandes = simulerRecuperationToutesLesDemandes();
|
||||
|
||||
return toutesLesDemandes.stream()
|
||||
.filter(demande -> correspondAuxFiltres(demande, filtres))
|
||||
.sorted(this::comparerParPriorite)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les demandes urgentes pour une organisation
|
||||
*
|
||||
* @param organisationId UUID de l'organisation
|
||||
* @return Liste des demandes urgentes
|
||||
*/
|
||||
public List<DemandeAideDTO> obtenirDemandesUrgentes(UUID organisationId) {
|
||||
LOG.debugf("Récupération des demandes urgentes pour: %s", organisationId);
|
||||
|
||||
Map<String, Object> filtres =
|
||||
Map.of(
|
||||
"organisationId", organisationId,
|
||||
"priorite", List.of(PrioriteAide.CRITIQUE, PrioriteAide.URGENTE),
|
||||
"statut",
|
||||
List.of(
|
||||
StatutAide.SOUMISE,
|
||||
StatutAide.EN_ATTENTE,
|
||||
StatutAide.EN_COURS_EVALUATION,
|
||||
StatutAide.APPROUVEE));
|
||||
|
||||
return rechercherAvecFiltres(filtres);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les demandes en retard (délai dépassé)
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des demandes en retard
|
||||
*/
|
||||
public List<DemandeAideDTO> obtenirDemandesEnRetard(UUID organisationId) {
|
||||
LOG.debugf("Récupération des demandes en retard pour: %s", organisationId);
|
||||
|
||||
return simulerRecuperationToutesLesDemandes().stream()
|
||||
.filter(demande -> demande.getAssociationId().equals(organisationId))
|
||||
.filter(DemandeAideDTO::estDelaiDepasse)
|
||||
.filter(demande -> !demande.estTerminee())
|
||||
.sorted(this::comparerParPriorite)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES PRIVÉES ===
|
||||
|
||||
/** Génère un numéro de référence unique */
|
||||
private String genererNumeroReference() {
|
||||
int annee = LocalDateTime.now().getYear();
|
||||
int numero = (int) (Math.random() * 999999) + 1;
|
||||
return String.format("DA-%04d-%06d", annee, numero);
|
||||
}
|
||||
|
||||
/** Calcule le score de priorité d'une demande */
|
||||
private double calculerScorePriorite(DemandeAideDTO demande) {
|
||||
double score = demande.getPriorite().getScorePriorite();
|
||||
|
||||
// Bonus pour type d'aide urgent
|
||||
if (demande.getTypeAide().isUrgent()) {
|
||||
score -= 1.0;
|
||||
}
|
||||
|
||||
// Bonus pour montant élevé (aide financière)
|
||||
if (demande.getTypeAide().isFinancier() && demande.getMontantDemande() != null) {
|
||||
if (demande.getMontantDemande().compareTo(new BigDecimal("50000")) > 0) {
|
||||
score -= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Malus pour ancienneté
|
||||
long joursDepuisCreation =
|
||||
java.time.Duration.between(demande.getDateCreation(), LocalDateTime.now()).toDays();
|
||||
if (joursDepuisCreation > 7) {
|
||||
score += 0.3;
|
||||
}
|
||||
|
||||
return Math.max(0.1, score);
|
||||
}
|
||||
|
||||
/** Vérifie si une demande correspond aux filtres */
|
||||
private boolean correspondAuxFiltres(DemandeAideDTO demande, Map<String, Object> filtres) {
|
||||
for (Map.Entry<String, Object> filtre : filtres.entrySet()) {
|
||||
String cle = filtre.getKey();
|
||||
Object valeur = filtre.getValue();
|
||||
|
||||
switch (cle) {
|
||||
case "organisationId" -> {
|
||||
if (!demande.getAssociationId().equals(valeur)) return false;
|
||||
}
|
||||
case "typeAide" -> {
|
||||
if (valeur instanceof List<?> liste) {
|
||||
if (!liste.contains(demande.getTypeAide())) return false;
|
||||
} else if (!demande.getTypeAide().equals(valeur)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "statut" -> {
|
||||
if (valeur instanceof List<?> liste) {
|
||||
if (!liste.contains(demande.getStatut())) return false;
|
||||
} else if (!demande.getStatut().equals(valeur)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "priorite" -> {
|
||||
if (valeur instanceof List<?> liste) {
|
||||
if (!liste.contains(demande.getPriorite())) return false;
|
||||
} else if (!demande.getPriorite().equals(valeur)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "demandeurId" -> {
|
||||
if (!demande.getMembreDemandeurId().equals(valeur)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Compare deux demandes par priorité */
|
||||
private int comparerParPriorite(DemandeAideDTO d1, DemandeAideDTO d2) {
|
||||
// D'abord par score de priorité (plus bas = plus prioritaire)
|
||||
int comparaisonScore = Double.compare(d1.getScorePriorite(), d2.getScorePriorite());
|
||||
if (comparaisonScore != 0) return comparaisonScore;
|
||||
|
||||
// Puis par date de création (plus ancien = plus prioritaire)
|
||||
return d1.getDateCreation().compareTo(d2.getDateCreation());
|
||||
}
|
||||
|
||||
// === GESTION DU CACHE ===
|
||||
|
||||
private void ajouterAuCache(DemandeAideDTO demande) {
|
||||
cacheDemandesRecentes.put(demande.getId(), demande);
|
||||
cacheTimestamps.put(demande.getId(), LocalDateTime.now());
|
||||
|
||||
// Nettoyage du cache si trop volumineux
|
||||
if (cacheDemandesRecentes.size() > 100) {
|
||||
nettoyerCache();
|
||||
}
|
||||
}
|
||||
|
||||
private DemandeAideDTO obtenirDuCache(UUID id) {
|
||||
LocalDateTime timestamp = cacheTimestamps.get(id);
|
||||
if (timestamp == null) return null;
|
||||
|
||||
// Vérification de l'expiration
|
||||
if (LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES).isAfter(timestamp)) {
|
||||
cacheDemandesRecentes.remove(id);
|
||||
cacheTimestamps.remove(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
return cacheDemandesRecentes.get(id);
|
||||
}
|
||||
|
||||
private void nettoyerCache() {
|
||||
LocalDateTime limite = LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES);
|
||||
|
||||
cacheTimestamps.entrySet().removeIf(entry -> entry.getValue().isBefore(limite));
|
||||
cacheDemandesRecentes.keySet().retainAll(cacheTimestamps.keySet());
|
||||
}
|
||||
|
||||
// === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) ===
|
||||
|
||||
private DemandeAideDTO simulerRecuperationBDD(UUID id) {
|
||||
// Simulation - dans une vraie implémentation, ceci ferait appel au repository
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<DemandeAideDTO> simulerRecuperationToutesLesDemandes() {
|
||||
// Simulation - dans une vraie implémentation, ceci ferait appel au repository
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.document.DocumentDTO;
|
||||
import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO;
|
||||
import dev.lions.unionflow.server.entity.*;
|
||||
import dev.lions.unionflow.server.repository.*;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion documentaire
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class DocumentService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DocumentService.class);
|
||||
|
||||
@Inject DocumentRepository documentRepository;
|
||||
|
||||
@Inject PieceJointeRepository pieceJointeRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject AdhesionRepository adhesionRepository;
|
||||
|
||||
@Inject DemandeAideRepository demandeAideRepository;
|
||||
|
||||
@Inject TransactionWaveRepository transactionWaveRepository;
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/**
|
||||
* Crée un nouveau document
|
||||
*
|
||||
* @param documentDTO DTO du document à créer
|
||||
* @return DTO du document créé
|
||||
*/
|
||||
@Transactional
|
||||
public DocumentDTO creerDocument(DocumentDTO documentDTO) {
|
||||
LOG.infof("Création d'un nouveau document: %s", documentDTO.getNomFichier());
|
||||
|
||||
Document document = convertToEntity(documentDTO);
|
||||
document.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
documentRepository.persist(document);
|
||||
LOG.infof("Document créé avec succès: ID=%s, Fichier=%s", document.getId(), document.getNomFichier());
|
||||
|
||||
return convertToDTO(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un document par son ID
|
||||
*
|
||||
* @param id ID du document
|
||||
* @return DTO du document
|
||||
*/
|
||||
public DocumentDTO trouverParId(UUID id) {
|
||||
return documentRepository
|
||||
.findDocumentById(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un téléchargement de document
|
||||
*
|
||||
* @param id ID du document
|
||||
*/
|
||||
@Transactional
|
||||
public void enregistrerTelechargement(UUID id) {
|
||||
Document document =
|
||||
documentRepository
|
||||
.findDocumentById(id)
|
||||
.orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id));
|
||||
|
||||
document.setNombreTelechargements(
|
||||
(document.getNombreTelechargements() != null ? document.getNombreTelechargements() : 0) + 1);
|
||||
document.setDateDernierTelechargement(LocalDateTime.now());
|
||||
document.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
documentRepository.persist(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une pièce jointe
|
||||
*
|
||||
* @param pieceJointeDTO DTO de la pièce jointe à créer
|
||||
* @return DTO de la pièce jointe créée
|
||||
*/
|
||||
@Transactional
|
||||
public PieceJointeDTO creerPieceJointe(PieceJointeDTO pieceJointeDTO) {
|
||||
LOG.infof("Création d'une nouvelle pièce jointe");
|
||||
|
||||
PieceJointe pieceJointe = convertToEntity(pieceJointeDTO);
|
||||
|
||||
// Vérifier qu'une seule relation est renseignée
|
||||
if (!pieceJointe.isValide()) {
|
||||
throw new IllegalArgumentException("Une seule relation doit être renseignée pour une pièce jointe");
|
||||
}
|
||||
|
||||
pieceJointe.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
pieceJointeRepository.persist(pieceJointe);
|
||||
|
||||
LOG.infof("Pièce jointe créée avec succès: ID=%s", pieceJointe.getId());
|
||||
return convertToDTO(pieceJointe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les pièces jointes d'un document
|
||||
*
|
||||
* @param documentId ID du document
|
||||
* @return Liste des pièces jointes
|
||||
*/
|
||||
public List<PieceJointeDTO> listerPiecesJointesParDocument(UUID documentId) {
|
||||
return pieceJointeRepository.findByDocumentId(documentId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MÉTHODES PRIVÉES
|
||||
// ========================================
|
||||
|
||||
/** Convertit une entité Document en DTO */
|
||||
private DocumentDTO convertToDTO(Document document) {
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DocumentDTO dto = new DocumentDTO();
|
||||
dto.setId(document.getId());
|
||||
dto.setNomFichier(document.getNomFichier());
|
||||
dto.setNomOriginal(document.getNomOriginal());
|
||||
dto.setCheminStockage(document.getCheminStockage());
|
||||
dto.setTypeMime(document.getTypeMime());
|
||||
dto.setTailleOctets(document.getTailleOctets());
|
||||
dto.setTypeDocument(document.getTypeDocument());
|
||||
dto.setHashMd5(document.getHashMd5());
|
||||
dto.setHashSha256(document.getHashSha256());
|
||||
dto.setDescription(document.getDescription());
|
||||
dto.setNombreTelechargements(document.getNombreTelechargements());
|
||||
dto.setTailleFormatee(document.getTailleFormatee());
|
||||
dto.setDateCreation(document.getDateCreation());
|
||||
dto.setDateModification(document.getDateModification());
|
||||
dto.setActif(document.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité Document */
|
||||
private Document convertToEntity(DocumentDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Document document = new Document();
|
||||
document.setNomFichier(dto.getNomFichier());
|
||||
document.setNomOriginal(dto.getNomOriginal());
|
||||
document.setCheminStockage(dto.getCheminStockage());
|
||||
document.setTypeMime(dto.getTypeMime());
|
||||
document.setTailleOctets(dto.getTailleOctets());
|
||||
document.setTypeDocument(dto.getTypeDocument() != null ? dto.getTypeDocument() : dev.lions.unionflow.server.api.enums.document.TypeDocument.AUTRE);
|
||||
document.setHashMd5(dto.getHashMd5());
|
||||
document.setHashSha256(dto.getHashSha256());
|
||||
document.setDescription(dto.getDescription());
|
||||
document.setNombreTelechargements(dto.getNombreTelechargements() != null ? dto.getNombreTelechargements() : 0);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
/** Convertit une entité PieceJointe en DTO */
|
||||
private PieceJointeDTO convertToDTO(PieceJointe pieceJointe) {
|
||||
if (pieceJointe == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PieceJointeDTO dto = new PieceJointeDTO();
|
||||
dto.setId(pieceJointe.getId());
|
||||
dto.setOrdre(pieceJointe.getOrdre());
|
||||
dto.setLibelle(pieceJointe.getLibelle());
|
||||
dto.setCommentaire(pieceJointe.getCommentaire());
|
||||
|
||||
if (pieceJointe.getDocument() != null) {
|
||||
dto.setDocumentId(pieceJointe.getDocument().getId());
|
||||
}
|
||||
if (pieceJointe.getMembre() != null) {
|
||||
dto.setMembreId(pieceJointe.getMembre().getId());
|
||||
}
|
||||
if (pieceJointe.getOrganisation() != null) {
|
||||
dto.setOrganisationId(pieceJointe.getOrganisation().getId());
|
||||
}
|
||||
if (pieceJointe.getCotisation() != null) {
|
||||
dto.setCotisationId(pieceJointe.getCotisation().getId());
|
||||
}
|
||||
if (pieceJointe.getAdhesion() != null) {
|
||||
dto.setAdhesionId(pieceJointe.getAdhesion().getId());
|
||||
}
|
||||
if (pieceJointe.getDemandeAide() != null) {
|
||||
dto.setDemandeAideId(pieceJointe.getDemandeAide().getId());
|
||||
}
|
||||
if (pieceJointe.getTransactionWave() != null) {
|
||||
dto.setTransactionWaveId(pieceJointe.getTransactionWave().getId());
|
||||
}
|
||||
|
||||
dto.setDateCreation(pieceJointe.getDateCreation());
|
||||
dto.setDateModification(pieceJointe.getDateModification());
|
||||
dto.setActif(pieceJointe.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité PieceJointe */
|
||||
private PieceJointe convertToEntity(PieceJointeDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PieceJointe pieceJointe = new PieceJointe();
|
||||
pieceJointe.setOrdre(dto.getOrdre() != null ? dto.getOrdre() : 1);
|
||||
pieceJointe.setLibelle(dto.getLibelle());
|
||||
pieceJointe.setCommentaire(dto.getCommentaire());
|
||||
|
||||
// Relation Document
|
||||
if (dto.getDocumentId() != null) {
|
||||
Document document =
|
||||
documentRepository
|
||||
.findDocumentById(dto.getDocumentId())
|
||||
.orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + dto.getDocumentId()));
|
||||
pieceJointe.setDocument(document);
|
||||
}
|
||||
|
||||
// Relations flexibles (une seule doit être renseignée)
|
||||
if (dto.getMembreId() != null) {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(dto.getMembreId())
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
|
||||
pieceJointe.setMembre(membre);
|
||||
}
|
||||
|
||||
if (dto.getOrganisationId() != null) {
|
||||
Organisation org =
|
||||
organisationRepository
|
||||
.findByIdOptional(dto.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
|
||||
pieceJointe.setOrganisation(org);
|
||||
}
|
||||
|
||||
if (dto.getCotisationId() != null) {
|
||||
Cotisation cotisation =
|
||||
cotisationRepository
|
||||
.findByIdOptional(dto.getCotisationId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + dto.getCotisationId()));
|
||||
pieceJointe.setCotisation(cotisation);
|
||||
}
|
||||
|
||||
if (dto.getAdhesionId() != null) {
|
||||
Adhesion adhesion =
|
||||
adhesionRepository
|
||||
.findByIdOptional(dto.getAdhesionId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + dto.getAdhesionId()));
|
||||
pieceJointe.setAdhesion(adhesion);
|
||||
}
|
||||
|
||||
if (dto.getDemandeAideId() != null) {
|
||||
DemandeAide demandeAide =
|
||||
demandeAideRepository
|
||||
.findByIdOptional(dto.getDemandeAideId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Demande d'aide non trouvée avec l'ID: " + dto.getDemandeAideId()));
|
||||
pieceJointe.setDemandeAide(demandeAide);
|
||||
}
|
||||
|
||||
if (dto.getTransactionWaveId() != null) {
|
||||
TransactionWave transactionWave =
|
||||
transactionWaveRepository
|
||||
.findTransactionWaveById(dto.getTransactionWaveId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Transaction Wave non trouvée avec l'ID: " + dto.getTransactionWaveId()));
|
||||
pieceJointe.setTransactionWave(transactionWave);
|
||||
}
|
||||
|
||||
return pieceJointe;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
|
||||
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des événements Version simplifiée pour tester les imports et
|
||||
* Lombok
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class EvenementService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(EvenementService.class);
|
||||
|
||||
@Inject EvenementRepository evenementRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/**
|
||||
* Crée un nouvel événement
|
||||
*
|
||||
* @param evenement l'événement à créer
|
||||
* @return l'événement créé
|
||||
* @throws IllegalArgumentException si les données sont invalides
|
||||
*/
|
||||
@Transactional
|
||||
public Evenement creerEvenement(Evenement evenement) {
|
||||
LOG.infof("Création événement: %s", evenement.getTitre());
|
||||
|
||||
// Validation des données
|
||||
validerEvenement(evenement);
|
||||
|
||||
// Vérifier l'unicité du titre dans l'organisation
|
||||
if (evenement.getOrganisation() != null) {
|
||||
Optional<Evenement> existant = evenementRepository.findByTitre(evenement.getTitre());
|
||||
if (existant.isPresent()
|
||||
&& existant.get().getOrganisation().getId().equals(evenement.getOrganisation().getId())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Un événement avec ce titre existe déjà dans cette organisation");
|
||||
}
|
||||
}
|
||||
|
||||
// Métadonnées de création
|
||||
evenement.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
// Valeurs par défaut
|
||||
if (evenement.getStatut() == null) {
|
||||
evenement.setStatut(StatutEvenement.PLANIFIE);
|
||||
}
|
||||
if (evenement.getActif() == null) {
|
||||
evenement.setActif(true);
|
||||
}
|
||||
if (evenement.getVisiblePublic() == null) {
|
||||
evenement.setVisiblePublic(true);
|
||||
}
|
||||
if (evenement.getInscriptionRequise() == null) {
|
||||
evenement.setInscriptionRequise(true);
|
||||
}
|
||||
|
||||
evenementRepository.persist(evenement);
|
||||
|
||||
LOG.infof("Événement créé avec succès: ID=%s, Titre=%s", evenement.getId(), evenement.getTitre());
|
||||
return evenement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un événement existant
|
||||
*
|
||||
* @param id l'UUID de l'événement
|
||||
* @param evenementMisAJour les nouvelles données
|
||||
* @return l'événement mis à jour
|
||||
* @throws IllegalArgumentException si l'événement n'existe pas
|
||||
*/
|
||||
@Transactional
|
||||
public Evenement mettreAJourEvenement(UUID id, Evenement evenementMisAJour) {
|
||||
LOG.infof("Mise à jour événement ID: %s", id);
|
||||
|
||||
Evenement evenementExistant =
|
||||
evenementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id));
|
||||
|
||||
// Vérifier les permissions
|
||||
if (!peutModifierEvenement(evenementExistant)) {
|
||||
throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement");
|
||||
}
|
||||
|
||||
// Validation des nouvelles données
|
||||
validerEvenement(evenementMisAJour);
|
||||
|
||||
// Mise à jour des champs
|
||||
evenementExistant.setTitre(evenementMisAJour.getTitre());
|
||||
evenementExistant.setDescription(evenementMisAJour.getDescription());
|
||||
evenementExistant.setDateDebut(evenementMisAJour.getDateDebut());
|
||||
evenementExistant.setDateFin(evenementMisAJour.getDateFin());
|
||||
evenementExistant.setLieu(evenementMisAJour.getLieu());
|
||||
evenementExistant.setAdresse(evenementMisAJour.getAdresse());
|
||||
evenementExistant.setTypeEvenement(evenementMisAJour.getTypeEvenement());
|
||||
evenementExistant.setCapaciteMax(evenementMisAJour.getCapaciteMax());
|
||||
evenementExistant.setPrix(evenementMisAJour.getPrix());
|
||||
evenementExistant.setInscriptionRequise(evenementMisAJour.getInscriptionRequise());
|
||||
evenementExistant.setDateLimiteInscription(evenementMisAJour.getDateLimiteInscription());
|
||||
evenementExistant.setInstructionsParticulieres(
|
||||
evenementMisAJour.getInstructionsParticulieres());
|
||||
evenementExistant.setContactOrganisateur(evenementMisAJour.getContactOrganisateur());
|
||||
evenementExistant.setMaterielRequis(evenementMisAJour.getMaterielRequis());
|
||||
evenementExistant.setVisiblePublic(evenementMisAJour.getVisiblePublic());
|
||||
|
||||
// Métadonnées de modification
|
||||
evenementExistant.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
evenementRepository.update(evenementExistant);
|
||||
|
||||
LOG.infof("Événement mis à jour avec succès: ID=%s", id);
|
||||
return evenementExistant;
|
||||
}
|
||||
|
||||
/** Trouve un événement par ID */
|
||||
public Optional<Evenement> trouverParId(UUID id) {
|
||||
return evenementRepository.findByIdOptional(id);
|
||||
}
|
||||
|
||||
/** Liste tous les événements actifs avec pagination */
|
||||
public List<Evenement> listerEvenementsActifs(Page page, Sort sort) {
|
||||
return evenementRepository.findAllActifs(page, sort);
|
||||
}
|
||||
|
||||
/** Liste les événements à venir */
|
||||
public List<Evenement> listerEvenementsAVenir(Page page, Sort sort) {
|
||||
return evenementRepository.findEvenementsAVenir(page, sort);
|
||||
}
|
||||
|
||||
/** Liste les événements publics */
|
||||
public List<Evenement> listerEvenementsPublics(Page page, Sort sort) {
|
||||
return evenementRepository.findEvenementsPublics(page, sort);
|
||||
}
|
||||
|
||||
/** Recherche d'événements par terme */
|
||||
public List<Evenement> rechercherEvenements(String terme, Page page, Sort sort) {
|
||||
return evenementRepository.rechercheAvancee(
|
||||
terme, null, null, null, null, null, null, null, null, null, page, sort);
|
||||
}
|
||||
|
||||
/** Liste les événements par type */
|
||||
public List<Evenement> listerParType(TypeEvenement type, Page page, Sort sort) {
|
||||
return evenementRepository.findByType(type, page, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime logiquement un événement
|
||||
*
|
||||
* @param id l'UUID de l'événement à supprimer
|
||||
* @throws IllegalArgumentException si l'événement n'existe pas
|
||||
*/
|
||||
@Transactional
|
||||
public void supprimerEvenement(UUID id) {
|
||||
LOG.infof("Suppression événement ID: %s", id);
|
||||
|
||||
Evenement evenement =
|
||||
evenementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id));
|
||||
|
||||
// Vérifier les permissions
|
||||
if (!peutModifierEvenement(evenement)) {
|
||||
throw new SecurityException("Vous n'avez pas les permissions pour supprimer cet événement");
|
||||
}
|
||||
|
||||
// Vérifier s'il y a des inscriptions
|
||||
if (evenement.getNombreInscrits() > 0) {
|
||||
throw new IllegalStateException("Impossible de supprimer un événement avec des inscriptions");
|
||||
}
|
||||
|
||||
// Suppression logique
|
||||
evenement.setActif(false);
|
||||
evenement.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
evenementRepository.update(evenement);
|
||||
|
||||
LOG.infof("Événement supprimé avec succès: ID=%s", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le statut d'un événement
|
||||
*
|
||||
* @param id l'UUID de l'événement
|
||||
* @param nouveauStatut le nouveau statut
|
||||
* @return l'événement mis à jour
|
||||
*/
|
||||
@Transactional
|
||||
public Evenement changerStatut(UUID id, StatutEvenement nouveauStatut) {
|
||||
LOG.infof("Changement statut événement ID: %s vers %s", id, nouveauStatut);
|
||||
|
||||
Evenement evenement =
|
||||
evenementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id));
|
||||
|
||||
// Vérifier les permissions
|
||||
if (!peutModifierEvenement(evenement)) {
|
||||
throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement");
|
||||
}
|
||||
|
||||
// Valider le changement de statut
|
||||
validerChangementStatut(evenement.getStatut(), nouveauStatut);
|
||||
|
||||
evenement.setStatut(nouveauStatut);
|
||||
evenement.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
evenementRepository.update(evenement);
|
||||
|
||||
LOG.infof("Statut événement changé avec succès: ID=%s, Nouveau statut=%s", id, nouveauStatut);
|
||||
return evenement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre total d'événements
|
||||
*
|
||||
* @return le nombre total d'événements
|
||||
*/
|
||||
public long countEvenements() {
|
||||
return evenementRepository.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'événements actifs
|
||||
*
|
||||
* @return le nombre d'événements actifs
|
||||
*/
|
||||
public long countEvenementsActifs() {
|
||||
return evenementRepository.countActifs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les statistiques des événements
|
||||
*
|
||||
* @return les statistiques sous forme de Map
|
||||
*/
|
||||
public Map<String, Object> obtenirStatistiques() {
|
||||
Map<String, Long> statsBase = evenementRepository.getStatistiques();
|
||||
|
||||
long total = statsBase.getOrDefault("total", 0L);
|
||||
long actifs = statsBase.getOrDefault("actifs", 0L);
|
||||
long aVenir = statsBase.getOrDefault("aVenir", 0L);
|
||||
long enCours = statsBase.getOrDefault("enCours", 0L);
|
||||
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("total", total);
|
||||
result.put("actifs", actifs);
|
||||
result.put("aVenir", aVenir);
|
||||
result.put("enCours", enCours);
|
||||
result.put("passes", statsBase.getOrDefault("passes", 0L));
|
||||
result.put("publics", statsBase.getOrDefault("publics", 0L));
|
||||
result.put("avecInscription", statsBase.getOrDefault("avecInscription", 0L));
|
||||
result.put("tauxActivite", total > 0 ? (actifs * 100.0 / total) : 0.0);
|
||||
result.put("tauxEvenementsAVenir", total > 0 ? (aVenir * 100.0 / total) : 0.0);
|
||||
result.put("tauxEvenementsEnCours", total > 0 ? (enCours * 100.0 / total) : 0.0);
|
||||
result.put("timestamp", LocalDateTime.now());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Méthodes privées de validation et permissions
|
||||
|
||||
/** Valide les données d'un événement */
|
||||
private void validerEvenement(Evenement evenement) {
|
||||
if (evenement.getTitre() == null || evenement.getTitre().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le titre de l'événement est obligatoire");
|
||||
}
|
||||
|
||||
if (evenement.getDateDebut() == null) {
|
||||
throw new IllegalArgumentException("La date de début est obligatoire");
|
||||
}
|
||||
|
||||
if (evenement.getDateDebut().isBefore(LocalDateTime.now().minusHours(1))) {
|
||||
throw new IllegalArgumentException("La date de début ne peut pas être dans le passé");
|
||||
}
|
||||
|
||||
if (evenement.getDateFin() != null
|
||||
&& evenement.getDateFin().isBefore(evenement.getDateDebut())) {
|
||||
throw new IllegalArgumentException(
|
||||
"La date de fin ne peut pas être antérieure à la date de début");
|
||||
}
|
||||
|
||||
if (evenement.getCapaciteMax() != null && evenement.getCapaciteMax() <= 0) {
|
||||
throw new IllegalArgumentException("La capacité maximale doit être positive");
|
||||
}
|
||||
|
||||
if (evenement.getPrix() != null
|
||||
&& evenement.getPrix().compareTo(java.math.BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("Le prix ne peut pas être négatif");
|
||||
}
|
||||
}
|
||||
|
||||
/** Valide un changement de statut */
|
||||
private void validerChangementStatut(
|
||||
StatutEvenement statutActuel, StatutEvenement nouveauStatut) {
|
||||
// Règles de transition simplifiées pour la version mobile
|
||||
if (statutActuel == StatutEvenement.TERMINE || statutActuel == StatutEvenement.ANNULE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Impossible de changer le statut d'un événement terminé ou annulé");
|
||||
}
|
||||
}
|
||||
|
||||
/** Vérifie les permissions de modification pour l'application mobile */
|
||||
private boolean peutModifierEvenement(Evenement evenement) {
|
||||
if (keycloakService.hasRole("ADMIN") || keycloakService.hasRole("ORGANISATEUR_EVENEMENT")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String utilisateurActuel = keycloakService.getCurrentUserEmail();
|
||||
return utilisateurActuel != null && utilisateurActuel.equals(evenement.getCreePar());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Service d'export des données en Excel et PDF
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ExportService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ExportService.class);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
|
||||
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
|
||||
|
||||
@Inject
|
||||
CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject
|
||||
CotisationService cotisationService;
|
||||
|
||||
/**
|
||||
* Exporte les cotisations en format CSV (compatible Excel)
|
||||
*/
|
||||
public byte[] exporterCotisationsCSV(List<UUID> cotisationIds) {
|
||||
LOG.infof("Export CSV de %d cotisations", cotisationIds.size());
|
||||
|
||||
StringBuilder csv = new StringBuilder();
|
||||
csv.append("Numéro Référence;Membre;Type;Montant Dû;Montant Payé;Statut;Date Échéance;Date Paiement;Méthode Paiement\n");
|
||||
|
||||
for (UUID id : cotisationIds) {
|
||||
Optional<Cotisation> cotisationOpt = cotisationRepository.findByIdOptional(id);
|
||||
if (cotisationOpt.isPresent()) {
|
||||
Cotisation c = cotisationOpt.get();
|
||||
String nomMembre = c.getMembre() != null
|
||||
? c.getMembre().getNom() + " " + c.getMembre().getPrenom()
|
||||
: "";
|
||||
csv.append(String.format("%s;%s;%s;%s;%s;%s;%s;%s;%s\n",
|
||||
c.getNumeroReference() != null ? c.getNumeroReference() : "",
|
||||
nomMembre,
|
||||
c.getTypeCotisation() != null ? c.getTypeCotisation() : "",
|
||||
c.getMontantDu() != null ? c.getMontantDu().toString() : "0",
|
||||
c.getMontantPaye() != null ? c.getMontantPaye().toString() : "0",
|
||||
c.getStatut() != null ? c.getStatut() : "",
|
||||
c.getDateEcheance() != null ? c.getDateEcheance().format(DATE_FORMATTER) : "",
|
||||
c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "",
|
||||
c.getMethodePaiement() != null ? c.getMethodePaiement() : ""
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte toutes les cotisations filtrées en CSV
|
||||
*/
|
||||
public byte[] exporterToutesCotisationsCSV(String statut, String type, UUID associationId) {
|
||||
LOG.info("Export CSV de toutes les cotisations");
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.listAll();
|
||||
|
||||
// Filtrer
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> c.getStatut() != null && c.getStatut().equals(statut))
|
||||
.toList();
|
||||
}
|
||||
if (type != null && !type.isEmpty()) {
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> c.getTypeCotisation() != null && c.getTypeCotisation().equals(type))
|
||||
.toList();
|
||||
}
|
||||
// Note: le filtrage par association n'est pas disponible car Membre n'a pas de lien direct
|
||||
// avec Association dans cette version du modèle
|
||||
|
||||
List<UUID> ids = cotisations.stream().map(Cotisation::getId).toList();
|
||||
return exporterCotisationsCSV(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un reçu de paiement en format texte (pour impression)
|
||||
*/
|
||||
public byte[] genererRecuPaiement(UUID cotisationId) {
|
||||
LOG.infof("Génération reçu pour cotisation: %s", cotisationId);
|
||||
|
||||
Optional<Cotisation> cotisationOpt = cotisationRepository.findByIdOptional(cotisationId);
|
||||
if (cotisationOpt.isEmpty()) {
|
||||
return "Cotisation non trouvée".getBytes();
|
||||
}
|
||||
|
||||
Cotisation c = cotisationOpt.get();
|
||||
|
||||
StringBuilder recu = new StringBuilder();
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n");
|
||||
recu.append(" REÇU DE PAIEMENT\n");
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n\n");
|
||||
|
||||
recu.append("Numéro de reçu : ").append(c.getNumeroReference()).append("\n");
|
||||
recu.append("Date : ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n");
|
||||
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
recu.append(" INFORMATIONS MEMBRE\n");
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
|
||||
if (c.getMembre() != null) {
|
||||
recu.append("Nom : ").append(c.getMembre().getNom()).append(" ").append(c.getMembre().getPrenom()).append("\n");
|
||||
recu.append("Numéro membre : ").append(c.getMembre().getNumeroMembre()).append("\n");
|
||||
}
|
||||
|
||||
recu.append("\n───────────────────────────────────────────────────────────────\n");
|
||||
recu.append(" DÉTAILS DU PAIEMENT\n");
|
||||
recu.append("───────────────────────────────────────────────────────────────\n");
|
||||
|
||||
recu.append("Type cotisation : ").append(c.getTypeCotisation() != null ? c.getTypeCotisation() : "").append("\n");
|
||||
recu.append("Période : ").append(c.getPeriode() != null ? c.getPeriode() : "").append("\n");
|
||||
recu.append("Montant dû : ").append(formatMontant(c.getMontantDu())).append("\n");
|
||||
recu.append("Montant payé : ").append(formatMontant(c.getMontantPaye())).append("\n");
|
||||
recu.append("Mode de paiement : ").append(c.getMethodePaiement() != null ? c.getMethodePaiement() : "").append("\n");
|
||||
recu.append("Date de paiement : ").append(c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "").append("\n");
|
||||
recu.append("Statut : ").append(c.getStatut() != null ? c.getStatut() : "").append("\n");
|
||||
|
||||
recu.append("\n═══════════════════════════════════════════════════════════════\n");
|
||||
recu.append(" Ce document fait foi de paiement de cotisation\n");
|
||||
recu.append(" Merci de votre confiance !\n");
|
||||
recu.append("═══════════════════════════════════════════════════════════════\n");
|
||||
|
||||
return recu.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère plusieurs reçus de paiement
|
||||
*/
|
||||
public byte[] genererRecusGroupes(List<UUID> cotisationIds) {
|
||||
LOG.infof("Génération de %d reçus groupés", cotisationIds.size());
|
||||
|
||||
StringBuilder allRecus = new StringBuilder();
|
||||
for (int i = 0; i < cotisationIds.size(); i++) {
|
||||
byte[] recu = genererRecuPaiement(cotisationIds.get(i));
|
||||
allRecus.append(new String(recu, java.nio.charset.StandardCharsets.UTF_8));
|
||||
if (i < cotisationIds.size() - 1) {
|
||||
allRecus.append("\n\n════════════════════════ PAGE SUIVANTE ════════════════════════\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
return allRecus.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un rapport mensuel
|
||||
*/
|
||||
public byte[] genererRapportMensuel(int annee, int mois, UUID associationId) {
|
||||
LOG.infof("Génération rapport mensuel: %d/%d", mois, annee);
|
||||
|
||||
List<Cotisation> cotisations = cotisationRepository.listAll();
|
||||
|
||||
// Filtrer par mois/année et association
|
||||
LocalDate debut = LocalDate.of(annee, mois, 1);
|
||||
LocalDate fin = debut.plusMonths(1).minusDays(1);
|
||||
|
||||
cotisations = cotisations.stream()
|
||||
.filter(c -> {
|
||||
if (c.getDateCreation() == null) return false;
|
||||
LocalDate dateCot = c.getDateCreation().toLocalDate();
|
||||
return !dateCot.isBefore(debut) && !dateCot.isAfter(fin);
|
||||
})
|
||||
// Note: le filtrage par association n'est pas implémenté ici
|
||||
.toList();
|
||||
|
||||
// Calculer les statistiques
|
||||
long total = cotisations.size();
|
||||
long payees = cotisations.stream().filter(c -> "PAYEE".equals(c.getStatut())).count();
|
||||
long enAttente = cotisations.stream().filter(c -> "EN_ATTENTE".equals(c.getStatut())).count();
|
||||
long enRetard = cotisations.stream().filter(c -> "EN_RETARD".equals(c.getStatut())).count();
|
||||
|
||||
BigDecimal montantTotal = cotisations.stream()
|
||||
.map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal montantCollecte = cotisations.stream()
|
||||
.filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
|
||||
.map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
double tauxRecouvrement = montantTotal.compareTo(BigDecimal.ZERO) > 0
|
||||
? montantCollecte.multiply(BigDecimal.valueOf(100)).divide(montantTotal, 2, java.math.RoundingMode.HALF_UP).doubleValue()
|
||||
: 0;
|
||||
|
||||
// Construire le rapport
|
||||
StringBuilder rapport = new StringBuilder();
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n");
|
||||
rapport.append(" RAPPORT MENSUEL DES COTISATIONS\n");
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n\n");
|
||||
|
||||
rapport.append("Période : ").append(String.format("%02d/%d", mois, annee)).append("\n");
|
||||
rapport.append("Date de génération: ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n");
|
||||
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n");
|
||||
rapport.append(" RÉSUMÉ\n");
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n\n");
|
||||
|
||||
rapport.append("Total cotisations : ").append(total).append("\n");
|
||||
rapport.append("Cotisations payées : ").append(payees).append("\n");
|
||||
rapport.append("Cotisations en attente: ").append(enAttente).append("\n");
|
||||
rapport.append("Cotisations en retard : ").append(enRetard).append("\n\n");
|
||||
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n");
|
||||
rapport.append(" FINANCIER\n");
|
||||
rapport.append("───────────────────────────────────────────────────────────────\n\n");
|
||||
|
||||
rapport.append("Montant total attendu : ").append(formatMontant(montantTotal)).append("\n");
|
||||
rapport.append("Montant collecté : ").append(formatMontant(montantCollecte)).append("\n");
|
||||
rapport.append("Taux de recouvrement : ").append(String.format("%.1f%%", tauxRecouvrement)).append("\n\n");
|
||||
|
||||
rapport.append("═══════════════════════════════════════════════════════════════\n");
|
||||
|
||||
return rapport.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String formatMontant(BigDecimal montant) {
|
||||
if (montant == null) return "0 FCFA";
|
||||
return String.format("%,.0f FCFA", montant.doubleValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
|
||||
import dev.lions.unionflow.server.repository.CotisationRepository;
|
||||
import dev.lions.unionflow.server.repository.DemandeAideRepository;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Service spécialisé dans le calcul des KPI (Key Performance Indicators)
|
||||
*
|
||||
* <p>Ce service fournit des méthodes optimisées pour calculer les indicateurs de performance clés
|
||||
* de l'application UnionFlow.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class KPICalculatorService {
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject CotisationRepository cotisationRepository;
|
||||
|
||||
@Inject EvenementRepository evenementRepository;
|
||||
|
||||
@Inject DemandeAideRepository demandeAideRepository;
|
||||
|
||||
/**
|
||||
* Calcule tous les KPI principaux pour une organisation
|
||||
*
|
||||
* @param organisationId L'ID de l'organisation
|
||||
* @param dateDebut Date de début de la période
|
||||
* @param dateFin Date de fin de la période
|
||||
* @return Map contenant tous les KPI calculés
|
||||
*/
|
||||
public Map<TypeMetrique, BigDecimal> calculerTousLesKPI(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.info(
|
||||
"Calcul de tous les KPI pour l'organisation {} sur la période {} - {}",
|
||||
organisationId,
|
||||
dateDebut,
|
||||
dateFin);
|
||||
|
||||
Map<TypeMetrique, BigDecimal> kpis = new HashMap<>();
|
||||
|
||||
// KPI Membres
|
||||
kpis.put(
|
||||
TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
|
||||
calculerKPIMembresActifs(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.NOMBRE_MEMBRES_INACTIFS,
|
||||
calculerKPIMembresInactifs(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.TAUX_CROISSANCE_MEMBRES,
|
||||
calculerKPITauxCroissanceMembres(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.MOYENNE_AGE_MEMBRES,
|
||||
calculerKPIMoyenneAgeMembres(organisationId, dateDebut, dateFin));
|
||||
|
||||
// KPI Financiers
|
||||
kpis.put(
|
||||
TypeMetrique.TOTAL_COTISATIONS_COLLECTEES,
|
||||
calculerKPITotalCotisations(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.COTISATIONS_EN_ATTENTE,
|
||||
calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS,
|
||||
calculerKPITauxRecouvrement(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.MOYENNE_COTISATION_MEMBRE,
|
||||
calculerKPIMoyenneCotisationMembre(organisationId, dateDebut, dateFin));
|
||||
|
||||
// KPI Événements
|
||||
kpis.put(
|
||||
TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES,
|
||||
calculerKPINombreEvenements(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS,
|
||||
calculerKPITauxParticipation(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.MOYENNE_PARTICIPANTS_EVENEMENT,
|
||||
calculerKPIMoyenneParticipants(organisationId, dateDebut, dateFin));
|
||||
|
||||
// KPI Solidarité
|
||||
kpis.put(
|
||||
TypeMetrique.NOMBRE_DEMANDES_AIDE,
|
||||
calculerKPINombreDemandesAide(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.MONTANT_AIDES_ACCORDEES,
|
||||
calculerKPIMontantAides(organisationId, dateDebut, dateFin));
|
||||
kpis.put(
|
||||
TypeMetrique.TAUX_APPROBATION_AIDES,
|
||||
calculerKPITauxApprobationAides(organisationId, dateDebut, dateFin));
|
||||
|
||||
log.info("Calcul terminé : {} KPI calculés", kpis.size());
|
||||
return kpis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le KPI de performance globale de l'organisation
|
||||
*
|
||||
* @param organisationId L'ID de l'organisation
|
||||
* @param dateDebut Date de début de la période
|
||||
* @param dateFin Date de fin de la période
|
||||
* @return Score de performance global (0-100)
|
||||
*/
|
||||
public BigDecimal calculerKPIPerformanceGlobale(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.info("Calcul du KPI de performance globale pour l'organisation {}", organisationId);
|
||||
|
||||
Map<TypeMetrique, BigDecimal> kpis = calculerTousLesKPI(organisationId, dateDebut, dateFin);
|
||||
|
||||
// Pondération des différents KPI pour le score global
|
||||
BigDecimal scoreMembers = calculerScoreMembres(kpis).multiply(new BigDecimal("0.30")); // 30%
|
||||
BigDecimal scoreFinancier =
|
||||
calculerScoreFinancier(kpis).multiply(new BigDecimal("0.35")); // 35%
|
||||
BigDecimal scoreEvenements =
|
||||
calculerScoreEvenements(kpis).multiply(new BigDecimal("0.20")); // 20%
|
||||
BigDecimal scoreSolidarite =
|
||||
calculerScoreSolidarite(kpis).multiply(new BigDecimal("0.15")); // 15%
|
||||
|
||||
BigDecimal scoreGlobal =
|
||||
scoreMembers.add(scoreFinancier).add(scoreEvenements).add(scoreSolidarite);
|
||||
|
||||
log.info("Score de performance globale calculé : {}", scoreGlobal);
|
||||
return scoreGlobal.setScale(1, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les KPI de comparaison avec la période précédente
|
||||
*
|
||||
* @param organisationId L'ID de l'organisation
|
||||
* @param dateDebut Date de début de la période actuelle
|
||||
* @param dateFin Date de fin de la période actuelle
|
||||
* @return Map des évolutions en pourcentage
|
||||
*/
|
||||
public Map<TypeMetrique, BigDecimal> calculerEvolutionsKPI(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.info("Calcul des évolutions KPI pour l'organisation {}", organisationId);
|
||||
|
||||
// Période actuelle
|
||||
Map<TypeMetrique, BigDecimal> kpisActuels =
|
||||
calculerTousLesKPI(organisationId, dateDebut, dateFin);
|
||||
|
||||
// Période précédente (même durée, décalée)
|
||||
long dureeJours = java.time.Duration.between(dateDebut, dateFin).toDays();
|
||||
LocalDateTime dateDebutPrecedente = dateDebut.minusDays(dureeJours);
|
||||
LocalDateTime dateFinPrecedente = dateFin.minusDays(dureeJours);
|
||||
Map<TypeMetrique, BigDecimal> kpisPrecedents =
|
||||
calculerTousLesKPI(organisationId, dateDebutPrecedente, dateFinPrecedente);
|
||||
|
||||
Map<TypeMetrique, BigDecimal> evolutions = new HashMap<>();
|
||||
|
||||
for (TypeMetrique typeMetrique : kpisActuels.keySet()) {
|
||||
BigDecimal valeurActuelle = kpisActuels.get(typeMetrique);
|
||||
BigDecimal valeurPrecedente = kpisPrecedents.get(typeMetrique);
|
||||
|
||||
BigDecimal evolution = calculerPourcentageEvolution(valeurActuelle, valeurPrecedente);
|
||||
evolutions.put(typeMetrique, evolution);
|
||||
}
|
||||
|
||||
return evolutions;
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES DE CALCUL DES KPI ===
|
||||
|
||||
private BigDecimal calculerKPIMembresActifs(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPIMembresInactifs(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = membreRepository.countMembresInactifs(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITauxCroissanceMembres(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long membresActuels = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
Long membresPrecedents =
|
||||
membreRepository.countMembresActifs(
|
||||
organisationId, dateDebut.minusMonths(1), dateFin.minusMonths(1));
|
||||
|
||||
return calculerTauxCroissance(
|
||||
new BigDecimal(membresActuels), new BigDecimal(membresPrecedents));
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPIMoyenneAgeMembres(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Double moyenneAge = membreRepository.calculerMoyenneAge(organisationId, dateDebut, dateFin);
|
||||
return moyenneAge != null
|
||||
? new BigDecimal(moyenneAge).setScale(1, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITotalCotisations(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total = cotisationRepository.sumMontantsPayes(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPICotisationsEnAttente(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total =
|
||||
cotisationRepository.sumMontantsEnAttente(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITauxRecouvrement(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal collectees = calculerKPITotalCotisations(organisationId, dateDebut, dateFin);
|
||||
BigDecimal enAttente = calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin);
|
||||
BigDecimal total = collectees.add(enAttente);
|
||||
|
||||
if (total.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
|
||||
|
||||
return collectees.divide(total, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPIMoyenneCotisationMembre(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total = calculerKPITotalCotisations(organisationId, dateDebut, dateFin);
|
||||
Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
|
||||
if (nombreMembres == 0) return BigDecimal.ZERO;
|
||||
|
||||
return total.divide(new BigDecimal(nombreMembres), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPINombreEvenements(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = evenementRepository.countEvenements(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITauxParticipation(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
// Calcul basé sur les participations aux événements
|
||||
Long totalParticipations =
|
||||
evenementRepository.countTotalParticipations(organisationId, dateDebut, dateFin);
|
||||
Long nombreEvenements = evenementRepository.countEvenements(organisationId, dateDebut, dateFin);
|
||||
Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
|
||||
|
||||
if (nombreEvenements == 0 || nombreMembres == 0) return BigDecimal.ZERO;
|
||||
|
||||
BigDecimal participationsAttendues =
|
||||
new BigDecimal(nombreEvenements).multiply(new BigDecimal(nombreMembres));
|
||||
BigDecimal tauxParticipation =
|
||||
new BigDecimal(totalParticipations)
|
||||
.divide(participationsAttendues, 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
|
||||
return tauxParticipation;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPIMoyenneParticipants(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Double moyenne =
|
||||
evenementRepository.calculerMoyenneParticipants(organisationId, dateDebut, dateFin);
|
||||
return moyenne != null
|
||||
? new BigDecimal(moyenne).setScale(1, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPINombreDemandesAide(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long count = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
|
||||
return new BigDecimal(count);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPIMontantAides(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total =
|
||||
demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITauxApprobationAides(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
Long totalDemandes = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
|
||||
Long demandesApprouvees =
|
||||
demandeAideRepository.countDemandesApprouvees(organisationId, dateDebut, dateFin);
|
||||
|
||||
if (totalDemandes == 0) return BigDecimal.ZERO;
|
||||
|
||||
return new BigDecimal(demandesApprouvees)
|
||||
.divide(new BigDecimal(totalDemandes), 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
private BigDecimal calculerTauxCroissance(
|
||||
BigDecimal valeurActuelle, BigDecimal valeurPrecedente) {
|
||||
if (valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
|
||||
|
||||
return valeurActuelle
|
||||
.subtract(valeurPrecedente)
|
||||
.divide(valeurPrecedente, 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private BigDecimal calculerPourcentageEvolution(
|
||||
BigDecimal valeurActuelle, BigDecimal valeurPrecedente) {
|
||||
if (valeurPrecedente == null || valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
return valeurActuelle
|
||||
.subtract(valeurPrecedente)
|
||||
.divide(valeurPrecedente, 4, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"));
|
||||
}
|
||||
|
||||
private BigDecimal calculerScoreMembres(Map<TypeMetrique, BigDecimal> kpis) {
|
||||
// Score basé sur la croissance et l'activité des membres
|
||||
BigDecimal tauxCroissance = kpis.get(TypeMetrique.TAUX_CROISSANCE_MEMBRES);
|
||||
BigDecimal nombreActifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
|
||||
BigDecimal nombreInactifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_INACTIFS);
|
||||
|
||||
// Calcul du score (logique simplifiée)
|
||||
BigDecimal scoreActivite =
|
||||
nombreActifs
|
||||
.divide(nombreActifs.add(nombreInactifs), 2, RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("50"));
|
||||
BigDecimal scoreCroissance = tauxCroissance.min(new BigDecimal("50")); // Plafonné à 50
|
||||
|
||||
return scoreActivite.add(scoreCroissance);
|
||||
}
|
||||
|
||||
private BigDecimal calculerScoreFinancier(Map<TypeMetrique, BigDecimal> kpis) {
|
||||
// Score basé sur le recouvrement et les montants
|
||||
BigDecimal tauxRecouvrement = kpis.get(TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS);
|
||||
return tauxRecouvrement; // Score direct basé sur le taux de recouvrement
|
||||
}
|
||||
|
||||
private BigDecimal calculerScoreEvenements(Map<TypeMetrique, BigDecimal> kpis) {
|
||||
// Score basé sur la participation aux événements
|
||||
BigDecimal tauxParticipation = kpis.get(TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS);
|
||||
return tauxParticipation; // Score direct basé sur le taux de participation
|
||||
}
|
||||
|
||||
private BigDecimal calculerScoreSolidarite(Map<TypeMetrique, BigDecimal> kpis) {
|
||||
// Score basé sur l'efficacité du système de solidarité
|
||||
BigDecimal tauxApprobation = kpis.get(TypeMetrique.TAUX_APPROBATION_AIDES);
|
||||
return tauxApprobation; // Score direct basé sur le taux d'approbation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service pour l'intégration avec Keycloak
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class KeycloakService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KeycloakService.class);
|
||||
|
||||
@Inject SecurityIdentity securityIdentity;
|
||||
|
||||
@Inject JsonWebToken jwt;
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel est authentifié
|
||||
*
|
||||
* @return true si l'utilisateur est authentifié
|
||||
*/
|
||||
public boolean isAuthenticated() {
|
||||
return securityIdentity != null && !securityIdentity.isAnonymous();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient l'ID de l'utilisateur actuel depuis Keycloak
|
||||
*
|
||||
* @return l'ID de l'utilisateur ou null si non authentifié
|
||||
*/
|
||||
public String getCurrentUserId() {
|
||||
if (!isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.getSubject();
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération de l'ID utilisateur: %s", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient l'email de l'utilisateur actuel
|
||||
*
|
||||
* @return l'email de l'utilisateur ou null si non authentifié
|
||||
*/
|
||||
public String getCurrentUserEmail() {
|
||||
if (!isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.getClaim("email");
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération de l'email utilisateur: %s", e.getMessage());
|
||||
return securityIdentity.getPrincipal().getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le nom complet de l'utilisateur actuel
|
||||
*
|
||||
* @return le nom complet ou null si non disponible
|
||||
*/
|
||||
public String getCurrentUserFullName() {
|
||||
if (!isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String firstName = jwt.getClaim("given_name");
|
||||
String lastName = jwt.getClaim("family_name");
|
||||
|
||||
if (firstName != null && lastName != null) {
|
||||
return firstName + " " + lastName;
|
||||
} else if (firstName != null) {
|
||||
return firstName;
|
||||
} else if (lastName != null) {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
// Fallback sur le nom d'utilisateur
|
||||
return jwt.getClaim("preferred_username");
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération du nom utilisateur: %s", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient tous les rôles de l'utilisateur actuel
|
||||
*
|
||||
* @return les rôles de l'utilisateur
|
||||
*/
|
||||
public Set<String> getCurrentUserRoles() {
|
||||
if (!isAuthenticated()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
return securityIdentity.getRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a un rôle spécifique
|
||||
*
|
||||
* @param role le rôle à vérifier
|
||||
* @return true si l'utilisateur a le rôle
|
||||
*/
|
||||
public boolean hasRole(String role) {
|
||||
if (!isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return securityIdentity.hasRole(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a au moins un des rôles spécifiés
|
||||
*
|
||||
* @param roles les rôles à vérifier
|
||||
* @return true si l'utilisateur a au moins un des rôles
|
||||
*/
|
||||
public boolean hasAnyRole(String... roles) {
|
||||
if (!isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String role : roles) {
|
||||
if (securityIdentity.hasRole(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel a tous les rôles spécifiés
|
||||
*
|
||||
* @param roles les rôles à vérifier
|
||||
* @return true si l'utilisateur a tous les rôles
|
||||
*/
|
||||
public boolean hasAllRoles(String... roles) {
|
||||
if (!isAuthenticated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String role : roles) {
|
||||
if (!securityIdentity.hasRole(role)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient une claim spécifique du JWT
|
||||
*
|
||||
* @param claimName le nom de la claim
|
||||
* @return la valeur de la claim ou null si non trouvée
|
||||
*/
|
||||
public <T> T getClaim(String claimName) {
|
||||
if (!isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.getClaim(claimName);
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération de la claim %s: %s", claimName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient toutes les claims du JWT
|
||||
*
|
||||
* @return toutes les claims ou une map vide si non authentifié
|
||||
*/
|
||||
public Set<String> getAllClaimNames() {
|
||||
if (!isAuthenticated()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.getClaimNames();
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération des claims: %s", e.getMessage());
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les informations utilisateur pour les logs
|
||||
*
|
||||
* @return informations utilisateur formatées
|
||||
*/
|
||||
public String getUserInfoForLogging() {
|
||||
if (!isAuthenticated()) {
|
||||
return "Utilisateur non authentifié";
|
||||
}
|
||||
|
||||
String email = getCurrentUserEmail();
|
||||
String fullName = getCurrentUserFullName();
|
||||
Set<String> roles = getCurrentUserRoles();
|
||||
|
||||
return String.format(
|
||||
"Utilisateur: %s (%s), Rôles: %s",
|
||||
fullName != null ? fullName : "N/A", email != null ? email : "N/A", roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel est un administrateur
|
||||
*
|
||||
* @return true si l'utilisateur est administrateur
|
||||
*/
|
||||
public boolean isAdmin() {
|
||||
return hasRole("ADMIN") || hasRole("admin");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les membres
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les membres
|
||||
*/
|
||||
public boolean canManageMembers() {
|
||||
return hasAnyRole(
|
||||
"ADMIN",
|
||||
"GESTIONNAIRE_MEMBRE",
|
||||
"PRESIDENT",
|
||||
"SECRETAIRE",
|
||||
"admin",
|
||||
"gestionnaire_membre",
|
||||
"president",
|
||||
"secretaire");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les finances
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les finances
|
||||
*/
|
||||
public boolean canManageFinances() {
|
||||
return hasAnyRole("ADMIN", "TRESORIER", "PRESIDENT", "admin", "tresorier", "president");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les événements
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les événements
|
||||
*/
|
||||
public boolean canManageEvents() {
|
||||
return hasAnyRole(
|
||||
"ADMIN",
|
||||
"ORGANISATEUR_EVENEMENT",
|
||||
"PRESIDENT",
|
||||
"SECRETAIRE",
|
||||
"admin",
|
||||
"organisateur_evenement",
|
||||
"president",
|
||||
"secretaire");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuel peut gérer les organisations
|
||||
*
|
||||
* @return true si l'utilisateur peut gérer les organisations
|
||||
*/
|
||||
public boolean canManageOrganizations() {
|
||||
return hasAnyRole("ADMIN", "PRESIDENT", "admin", "president");
|
||||
}
|
||||
|
||||
/** Log les informations de sécurité pour debug */
|
||||
public void logSecurityInfo() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debugf("Informations de sécurité: %s", getUserInfoForLogging());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le token d'accès brut
|
||||
*
|
||||
* @return le token JWT brut ou null si non disponible
|
||||
*/
|
||||
public String getRawAccessToken() {
|
||||
if (!isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (jwt instanceof OidcJwtCallerPrincipal) {
|
||||
return ((OidcJwtCallerPrincipal) jwt).getRawToken();
|
||||
}
|
||||
return jwt.getRawToken();
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération du token brut: %s", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user