package dev.lions.btpxpress.application.service; import dev.lions.btpxpress.domain.core.entity.Chantier; import dev.lions.btpxpress.domain.core.entity.Client; import dev.lions.btpxpress.domain.core.entity.StatutChantier; import dev.lions.btpxpress.domain.infrastructure.repository.ChantierRepository; import dev.lions.btpxpress.domain.infrastructure.repository.ClientRepository; import dev.lions.btpxpress.domain.shared.dto.ChantierCreateDTO; import dev.lions.btpxpress.domain.shared.mapper.ChantierMapper; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.NotFoundException; import java.math.BigDecimal; import java.time.LocalDate; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Service de gestion des chantiers - Architecture 2025 MIGRATION: Préservation exacte de toutes les * fonctionnalités métier */ @ApplicationScoped public class ChantierService { private static final Logger logger = LoggerFactory.getLogger(ChantierService.class); @Inject ChantierRepository chantierRepository; @Inject ClientRepository clientRepository; @Inject ChantierMapper chantierMapper; // === MÉTHODES DE CONSULTATION - PRÉSERVÉES EXACTEMENT === public List findActifs() { logger.debug("Recherche de tous les chantiers actifs"); return chantierRepository.findActifs(); } public List findByChefChantier(UUID chefId) { logger.debug("Recherche des chantiers par chef: {}", chefId); return chantierRepository.findByChefChantier(chefId); } public List findChantiersEnRetard() { logger.debug("Recherche des chantiers en retard"); return chantierRepository.findChantiersEnRetard(); } public List findProchainsDemarrages(int jours) { logger.debug("Recherche des prochains démarrages: {} jours", jours); return chantierRepository.findProchainsDemarrages(jours); } public List findAll() { logger.debug("Recherche de tous les chantiers (actifs et inactifs)"); return chantierRepository.listAll(); } public List findAllActive() { logger.debug("Recherche de tous les chantiers actifs uniquement"); return chantierRepository.listAll().stream() .filter(c -> c.getActif() != null && c.getActif()) .collect(java.util.stream.Collectors.toList()); } public long count() { return chantierRepository.count(); } public Optional findById(UUID id) { logger.debug("Recherche du chantier par ID: {}", id); return chantierRepository.findByIdOptional(id); } public Chantier findByIdRequired(UUID id) { return findById(id) .orElseThrow(() -> new NotFoundException("Chantier non trouvé avec l'ID: " + id)); } public List findByClient(UUID clientId) { logger.debug("Recherche des chantiers pour le client: {}", clientId); return chantierRepository.findByClientId(clientId); } public List findByStatut(StatutChantier statut) { logger.debug("Recherche des chantiers par statut: {}", statut); return chantierRepository.findByStatut(statut); } public List findEnCours() { logger.debug("Recherche des chantiers en cours"); return chantierRepository.findByStatut(StatutChantier.EN_COURS); } public List findPlanifies() { logger.debug("Recherche des chantiers planifiés"); return chantierRepository.findByStatut(StatutChantier.PLANIFIE); } public List findTermines() { logger.debug("Recherche des chantiers terminés"); return chantierRepository.findByStatut(StatutChantier.TERMINE); } public List findByVille(String ville) { logger.debug("Recherche des chantiers par ville: {}", ville); return chantierRepository.findByVille(ville); } @Transactional public Chantier demarrerChantier(UUID id) { logger.debug("Démarrage du chantier: {}", id); Chantier chantier = findByIdRequired(id); chantier.setStatut(StatutChantier.EN_COURS); chantier.setDateDebutReelle(LocalDate.now()); return chantier; } @Transactional public Chantier suspendreChantier(UUID id, String raison) { logger.debug("Suspension du chantier: {} - Raison: {}", id, raison); Chantier chantier = findByIdRequired(id); chantier.setStatut(StatutChantier.SUSPENDU); return chantier; } @Transactional public Chantier terminerChantier(UUID id, LocalDate dateFin, String commentaire) { logger.debug("Terminaison du chantier: {} - Date: {}", id, dateFin); Chantier chantier = findByIdRequired(id); chantier.setStatut(StatutChantier.TERMINE); chantier.setDateFinReelle(dateFin); return chantier; } @Transactional public Chantier updateAvancementGlobal(UUID id, BigDecimal avancement) { logger.debug("Mise à jour de l'avancement du chantier: {} - {}%", id, avancement); Chantier chantier = findByIdRequired(id); // Validation de l'avancement if (avancement.compareTo(BigDecimal.ZERO) < 0 || avancement.compareTo(BigDecimal.valueOf(100)) > 0) { throw new IllegalArgumentException("L'avancement doit être entre 0 et 100%"); } // Mise à jour de l'avancement chantier.setPourcentageAvancement(avancement); // Mise à jour automatique du statut si nécessaire if (avancement.compareTo(BigDecimal.valueOf(100)) == 0 && chantier.getStatut() != StatutChantier.TERMINE) { chantier.setStatut(StatutChantier.TERMINE); chantier.setDateFinReelle(LocalDate.now()); logger.info("Chantier automatiquement marqué comme terminé: {}", chantier.getNom()); } else if (avancement.compareTo(BigDecimal.ZERO) > 0 && chantier.getStatut() == StatutChantier.PLANIFIE) { chantier.setStatut(StatutChantier.EN_COURS); chantier.setDateDebutReelle(LocalDate.now()); logger.info("Chantier automatiquement marqué comme en cours: {}", chantier.getNom()); } chantierRepository.persist(chantier); logger.info("Avancement du chantier {} mis à jour: {}%", chantier.getNom(), avancement); return chantier; } public List searchChantiers(String query) { logger.debug("Recherche de chantiers: {}", query); if (query == null || query.trim().isEmpty()) { return chantierRepository.findActifs(); } return chantierRepository.searchByNomOrAdresse(query.trim()); } public Map getStatistiques() { logger.debug("Calcul des statistiques chantiers"); Map stats = new HashMap<>(); stats.put("total", count()); stats.put("enCours", findEnCours().size()); stats.put("planifies", findPlanifies().size()); stats.put("termines", findTermines().size()); stats.put("enRetard", findChantiersEnRetard().size()); return stats; } public Map calculerChiffreAffaires(Integer annee) { logger.debug("Calcul du chiffre d'affaires pour l'année: {}", annee); List chantiersAnnee = chantierRepository.findByAnnee(annee != null ? annee : LocalDate.now().getYear()); BigDecimal totalEnCours = chantiersAnnee.stream() .filter(c -> c.getStatut() == StatutChantier.EN_COURS) .map(c -> c.getMontantContrat() != null ? c.getMontantContrat() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal totalTermine = chantiersAnnee.stream() .filter(c -> c.getStatut() == StatutChantier.TERMINE) .map(c -> c.getMontantContrat() != null ? c.getMontantContrat() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal totalGlobal = totalEnCours.add(totalTermine); Map ca = new HashMap<>(); ca.put("annee", annee != null ? annee : LocalDate.now().getYear()); ca.put("total", totalGlobal); ca.put("enCours", totalEnCours); ca.put("termine", totalTermine); ca.put("nombreChantiers", chantiersAnnee.size()); ca.put( "montantMoyen", chantiersAnnee.isEmpty() ? BigDecimal.ZERO : totalGlobal.divide( BigDecimal.valueOf(chantiersAnnee.size()), 2, java.math.RoundingMode.HALF_UP)); return ca; } public Map getDashboardChantier(UUID id) { logger.debug("Dashboard du chantier: {}", id); Chantier chantier = findByIdRequired(id); Map dashboard = new HashMap<>(); dashboard.put("chantier", chantier); dashboard.put("avancement", chantier.getPourcentageAvancement()); dashboard.put("enRetard", chantier.isEnRetard()); dashboard.put("montantContrat", chantier.getMontantContrat()); dashboard.put("coutReel", chantier.getCoutReel()); return dashboard; } // =========================================== // MÉTHODES DE GESTION // =========================================== @Transactional public Chantier create(ChantierCreateDTO dto) { logger.debug("Création d'un nouveau chantier: {}", dto.getNom()); // Validation du client Client client = clientRepository .findByIdOptional(dto.getClientId()) .orElseThrow( () -> new IllegalArgumentException("Client non trouvé: " + dto.getClientId())); // Validation des dates if (dto.getDateDebut() != null && dto.getDateFinPrevue() != null) { if (dto.getDateDebut().isAfter(dto.getDateFinPrevue())) { throw new IllegalArgumentException( "La date de début ne peut pas être après la date de fin prévue"); } } Chantier chantier = chantierMapper.toEntity(dto, client); chantierRepository.persist(chantier); logger.info( "Chantier créé avec succès: {} pour le client: {}", chantier.getNom(), client.getNom()); return chantier; } @Transactional public Chantier update(UUID id, ChantierCreateDTO dto) { logger.debug("Mise à jour du chantier: {}", id); Chantier chantier = chantierRepository .findByIdOptional(id) .orElseThrow(() -> new IllegalArgumentException("Chantier non trouvé: " + id)); // Validation du client si changé Client client = clientRepository .findByIdOptional(dto.getClientId()) .orElseThrow( () -> new IllegalArgumentException("Client non trouvé: " + dto.getClientId())); // Validation des dates if (dto.getDateDebut() != null && dto.getDateFinPrevue() != null) { if (dto.getDateDebut().isAfter(dto.getDateFinPrevue())) { throw new IllegalArgumentException( "La date de début ne peut pas être après la date de fin prévue"); } } chantierMapper.updateEntity(chantier, dto, client); chantierRepository.persist(chantier); logger.info("Chantier mis à jour avec succès: {}", chantier.getNom()); return chantier; } @Transactional public Chantier updateStatut(UUID id, StatutChantier nouveauStatut) { logger.debug("Mise à jour du statut du chantier {} vers {}", id, nouveauStatut); Chantier chantier = chantierRepository .findByIdOptional(id) .orElseThrow(() -> new IllegalArgumentException("Chantier non trouvé: " + id)); // Validation des transitions de statut if (!isTransitionValide(chantier.getStatut(), nouveauStatut)) { throw new IllegalArgumentException( String.format( "Transition de statut invalide: %s -> %s", chantier.getStatut(), nouveauStatut)); } StatutChantier ancienStatut = chantier.getStatut(); chantier.setStatut(nouveauStatut); // Mise à jour automatique de la date de fin réelle si terminé if (nouveauStatut == StatutChantier.TERMINE && chantier.getDateFinReelle() == null) { chantier.setDateFinReelle(LocalDate.now()); } chantierRepository.persist(chantier); logger.info( "Statut du chantier {} changé de {} vers {}", chantier.getNom(), ancienStatut, nouveauStatut); return chantier; } @Transactional public void delete(UUID id) { logger.debug("Suppression logique du chantier: {}", id); Chantier chantier = chantierRepository .findByIdOptional(id) .orElseThrow(() -> new IllegalArgumentException("Chantier non trouvé: " + id)); // Vérification qu'on peut supprimer (pas de devis/factures en cours) if (chantier.getStatut() == StatutChantier.EN_COURS) { throw new IllegalStateException("Impossible de supprimer un chantier en cours"); } chantierRepository.softDelete(id); logger.info("Chantier supprimé logiquement: {}", chantier.getNom()); } @Transactional public void deletePhysically(UUID id) { logger.debug("Suppression physique du chantier: {}", id); Chantier chantier = chantierRepository .findByIdOptional(id) .orElseThrow(() -> new IllegalArgumentException("Chantier non trouvé: " + id)); // Vérifications plus strictes pour suppression physique if (chantier.getStatut() == StatutChantier.EN_COURS) { throw new IllegalStateException("Impossible de supprimer physiquement un chantier en cours"); } if (chantier.getStatut() == StatutChantier.TERMINE) { throw new IllegalStateException("Impossible de supprimer physiquement un chantier terminé"); } chantierRepository.physicalDelete(id); logger.warn("Chantier supprimé physiquement (DÉFINITIVEMENT): {}", chantier.getNom()); } // =========================================== // MÉTHODES DE RECHERCHE ET FILTRAGE // =========================================== public List search(String searchTerm) { if (searchTerm == null || searchTerm.trim().isEmpty()) { return findAll(); } logger.debug("Recherche de chantiers avec le terme: {}", searchTerm); return chantierRepository.searchByNomOrAdresse(searchTerm.trim()); } public List findByDateRange(LocalDate dateDebut, LocalDate dateFin) { logger.debug("Recherche des chantiers entre {} et {}", dateDebut, dateFin); return chantierRepository.findByDateRange(dateDebut, dateFin); } public List findRecents(int limit) { logger.debug("Recherche des {} chantiers les plus récents", limit); return chantierRepository.findRecents(limit); } // =========================================== // MÉTHODES DE VALIDATION MÉTIER // =========================================== private boolean isTransitionValide(StatutChantier ancienStatut, StatutChantier nouveauStatut) { if (ancienStatut == nouveauStatut) { return true; } return switch (ancienStatut) { case PLANIFIE -> nouveauStatut == StatutChantier.EN_COURS || nouveauStatut == StatutChantier.ANNULE; case EN_COURS -> nouveauStatut == StatutChantier.TERMINE || nouveauStatut == StatutChantier.SUSPENDU || nouveauStatut == StatutChantier.ANNULE; case SUSPENDU -> nouveauStatut == StatutChantier.EN_COURS || nouveauStatut == StatutChantier.ANNULE; case TERMINE -> false; // Un chantier terminé ne peut plus changer case ANNULE -> false; // Un chantier annulé ne peut plus changer }; } // =========================================== // MÉTHODES STATISTIQUES // =========================================== public long countByStatut(StatutChantier statut) { return chantierRepository.countByStatut(statut); } public Object getStatistics() { logger.debug("Génération des statistiques des chantiers"); return new Object() { public final long total = count(); public final long planifies = countByStatut(StatutChantier.PLANIFIE); public final long enCours = countByStatut(StatutChantier.EN_COURS); public final long termines = countByStatut(StatutChantier.TERMINE); public final long suspendus = countByStatut(StatutChantier.SUSPENDU); public final long annules = countByStatut(StatutChantier.ANNULE); }; } }