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

451 lines
16 KiB
Java

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<Chantier> findActifs() {
logger.debug("Recherche de tous les chantiers actifs");
return chantierRepository.findActifs();
}
public List<Chantier> findByChefChantier(UUID chefId) {
logger.debug("Recherche des chantiers par chef: {}", chefId);
return chantierRepository.findByChefChantier(chefId);
}
public List<Chantier> findChantiersEnRetard() {
logger.debug("Recherche des chantiers en retard");
return chantierRepository.findChantiersEnRetard();
}
public List<Chantier> findProchainsDemarrages(int jours) {
logger.debug("Recherche des prochains démarrages: {} jours", jours);
return chantierRepository.findProchainsDemarrages(jours);
}
public List<Chantier> findAll() {
logger.debug("Recherche de tous les chantiers (actifs et inactifs)");
return chantierRepository.listAll();
}
public List<Chantier> 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<Chantier> 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<Chantier> findByClient(UUID clientId) {
logger.debug("Recherche des chantiers pour le client: {}", clientId);
return chantierRepository.findByClientId(clientId);
}
public List<Chantier> findByStatut(StatutChantier statut) {
logger.debug("Recherche des chantiers par statut: {}", statut);
return chantierRepository.findByStatut(statut);
}
public List<Chantier> findEnCours() {
logger.debug("Recherche des chantiers en cours");
return chantierRepository.findByStatut(StatutChantier.EN_COURS);
}
public List<Chantier> findPlanifies() {
logger.debug("Recherche des chantiers planifiés");
return chantierRepository.findByStatut(StatutChantier.PLANIFIE);
}
public List<Chantier> findTermines() {
logger.debug("Recherche des chantiers terminés");
return chantierRepository.findByStatut(StatutChantier.TERMINE);
}
public List<Chantier> 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<Chantier> 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<String, Object> getStatistiques() {
logger.debug("Calcul des statistiques chantiers");
Map<String, Object> 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<String, Object> calculerChiffreAffaires(Integer annee) {
logger.debug("Calcul du chiffre d'affaires pour l'année: {}", annee);
List<Chantier> 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<String, Object> 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<String, Object> getDashboardChantier(UUID id) {
logger.debug("Dashboard du chantier: {}", id);
Chantier chantier = findByIdRequired(id);
Map<String, Object> 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<Chantier> 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<Chantier> findByDateRange(LocalDate dateDebut, LocalDate dateFin) {
logger.debug("Recherche des chantiers entre {} et {}", dateDebut, dateFin);
return chantierRepository.findByDateRange(dateDebut, dateFin);
}
public List<Chantier> 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);
};
}
}