Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
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 propositions d'aide
|
||||
*
|
||||
* <p>Ce service gère le cycle de vie des propositions d'aide : création, activation, matching,
|
||||
* suivi des performances.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PropositionAideService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PropositionAideService.class);
|
||||
|
||||
// Cache pour les propositions actives
|
||||
private final Map<String, PropositionAideDTO> cachePropositionsActives = new HashMap<>();
|
||||
private final Map<TypeAide, List<PropositionAideDTO>> indexParType = new HashMap<>();
|
||||
|
||||
// === OPÉRATIONS CRUD ===
|
||||
|
||||
/**
|
||||
* Crée une nouvelle proposition d'aide
|
||||
*
|
||||
* @param propositionDTO La proposition à créer
|
||||
* @return La proposition créée avec ID généré
|
||||
*/
|
||||
@Transactional
|
||||
public PropositionAideDTO creerProposition(@Valid PropositionAideDTO propositionDTO) {
|
||||
LOG.infof("Création d'une nouvelle proposition d'aide: %s", propositionDTO.getTitre());
|
||||
|
||||
// Génération des identifiants
|
||||
propositionDTO.setId(UUID.randomUUID().toString());
|
||||
propositionDTO.setNumeroReference(genererNumeroReference());
|
||||
|
||||
// Initialisation des dates
|
||||
LocalDateTime maintenant = LocalDateTime.now();
|
||||
propositionDTO.setDateCreation(maintenant);
|
||||
propositionDTO.setDateModification(maintenant);
|
||||
|
||||
// Statut initial
|
||||
if (propositionDTO.getStatut() == null) {
|
||||
propositionDTO.setStatut(PropositionAideDTO.StatutProposition.ACTIVE);
|
||||
}
|
||||
|
||||
// Calcul de la date d'expiration si non définie
|
||||
if (propositionDTO.getDateExpiration() == null) {
|
||||
propositionDTO.setDateExpiration(maintenant.plusMonths(6)); // 6 mois par défaut
|
||||
}
|
||||
|
||||
// Initialisation des compteurs
|
||||
propositionDTO.setNombreDemandesTraitees(0);
|
||||
propositionDTO.setNombreBeneficiairesAides(0);
|
||||
propositionDTO.setMontantTotalVerse(0.0);
|
||||
propositionDTO.setNombreVues(0);
|
||||
propositionDTO.setNombreCandidatures(0);
|
||||
propositionDTO.setNombreEvaluations(0);
|
||||
|
||||
// Calcul du score de pertinence initial
|
||||
propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO));
|
||||
|
||||
// Ajout au cache et index
|
||||
ajouterAuCache(propositionDTO);
|
||||
ajouterAIndex(propositionDTO);
|
||||
|
||||
LOG.infof("Proposition d'aide créée avec succès: %s", propositionDTO.getId());
|
||||
return propositionDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une proposition d'aide existante
|
||||
*
|
||||
* @param propositionDTO La proposition à mettre à jour
|
||||
* @return La proposition mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public PropositionAideDTO mettreAJour(@Valid PropositionAideDTO propositionDTO) {
|
||||
LOG.infof("Mise à jour de la proposition d'aide: %s", propositionDTO.getId());
|
||||
|
||||
// Mise à jour de la date de modification
|
||||
propositionDTO.setDateModification(LocalDateTime.now());
|
||||
|
||||
// Recalcul du score de pertinence
|
||||
propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO));
|
||||
|
||||
// Mise à jour du cache et index
|
||||
ajouterAuCache(propositionDTO);
|
||||
mettreAJourIndex(propositionDTO);
|
||||
|
||||
LOG.infof("Proposition d'aide mise à jour avec succès: %s", propositionDTO.getId());
|
||||
return propositionDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient une proposition d'aide par son ID
|
||||
*
|
||||
* @param id ID de la proposition
|
||||
* @return La proposition trouvée
|
||||
*/
|
||||
public PropositionAideDTO obtenirParId(@NotBlank String id) {
|
||||
LOG.debugf("Récupération de la proposition d'aide: %s", id);
|
||||
|
||||
// Vérification du cache
|
||||
PropositionAideDTO propositionCachee = cachePropositionsActives.get(id);
|
||||
if (propositionCachee != null) {
|
||||
// Incrémenter le nombre de vues
|
||||
propositionCachee.setNombreVues(propositionCachee.getNombreVues() + 1);
|
||||
return propositionCachee;
|
||||
}
|
||||
|
||||
// Simulation de récupération depuis la base de données
|
||||
PropositionAideDTO proposition = simulerRecuperationBDD(id);
|
||||
|
||||
if (proposition != null) {
|
||||
ajouterAuCache(proposition);
|
||||
ajouterAIndex(proposition);
|
||||
}
|
||||
|
||||
return proposition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Active ou désactive une proposition d'aide
|
||||
*
|
||||
* @param propositionId ID de la proposition
|
||||
* @param activer true pour activer, false pour désactiver
|
||||
* @return La proposition mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public PropositionAideDTO changerStatutActivation(
|
||||
@NotBlank String propositionId, boolean activer) {
|
||||
LOG.infof(
|
||||
"Changement de statut d'activation pour la proposition %s: %s",
|
||||
propositionId, activer ? "ACTIVE" : "SUSPENDUE");
|
||||
|
||||
PropositionAideDTO proposition = obtenirParId(propositionId);
|
||||
if (proposition == null) {
|
||||
throw new IllegalArgumentException("Proposition non trouvée: " + propositionId);
|
||||
}
|
||||
|
||||
if (activer) {
|
||||
// Vérifications avant activation
|
||||
if (proposition.isExpiree()) {
|
||||
throw new IllegalStateException("Impossible d'activer une proposition expirée");
|
||||
}
|
||||
proposition.setStatut(PropositionAideDTO.StatutProposition.ACTIVE);
|
||||
proposition.setEstDisponible(true);
|
||||
} else {
|
||||
proposition.setStatut(PropositionAideDTO.StatutProposition.SUSPENDUE);
|
||||
proposition.setEstDisponible(false);
|
||||
}
|
||||
|
||||
proposition.setDateModification(LocalDateTime.now());
|
||||
|
||||
// Mise à jour du cache et index
|
||||
ajouterAuCache(proposition);
|
||||
mettreAJourIndex(proposition);
|
||||
|
||||
return proposition;
|
||||
}
|
||||
|
||||
// === RECHERCHE ET MATCHING ===
|
||||
|
||||
/**
|
||||
* Recherche des propositions compatibles avec une demande
|
||||
*
|
||||
* @param demande La demande d'aide
|
||||
* @return Liste des propositions compatibles triées par score
|
||||
*/
|
||||
public List<PropositionAideDTO> rechercherPropositionsCompatibles(DemandeAideDTO demande) {
|
||||
LOG.debugf("Recherche de propositions compatibles pour la demande: %s", demande.getId());
|
||||
|
||||
// Recherche par type d'aide d'abord
|
||||
List<PropositionAideDTO> candidats =
|
||||
indexParType.getOrDefault(demande.getTypeAide(), new ArrayList<>());
|
||||
|
||||
// Si pas de correspondance exacte, chercher dans la même catégorie
|
||||
if (candidats.isEmpty()) {
|
||||
candidats =
|
||||
cachePropositionsActives.values().stream()
|
||||
.filter(
|
||||
p -> p.getTypeAide().getCategorie().equals(demande.getTypeAide().getCategorie()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Filtrage et scoring
|
||||
return candidats.stream()
|
||||
.filter(PropositionAideDTO::isActiveEtDisponible)
|
||||
.filter(p -> p.peutAccepterBeneficiaires())
|
||||
.map(
|
||||
p -> {
|
||||
double score = p.getScoreCompatibilite(demande);
|
||||
// Stocker le score temporairement dans les données personnalisées
|
||||
if (p.getDonneesPersonnalisees() == null) {
|
||||
p.setDonneesPersonnalisees(new HashMap<>());
|
||||
}
|
||||
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");
|
||||
Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreCompatibilite");
|
||||
return Double.compare(score2, score1); // Ordre décroissant
|
||||
})
|
||||
.limit(10) // Limiter à 10 meilleures propositions
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche des propositions par critères
|
||||
*
|
||||
* @param filtres Map des critères de recherche
|
||||
* @return Liste des propositions correspondantes
|
||||
*/
|
||||
public List<PropositionAideDTO> rechercherAvecFiltres(Map<String, Object> filtres) {
|
||||
LOG.debugf("Recherche de propositions avec filtres: %s", filtres);
|
||||
|
||||
return cachePropositionsActives.values().stream()
|
||||
.filter(proposition -> correspondAuxFiltres(proposition, filtres))
|
||||
.sorted(this::comparerParPertinence)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les propositions actives pour un type d'aide
|
||||
*
|
||||
* @param typeAide Type d'aide recherché
|
||||
* @return Liste des propositions actives
|
||||
*/
|
||||
public List<PropositionAideDTO> obtenirPropositionsActives(TypeAide typeAide) {
|
||||
LOG.debugf("Récupération des propositions actives pour le type: %s", typeAide);
|
||||
|
||||
return indexParType.getOrDefault(typeAide, new ArrayList<>()).stream()
|
||||
.filter(PropositionAideDTO::isActiveEtDisponible)
|
||||
.sorted(this::comparerParPertinence)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les meilleures propositions (top performers)
|
||||
*
|
||||
* @param limite Nombre maximum de propositions à retourner
|
||||
* @return Liste des meilleures propositions
|
||||
*/
|
||||
public List<PropositionAideDTO> obtenirMeilleuresPropositions(int limite) {
|
||||
LOG.debugf("Récupération des %d meilleures propositions", limite);
|
||||
|
||||
return cachePropositionsActives.values().stream()
|
||||
.filter(PropositionAideDTO::isActiveEtDisponible)
|
||||
.filter(p -> p.getNombreEvaluations() >= 3) // Au moins 3 évaluations
|
||||
.filter(p -> p.getNoteMoyenne() != null && p.getNoteMoyenne() >= 4.0)
|
||||
.sorted(
|
||||
(p1, p2) -> {
|
||||
// Tri par note moyenne puis par nombre d'aides réalisées
|
||||
int compareNote = Double.compare(p2.getNoteMoyenne(), p1.getNoteMoyenne());
|
||||
if (compareNote != 0) return compareNote;
|
||||
return Integer.compare(
|
||||
p2.getNombreBeneficiairesAides(), p1.getNombreBeneficiairesAides());
|
||||
})
|
||||
.limit(limite)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// === GESTION DES PERFORMANCES ===
|
||||
|
||||
/**
|
||||
* Met à jour les statistiques d'une proposition après une aide fournie
|
||||
*
|
||||
* @param propositionId ID de la proposition
|
||||
* @param montantVerse Montant versé (si applicable)
|
||||
* @param nombreBeneficiaires Nombre de bénéficiaires aidés
|
||||
* @return La proposition mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public PropositionAideDTO mettreAJourStatistiques(
|
||||
@NotBlank String propositionId, Double montantVerse, int nombreBeneficiaires) {
|
||||
LOG.infof("Mise à jour des statistiques pour la proposition: %s", propositionId);
|
||||
|
||||
PropositionAideDTO proposition = obtenirParId(propositionId);
|
||||
if (proposition == null) {
|
||||
throw new IllegalArgumentException("Proposition non trouvée: " + propositionId);
|
||||
}
|
||||
|
||||
// Mise à jour des compteurs
|
||||
proposition.setNombreDemandesTraitees(proposition.getNombreDemandesTraitees() + 1);
|
||||
proposition.setNombreBeneficiairesAides(
|
||||
proposition.getNombreBeneficiairesAides() + nombreBeneficiaires);
|
||||
|
||||
if (montantVerse != null) {
|
||||
proposition.setMontantTotalVerse(proposition.getMontantTotalVerse() + montantVerse);
|
||||
}
|
||||
|
||||
// Recalcul du score de pertinence
|
||||
proposition.setScorePertinence(calculerScorePertinence(proposition));
|
||||
|
||||
// Vérification si la capacité maximale est atteinte
|
||||
if (proposition.getNombreBeneficiairesAides() >= proposition.getNombreMaxBeneficiaires()) {
|
||||
proposition.setEstDisponible(false);
|
||||
proposition.setStatut(PropositionAideDTO.StatutProposition.TERMINEE);
|
||||
}
|
||||
|
||||
proposition.setDateModification(LocalDateTime.now());
|
||||
|
||||
// Mise à jour du cache
|
||||
ajouterAuCache(proposition);
|
||||
|
||||
return proposition;
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES PRIVÉES ===
|
||||
|
||||
/** Génère un numéro de référence unique pour les propositions */
|
||||
private String genererNumeroReference() {
|
||||
int annee = LocalDateTime.now().getYear();
|
||||
int numero = (int) (Math.random() * 999999) + 1;
|
||||
return String.format("PA-%04d-%06d", annee, numero);
|
||||
}
|
||||
|
||||
/** Calcule le score de pertinence d'une proposition */
|
||||
private double calculerScorePertinence(PropositionAideDTO proposition) {
|
||||
double score = 50.0; // Score de base
|
||||
|
||||
// Bonus pour l'expérience (nombre d'aides réalisées)
|
||||
score += Math.min(20.0, proposition.getNombreBeneficiairesAides() * 2.0);
|
||||
|
||||
// Bonus pour la note moyenne
|
||||
if (proposition.getNoteMoyenne() != null) {
|
||||
score += (proposition.getNoteMoyenne() - 3.0) * 10.0; // +10 par point au-dessus de 3
|
||||
}
|
||||
|
||||
// Bonus pour la récence
|
||||
long joursDepuisCreation =
|
||||
java.time.Duration.between(proposition.getDateCreation(), LocalDateTime.now()).toDays();
|
||||
if (joursDepuisCreation <= 30) {
|
||||
score += 10.0;
|
||||
} else if (joursDepuisCreation <= 90) {
|
||||
score += 5.0;
|
||||
}
|
||||
|
||||
// Bonus pour la disponibilité
|
||||
if (proposition.isActiveEtDisponible()) {
|
||||
score += 15.0;
|
||||
}
|
||||
|
||||
// Malus pour l'inactivité
|
||||
if (proposition.getNombreVues() == 0) {
|
||||
score -= 10.0;
|
||||
}
|
||||
|
||||
return Math.max(0.0, Math.min(100.0, score));
|
||||
}
|
||||
|
||||
/** Vérifie si une proposition correspond aux filtres */
|
||||
private boolean correspondAuxFiltres(
|
||||
PropositionAideDTO proposition, Map<String, Object> filtres) {
|
||||
for (Map.Entry<String, Object> filtre : filtres.entrySet()) {
|
||||
String cle = filtre.getKey();
|
||||
Object valeur = filtre.getValue();
|
||||
|
||||
switch (cle) {
|
||||
case "typeAide" -> {
|
||||
if (!proposition.getTypeAide().equals(valeur)) return false;
|
||||
}
|
||||
case "statut" -> {
|
||||
if (!proposition.getStatut().equals(valeur)) return false;
|
||||
}
|
||||
case "proposantId" -> {
|
||||
if (!proposition.getProposantId().equals(valeur)) return false;
|
||||
}
|
||||
case "organisationId" -> {
|
||||
if (!proposition.getOrganisationId().equals(valeur)) return false;
|
||||
}
|
||||
case "estDisponible" -> {
|
||||
if (!proposition.getEstDisponible().equals(valeur)) return false;
|
||||
}
|
||||
case "montantMaximum" -> {
|
||||
if (proposition.getMontantMaximum() == null
|
||||
|| proposition.getMontantMaximum().compareTo(BigDecimal.valueOf((Double) valeur)) < 0) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Compare deux propositions par pertinence */
|
||||
private int comparerParPertinence(PropositionAideDTO p1, PropositionAideDTO p2) {
|
||||
// D'abord par score de pertinence (plus haut = meilleur)
|
||||
int compareScore = Double.compare(p2.getScorePertinence(), p1.getScorePertinence());
|
||||
if (compareScore != 0) return compareScore;
|
||||
|
||||
// Puis par date de création (plus récent = meilleur)
|
||||
return p2.getDateCreation().compareTo(p1.getDateCreation());
|
||||
}
|
||||
|
||||
// === GESTION DU CACHE ET INDEX ===
|
||||
|
||||
private void ajouterAuCache(PropositionAideDTO proposition) {
|
||||
cachePropositionsActives.put(proposition.getId(), proposition);
|
||||
}
|
||||
|
||||
private void ajouterAIndex(PropositionAideDTO proposition) {
|
||||
indexParType
|
||||
.computeIfAbsent(proposition.getTypeAide(), k -> new ArrayList<>())
|
||||
.add(proposition);
|
||||
}
|
||||
|
||||
private void mettreAJourIndex(PropositionAideDTO proposition) {
|
||||
// Supprimer de tous les index
|
||||
indexParType
|
||||
.values()
|
||||
.forEach(liste -> liste.removeIf(p -> p.getId().equals(proposition.getId())));
|
||||
|
||||
// Ré-ajouter si la proposition est active
|
||||
if (proposition.isActiveEtDisponible()) {
|
||||
ajouterAIndex(proposition);
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) ===
|
||||
|
||||
private PropositionAideDTO simulerRecuperationBDD(String id) {
|
||||
// Simulation - dans une vraie implémentation, ceci ferait appel au repository
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user