Initial commit

This commit is contained in:
dahoud
2025-10-01 01:37:34 +00:00
commit f2bb633142
310 changed files with 86051 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
package dev.lions.btpxpress.application.service;
import dev.lions.btpxpress.domain.core.entity.*;
import dev.lions.btpxpress.domain.infrastructure.repository.MaterielRepository;
import dev.lions.btpxpress.domain.infrastructure.repository.PlanningMaterielRepository;
import dev.lions.btpxpress.domain.infrastructure.repository.ReservationMaterielRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service de gestion des plannings matériel ORCHESTRATION: Logique métier planning, conflits et
* optimisation
*/
@ApplicationScoped
public class PlanningMaterielService {
private static final Logger logger = LoggerFactory.getLogger(PlanningMaterielService.class);
@Inject PlanningMaterielRepository planningRepository;
@Inject MaterielRepository materielRepository;
@Inject ReservationMaterielRepository reservationRepository;
// === OPÉRATIONS CRUD DE BASE ===
/** Récupère tous les plannings avec pagination */
public List<PlanningMateriel> findAll(int page, int size) {
logger.debug("Récupération des plannings - page: {}, size: {}", page, size);
return planningRepository.findAllActifs(page, size);
}
/** Récupère tous les plannings actifs */
public List<PlanningMateriel> findAll() {
return planningRepository.find("actif = true").list();
}
/** Trouve un planning par ID avec exception si non trouvé */
public PlanningMateriel findByIdRequired(UUID id) {
return planningRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Planning non trouvé avec l'ID: " + id));
}
/** Trouve un planning par ID */
public Optional<PlanningMateriel> findById(UUID id) {
return planningRepository.findByIdOptional(id);
}
// === RECHERCHES SPÉCIALISÉES ===
/** Trouve les plannings pour un matériel */
public List<PlanningMateriel> findByMateriel(UUID materielId) {
logger.debug("Recherche plannings pour matériel: {}", materielId);
return planningRepository.findByMateriel(materielId);
}
/** Trouve les plannings sur une période */
public List<PlanningMateriel> findByPeriode(LocalDate dateDebut, LocalDate dateFin) {
if (dateDebut.isAfter(dateFin)) {
throw new BadRequestException("La date de début doit être antérieure à la date de fin");
}
return planningRepository.findByPeriode(dateDebut, dateFin);
}
/** Trouve les plannings par statut */
public List<PlanningMateriel> findByStatut(StatutPlanning statut) {
return planningRepository.findByStatut(statut);
}
/** Trouve les plannings par type */
public List<PlanningMateriel> findByType(TypePlanning type) {
return planningRepository.findByType(type);
}
/** Recherche textuelle dans les plannings */
public List<PlanningMateriel> search(String terme) {
if (terme == null || terme.trim().isEmpty()) {
return findAll();
}
return planningRepository.search(terme.trim());
}
// === REQUÊTES MÉTIER SPÉCIALISÉES ===
/** Trouve les plannings avec conflits */
public List<PlanningMateriel> findAvecConflits() {
return planningRepository.findAvecConflits();
}
/** Trouve les plannings nécessitant attention */
public List<PlanningMateriel> findNecessitantAttention() {
return planningRepository.findNecessitantAttention();
}
/** Trouve les plannings en retard de validation */
public List<PlanningMateriel> findEnRetardValidation() {
return planningRepository.findEnRetardValidation();
}
/** Trouve les plannings prioritaires */
public List<PlanningMateriel> findPrioritaires() {
return planningRepository.findPrioritaires();
}
/** Trouve les plannings en cours */
public List<PlanningMateriel> findEnCours() {
return planningRepository.findEnCours();
}
// === CRÉATION ET MODIFICATION ===
/** Crée un nouveau planning matériel */
@Transactional
public PlanningMateriel createPlanning(
UUID materielId,
String nomPlanning,
String description,
LocalDate dateDebut,
LocalDate dateFin,
TypePlanning type,
String planificateur) {
logger.info("Création planning matériel: {} pour matériel: {}", nomPlanning, materielId);
// Validation des données
if (dateDebut.isAfter(dateFin)) {
throw new BadRequestException("La date de début doit être antérieure à la date de fin");
}
Materiel materiel =
materielRepository
.findByIdOptional(materielId)
.orElseThrow(() -> new NotFoundException("Matériel non trouvé: " + materielId));
// Création du planning
PlanningMateriel planning =
PlanningMateriel.builder()
.materiel(materiel)
.nomPlanning(nomPlanning)
.descriptionPlanning(description)
.dateDebut(dateDebut)
.dateFin(dateFin)
.typePlanning(type)
.planificateur(planificateur)
.creePar(planificateur)
.build();
// Génération automatique du nom si nécessaire
planning.genererNomPlanning();
// Définition de la couleur par défaut selon le type
if (planning.getCouleurPlanning() == null) {
planning.setCouleurPlanning(type.getCouleurDefaut());
}
planningRepository.persist(planning);
// Vérification des conflits immédiate
verifierConflits(planning);
logger.info("Planning créé avec succès: {}", planning.getId());
return planning;
}
/** Met à jour un planning existant */
@Transactional
public PlanningMateriel updatePlanning(
UUID id,
String nomPlanning,
String description,
LocalDate dateDebut,
LocalDate dateFin,
String modifiePar) {
logger.info("Mise à jour planning: {}", id);
PlanningMateriel planning = findByIdRequired(id);
if (!planning.peutEtreModifie()) {
throw new BadRequestException(
"Ce planning ne peut pas être modifié dans son état actuel: "
+ planning.getStatutPlanning());
}
// Validation des nouvelles données
if (dateDebut != null && dateFin != null && dateDebut.isAfter(dateFin)) {
throw new BadRequestException("La date de début doit être antérieure à la date de fin");
}
// Mise à jour des champs
if (nomPlanning != null) planning.setNomPlanning(nomPlanning);
if (description != null) planning.setDescriptionPlanning(description);
if (dateDebut != null) planning.setDateDebut(dateDebut);
if (dateFin != null) planning.setDateFin(dateFin);
if (modifiePar != null) planning.setModifiePar(modifiePar);
// Revérification des conflits après modification
verifierConflits(planning);
return planning;
}
// === GESTION DU WORKFLOW ===
/** Valide un planning */
@Transactional
public PlanningMateriel validerPlanning(UUID id, String valideur, String commentaires) {
logger.info("Validation planning: {} par: {}", id, valideur);
PlanningMateriel planning = findByIdRequired(id);
if (planning.getStatutPlanning() != StatutPlanning.BROUILLON
&& planning.getStatutPlanning() != StatutPlanning.EN_REVISION) {
throw new BadRequestException("Ce planning ne peut pas être validé dans son état actuel");
}
// Vérification finale des conflits avant validation
verifierConflits(planning);
if (planning.getConflitsDetectes()) {
throw new BadRequestException(
"Impossible de valider un planning avec des conflits non résolus");
}
planning.valider(valideur, commentaires);
// Calcul du score d'optimisation initial
calculerScoreOptimisation(planning);
logger.info("Planning validé avec succès: {}", id);
return planning;
}
/** Met un planning en révision */
@Transactional
public PlanningMateriel mettreEnRevision(UUID id, String motif) {
logger.info("Mise en révision planning: {}", id);
PlanningMateriel planning = findByIdRequired(id);
planning.mettreEnRevision(motif);
return planning;
}
/** Archive un planning */
@Transactional
public PlanningMateriel archiverPlanning(UUID id) {
logger.info("Archivage planning: {}", id);
PlanningMateriel planning = findByIdRequired(id);
planning.archiver();
return planning;
}
/** Suspend un planning */
@Transactional
public PlanningMateriel suspendrePlanning(UUID id) {
logger.info("Suspension planning: {}", id);
PlanningMateriel planning = findByIdRequired(id);
if (planning.getStatutPlanning() != StatutPlanning.VALIDE) {
throw new BadRequestException("Seuls les plannings validés peuvent être suspendus");
}
planning.setStatutPlanning(StatutPlanning.SUSPENDU);
return planning;
}
/** Réactive un planning suspendu */
@Transactional
public PlanningMateriel reactiverPlanning(UUID id) {
logger.info("Réactivation planning: {}", id);
PlanningMateriel planning = findByIdRequired(id);
if (planning.getStatutPlanning() != StatutPlanning.SUSPENDU) {
throw new BadRequestException("Seuls les plannings suspendus peuvent être réactivés");
}
// Revérification des conflits avant réactivation
verifierConflits(planning);
planning.setStatutPlanning(StatutPlanning.VALIDE);
return planning;
}
// === GESTION DES CONFLITS ===
/** Vérifie et met à jour les conflits d'un planning */
@Transactional
public void verifierConflits(PlanningMateriel planning) {
logger.debug("Vérification conflits pour planning: {}", planning.getId());
List<PlanningMateriel> conflits =
planningRepository.findConflits(
planning.getMateriel().getId(),
planning.getDateDebut(),
planning.getDateFin(),
planning.getId());
planning.mettreAJourConflits(conflits.size());
if (!conflits.isEmpty()) {
logger.warn(
"Conflits détectés pour planning {}: {} conflit(s)", planning.getId(), conflits.size());
}
}
/** Trouve les conflits pour un matériel sur une période */
public List<PlanningMateriel> checkConflits(
UUID materielId, LocalDate dateDebut, LocalDate dateFin, UUID excludeId) {
return planningRepository.findConflits(materielId, dateDebut, dateFin, excludeId);
}
/** Analyse la disponibilité d'un matériel sur une période */
public Map<String, Object> analyserDisponibilite(
UUID materielId, LocalDate dateDebut, LocalDate dateFin) {
logger.debug("Analyse disponibilité matériel: {} du {} au {}", materielId, dateDebut, dateFin);
List<PlanningMateriel> plannings =
planningRepository.findByPeriode(dateDebut, dateFin).stream()
.filter(p -> p.getMateriel().getId().equals(materielId))
.filter(p -> p.getStatutPlanning() == StatutPlanning.VALIDE)
.sorted(Comparator.comparing(PlanningMateriel::getDateDebut))
.collect(Collectors.toList());
List<Map<String, Object>> periodesOccupees = new ArrayList<>();
List<Map<String, Object>> periodesLibres = new ArrayList<>();
LocalDate curseur = dateDebut;
for (PlanningMateriel planning : plannings) {
LocalDate debutPlanning =
planning.getDateDebut().isBefore(dateDebut) ? dateDebut : planning.getDateDebut();
LocalDate finPlanning =
planning.getDateFin().isAfter(dateFin) ? dateFin : planning.getDateFin();
// Période libre avant ce planning
if (curseur.isBefore(debutPlanning)) {
periodesLibres.add(
Map.of(
"debut", curseur,
"fin", debutPlanning.minusDays(1),
"duree", ChronoUnit.DAYS.between(curseur, debutPlanning)));
}
// Période occupée
periodesOccupees.add(
Map.of(
"debut",
debutPlanning,
"fin",
finPlanning,
"duree",
ChronoUnit.DAYS.between(debutPlanning, finPlanning) + 1,
"planning",
planning.getResume(),
"taux",
planning.getTauxUtilisationPrevu()));
curseur = finPlanning.plusDays(1);
}
// Période libre finale
if (curseur.isBefore(dateFin) || curseur.equals(dateFin)) {
periodesLibres.add(
Map.of(
"debut", curseur,
"fin", dateFin,
"duree", ChronoUnit.DAYS.between(curseur, dateFin) + 1));
}
long totalJours = ChronoUnit.DAYS.between(dateDebut, dateFin) + 1;
long joursOccupes = periodesOccupees.stream().mapToLong(p -> (Long) p.get("duree")).sum();
double tauxOccupation = totalJours > 0 ? (double) joursOccupes / totalJours * 100.0 : 0.0;
return Map.of(
"materielId", materielId,
"periode", Map.of("debut", dateDebut, "fin", dateFin),
"totalJours", totalJours,
"joursOccupes", joursOccupes,
"joursLibres", totalJours - joursOccupes,
"tauxOccupation", tauxOccupation,
"periodesOccupees", periodesOccupees,
"periodesLibres", periodesLibres,
"disponible", periodesLibres.size() > 0);
}
// === OPTIMISATION ===
/** Calcule le score d'optimisation d'un planning */
@Transactional
public void calculerScoreOptimisation(PlanningMateriel planning) {
logger.debug("Calcul score optimisation pour planning: {}", planning.getId());
double score = 100.0;
// Pénalité pour les conflits
if (planning.getConflitsDetectes()) {
score -= planning.getNombreConflits() * 15.0;
}
// Pénalité pour faible taux d'utilisation
if (planning.getTauxUtilisationPrevu() != null) {
if (planning.getTauxUtilisationPrevu() < 50.0) {
score -= (50.0 - planning.getTauxUtilisationPrevu()) * 0.5;
}
}
// Bonus pour planification anticipée
long joursAvance = ChronoUnit.DAYS.between(LocalDate.now(), planning.getDateDebut());
if (joursAvance > planning.getTypePlanning().getDelaiMinimumPreavis() / 24) {
score += Math.min(10.0, joursAvance * 0.1);
}
// Pénalité pour dépassement horizon recommandé
long duree = planning.getDureePlanningJours();
int horizonRecommande = planning.getTypePlanning().getHorizonPlanificationJours();
if (duree > horizonRecommande) {
score -= (duree - horizonRecommande) * 0.1;
}
// Normalisation du score
score = Math.max(0.0, Math.min(100.0, score));
planning.mettreAJourOptimisation(score);
logger.debug("Score d'optimisation calculé: {} pour planning: {}", score, planning.getId());
}
/** Optimise automatiquement les plannings éligibles */
@Transactional
public List<PlanningMateriel> optimiserPlannings() {
logger.info("Démarrage optimisation automatique des plannings");
List<PlanningMateriel> candidats = planningRepository.findCandidatsOptimisation();
List<PlanningMateriel> optimises = new ArrayList<>();
for (PlanningMateriel planning : candidats) {
try {
optimiserPlanning(planning);
optimises.add(planning);
} catch (Exception e) {
logger.error("Erreur lors de l'optimisation du planning: " + planning.getId(), e);
}
}
logger.info(
"Optimisation terminée: {} plannings optimisés sur {} candidats",
optimises.size(),
candidats.size());
return optimises;
}
/** Optimise un planning spécifique */
@Transactional
public void optimiserPlanning(PlanningMateriel planning) {
logger.debug("Optimisation planning: {}", planning.getId());
// Recalcul du score d'optimisation
calculerScoreOptimisation(planning);
// Vérification et résolution automatique des conflits si possible
if (planning.getResolutionConflitsAuto()) {
tenterResolutionConflits(planning);
}
// Optimisation du taux d'utilisation
optimiserTauxUtilisation(planning);
planning.setDerniereOptimisation(LocalDateTime.now());
}
/** Tente de résoudre automatiquement les conflits */
private void tenterResolutionConflits(PlanningMateriel planning) {
if (!planning.getConflitsDetectes()) {
return;
}
logger.debug("Tentative résolution conflits pour planning: {}", planning.getId());
List<PlanningMateriel> conflits =
checkConflits(
planning.getMateriel().getId(),
planning.getDateDebut(),
planning.getDateFin(),
planning.getId());
// Stratégie simple: décaler le planning si possible
for (PlanningMateriel conflit : conflits) {
if (conflit.getTypePlanning().estPrioritaireSur(planning.getTypePlanning())) {
// Le conflit est prioritaire, essayer de décaler notre planning
LocalDate nouvelleDate = conflit.getDateFin().plusDays(1);
long duree = planning.getDureePlanningJours();
// Vérifier si le décalage est dans les limites acceptables
if (ChronoUnit.DAYS.between(planning.getDateDebut(), nouvelleDate) <= 30) {
planning.setDateDebut(nouvelleDate);
planning.setDateFin(nouvelleDate.plusDays(duree - 1));
// Revérifier les conflits après décalage
verifierConflits(planning);
if (!planning.getConflitsDetectes()) {
logger.info("Conflit résolu par décalage pour planning: {}", planning.getId());
break;
}
}
}
}
}
/** Optimise le taux d'utilisation d'un planning */
private void optimiserTauxUtilisation(PlanningMateriel planning) {
// Analyser les réservations associées pour calculer un taux optimal
if (planning.getReservations() != null && !planning.getReservations().isEmpty()) {
double tauxMoyen =
planning.getReservations().stream()
.filter(
r ->
r.getStatut() == StatutReservationMateriel.VALIDEE
|| r.getStatut() == StatutReservationMateriel.EN_COURS)
.mapToDouble(r -> 80.0) // Taux standard par réservation
.average()
.orElse(60.0);
planning.setTauxUtilisationPrevu(Math.min(100.0, tauxMoyen));
}
}
// === STATISTIQUES ET ANALYSES ===
/** Génère les statistiques des plannings */
public Map<String, Object> getStatistiques() {
logger.debug("Génération statistiques plannings");
Map<String, Object> stats = planningRepository.calculerMetriques();
Map<StatutPlanning, Long> repartitionStatuts = planningRepository.compterParStatut();
List<Object[]> conflitsParType = planningRepository.analyserConflitsParType();
return Map.of(
"metriques", stats,
"repartitionStatuts", repartitionStatuts,
"conflitsParType", conflitsParType,
"dateGeneration", LocalDateTime.now());
}
/** Génère le tableau de bord des plannings */
public Map<String, Object> getTableauBordPlannings() {
logger.debug("Génération tableau de bord plannings");
return Map.of(
"planningsEnCours", findEnCours(),
"planningsAvecConflits", findAvecConflits(),
"planningsEnRetard", findEnRetardValidation(),
"planningsPrioritaires", findPrioritaires(),
"planningsNecessitantAttention", findNecessitantAttention(),
"statistiques", getStatistiques());
}
/** Analyse les taux d'utilisation par matériel */
public List<Object> analyserTauxUtilisation(LocalDate dateDebut, LocalDate dateFin) {
List<Object[]> resultats =
planningRepository.calculerTauxUtilisationParMateriel(dateDebut, dateFin);
return resultats.stream()
.map(
row ->
Map.of(
"materiel", row[0],
"tauxMoyen", row[1],
"nombrePlannings", row[2]))
.collect(Collectors.toList());
}
// === GESTION AUTOMATIQUE ===
/** Vérifie tous les plannings nécessitant une vérification des conflits */
@Transactional
public void verifierTousConflits() {
logger.info("Vérification automatique des conflits pour tous les plannings");
List<PlanningMateriel> plannings = planningRepository.findNecessitantVerificationConflits();
for (PlanningMateriel planning : plannings) {
try {
verifierConflits(planning);
} catch (Exception e) {
logger.error(
"Erreur lors de la vérification des conflits pour planning: " + planning.getId(), e);
}
}
logger.info("Vérification des conflits terminée pour {} plannings", plannings.size());
}
}