package dev.lions.btpxpress.application.service; import dev.lions.btpxpress.domain.core.entity.Disponibilite; import dev.lions.btpxpress.domain.core.entity.Employe; import dev.lions.btpxpress.domain.core.entity.TypeDisponibilite; import dev.lions.btpxpress.domain.infrastructure.repository.DisponibiliteRepository; import dev.lions.btpxpress.domain.infrastructure.repository.EmployeRepository; 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.util.List; import java.util.Optional; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Service de gestion des disponibilités - Architecture 2025 RH: Logique complète de gestion des * disponibilités employés */ @ApplicationScoped public class DisponibiliteService { private static final Logger logger = LoggerFactory.getLogger(DisponibiliteService.class); @Inject DisponibiliteRepository disponibiliteRepository; @Inject EmployeRepository employeRepository; // === MÉTHODES DE CONSULTATION === public List findAll() { logger.debug("Recherche de toutes les disponibilités"); return disponibiliteRepository.findActifs(); } public List findAll(int page, int size) { logger.debug("Recherche des disponibilités - page: {}, taille: {}", page, size); return disponibiliteRepository.findActifs(page, size); } public Optional findById(UUID id) { logger.debug("Recherche de la disponibilité avec l'ID: {}", id); return disponibiliteRepository.findByIdOptional(id); } public Disponibilite findByIdRequired(UUID id) { return findById(id) .orElseThrow(() -> new NotFoundException("Disponibilité non trouvée avec l'ID: " + id)); } public List findByEmployeId(UUID employeId) { logger.debug("Recherche des disponibilités pour l'employé: {}", employeId); return disponibiliteRepository.findByEmployeId(employeId); } public List findByType(TypeDisponibilite type) { logger.debug("Recherche des disponibilités par type: {}", type); return disponibiliteRepository.findByType(type); } public List findByDateRange(LocalDateTime dateDebut, LocalDateTime dateFin) { logger.debug("Recherche des disponibilités entre {} et {}", dateDebut, dateFin); validateDateRange(dateDebut, dateFin); return disponibiliteRepository.findByDateRange(dateDebut, dateFin); } public List findEnAttente() { logger.debug("Recherche des demandes en attente d'approbation"); return disponibiliteRepository.findEnAttente(); } public List findApprouvees() { logger.debug("Recherche des disponibilités approuvées"); return disponibiliteRepository.findApprouvees(); } public List findActuelles() { logger.debug("Recherche des disponibilités actuellement actives"); return disponibiliteRepository.findActuelles(); } public List findFutures() { logger.debug("Recherche des disponibilités futures"); return disponibiliteRepository.findFutures(); } public List findPourPeriode(LocalDate dateDebut, LocalDate dateFin) { logger.debug("Recherche des disponibilités pour la période {} - {}", dateDebut, dateFin); validateDateRange(dateDebut, dateFin); return disponibiliteRepository.findPourPeriode(dateDebut, dateFin); } // === MÉTHODES CRUD === @Transactional public Disponibilite createDisponibilite( UUID employeId, LocalDateTime dateDebut, LocalDateTime dateFin, String typeStr, String motif) { logger.info("Création d'une nouvelle disponibilité pour l'employé: {}", employeId); // Validation des données validateDisponibiliteData(employeId, dateDebut, dateFin, typeStr); TypeDisponibilite type = parseType(typeStr); // Récupération de l'employé Employe employe = employeRepository .findByIdOptional(employeId) .orElseThrow(() -> new BadRequestException("Employé non trouvé: " + employeId)); // Vérification des conflits if (disponibiliteRepository.hasConflicts(employeId, dateDebut, dateFin, null)) { throw new BadRequestException("Une disponibilité existe déjà pour cette période"); } // Création de la disponibilité Disponibilite disponibilite = Disponibilite.builder() .employe(employe) .dateDebut(dateDebut) .dateFin(dateFin) .type(type) .motif(motif) .approuvee(false) // Par défaut en attente d'approbation .build(); disponibiliteRepository.persist(disponibilite); logger.info( "Disponibilité créée avec succès pour {} du {} au {}", employe.getNom() + " " + employe.getPrenom(), dateDebut, dateFin); return disponibilite; } @Transactional public Disponibilite updateDisponibilite( UUID id, LocalDateTime dateDebut, LocalDateTime dateFin, String motif) { logger.info("Mise à jour de la disponibilité: {}", id); Disponibilite disponibilite = findByIdRequired(id); // Validation des nouvelles données si fournies if (dateDebut != null && dateFin != null) { validateDateRange(dateDebut, dateFin); // Vérifier les conflits (en excluant la disponibilité actuelle) if (disponibiliteRepository.hasConflicts( disponibilite.getEmploye().getId(), dateDebut, dateFin, id)) { throw new BadRequestException("Conflit avec une autre disponibilité pour cette période"); } disponibilite.setDateDebut(dateDebut); disponibilite.setDateFin(dateFin); } if (motif != null) { disponibilite.setMotif(motif); } disponibilite.setDateModification(LocalDateTime.now()); disponibiliteRepository.persist(disponibilite); logger.info("Disponibilité mise à jour avec succès"); return disponibilite; } @Transactional public Disponibilite approuverDisponibilite(UUID id) { logger.info("Approbation de la disponibilité: {}", id); Disponibilite disponibilite = findByIdRequired(id); if (disponibilite.getApprouvee()) { throw new BadRequestException("Cette disponibilité est déjà approuvée"); } disponibilite.setApprouvee(true); disponibilite.setDateModification(LocalDateTime.now()); disponibiliteRepository.persist(disponibilite); logger.info( "Disponibilité approuvée avec succès pour l'employé: {}", disponibilite.getEmploye().getNom() + " " + disponibilite.getEmploye().getPrenom()); return disponibilite; } @Transactional public Disponibilite rejeterDisponibilite(UUID id, String raisonRejet) { logger.info("Rejet de la disponibilité: {} - Raison: {}", id, raisonRejet); Disponibilite disponibilite = findByIdRequired(id); if (disponibilite.getApprouvee()) { throw new BadRequestException("Impossible de rejeter une disponibilité déjà approuvée"); } // Ajouter la raison du rejet au motif String nouveauMotif = disponibilite.getMotif() != null ? disponibilite.getMotif() + " [REJETÉE: " + raisonRejet + "]" : "[REJETÉE: " + raisonRejet + "]"; disponibilite.setMotif(nouveauMotif); disponibilite.setDateModification(LocalDateTime.now()); disponibiliteRepository.persist(disponibilite); logger.info( "Disponibilité rejetée pour l'employé: {}", disponibilite.getEmploye().getNom() + " " + disponibilite.getEmploye().getPrenom()); return disponibilite; } @Transactional public void deleteDisponibilite(UUID id) { logger.info("Suppression de la disponibilité: {}", id); Disponibilite disponibilite = findByIdRequired(id); // Vérifier qu'on ne supprime pas une disponibilité en cours if (disponibilite.isActive()) { throw new BadRequestException("Impossible de supprimer une disponibilité en cours"); } disponibiliteRepository.delete(disponibilite); logger.info("Disponibilité supprimée avec succès"); } // === MÉTHODES DE VALIDATION ET VÉRIFICATION === public boolean isEmployeDisponible( UUID employeId, LocalDateTime dateDebut, LocalDateTime dateFin) { logger.debug( "Vérification de disponibilité de l'employé {} du {} au {}", employeId, dateDebut, dateFin); List conflits = disponibiliteRepository.findByEmployeIdAndDateRange(employeId, dateDebut, dateFin); // Filtrer seulement les disponibilités approuvées qui rendent l'employé indisponible return conflits.stream() .filter(Disponibilite::getApprouvee) .filter(d -> isTypeBloquant(d.getType())) .findAny() .isEmpty(); } public List getConflicts( UUID employeId, LocalDateTime dateDebut, LocalDateTime dateFin, UUID excludeId) { logger.debug( "Recherche de conflits pour l'employé {} du {} au {}", employeId, dateDebut, dateFin); return disponibiliteRepository.findConflictuelles(employeId, dateDebut, dateFin, excludeId); } // === MÉTHODES STATISTIQUES === public Object getStatistics() { logger.debug("Génération des statistiques des disponibilités"); return new Object() { public final long totalDisponibilites = disponibiliteRepository.count(); public final long enAttente = disponibiliteRepository.countEnAttente(); public final long approuvees = disponibiliteRepository.countApprouvees(); public final long congesPayes = disponibiliteRepository.countByType(TypeDisponibilite.CONGE_PAYE); public final long arretsMaladie = disponibiliteRepository.countByType(TypeDisponibilite.ARRET_MALADIE); public final long formations = disponibiliteRepository.countByType(TypeDisponibilite.FORMATION); public final long absences = disponibiliteRepository.countByType(TypeDisponibilite.ABSENCE); }; } public List getStatsByType() { logger.debug("Génération des statistiques par type"); return disponibiliteRepository.getStatsByType(); } public List getStatsByEmployee() { logger.debug("Génération des statistiques par employé"); return disponibiliteRepository.getStatsByEmployee(); } public List getExpiringRequests(int jours) { logger.debug("Recherche des demandes expirant dans {} jours", jours); return disponibiliteRepository.findExpiringRequests(jours); } // === MÉTHODES PRIVÉES DE VALIDATION === private void validateDisponibiliteData( UUID employeId, LocalDateTime dateDebut, LocalDateTime dateFin, String type) { if (employeId == null) { throw new BadRequestException("L'employé est obligatoire"); } validateDateRange(dateDebut, dateFin); if (type == null || type.trim().isEmpty()) { throw new BadRequestException("Le type de disponibilité est obligatoire"); } } private void validateDateRange(LocalDateTime dateDebut, LocalDateTime dateFin) { if (dateDebut == null || dateFin == null) { throw new BadRequestException("Les dates de début et fin sont obligatoires"); } if (dateDebut.isAfter(dateFin)) { throw new BadRequestException("La date de début ne peut pas être après la date de fin"); } if (dateDebut.isBefore(LocalDateTime.now().minusHours(1))) { throw new BadRequestException("La disponibilité ne peut pas être créée dans le passé"); } } private void validateDateRange(LocalDate dateDebut, LocalDate dateFin) { if (dateDebut == null || dateFin == null) { throw new BadRequestException("Les dates de début et fin sont obligatoires"); } if (dateDebut.isAfter(dateFin)) { throw new BadRequestException("La date de début ne peut pas être après la date de fin"); } } private TypeDisponibilite parseType(String typeStr) { try { return TypeDisponibilite.valueOf(typeStr.toUpperCase()); } catch (IllegalArgumentException e) { throw new BadRequestException( "Type de disponibilité invalide: " + typeStr + ". Valeurs autorisées: CONGE_PAYE, CONGE_SANS_SOLDE, ARRET_MALADIE, FORMATION," + " ABSENCE, HORAIRE_REDUIT"); } } private boolean isTypeBloquant(TypeDisponibilite type) { // Les types qui rendent l'employé indisponible pour le travail return type == TypeDisponibilite.CONGE_PAYE || type == TypeDisponibilite.CONGE_SANS_SOLDE || type == TypeDisponibilite.ARRET_MALADIE || type == TypeDisponibilite.ABSENCE; } }