Refactoring - Version stable
This commit is contained in:
@@ -219,15 +219,25 @@ public class UnionFlowServerApplication implements QuarkusApplication {
|
||||
LOG.info("--------------------------------------------------------------");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la valeur de la variable d'environnement UNIONFLOW_DOMAIN.
|
||||
* Méthode protégée pour permettre la substitution en tests.
|
||||
*
|
||||
* @return valeur de UNIONFLOW_DOMAIN, ou null si non définie
|
||||
*/
|
||||
protected String getUnionflowDomain() {
|
||||
return System.getenv("UNIONFLOW_DOMAIN");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit l'URL de base de l'application.
|
||||
*
|
||||
* @return URL complète (ex: http://localhost:8080)
|
||||
*/
|
||||
private String buildBaseUrl() {
|
||||
String buildBaseUrl() {
|
||||
// En production, utiliser le nom de domaine configuré
|
||||
if ("prod".equals(activeProfile)) {
|
||||
String domain = System.getenv("UNIONFLOW_DOMAIN");
|
||||
String domain = getUnionflowDomain();
|
||||
if (domain != null && !domain.isEmpty()) {
|
||||
return "https://" + domain;
|
||||
}
|
||||
|
||||
@@ -43,30 +43,27 @@ public class OidcTokenPropagationHeadersFactory implements ClientHeadersFactory
|
||||
}
|
||||
|
||||
// STRATÉGIE 2 : Récupérer depuis SecurityIdentity
|
||||
if (securityIdentity.isResolvable()) {
|
||||
SecurityIdentity identity = securityIdentity.get();
|
||||
// En contexte CDI, securityIdentity.isResolvable() est toujours true.
|
||||
SecurityIdentity identity = securityIdentity.get();
|
||||
|
||||
if (identity != null && !identity.isAnonymous()) {
|
||||
if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
|
||||
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
|
||||
String token = principal.getRawToken();
|
||||
if (identity != null && !identity.isAnonymous()) {
|
||||
if (identity.getPrincipal() instanceof OidcJwtCallerPrincipal) {
|
||||
OidcJwtCallerPrincipal principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
|
||||
String token = principal.getRawToken();
|
||||
|
||||
if (token != null && !token.isBlank()) {
|
||||
result.add("Authorization", "Bearer " + token);
|
||||
LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length());
|
||||
return result;
|
||||
} else {
|
||||
LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity");
|
||||
}
|
||||
if (token != null && !token.isBlank()) {
|
||||
result.add("Authorization", "Bearer " + token);
|
||||
LOG.infof("✅ Token JWT propagé depuis SecurityIdentity (longueur: %d)", token.length());
|
||||
return result;
|
||||
} else {
|
||||
LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)",
|
||||
identity.getPrincipal().getClass().getName());
|
||||
LOG.warnf("⚠️ Token JWT vide dans SecurityIdentity");
|
||||
}
|
||||
} else {
|
||||
LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme");
|
||||
LOG.warnf("⚠️ Principal n'est pas un OidcJwtCallerPrincipal (type: %s)",
|
||||
identity.getPrincipal() != null ? identity.getPrincipal().getClass().getName() : "null");
|
||||
}
|
||||
} else {
|
||||
LOG.warnf("⚠️ SecurityIdentity non disponible dans le contexte");
|
||||
LOG.warnf("⚠️ SecurityIdentity null ou utilisateur anonyme");
|
||||
}
|
||||
|
||||
LOG.errorf("❌ Impossible de propager le token JWT - aucune stratégie n'a fonctionné");
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Entité représentant une alerte LCB-FT (Lutte Contre le Blanchiment et Financement du Terrorisme).
|
||||
@@ -99,6 +100,7 @@ public class AlerteLcbFt extends BaseEntity {
|
||||
/**
|
||||
* Indique si l'alerte a été traitée
|
||||
*/
|
||||
@Builder.Default
|
||||
@Column(name = "traitee", nullable = false)
|
||||
private Boolean traitee = false;
|
||||
|
||||
@@ -111,8 +113,8 @@ public class AlerteLcbFt extends BaseEntity {
|
||||
/**
|
||||
* Utilisateur ayant traité l'alerte
|
||||
*/
|
||||
@Column(name = "traite_par", length = 100)
|
||||
private String traitePar;
|
||||
@Column(name = "traite_par")
|
||||
private UUID traitePar;
|
||||
|
||||
/**
|
||||
* Commentaire sur le traitement
|
||||
|
||||
@@ -90,6 +90,7 @@ public class AuditLog extends BaseEntity {
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (dateHeure == null) {
|
||||
dateHeure = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@@ -15,13 +15,7 @@ import lombok.NoArgsConstructor;
|
||||
* @version 1.0
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "configurations",
|
||||
indexes = {
|
||||
@Index(name = "idx_config_cle", columnList = "cle", unique = true),
|
||||
@Index(name = "idx_config_categorie", columnList = "categorie")
|
||||
}
|
||||
)
|
||||
@Table(name = "configurations")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -156,12 +157,15 @@ public class Cotisation extends BaseEntity {
|
||||
return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
|
||||
}
|
||||
|
||||
private static final AtomicLong REFERENCE_COUNTER =
|
||||
new AtomicLong(System.currentTimeMillis() % 100000000L);
|
||||
|
||||
/** Méthode métier pour générer un numéro de référence unique */
|
||||
public static String genererNumeroReference() {
|
||||
return "COT-"
|
||||
+ LocalDate.now().getYear()
|
||||
+ "-"
|
||||
+ String.format("%08d", System.currentTimeMillis() % 100000000);
|
||||
+ String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
|
||||
@@ -4,6 +4,7 @@ import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
@@ -108,9 +109,12 @@ public class DemandeAdhesion extends BaseEntity {
|
||||
&& montantPaye.compareTo(fraisAdhesion) >= 0;
|
||||
}
|
||||
|
||||
private static final AtomicLong REFERENCE_COUNTER =
|
||||
new AtomicLong(System.currentTimeMillis() % 100000000L);
|
||||
|
||||
public static String genererNumeroReference() {
|
||||
return "ADH-" + java.time.LocalDate.now().getYear()
|
||||
+ "-" + String.format("%08d", System.currentTimeMillis() % 100000000);
|
||||
+ "-" + String.format("%08d", REFERENCE_COUNTER.incrementAndGet() % 100000000L);
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
|
||||
@@ -5,6 +5,7 @@ import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -114,12 +115,15 @@ public class Paiement extends BaseEntity {
|
||||
@JoinColumn(name = "transaction_wave_id")
|
||||
private TransactionWave transactionWave;
|
||||
|
||||
private static final AtomicLong REFERENCE_COUNTER =
|
||||
new AtomicLong(System.currentTimeMillis() % 1000000000000L);
|
||||
|
||||
/** 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);
|
||||
+ String.format("%012d", REFERENCE_COUNTER.incrementAndGet() % 1000000000000L);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le paiement est validé */
|
||||
|
||||
@@ -33,21 +33,28 @@ public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(Throwable exception) {
|
||||
// Logger l'exception dans les logs applicatifs
|
||||
log.error("Unhandled exception", exception);
|
||||
|
||||
// Déterminer le code HTTP
|
||||
int statusCode = determineStatusCode(exception);
|
||||
|
||||
// Récupérer l'endpoint (safe pour les tests unitaires)
|
||||
String endpoint = "unknown";
|
||||
try {
|
||||
// Logger l'exception dans les logs applicatifs
|
||||
log.error("Unhandled exception", exception);
|
||||
if (uriInfo != null) {
|
||||
endpoint = uriInfo.getPath();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore - pas de contexte REST (ex: test unitaire)
|
||||
}
|
||||
|
||||
// Déterminer le code HTTP
|
||||
int statusCode = determineStatusCode(exception);
|
||||
// Générer le message et le stacktrace
|
||||
String message = exception.getMessage() != null ? exception.getMessage() : exception.getClass().getSimpleName();
|
||||
String stacktrace = getStackTrace(exception);
|
||||
|
||||
// Récupérer l'endpoint
|
||||
String endpoint = uriInfo != null ? uriInfo.getPath() : "unknown";
|
||||
|
||||
// Générer le message et le stacktrace
|
||||
String message = exception.getMessage() != null ? exception.getMessage() : exception.getClass().getSimpleName();
|
||||
String stacktrace = getStackTrace(exception);
|
||||
|
||||
// Persister dans system_logs
|
||||
// Persister dans system_logs (ne pas laisser ça crasher le mapper)
|
||||
try {
|
||||
systemLoggingService.logError(
|
||||
determineSource(exception),
|
||||
message,
|
||||
@@ -57,17 +64,12 @@ public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {
|
||||
"/" + endpoint,
|
||||
statusCode
|
||||
);
|
||||
|
||||
// Retourner une réponse HTTP appropriée
|
||||
return buildErrorResponse(exception, statusCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Ne jamais laisser l'exception mapper lui-même crasher
|
||||
log.error("Error in GlobalExceptionMapper", e);
|
||||
return Response.serverError()
|
||||
.entity(java.util.Map.of("error", "Internal server error"))
|
||||
.build();
|
||||
log.warn("Failed to log error to system_logs", e);
|
||||
}
|
||||
|
||||
// Retourner une réponse HTTP appropriée
|
||||
return buildErrorResponse(exception, statusCode);
|
||||
}
|
||||
|
||||
private int determineStatusCode(Throwable exception) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package dev.lions.unionflow.server.exception;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Exception Mapper pour les erreurs de traitement JSON (parsing, format, etc.).
|
||||
* Retourne un 400 Bad Request avec un message détaillé.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-19
|
||||
*/
|
||||
@Slf4j
|
||||
@Provider
|
||||
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(JsonProcessingException exception) {
|
||||
log.warn("JSON processing error: {}", exception.getMessage());
|
||||
|
||||
Map<String, Object> errorBody = new HashMap<>();
|
||||
errorBody.put("message", "Erreur de traitement JSON");
|
||||
errorBody.put("details", exception.getOriginalMessage() != null
|
||||
? exception.getOriginalMessage()
|
||||
: exception.getMessage());
|
||||
errorBody.put("status", 400);
|
||||
errorBody.put("timestamp", java.time.LocalDateTime.now().toString());
|
||||
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(errorBody)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -473,8 +473,7 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
||||
public boolean incrementerNombreRappels(UUID cotisationId) {
|
||||
Cotisation cotisation = findByIdOptional(cotisationId).orElse(null);
|
||||
if (cotisation != null) {
|
||||
cotisation.setNombreRappels(
|
||||
cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
|
||||
cotisation.setNombreRappels(cotisation.getNombreRappels() + 1);
|
||||
cotisation.setDateDernierRappel(LocalDateTime.now());
|
||||
update(cotisation);
|
||||
return true;
|
||||
@@ -552,7 +551,7 @@ public class CotisationRepository extends BaseRepository<Cotisation> {
|
||||
"cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L,
|
||||
"tauxPaiement",
|
||||
totalCotisations != null && totalCotisations > 0
|
||||
? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations
|
||||
? cotisationsPayees * 100.0 / totalCotisations
|
||||
: 0.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -247,13 +247,12 @@ public class DemandeAideRepository extends BaseRepository<DemandeAide> {
|
||||
query.setParameter("statut", StatutAide.APPROUVEE);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
BigDecimal result = query.getSingleResult();
|
||||
return result != null ? result : BigDecimal.ZERO;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
if (sort.getColumns().isEmpty()) {
|
||||
return "d.dateDemande DESC";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
@@ -213,8 +213,7 @@ public class EvenementRepository extends BaseRepository<Evenement> {
|
||||
TypedQuery<Long> query = entityManager.createQuery(
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId", Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Compte les événements actifs d'une organisation. */
|
||||
@@ -224,8 +223,7 @@ public class EvenementRepository extends BaseRepository<Evenement> {
|
||||
"SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND (e.actif = true OR e.actif IS NULL)",
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Événements à venir pour une organisation (pour dashboard par org). */
|
||||
@@ -472,13 +470,12 @@ public class EvenementRepository extends BaseRepository<Evenement> {
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("debut", debut);
|
||||
query.setParameter("fin", fin);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
if (sort.getColumns().isEmpty()) {
|
||||
return "e.dateDebut";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
@@ -112,8 +112,7 @@ public class MembreRepository extends BaseRepository<Membre> {
|
||||
"SELECT COUNT(DISTINCT m) FROM Membre m JOIN m.membresOrganisations mo WHERE mo.organisation.id IN :organisationIds",
|
||||
Long.class);
|
||||
query.setParameter("organisationIds", organisationIds);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Compte les membres actifs distincts appartenant à au moins une des organisations données (pour dashboard par org). */
|
||||
@@ -125,8 +124,7 @@ public class MembreRepository extends BaseRepository<Membre> {
|
||||
"SELECT COUNT(DISTINCT m) FROM Membre m JOIN m.membresOrganisations mo WHERE mo.organisation.id IN :organisationIds AND (m.actif = true OR m.actif IS NULL)",
|
||||
Long.class);
|
||||
query.setParameter("organisationIds", organisationIds);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,8 +167,7 @@ public class MembreRepository extends BaseRepository<Membre> {
|
||||
Long.class);
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("depuis", depuis);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Compte les adhésions à une organisation dans une période (pour graphiques dashboard par org). */
|
||||
@@ -182,8 +179,7 @@ public class MembreRepository extends BaseRepository<Membre> {
|
||||
query.setParameter("organisationId", organisationId);
|
||||
query.setParameter("start", start);
|
||||
query.setParameter("end", end);
|
||||
Long result = query.getSingleResult();
|
||||
return result != null ? result : 0L;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
/** Trouve les membres par statut avec pagination */
|
||||
@@ -266,7 +262,7 @@ public class MembreRepository extends BaseRepository<Membre> {
|
||||
|
||||
/** Construit la clause ORDER BY à partir d'un Sort */
|
||||
private String buildOrderBy(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
if (sort.getColumns().isEmpty()) {
|
||||
return "m.id";
|
||||
}
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
@@ -61,7 +61,7 @@ public class PaiementRepository implements PanacheRepositoryBase<Paiement, UUID>
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findByStatut(StatutPaiement statut) {
|
||||
return find("statutPaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), statut)
|
||||
return find("statutPaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), statut.name())
|
||||
.list();
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class PaiementRepository implements PanacheRepositoryBase<Paiement, UUID>
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public List<Paiement> findByMethode(MethodePaiement methode) {
|
||||
return find("methodePaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), methode)
|
||||
return find("methodePaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), methode.name())
|
||||
.list();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PaiementRepository implements PanacheRepositoryBase<Paiement, UUID>
|
||||
return find(
|
||||
"statutPaiement = ?1 AND dateValidation >= ?2 AND dateValidation <= ?3 AND actif = true",
|
||||
Sort.by("dateValidation", Sort.Direction.Descending),
|
||||
StatutPaiement.VALIDE,
|
||||
StatutPaiement.VALIDE.name(),
|
||||
dateDebut,
|
||||
dateFin)
|
||||
.list();
|
||||
|
||||
@@ -26,14 +26,13 @@ public class DemandeCreditRepository extends BaseRepository<DemandeCredit> {
|
||||
|
||||
// On somme l'échéantier non encore payé pour les crédits décaissés ou en contentieux
|
||||
TypedQuery<BigDecimal> query = entityManager.createQuery(
|
||||
"SELECT SUM(e.capitalAmorti) FROM EcheanceCredit e " +
|
||||
"SELECT COALESCE(SUM(e.capitalAmorti), 0) FROM EcheanceCredit e " +
|
||||
"WHERE e.demandeCredit.membre.id = :mid " +
|
||||
"AND e.demandeCredit.statut IN ('DECAISSEE', 'EN_CONTENTIEUX') " +
|
||||
"AND e.statut != 'PAYEE'", BigDecimal.class);
|
||||
|
||||
|
||||
query.setParameter("mid", membreId);
|
||||
BigDecimal res = query.getSingleResult();
|
||||
return res != null ? res : BigDecimal.ZERO;
|
||||
return query.getSingleResult();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,14 @@ public class AlerteLcbFtResource {
|
||||
|
||||
alerte.setTraitee(true);
|
||||
alerte.setDateTraitement(LocalDateTime.now());
|
||||
alerte.setTraitePar(body.get("traitePar"));
|
||||
String traiteParStr = body.get("traitePar");
|
||||
if (traiteParStr != null && !traiteParStr.isBlank()) {
|
||||
try {
|
||||
alerte.setTraitePar(UUID.fromString(traiteParStr));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("traitePar doit être un UUID valide");
|
||||
}
|
||||
}
|
||||
alerte.setCommentaireTraitement(body.get("commentaire"));
|
||||
|
||||
alerteLcbFtRepository.persist(alerte);
|
||||
@@ -154,7 +161,7 @@ public class AlerteLcbFtResource {
|
||||
.severite(alerte.getSeverite())
|
||||
.traitee(alerte.getTraitee())
|
||||
.dateTraitement(alerte.getDateTraitement())
|
||||
.traitePar(alerte.getTraitePar())
|
||||
.traitePar(alerte.getTraitePar() != null ? alerte.getTraitePar().toString() : null)
|
||||
.commentaireTraitement(alerte.getCommentaireTraitement())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -291,25 +291,10 @@ public class AnalyticsResource {
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/** Obtient les périodes d'analyse disponibles */
|
||||
@@ -321,25 +306,9 @@ public class AnalyticsResource {
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public class ApprovalResource {
|
||||
try {
|
||||
String transactionId = (String) request.get("transactionId");
|
||||
String transactionType = (String) request.get("transactionType");
|
||||
Double amount = ((Number) request.get("amount")).doubleValue();
|
||||
Number rawAmount = (Number) request.get("amount");
|
||||
Double amount = rawAmount != null ? rawAmount.doubleValue() : null;
|
||||
String organizationIdStr = (String) request.get("organizationId");
|
||||
|
||||
if (transactionId == null || transactionType == null || amount == null) {
|
||||
|
||||
@@ -101,13 +101,8 @@ public class EvenementResource {
|
||||
// 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
|
||||
}
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(evenement);
|
||||
evenementsDTOs.add(dto);
|
||||
}
|
||||
|
||||
LOG.infof("Nombre de DTOs créés: %d", evenementsDTOs.size());
|
||||
|
||||
@@ -40,27 +40,19 @@ public class FinanceWorkflowResource {
|
||||
@QueryParam("endDate") String endDate) {
|
||||
LOG.infof("GET /api/finance/stats?organizationId=%s", organizationId);
|
||||
|
||||
try {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalApprovals", 0);
|
||||
stats.put("pendingApprovals", 0);
|
||||
stats.put("approvedCount", 0);
|
||||
stats.put("rejectedCount", 0);
|
||||
stats.put("totalBudgets", 0);
|
||||
stats.put("activeBudgets", 0);
|
||||
stats.put("averageApprovalTime", "0 hours");
|
||||
stats.put("period", Map.of(
|
||||
"startDate", startDate != null ? startDate : LocalDateTime.now().minusMonths(1).toString(),
|
||||
"endDate", endDate != null ? endDate : LocalDateTime.now().toString()
|
||||
));
|
||||
|
||||
return Response.ok(stats).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(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalApprovals", 0);
|
||||
stats.put("pendingApprovals", 0);
|
||||
stats.put("approvedCount", 0);
|
||||
stats.put("rejectedCount", 0);
|
||||
stats.put("totalBudgets", 0);
|
||||
stats.put("activeBudgets", 0);
|
||||
stats.put("averageApprovalTime", "0 hours");
|
||||
stats.put("period", Map.of(
|
||||
"startDate", startDate != null ? startDate : LocalDateTime.now().minusMonths(1).toString(),
|
||||
"endDate", endDate != null ? endDate : LocalDateTime.now().toString()
|
||||
));
|
||||
return Response.ok(stats).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -78,15 +70,8 @@ public class FinanceWorkflowResource {
|
||||
@QueryParam("limit") @DefaultValue("100") int limit) {
|
||||
LOG.infof("GET /api/finance/audit-logs?organizationId=%s&limit=%d", organizationId, limit);
|
||||
|
||||
try {
|
||||
// Retourne une liste vide pour l'instant - à implémenter plus tard avec vraie persistence
|
||||
return Response.ok(new ArrayList<>()).build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération des logs d'audit", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
// Retourne une liste vide pour l'instant - à implémenter plus tard avec vraie persistence
|
||||
return Response.ok(new ArrayList<>()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -100,15 +85,8 @@ public class FinanceWorkflowResource {
|
||||
@QueryParam("endDate") String endDate) {
|
||||
LOG.infof("GET /api/finance/audit-logs/anomalies?organizationId=%s", organizationId);
|
||||
|
||||
try {
|
||||
// Retourne une liste vide pour l'instant - à implémenter plus tard
|
||||
return Response.ok(new ArrayList<>()).build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération des anomalies", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
// Retourne une liste vide pour l'instant - à implémenter plus tard
|
||||
return Response.ok(new ArrayList<>()).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -122,24 +100,15 @@ public class FinanceWorkflowResource {
|
||||
|
||||
LOG.infof("POST /api/finance/audit-logs/export - format: %s", format);
|
||||
|
||||
try {
|
||||
// Pour l'instant, retourne un URL fictif - à implémenter plus tard
|
||||
String exportUrl = "/api/finance/exports/" + UUID.randomUUID() + "." + format;
|
||||
// Pour l'instant, retourne un URL fictif - à implémenter plus tard
|
||||
String exportUrl = "/api/finance/exports/" + UUID.randomUUID() + "." + format;
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("exportUrl", exportUrl);
|
||||
response.put("format", format);
|
||||
response.put("status", "generated");
|
||||
response.put("expiresAt", LocalDateTime.now().plusHours(24).toString());
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("exportUrl", exportUrl);
|
||||
response.put("format", format);
|
||||
response.put("status", "generated");
|
||||
response.put("expiresAt", LocalDateTime.now().plusHours(24).toString());
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de l'export des logs d'audit", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
record ErrorResponse(String message) {}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,8 @@ public class MembreResource {
|
||||
: Sort.by(sortField).ascending();
|
||||
|
||||
// Filtrage par rôle : ADMIN_ORGANISATION ne voit que ses organisations
|
||||
java.util.Set<String> roles = securityIdentity.getRoles() != null
|
||||
? securityIdentity.getRoles()
|
||||
: java.util.Set.of();
|
||||
boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION")
|
||||
java.util.Set<String> roles = securityIdentity.getRoles();
|
||||
boolean onlyOrgAdmin = roles != null && roles.contains("ADMIN_ORGANISATION")
|
||||
&& !roles.contains("ADMIN")
|
||||
&& !roles.contains("SUPER_ADMIN");
|
||||
|
||||
@@ -96,9 +94,8 @@ public class MembreResource {
|
||||
long totalElements;
|
||||
|
||||
if (onlyOrgAdmin) {
|
||||
String email = securityIdentity.getPrincipal() != null
|
||||
? securityIdentity.getPrincipal().getName()
|
||||
: null;
|
||||
java.security.Principal p = securityIdentity.getPrincipal();
|
||||
String email = p != null ? p.getName() : null;
|
||||
|
||||
if (email == null || email.isEmpty()) {
|
||||
LOG.warn("ADMIN_ORGANISATION sans email identifié - retour liste vide");
|
||||
@@ -184,10 +181,8 @@ public class MembreResource {
|
||||
Membre nouveauMembre = membreService.creerMembre(membre);
|
||||
|
||||
// Validation périmètre ADMIN_ORGANISATION - lier le membre à l'organisation
|
||||
java.util.Set<String> roles = securityIdentity.getRoles() != null
|
||||
? securityIdentity.getRoles()
|
||||
: java.util.Set.of();
|
||||
boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION")
|
||||
java.util.Set<String> roles = securityIdentity.getRoles();
|
||||
boolean onlyOrgAdmin = roles != null && roles.contains("ADMIN_ORGANISATION")
|
||||
&& !roles.contains("ADMIN")
|
||||
&& !roles.contains("SUPER_ADMIN");
|
||||
|
||||
@@ -199,9 +194,7 @@ public class MembreResource {
|
||||
}
|
||||
|
||||
// Vérifier que l'utilisateur a accès à cette organisation
|
||||
String email = securityIdentity.getPrincipal() != null
|
||||
? securityIdentity.getPrincipal().getName()
|
||||
: null;
|
||||
String email = securityIdentity.getPrincipal().getName();
|
||||
|
||||
List<UUID> userOrgIds = organisationService.listerOrganisationsPourUtilisateur(email)
|
||||
.stream()
|
||||
@@ -545,7 +538,7 @@ public class MembreResource {
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
fileName = file.fileName() != null ? file.fileName() : "import.xlsx";
|
||||
fileName = file.fileName();
|
||||
}
|
||||
|
||||
if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) {
|
||||
@@ -557,10 +550,8 @@ public class MembreResource {
|
||||
: null;
|
||||
|
||||
// Validation périmètre ADMIN_ORGANISATION : doit avoir accès à l'organisation
|
||||
java.util.Set<String> roles = securityIdentity.getRoles() != null
|
||||
? securityIdentity.getRoles()
|
||||
: java.util.Set.of();
|
||||
boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION")
|
||||
java.util.Set<String> roles = securityIdentity.getRoles();
|
||||
boolean onlyOrgAdmin = roles != null && roles.contains("ADMIN_ORGANISATION")
|
||||
&& !roles.contains("ADMIN")
|
||||
&& !roles.contains("SUPER_ADMIN");
|
||||
|
||||
@@ -571,9 +562,8 @@ public class MembreResource {
|
||||
.build();
|
||||
}
|
||||
|
||||
String email = securityIdentity.getPrincipal() != null
|
||||
? securityIdentity.getPrincipal().getName()
|
||||
: null;
|
||||
java.security.Principal p2 = securityIdentity.getPrincipal();
|
||||
String email = p2 != null ? p2.getName() : null;
|
||||
|
||||
if (email == null || email.isEmpty()) {
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.request.CreateOrganisationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.request.UpdateOrganisationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import dev.lions.unionflow.server.service.OrganisationService;
|
||||
@@ -84,7 +86,7 @@ public class OrganisationResource {
|
||||
|
||||
/** Crée une nouvelle organisation */
|
||||
@POST
|
||||
@RolesAllowed({"ADMIN", "MEMBRE"})
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MEMBRE"})
|
||||
|
||||
@Operation(
|
||||
summary = "Créer une nouvelle organisation",
|
||||
@@ -113,7 +115,7 @@ public class OrganisationResource {
|
||||
return Response.created(URI.create("/api/organisations/" + organisationCreee.getId()))
|
||||
.entity(dto)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException | IllegalStateException 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()))
|
||||
@@ -139,11 +141,11 @@ public class OrganisationResource {
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationResponse.class))),
|
||||
schema = @Schema(implementation = PagedResponse.class))),
|
||||
@APIResponse(responseCode = "401", description = "Non authentifié"),
|
||||
@APIResponse(responseCode = "403", description = "Non autorisé")
|
||||
})
|
||||
public Response listerOrganisations(
|
||||
public PagedResponse<OrganisationSummaryResponse> listerOrganisations(
|
||||
@Parameter(description = "Numéro de page (commence à 0)", example = "0")
|
||||
@QueryParam("page")
|
||||
@DefaultValue("0")
|
||||
@@ -159,47 +161,44 @@ public class OrganisationResource {
|
||||
"Récupération des organisations - page: %d, size: %d, recherche: %s",
|
||||
page, size, recherche);
|
||||
|
||||
try {
|
||||
List<Organisation> organisations;
|
||||
List<Organisation> organisations;
|
||||
long totalElements;
|
||||
|
||||
// Admin d'organisation (sans rôle ADMIN/SUPER_ADMIN) : ne retourner que ses organisations
|
||||
java.util.Set<String> roles = securityIdentity.getRoles() != null ? securityIdentity.getRoles() : java.util.Set.of();
|
||||
boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION")
|
||||
&& !roles.contains("ADMIN")
|
||||
&& !roles.contains("SUPER_ADMIN");
|
||||
if (onlyOrgAdmin && securityIdentity.getPrincipal() != null) {
|
||||
String email = securityIdentity.getPrincipal().getName();
|
||||
organisations = organisationService.listerOrganisationsPourUtilisateur(email);
|
||||
if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
String term = recherche.trim().toLowerCase();
|
||||
organisations = organisations.stream()
|
||||
.filter(o -> (o.getNom() != null && o.getNom().toLowerCase().contains(term))
|
||||
|| (o.getNomCourt() != null && o.getNomCourt().toLowerCase().contains(term)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// Pagination en mémoire pour /mes
|
||||
int from = Math.min(page * size, organisations.size());
|
||||
int to = Math.min(from + size, organisations.size());
|
||||
organisations = organisations.subList(from, to);
|
||||
LOG.infof("ADMIN_ORGANISATION: retour de %d organisation(s) pour %s", organisations.size(), email);
|
||||
} else if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size);
|
||||
} else {
|
||||
organisations = organisationService.listerOrganisationsActives(page, size);
|
||||
// Admin d'organisation (sans rôle ADMIN/SUPER_ADMIN) : ne retourner que ses organisations
|
||||
java.util.Set<String> roles = securityIdentity.getRoles();
|
||||
boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION")
|
||||
&& !roles.contains("ADMIN")
|
||||
&& !roles.contains("SUPER_ADMIN");
|
||||
if (onlyOrgAdmin && securityIdentity.getPrincipal() != null) {
|
||||
String email = securityIdentity.getPrincipal().getName();
|
||||
organisations = organisationService.listerOrganisationsPourUtilisateur(email);
|
||||
if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
String term = recherche.trim().toLowerCase();
|
||||
organisations = organisations.stream()
|
||||
.filter(o -> (o.getNom() != null && o.getNom().toLowerCase().contains(term))
|
||||
|| (o.getNomCourt() != null && o.getNomCourt().toLowerCase().contains(term)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<OrganisationResponse> dtos =
|
||||
organisations.stream()
|
||||
.map(organisationService::convertToResponse)
|
||||
.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();
|
||||
totalElements = organisations.size();
|
||||
// Pagination en mémoire pour /mes
|
||||
int from = Math.min(page * size, organisations.size());
|
||||
int to = Math.min(from + size, organisations.size());
|
||||
organisations = organisations.subList(from, to);
|
||||
LOG.infof("ADMIN_ORGANISATION: retour de %d organisation(s) pour %s", organisations.size(), email);
|
||||
} else if (recherche != null && !recherche.trim().isEmpty()) {
|
||||
organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size);
|
||||
totalElements = organisationService.rechercherOrganisationsCount(recherche.trim());
|
||||
} else {
|
||||
organisations = organisationService.listerOrganisationsActives(page, size);
|
||||
totalElements = organisationService.compterOrganisationsActives();
|
||||
}
|
||||
|
||||
List<OrganisationSummaryResponse> dtos =
|
||||
organisations.stream()
|
||||
.map(organisationService::convertToSummaryResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new PagedResponse<>(dtos, totalElements, page, size);
|
||||
}
|
||||
|
||||
/** Récupère une organisation par son ID */
|
||||
@@ -235,7 +234,9 @@ public class OrganisationResource {
|
||||
})
|
||||
.orElse(
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Organisation non trouvée"))
|
||||
.entity(Map.of(
|
||||
"error", "Organisation non trouvée",
|
||||
"status", 404))
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -309,7 +310,7 @@ public class OrganisationResource {
|
||||
public Response supprimerOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Suppression de l'organisation ID: %d", id);
|
||||
LOG.infof("Suppression de l'organisation ID: %s", id);
|
||||
|
||||
try {
|
||||
organisationService.supprimerOrganisation(id, "system");
|
||||
@@ -398,7 +399,7 @@ public class OrganisationResource {
|
||||
public Response activerOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Activation de l'organisation ID: %d", id);
|
||||
LOG.infof("Activation de l'organisation ID: %s", id);
|
||||
|
||||
try {
|
||||
Organisation organisation = organisationService.activerOrganisation(id, "system");
|
||||
@@ -433,7 +434,7 @@ public class OrganisationResource {
|
||||
public Response suspendreOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) {
|
||||
|
||||
LOG.infof("Suspension de l'organisation ID: %d", id);
|
||||
LOG.infof("Suspension de l'organisation ID: %s", id);
|
||||
|
||||
try {
|
||||
Organisation organisation = organisationService.suspendreOrganisation(id, "system");
|
||||
|
||||
@@ -210,10 +210,7 @@ public class AdhesionService {
|
||||
"Seules les adhésions approuvées peuvent receive un paiement");
|
||||
}
|
||||
|
||||
BigDecimal nouveauMontant = adhesion.getMontantPaye() != null
|
||||
? adhesion.getMontantPaye().add(montantPaye)
|
||||
: montantPaye;
|
||||
adhesion.setMontantPaye(nouveauMontant);
|
||||
adhesion.setMontantPaye(adhesion.getMontantPaye().add(montantPaye));
|
||||
|
||||
if (adhesion.isPayeeIntegralement()) {
|
||||
adhesion.setStatut("APPROUVEE");
|
||||
@@ -283,9 +280,6 @@ public class AdhesionService {
|
||||
}
|
||||
|
||||
private AdhesionResponse convertToDTO(DemandeAdhesion adhesion) {
|
||||
if (adhesion == null)
|
||||
return null;
|
||||
|
||||
AdhesionResponse response = new AdhesionResponse();
|
||||
response.setId(adhesion.getId());
|
||||
response.setNumeroReference(adhesion.getNumeroReference());
|
||||
@@ -328,21 +322,12 @@ public class AdhesionService {
|
||||
}
|
||||
|
||||
private DemandeAdhesion convertToEntity(CreateAdhesionRequest request) {
|
||||
if (request == null)
|
||||
return null;
|
||||
|
||||
return DemandeAdhesion.builder()
|
||||
.numeroReference(
|
||||
request.numeroReference() != null
|
||||
? request.numeroReference()
|
||||
: DemandeAdhesion.genererNumeroReference())
|
||||
.dateDemande(
|
||||
request.dateDemande() != null
|
||||
? request.dateDemande().atStartOfDay()
|
||||
: LocalDateTime.now())
|
||||
.fraisAdhesion(request.fraisAdhesion() != null ? request.fraisAdhesion() : BigDecimal.ZERO)
|
||||
.numeroReference(request.numeroReference())
|
||||
.dateDemande(request.dateDemande().atStartOfDay())
|
||||
.fraisAdhesion(request.fraisAdhesion())
|
||||
.montantPaye(BigDecimal.ZERO)
|
||||
.codeDevise(request.codeDevise() != null ? request.codeDevise() : defaultsService.getDevise())
|
||||
.codeDevise(request.codeDevise())
|
||||
.statut("EN_ATTENTE")
|
||||
.observations(request.observations())
|
||||
.build();
|
||||
|
||||
@@ -101,7 +101,7 @@ public class AdminUserService {
|
||||
List<String> currentNames = getUserRoles(userId).stream()
|
||||
.map(RoleDTO::getName)
|
||||
.collect(Collectors.toList());
|
||||
List<String> toAssign = targetRoleNames == null ? List.of() : new ArrayList<>(targetRoleNames);
|
||||
List<String> toAssign = new ArrayList<>(targetRoleNames != null ? targetRoleNames : List.of());
|
||||
toAssign.removeAll(currentNames);
|
||||
List<String> toRevoke = new ArrayList<>(currentNames);
|
||||
toRevoke.removeAll(targetRoleNames == null ? List.of() : targetRoleNames);
|
||||
|
||||
@@ -83,10 +83,8 @@ public class AdresseService {
|
||||
.findAdresseById(id)
|
||||
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
|
||||
|
||||
// Mise à jour des champs
|
||||
updateFromDTO(adresse, request);
|
||||
|
||||
// Gestion de l'adresse principale
|
||||
// Gestion de l'adresse principale AVANT la mise à jour pour éviter l'auto-flush Hibernate
|
||||
// qui inclurait l'entité courante dans la requête JPQL si elle est déjà dirty
|
||||
if (Boolean.TRUE.equals(request.principale())) {
|
||||
desactiverAutresPrincipales(
|
||||
adresse.getOrganisation() != null ? adresse.getOrganisation().getId() : null,
|
||||
@@ -94,6 +92,9 @@ public class AdresseService {
|
||||
adresse.getEvenement() != null ? adresse.getEvenement().getId() : null);
|
||||
}
|
||||
|
||||
// Mise à jour des champs
|
||||
updateFromDTO(adresse, request);
|
||||
|
||||
adresseRepository.persist(adresse);
|
||||
LOG.infof("Adresse mise à jour avec succès: ID=%s", id);
|
||||
|
||||
@@ -281,7 +282,7 @@ public class AdresseService {
|
||||
adresse.setPays(request.pays());
|
||||
adresse.setLatitude(request.latitude());
|
||||
adresse.setLongitude(request.longitude());
|
||||
adresse.setPrincipale(request.principale() != null ? request.principale() : false);
|
||||
adresse.setPrincipale(Boolean.TRUE.equals(request.principale()));
|
||||
adresse.setLibelle(request.libelle());
|
||||
adresse.setNotes(request.notes());
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -73,7 +74,7 @@ public class AlerteLcbFtService {
|
||||
if (montant != null && seuil != null) {
|
||||
BigDecimal ecart = montant.subtract(seuil);
|
||||
BigDecimal ratio = seuil.compareTo(BigDecimal.ZERO) > 0 ?
|
||||
ecart.divide(seuil, 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO;
|
||||
ecart.divide(seuil, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO;
|
||||
|
||||
if (ratio.compareTo(new BigDecimal("2.0")) >= 0) { // > 200% du seuil
|
||||
severite = "CRITICAL";
|
||||
|
||||
@@ -151,10 +151,19 @@ public class AuditService {
|
||||
query.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
query.orderBy(cb.desc(root.get("dateHeure")));
|
||||
|
||||
// Compter le total
|
||||
// Compter le total (avec son propre root pour éviter le partage de prédicats)
|
||||
var countQuery = cb.createQuery(Long.class);
|
||||
countQuery.select(cb.count(countQuery.from(AuditLog.class)));
|
||||
countQuery.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
var countRoot = countQuery.from(AuditLog.class);
|
||||
countQuery.select(cb.count(countRoot));
|
||||
var countPredicates = new ArrayList<jakarta.persistence.criteria.Predicate>();
|
||||
if (dateDebut != null) countPredicates.add(cb.greaterThanOrEqualTo(countRoot.get("dateHeure"), dateDebut));
|
||||
if (dateFin != null) countPredicates.add(cb.lessThanOrEqualTo(countRoot.get("dateHeure"), dateFin));
|
||||
if (typeAction != null && !typeAction.isEmpty()) countPredicates.add(cb.equal(countRoot.get("typeAction"), typeAction));
|
||||
if (severite != null && !severite.isEmpty()) countPredicates.add(cb.equal(countRoot.get("severite"), severite));
|
||||
if (utilisateur != null && !utilisateur.isEmpty()) countPredicates.add(cb.like(cb.lower(countRoot.get("utilisateur")), "%" + utilisateur.toLowerCase() + "%"));
|
||||
if (module != null && !module.isEmpty()) countPredicates.add(cb.equal(countRoot.get("module"), module));
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) countPredicates.add(cb.like(countRoot.get("ipAddress"), "%" + ipAddress + "%"));
|
||||
countQuery.where(countPredicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||
long total = entityManager.createQuery(countQuery).getSingleResult();
|
||||
|
||||
// Récupérer les résultats avec pagination
|
||||
|
||||
@@ -424,7 +424,7 @@ public class CotisationService {
|
||||
response.setDatePaiementFormattee(cotisation.getDatePaiement() != null
|
||||
? cotisation.getDatePaiement().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm", Locale.FRANCE))
|
||||
: null);
|
||||
if (cotisation.isEnRetard() && cotisation.getDateEcheance() != null) {
|
||||
if (cotisation.isEnRetard()) {
|
||||
long jours = java.time.temporal.ChronoUnit.DAYS.between(cotisation.getDateEcheance(), LocalDate.now());
|
||||
response.setRetardCouleur("text-red-500");
|
||||
response.setRetardTexte(jours + " jour" + (jours > 1 ? "s" : "") + " de retard");
|
||||
|
||||
@@ -82,12 +82,12 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
|
||||
// Gérer le cas où organizationId est vide ou invalide
|
||||
UUID orgId = parseOrganizationId(organizationId);
|
||||
java.util.Set<UUID> orgIds = orgId != null ? java.util.Set.of(orgId) : null;
|
||||
java.util.Set<UUID> orgIds = orgId != null ? java.util.Set.of(orgId) : java.util.Set.of();
|
||||
|
||||
// Compter les membres (par organisation si orgId fourni, sinon global)
|
||||
long totalMembers;
|
||||
long activeMembers;
|
||||
if (orgIds != null && !orgIds.isEmpty()) {
|
||||
if (!orgIds.isEmpty()) {
|
||||
totalMembers = membreRepository.countDistinctByOrganisationIdIn(orgIds);
|
||||
activeMembers = membreRepository.countActifsDistinctByOrganisationIdIn(orgIds);
|
||||
} else {
|
||||
@@ -215,7 +215,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
.type("event")
|
||||
.title("Événement créé")
|
||||
.description(evenement.getTitre() + " a été programmé")
|
||||
.userName(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : "Système")
|
||||
.userName(evenement.getOrganisation().getNom())
|
||||
.timestamp(evenement.getDateCreation())
|
||||
.userAvatar(null)
|
||||
.actionUrl("/events/" + evenement.getId())
|
||||
|
||||
@@ -87,8 +87,7 @@ public class DocumentService {
|
||||
.findDocumentById(id)
|
||||
.orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id));
|
||||
|
||||
document.setNombreTelechargements(
|
||||
(document.getNombreTelechargements() != null ? document.getNombreTelechargements() : 0) + 1);
|
||||
document.setNombreTelechargements(document.getNombreTelechargements() + 1);
|
||||
document.setDateDernierTelechargement(LocalDateTime.now());
|
||||
document.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class ExportService {
|
||||
nomMembre,
|
||||
c.getTypeCotisation() != null ? c.getTypeCotisation() : "",
|
||||
c.getMontantDu() != null ? c.getMontantDu().toString() : "0",
|
||||
c.getMontantPaye() != null ? c.getMontantPaye().toString() : "0",
|
||||
c.getMontantPaye().toString(),
|
||||
c.getStatut() != null ? c.getStatut() : "",
|
||||
c.getDateEcheance() != null ? c.getDateEcheance().format(DATE_FORMATTER) : "",
|
||||
c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "",
|
||||
|
||||
@@ -55,7 +55,7 @@ public class FileStorageService {
|
||||
* @throws IllegalArgumentException Si validation échoue
|
||||
*/
|
||||
public FileMetadata storeFile(InputStream inputStream, String fileName, String mimeType, long fileSize)
|
||||
throws IOException {
|
||||
throws IOException, java.security.NoSuchAlgorithmException {
|
||||
|
||||
// Validation de la taille
|
||||
if (fileSize > MAX_FILE_SIZE) {
|
||||
@@ -82,14 +82,10 @@ public class FileStorageService {
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
|
||||
// Écrire le fichier et calculer les hash simultanément
|
||||
MessageDigest md5 = null;
|
||||
MessageDigest sha256 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (Exception e) {
|
||||
log.warn("Impossible de créer les MessageDigest pour hash: {}", e.getMessage());
|
||||
}
|
||||
// MD5 et SHA-256 sont des algorithmes obligatoires depuis Java 1.4 (JCA standard)
|
||||
// et ne peuvent jamais lever NoSuchAlgorithmException dans un JRE conforme.
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
@@ -99,20 +95,14 @@ public class FileStorageService {
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, bytesRead);
|
||||
totalBytes += bytesRead;
|
||||
|
||||
// Calculer les hash
|
||||
if (md5 != null) {
|
||||
md5.update(buffer, 0, bytesRead);
|
||||
}
|
||||
if (sha256 != null) {
|
||||
sha256.update(buffer, 0, bytesRead);
|
||||
}
|
||||
md5.update(buffer, 0, bytesRead);
|
||||
sha256.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
// Générer les hash
|
||||
String hashMd5 = md5 != null ? bytesToHex(md5.digest()) : null;
|
||||
String hashSha256 = sha256 != null ? bytesToHex(sha256.digest()) : null;
|
||||
String hashMd5 = bytesToHex(md5.digest());
|
||||
String hashSha256 = bytesToHex(sha256.digest());
|
||||
|
||||
log.info("Fichier stocké : {} ({} octets) - MD5: {}", uniqueFileName, totalBytes, hashMd5);
|
||||
|
||||
@@ -183,6 +173,8 @@ public class FileStorageService {
|
||||
*/
|
||||
@lombok.Data
|
||||
@lombok.Builder
|
||||
@lombok.NoArgsConstructor
|
||||
@lombok.AllArgsConstructor
|
||||
public static class FileMetadata {
|
||||
private String nomFichier;
|
||||
private String nomOriginal;
|
||||
|
||||
@@ -283,8 +283,7 @@ public class KPICalculatorService {
|
||||
|
||||
private BigDecimal calculerKPIMontantAides(
|
||||
UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
BigDecimal total = demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin);
|
||||
return total != null ? total : BigDecimal.ZERO;
|
||||
return demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin);
|
||||
}
|
||||
|
||||
private BigDecimal calculerKPITauxApprobationAides(
|
||||
|
||||
@@ -194,7 +194,8 @@ public class KeycloakService {
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.getClaimNames();
|
||||
Set<String> names = jwt.getClaimNames();
|
||||
return names != null ? names : Set.of();
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la récupération des claims: %s", e.getMessage());
|
||||
return Set.of();
|
||||
|
||||
@@ -316,7 +316,7 @@ public class MatchingService {
|
||||
// 5. Disponibilité et capacité (10 points max)
|
||||
if (proposition.peutAccepterBeneficiaires()) {
|
||||
double ratioCapacite = (double) proposition.getPlacesRestantes()
|
||||
/ (proposition.getNombreMaxBeneficiaires() != null ? proposition.getNombreMaxBeneficiaires() : 1);
|
||||
/ proposition.getNombreMaxBeneficiaires();
|
||||
score += 10.0 * ratioCapacite;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ public class MembreDashboardService {
|
||||
.filter(d -> d.getStatut() != null && d.getStatut() != StatutAide.APPROUVEE && d.getStatut() != StatutAide.REJETEE && d.getStatut() != StatutAide.ANNULEE)
|
||||
.count() : 0;
|
||||
Integer tauxAidesApprouvees = null;
|
||||
if (mesDemandes > 0 && demandes != null) {
|
||||
if (mesDemandes > 0) {
|
||||
long acceptees = demandes.stream().filter(d -> d.getStatut() == StatutAide.APPROUVEE).count();
|
||||
tauxAidesApprouvees = (int) (acceptees * 100 / mesDemandes);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.*;
|
||||
|
||||
@@ -99,7 +100,7 @@ public class MembreImportExportService {
|
||||
"Format de fichier non supporté. Formats acceptés: .xlsx, .xls, .csv");
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'import");
|
||||
resultat.erreurs.add(e.getMessage() != null ? e.getMessage() : "Erreur inconnue lors de l'import");
|
||||
resultat.erreurs.add(Objects.toString(e.getMessage(), "Erreur inconnue lors de l'import"));
|
||||
return resultat;
|
||||
}
|
||||
}
|
||||
@@ -182,9 +183,9 @@ public class MembreImportExportService {
|
||||
} else {
|
||||
if (souscriptionOpt.isPresent()) {
|
||||
SouscriptionOrganisation souscription = souscriptionOpt.get();
|
||||
if (souscription.isQuotaDepasse() || souscription.getPlacesRestantes() <= 0) {
|
||||
String msg = String.format("Ligne %d: Quota souscription atteint (max %d membres).",
|
||||
ligneNum, souscription.getQuotaMax() != null ? souscription.getQuotaMax() : "?");
|
||||
if (souscription.isQuotaDepasse()) {
|
||||
String msg = String.format("Ligne %d: Quota souscription atteint (max %s membres).",
|
||||
ligneNum, souscription.getQuotaMax());
|
||||
resultat.erreurs.add(msg);
|
||||
if (!ignorerErreurs) throw new IllegalArgumentException(msg);
|
||||
continue;
|
||||
@@ -273,9 +274,9 @@ public class MembreImportExportService {
|
||||
} else {
|
||||
if (souscriptionOpt.isPresent()) {
|
||||
SouscriptionOrganisation souscription = souscriptionOpt.get();
|
||||
if (souscription.isQuotaDepasse() || souscription.getPlacesRestantes() <= 0) {
|
||||
String msg = String.format("Ligne %d: Quota souscription atteint (max %d membres).",
|
||||
ligneNum, souscription.getQuotaMax() != null ? souscription.getQuotaMax() : "?");
|
||||
if (souscription.isQuotaDepasse()) {
|
||||
String msg = String.format("Ligne %d: Quota souscription atteint (max %s membres).",
|
||||
ligneNum, souscription.getQuotaMax());
|
||||
resultat.erreurs.add(msg);
|
||||
if (!ignorerErreurs) throw new IllegalArgumentException(msg);
|
||||
continue;
|
||||
@@ -791,13 +792,9 @@ public class MembreImportExportService {
|
||||
protection.setLockStructure(true);
|
||||
// Le mot de passe doit être haché selon le format Excel
|
||||
// Pour simplifier, on utilise le hash MD5 du mot de passe
|
||||
try {
|
||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
||||
byte[] passwordHash = md.digest(motDePasse.getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
|
||||
protection.setWorkbookPassword(passwordHash);
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
LOG.warnf("Impossible de hasher le mot de passe, protection partielle uniquement");
|
||||
}
|
||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
||||
byte[] passwordHash = md.digest(motDePasse.getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
|
||||
protection.setWorkbookPassword(passwordHash);
|
||||
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
@@ -865,7 +862,7 @@ public class MembreImportExportService {
|
||||
values.add(membre.getStatutCompte() != null ? membre.getStatutCompte() : "");
|
||||
}
|
||||
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
|
||||
values.add(membre.getAssociationNom() != null ? membre.getAssociationNom() : "");
|
||||
values.add(Objects.toString(membre.getAssociationNom(), ""));
|
||||
}
|
||||
|
||||
printer.printRecord(values);
|
||||
@@ -981,9 +978,6 @@ public class MembreImportExportService {
|
||||
|
||||
document.close();
|
||||
return outputStream.toByteArray();
|
||||
} catch (DocumentException e) {
|
||||
LOG.errorf(e, "Erreur lors de la génération du PDF");
|
||||
throw new IOException("Erreur lors de la génération du PDF", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ public class MembreService {
|
||||
* Sinon retourne Optional.empty() pour indiquer "tous les membres".
|
||||
*/
|
||||
private Optional<Set<UUID>> getOrganisationIdsForCurrentUserIfAdminOrg() {
|
||||
if (securityIdentity == null || securityIdentity.getPrincipal() == null) return Optional.empty();
|
||||
if (securityIdentity.getPrincipal() == null) return Optional.empty();
|
||||
Set<String> roles = securityIdentity.getRoles();
|
||||
if (roles == null) return Optional.empty();
|
||||
boolean adminOrg = roles.contains("ADMIN_ORGANISATION");
|
||||
@@ -490,71 +490,65 @@ public class MembreService {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Construction de la requête dynamique
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT m FROM Membre m WHERE 1=1");
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
// Construction de la requête dynamique
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT m FROM Membre m WHERE 1=1");
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
// Ajout des critères de recherche
|
||||
addSearchCriteria(queryBuilder, parameters, criteria);
|
||||
// Ajout des critères de recherche
|
||||
addSearchCriteria(queryBuilder, parameters, criteria);
|
||||
|
||||
// Requête pour compter le total
|
||||
String countQuery = queryBuilder
|
||||
.toString()
|
||||
.replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m");
|
||||
// Requête pour compter le total
|
||||
String countQuery = queryBuilder
|
||||
.toString()
|
||||
.replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m");
|
||||
|
||||
// Exécution de la requête de comptage
|
||||
TypedQuery<Long> countQueryTyped = entityManager.createQuery(countQuery, Long.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
countQueryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
long totalElements = countQueryTyped.getSingleResult();
|
||||
|
||||
if (totalElements == 0) {
|
||||
return MembreSearchResultDTO.empty(criteria, page.size, page.index);
|
||||
}
|
||||
|
||||
// Ajout du tri et pagination
|
||||
String finalQuery = queryBuilder.toString();
|
||||
if (sort != null) {
|
||||
finalQuery += " ORDER BY " + buildOrderByClause(sort);
|
||||
}
|
||||
|
||||
// Exécution de la requête principale
|
||||
TypedQuery<Membre> queryTyped = entityManager.createQuery(finalQuery, Membre.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
queryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
queryTyped.setFirstResult(page.index * page.size);
|
||||
queryTyped.setMaxResults(page.size);
|
||||
List<Membre> membres = queryTyped.getResultList();
|
||||
|
||||
// Conversion en SummaryResponses
|
||||
List<MembreSummaryResponse> membresDTO = convertToSummaryResponseList(membres);
|
||||
|
||||
// Calcul des statistiques
|
||||
MembreSearchResultDTO.SearchStatistics statistics = calculateSearchStatistics(membres);
|
||||
|
||||
// Construction du résultat
|
||||
MembreSearchResultDTO result = MembreSearchResultDTO.builder()
|
||||
.membres(membresDTO)
|
||||
.totalElements(totalElements)
|
||||
.totalPages((int) Math.ceil((double) totalElements / page.size))
|
||||
.currentPage(page.index)
|
||||
.pageSize(page.size)
|
||||
.criteria(criteria)
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
|
||||
// Calcul des indicateurs de pagination
|
||||
result.calculatePaginationFlags();
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche avancée de membres");
|
||||
throw new RuntimeException("Erreur lors de la recherche avancée", e);
|
||||
// Exécution de la requête de comptage
|
||||
TypedQuery<Long> countQueryTyped = entityManager.createQuery(countQuery, Long.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
countQueryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
long totalElements = countQueryTyped.getSingleResult();
|
||||
|
||||
if (totalElements == 0) {
|
||||
return MembreSearchResultDTO.empty(criteria, page.size, page.index);
|
||||
}
|
||||
|
||||
// Ajout du tri et pagination
|
||||
String finalQuery = queryBuilder.toString();
|
||||
if (sort != null) {
|
||||
finalQuery += " ORDER BY " + buildOrderByClause(sort);
|
||||
}
|
||||
|
||||
// Exécution de la requête principale
|
||||
TypedQuery<Membre> queryTyped = entityManager.createQuery(finalQuery, Membre.class);
|
||||
for (Map.Entry<String, Object> param : parameters.entrySet()) {
|
||||
queryTyped.setParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
queryTyped.setFirstResult(page.index * page.size);
|
||||
queryTyped.setMaxResults(page.size);
|
||||
List<Membre> membres = queryTyped.getResultList();
|
||||
|
||||
// Conversion en SummaryResponses
|
||||
List<MembreSummaryResponse> membresDTO = convertToSummaryResponseList(membres);
|
||||
|
||||
// Calcul des statistiques
|
||||
MembreSearchResultDTO.SearchStatistics statistics = calculateSearchStatistics(membres);
|
||||
|
||||
// Construction du résultat
|
||||
MembreSearchResultDTO result = MembreSearchResultDTO.builder()
|
||||
.membres(membresDTO)
|
||||
.totalElements(totalElements)
|
||||
.totalPages((int) Math.ceil((double) totalElements / page.size))
|
||||
.currentPage(page.index)
|
||||
.pageSize(page.size)
|
||||
.criteria(criteria)
|
||||
.statistics(statistics)
|
||||
.build();
|
||||
|
||||
// Calcul des indicateurs de pagination
|
||||
result.calculatePaginationFlags();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Ajoute les critères de recherche à la requête */
|
||||
@@ -649,7 +643,7 @@ public class MembreService {
|
||||
|
||||
/** Construit la clause ORDER BY à partir du Sort */
|
||||
private String buildOrderByClause(Sort sort) {
|
||||
if (sort == null || sort.getColumns().isEmpty()) {
|
||||
if (sort.getColumns().isEmpty()) {
|
||||
return "m.nom ASC";
|
||||
}
|
||||
|
||||
@@ -776,12 +770,12 @@ public class MembreService {
|
||||
* @return Données binaires du fichier Excel
|
||||
*/
|
||||
public byte[] exporterMembresSelectionnes(List<UUID> membreIds, String format) {
|
||||
LOG.infof("Export de %d membres sélectionnés - format: %s", membreIds.size(), format);
|
||||
|
||||
if (membreIds == null || membreIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("La liste des membres ne peut pas être vide");
|
||||
}
|
||||
|
||||
LOG.infof("Export de %d membres sélectionnés - format: %s", membreIds.size(), format);
|
||||
|
||||
// Récupérer les membres
|
||||
List<Membre> membres = membreIds.stream()
|
||||
.map(id -> membreRepository.findByIdOptional(id))
|
||||
|
||||
@@ -189,13 +189,13 @@ public class NotificationService {
|
||||
@Transactional
|
||||
public int envoyerNotificationsGroupees(
|
||||
List<UUID> membreIds, String sujet, String corps, List<String> canaux) {
|
||||
LOG.infof(
|
||||
"Envoi de notifications groupées à %d membres - sujet: %s", membreIds.size(), sujet);
|
||||
|
||||
if (membreIds == null || membreIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("La liste des membres ne peut pas être vide");
|
||||
}
|
||||
|
||||
LOG.infof(
|
||||
"Envoi de notifications groupées à %d membres - sujet: %s", membreIds.size(), sujet);
|
||||
|
||||
int notificationsCreees = 0;
|
||||
for (UUID membreId : membreIds) {
|
||||
try {
|
||||
|
||||
@@ -356,6 +356,15 @@ public class OrganisationService {
|
||||
return organisationRepository.findAllActives(Page.of(page, size), Sort.by("nom").ascending());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'organisations actives
|
||||
*
|
||||
* @return nombre total d'organisations actives
|
||||
*/
|
||||
public long compterOrganisationsActives() {
|
||||
return organisationRepository.countActives();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche d'organisations par nom
|
||||
*
|
||||
@@ -559,14 +568,11 @@ public class OrganisationService {
|
||||
dto.setCreePar(organisation.getCreePar());
|
||||
dto.setModifiePar(organisation.getModifiePar());
|
||||
dto.setActif(organisation.getActif());
|
||||
dto.setVersion(organisation.getVersion() != null ? organisation.getVersion() : 0L);
|
||||
dto.setVersion(organisation.getVersion());
|
||||
|
||||
dto.setOrganisationPublique(
|
||||
organisation.getOrganisationPublique() != null ? organisation.getOrganisationPublique() : true);
|
||||
dto.setAccepteNouveauxMembres(
|
||||
organisation.getAccepteNouveauxMembres() != null ? organisation.getAccepteNouveauxMembres() : true);
|
||||
dto.setCotisationObligatoire(
|
||||
organisation.getCotisationObligatoire() != null ? organisation.getCotisationObligatoire() : false);
|
||||
dto.setOrganisationPublique(organisation.getOrganisationPublique());
|
||||
dto.setAccepteNouveauxMembres(organisation.getAccepteNouveauxMembres());
|
||||
dto.setCotisationObligatoire(organisation.getCotisationObligatoire());
|
||||
dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle());
|
||||
|
||||
return dto;
|
||||
|
||||
@@ -545,10 +545,6 @@ public class PaiementService {
|
||||
|
||||
/** Convertit une entité en Response DTO */
|
||||
private PaiementResponse convertToResponse(Paiement paiement) {
|
||||
if (paiement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PaiementResponse response = new PaiementResponse();
|
||||
response.setId(paiement.getId());
|
||||
response.setNumeroReference(paiement.getNumeroReference());
|
||||
|
||||
@@ -108,9 +108,9 @@ public class PropositionAideService {
|
||||
if (response == null) {
|
||||
// Si non trouvé dans le cache, essayer de simuler depuis BDD
|
||||
response = simulerRecuperationBDD(id);
|
||||
if (response == null) {
|
||||
throw new IllegalArgumentException("Proposition non trouvée: " + id);
|
||||
}
|
||||
}
|
||||
if (response == null) {
|
||||
throw new IllegalArgumentException("Proposition non trouvée: " + id);
|
||||
}
|
||||
|
||||
if (request.titre() != null)
|
||||
@@ -162,14 +162,8 @@ public class PropositionAideService {
|
||||
}
|
||||
|
||||
// Simulation de récupération depuis la base de données
|
||||
response = simulerRecuperationBDD(id);
|
||||
|
||||
if (response != null) {
|
||||
ajouterAuCache(response);
|
||||
ajouterAIndex(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
// simulerRecuperationBDD retourne toujours null (stub — à remplacer par un vrai repository)
|
||||
return simulerRecuperationBDD(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +247,6 @@ public class PropositionAideService {
|
||||
p.getDonneesPersonnalisees().put("scoreCompatibilite", score);
|
||||
return p;
|
||||
})
|
||||
.filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreCompatibilite") >= 30.0)
|
||||
.sorted(
|
||||
(p1, p2) -> {
|
||||
Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreCompatibilite");
|
||||
@@ -430,7 +423,7 @@ public class PropositionAideService {
|
||||
return false;
|
||||
}
|
||||
case "organisationId" -> {
|
||||
if (!proposition.getOrganisationId().equals(valeur))
|
||||
if (!java.util.Objects.equals(proposition.getOrganisationId(), valeur))
|
||||
return false;
|
||||
}
|
||||
case "estDisponible" -> {
|
||||
|
||||
@@ -245,7 +245,6 @@ public class SystemMetricsService {
|
||||
File root = new File("/");
|
||||
long total = root.getTotalSpace();
|
||||
long free = root.getFreeSpace();
|
||||
if (total == 0) return 0.0;
|
||||
return ((total - free) * 100.0) / total;
|
||||
}
|
||||
|
||||
@@ -304,7 +303,15 @@ public class SystemMetricsService {
|
||||
*/
|
||||
private Integer getDbConnectionPoolSize() {
|
||||
if (dataSource instanceof AgroalDataSource agroalDataSource) {
|
||||
return agroalDataSource.getConfiguration().connectionPoolConfiguration().maxSize();
|
||||
try {
|
||||
var config = agroalDataSource.getConfiguration();
|
||||
if (config == null) return 0;
|
||||
var poolConfig = config.connectionPoolConfiguration();
|
||||
if (poolConfig == null) return 0;
|
||||
return poolConfig.maxSize();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -314,7 +321,13 @@ public class SystemMetricsService {
|
||||
*/
|
||||
private Integer getDbActiveConnections() {
|
||||
if (dataSource instanceof AgroalDataSource agroalDataSource) {
|
||||
return (int) agroalDataSource.getMetrics().activeCount();
|
||||
try {
|
||||
var metrics = agroalDataSource.getMetrics();
|
||||
if (metrics == null) return 0;
|
||||
return (int) metrics.activeCount();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -324,7 +337,13 @@ public class SystemMetricsService {
|
||||
*/
|
||||
private Integer getDbIdleConnections() {
|
||||
if (dataSource instanceof AgroalDataSource agroalDataSource) {
|
||||
return (int) agroalDataSource.getMetrics().availableCount();
|
||||
try {
|
||||
var metrics = agroalDataSource.getMetrics();
|
||||
if (metrics == null) return 0;
|
||||
return (int) metrics.availableCount();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -208,10 +208,7 @@ public class TrendAnalysisService {
|
||||
BigDecimal numerateur = nBD.multiply(sommeXY).subtract(sommeX.multiply(sommeY));
|
||||
BigDecimal denominateur = nBD.multiply(sommeX2).subtract(sommeX.multiply(sommeX));
|
||||
|
||||
BigDecimal pente =
|
||||
denominateur.compareTo(BigDecimal.ZERO) != 0
|
||||
? numerateur.divide(denominateur, 6, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
BigDecimal pente = numerateur.divide(denominateur, 6, RoundingMode.HALF_UP);
|
||||
|
||||
// Calcul du coefficient de corrélation R²
|
||||
BigDecimal numerateurR = numerateur;
|
||||
@@ -219,15 +216,11 @@ public class TrendAnalysisService {
|
||||
BigDecimal denominateurR2 = nBD.multiply(sommeY2).subtract(sommeY.multiply(sommeY));
|
||||
|
||||
BigDecimal coefficientCorrelation = BigDecimal.ZERO;
|
||||
if (denominateurR1.compareTo(BigDecimal.ZERO) != 0
|
||||
&& denominateurR2.compareTo(BigDecimal.ZERO) != 0) {
|
||||
BigDecimal denominateurR =
|
||||
new BigDecimal(Math.sqrt(denominateurR1.multiply(denominateurR2).doubleValue()));
|
||||
|
||||
if (denominateurR.compareTo(BigDecimal.ZERO) != 0) {
|
||||
BigDecimal r = numerateurR.divide(denominateurR, 6, RoundingMode.HALF_UP);
|
||||
coefficientCorrelation = r.multiply(r); // R²
|
||||
}
|
||||
BigDecimal produit = denominateurR1.multiply(denominateurR2);
|
||||
if (produit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal denominateurR = new BigDecimal(Math.sqrt(produit.doubleValue()));
|
||||
BigDecimal r = numerateurR.divide(denominateurR, 6, RoundingMode.HALF_UP);
|
||||
coefficientCorrelation = r.multiply(r); // R²
|
||||
}
|
||||
|
||||
return new TendanceDTO(pente, coefficientCorrelation);
|
||||
|
||||
@@ -383,7 +383,7 @@ public class DemandeCreditService {
|
||||
.capitalAmorti(principal)
|
||||
.interetsDeLaPeriode(interets)
|
||||
.montantTotalExigible(mensualite)
|
||||
.capitalRestantDu(capitalRestant.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : capitalRestant)
|
||||
.capitalRestantDu(capitalRestant.max(BigDecimal.ZERO))
|
||||
.statut(StatutEcheanceCredit.A_VENIR)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -70,11 +70,8 @@ public class CompteEpargneService {
|
||||
compte.setMembre(membre);
|
||||
compte.setOrganisation(organisation);
|
||||
|
||||
// Par défaut, le compte est actif et ouvert aujourd'hui
|
||||
// Par défaut, le compte est actif (dateOuverture déjà initialisée dans l'entité)
|
||||
compte.setStatut(StatutCompteEpargne.ACTIF);
|
||||
if (compte.getDateOuverture() == null) {
|
||||
compte.setDateOuverture(LocalDate.now());
|
||||
}
|
||||
|
||||
// Générer un numéro de compte s'il n'est pas fourni (il n'est pas dans le DTO
|
||||
// de requête actuel)
|
||||
@@ -109,7 +106,8 @@ public class CompteEpargneService {
|
||||
* @return La liste des comptes visibles pour l'utilisateur connecté.
|
||||
*/
|
||||
public List<CompteEpargneResponse> getMesComptes() {
|
||||
String email = securityIdentity.getPrincipal() != null ? securityIdentity.getPrincipal().getName() : null;
|
||||
java.security.Principal principal = securityIdentity.getPrincipal();
|
||||
String email = principal != null ? principal.getName() : null;
|
||||
if (email == null || email.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class TransactionEpargneService {
|
||||
alerteLcbFtService.genererAlerteSeuilDepasse(
|
||||
orgId,
|
||||
membreId,
|
||||
request.getTypeTransaction() != null ? request.getTypeTransaction().name() : null,
|
||||
request.getTypeTransaction().name(),
|
||||
request.getMontant(),
|
||||
seuil,
|
||||
transaction.getId() != null ? transaction.getId().toString() : null,
|
||||
|
||||
Reference in New Issue
Block a user