Files
btpxpress-backend/src/main/java/dev/lions/btpxpress/application/service/DisponibiliteService.java
2025-10-01 01:37:34 +00:00

363 lines
13 KiB
Java

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<Disponibilite> findAll() {
logger.debug("Recherche de toutes les disponibilités");
return disponibiliteRepository.findActifs();
}
public List<Disponibilite> findAll(int page, int size) {
logger.debug("Recherche des disponibilités - page: {}, taille: {}", page, size);
return disponibiliteRepository.findActifs(page, size);
}
public Optional<Disponibilite> 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<Disponibilite> findByEmployeId(UUID employeId) {
logger.debug("Recherche des disponibilités pour l'employé: {}", employeId);
return disponibiliteRepository.findByEmployeId(employeId);
}
public List<Disponibilite> findByType(TypeDisponibilite type) {
logger.debug("Recherche des disponibilités par type: {}", type);
return disponibiliteRepository.findByType(type);
}
public List<Disponibilite> 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<Disponibilite> findEnAttente() {
logger.debug("Recherche des demandes en attente d'approbation");
return disponibiliteRepository.findEnAttente();
}
public List<Disponibilite> findApprouvees() {
logger.debug("Recherche des disponibilités approuvées");
return disponibiliteRepository.findApprouvees();
}
public List<Disponibilite> findActuelles() {
logger.debug("Recherche des disponibilités actuellement actives");
return disponibiliteRepository.findActuelles();
}
public List<Disponibilite> findFutures() {
logger.debug("Recherche des disponibilités futures");
return disponibiliteRepository.findFutures();
}
public List<Disponibilite> 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<Disponibilite> 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<Disponibilite> 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<Object[]> getStatsByType() {
logger.debug("Génération des statistiques par type");
return disponibiliteRepository.getStatsByType();
}
public List<Object[]> getStatsByEmployee() {
logger.debug("Génération des statistiques par employé");
return disponibiliteRepository.getStatsByEmployee();
}
public List<Disponibilite> 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;
}
}