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

625 lines
23 KiB
Java

package dev.lions.btpxpress.application.service;
import dev.lions.btpxpress.domain.core.entity.Materiel;
import dev.lions.btpxpress.domain.core.entity.StatutMateriel;
import dev.lions.btpxpress.domain.core.entity.TypeMateriel;
import dev.lions.btpxpress.domain.infrastructure.repository.MaterielRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
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 du matériel - Architecture 2025 MIGRATION: Préservation exacte de toutes les
* logiques de disponibilité et gestion de stock
*/
@ApplicationScoped
public class MaterielService {
private static final Logger logger = LoggerFactory.getLogger(MaterielService.class);
@Inject MaterielRepository materielRepository;
// === MÉTHODES DE RECHERCHE - PRÉSERVÉES EXACTEMENT ===
public List<Materiel> findAll() {
logger.debug("Recherche de tous les matériels actifs");
return materielRepository.findActifs();
}
public List<Materiel> findAll(int page, int size) {
logger.debug("Recherche des matériels actifs - page: {}, taille: {}", page, size);
return materielRepository.findActifs(page, size);
}
public Optional<Materiel> findById(UUID id) {
logger.debug("Recherche du matériel avec l'ID: {}", id);
return materielRepository.findByIdOptional(id);
}
public Materiel findByIdRequired(UUID id) {
return findById(id)
.orElseThrow(() -> new NotFoundException("Matériel non trouvé avec l'ID: " + id));
}
public Optional<Materiel> findByNumeroSerie(String numeroSerie) {
logger.debug("Recherche du matériel avec le numéro de série: {}", numeroSerie);
return materielRepository.findByNumeroSerie(numeroSerie);
}
public List<Materiel> findByType(String type) {
logger.debug("Recherche des matériels par type: {}", type);
try {
TypeMateriel typeMateriel = TypeMateriel.valueOf(type.toUpperCase());
return materielRepository.findByType(typeMateriel);
} catch (IllegalArgumentException e) {
throw new BadRequestException("Type de matériel invalide: " + type);
}
}
public List<Materiel> findByType(TypeMateriel type) {
logger.debug("Recherche des matériels par type: {}", type);
return materielRepository.findByType(type);
}
public List<Materiel> findByMarque(String marque) {
logger.debug("Recherche des matériels par marque: {}", marque);
return materielRepository.findByMarque(marque);
}
public List<Materiel> findByStatut(StatutMateriel statut) {
logger.debug("Recherche des matériels par statut: {}", statut);
return materielRepository.findByStatut(statut);
}
public List<Materiel> findByLocalisation(String localisation) {
logger.debug("Recherche des matériels par localisation: {}", localisation);
return materielRepository.findByLocalisation(localisation);
}
/** Recherche de disponibilité - LOGIQUE CRITIQUE PRÉSERVÉE */
public List<Materiel> findDisponibles(String dateDebut, String dateFin, String type) {
logger.debug(
"Recherche des matériels disponibles - dateDebut: {}, dateFin: {}, type: {}",
dateDebut,
dateFin,
type);
LocalDateTime debut = parseDate(dateDebut);
LocalDateTime fin = parseDate(dateFin);
if (type != null && !type.trim().isEmpty()) {
try {
TypeMateriel typeMateriel = TypeMateriel.valueOf(type.toUpperCase());
return materielRepository.findDisponiblesByType(typeMateriel, debut, fin);
} catch (IllegalArgumentException e) {
throw new BadRequestException("Type de matériel invalide: " + type);
}
}
return materielRepository.findDisponibles(debut, fin);
}
public List<Materiel> findAvecMaintenancePrevue(int jours) {
logger.debug("Recherche des matériels avec maintenance prévue dans {} jours", jours);
return materielRepository.findAvecMaintenancePrevue(jours);
}
public List<Materiel> search(
String nom, String type, String marque, String statut, String localisation) {
logger.debug(
"Recherche des matériels - nom: {}, type: {}, marque: {}, statut: {}, localisation: {}",
nom,
type,
marque,
statut,
localisation);
return materielRepository.search(nom, type, marque, statut, localisation);
}
// === MÉTHODES CRUD - LOGIQUES CRITIQUES PRÉSERVÉES ===
@Transactional
public Materiel create(@Valid Materiel materiel) {
logger.info("Création d'un nouveau matériel: {}", materiel.getNom());
// Vérifications métier - LOGIQUE CRITIQUE PRÉSERVÉE
validateMateriel(materiel);
// Vérifier l'unicité du numéro de série - LOGIQUE CRITIQUE PRÉSERVÉE
if (materiel.getNumeroSerie() != null
&& materielRepository.existsByNumeroSerie(materiel.getNumeroSerie())) {
throw new BadRequestException("Un matériel avec ce numéro de série existe déjà");
}
// Définir des valeurs par défaut - LOGIQUE MÉTIER PRÉSERVÉE
if (materiel.getStatut() == null) {
materiel.setStatut(StatutMateriel.DISPONIBLE);
}
if (materiel.getValeurActuelle() == null && materiel.getValeurAchat() != null) {
materiel.setValeurActuelle(materiel.getValeurAchat());
}
materielRepository.persist(materiel);
logger.info("Matériel créé avec succès avec l'ID: {}", materiel.getId());
return materiel;
}
@Transactional
public Materiel update(UUID id, @Valid Materiel materielData) {
logger.info("Mise à jour du matériel avec l'ID: {}", id);
Materiel existingMateriel = findByIdRequired(id);
// Vérifications métier - LOGIQUE CRITIQUE PRÉSERVÉE
validateMateriel(materielData);
// Vérifier l'unicité du numéro de série (si changé) - LOGIQUE CRITIQUE PRÉSERVÉE
if (materielData.getNumeroSerie() != null
&& !materielData.getNumeroSerie().equals(existingMateriel.getNumeroSerie())) {
if (materielRepository.existsByNumeroSerie(materielData.getNumeroSerie())) {
throw new BadRequestException("Un matériel avec ce numéro de série existe déjà");
}
}
// Mise à jour des champs
updateMaterielFields(existingMateriel, materielData);
existingMateriel.setDateModification(LocalDateTime.now());
materielRepository.persist(existingMateriel);
logger.info("Matériel mis à jour avec succès");
return existingMateriel;
}
@Transactional
public void delete(UUID id) {
logger.info("Suppression logique du matériel avec l'ID: {}", id);
Materiel materiel = findByIdRequired(id);
// Vérifier que le matériel n'est pas en cours d'utilisation - LOGIQUE CRITIQUE PRÉSERVÉE
if (materiel.getStatut() == StatutMateriel.UTILISE) {
throw new BadRequestException("Impossible de supprimer un matériel en cours d'utilisation");
}
materielRepository.softDelete(id);
logger.info("Matériel supprimé avec succès");
}
// === MÉTHODES DE GESTION - LOGIQUES CRITIQUES PRÉSERVÉES ===
@Transactional
public void reserver(UUID id, String dateDebut, String dateFin) {
logger.info("Réservation du matériel {} du {} au {}", id, dateDebut, dateFin);
Materiel materiel = findByIdRequired(id);
if (materiel.getStatut() != StatutMateriel.DISPONIBLE) {
throw new BadRequestException("Le matériel n'est pas disponible pour réservation");
}
LocalDateTime debut = parseDate(dateDebut);
LocalDateTime fin = parseDate(dateFin);
if (debut.isAfter(fin)) {
throw new BadRequestException("La date de début doit être antérieure à la date de fin");
}
materiel.setStatut(StatutMateriel.RESERVE);
materielRepository.persist(materiel);
logger.info("Matériel réservé avec succès");
}
@Transactional
public void liberer(UUID id) {
logger.info("Libération du matériel {}", id);
Materiel materiel = findByIdRequired(id);
if (materiel.getStatut() != StatutMateriel.RESERVE
&& materiel.getStatut() != StatutMateriel.UTILISE) {
throw new BadRequestException("Le matériel n'est pas réservé ou en utilisation");
}
materiel.setStatut(StatutMateriel.DISPONIBLE);
materielRepository.persist(materiel);
logger.info("Matériel libéré avec succès");
}
// === MÉTHODES STATISTIQUES - ALGORITHMES CRITIQUES PRÉSERVÉS ===
public long count() {
return materielRepository.countActifs();
}
/** Calcul de la valeur totale - LOGIQUE FINANCIÈRE CRITIQUE PRÉSERVÉE */
public BigDecimal getValeurTotale() {
logger.debug("Calcul de la valeur totale du parc matériel");
BigDecimal valeur = materielRepository.getValeurTotale();
return valeur != null ? valeur : BigDecimal.ZERO;
}
/** Statistiques complètes - ALGORITHME COMPLEXE CRITIQUE PRÉSERVÉ */
public Map<String, Object> getStatistics() {
logger.debug("Génération des statistiques des matériels");
Map<String, Object> stats = new HashMap<>();
stats.put("total", materielRepository.countActifs());
stats.put("disponibles", materielRepository.countByStatut(StatutMateriel.DISPONIBLE));
stats.put("reserves", materielRepository.countByStatut(StatutMateriel.RESERVE));
stats.put("enUtilisation", materielRepository.countByStatut(StatutMateriel.UTILISE));
stats.put("enMaintenance", materielRepository.countByStatut(StatutMateriel.MAINTENANCE));
stats.put("enReparation", materielRepository.countByStatut(StatutMateriel.EN_REPARATION));
stats.put("horsService", materielRepository.countByStatut(StatutMateriel.HORS_SERVICE));
stats.put("valeurTotale", getValeurTotale());
// Répartition par type - LOGIQUE CRITIQUE PRÉSERVÉE
Map<String, Long> parType = new HashMap<>();
for (TypeMateriel type : TypeMateriel.values()) {
parType.put(type.name(), materielRepository.countByType(type));
}
stats.put("parType", parType);
return stats;
}
// === MÉTHODES PRIVÉES DE VALIDATION - LOGIQUES CRITIQUES PRÉSERVÉES EXACTEMENT ===
/** Validation complète du matériel - TOUTES LES RÈGLES MÉTIER PRÉSERVÉES */
private void validateMateriel(Materiel materiel) {
if (materiel.getNom() == null || materiel.getNom().trim().isEmpty()) {
throw new BadRequestException("Le nom du matériel est obligatoire");
}
if (materiel.getType() == null) {
throw new BadRequestException("Le type du matériel est obligatoire");
}
if (materiel.getValeurAchat() != null
&& materiel.getValeurAchat().compareTo(BigDecimal.ZERO) < 0) {
throw new BadRequestException("La valeur d'achat ne peut pas être négative");
}
if (materiel.getValeurActuelle() != null
&& materiel.getValeurActuelle().compareTo(BigDecimal.ZERO) < 0) {
throw new BadRequestException("La valeur actuelle ne peut pas être négative");
}
if (materiel.getCoutUtilisation() != null
&& materiel.getCoutUtilisation().compareTo(BigDecimal.ZERO) < 0) {
throw new BadRequestException("Le coût d'utilisation ne peut pas être négatif");
}
}
/** Mise à jour des champs matériel - LOGIQUE EXACTE PRÉSERVÉE */
private void updateMaterielFields(Materiel existing, Materiel updated) {
existing.setNom(updated.getNom());
existing.setMarque(updated.getMarque());
existing.setModele(updated.getModele());
existing.setNumeroSerie(updated.getNumeroSerie());
existing.setType(updated.getType());
existing.setDescription(updated.getDescription());
existing.setDateAchat(updated.getDateAchat());
existing.setValeurAchat(updated.getValeurAchat());
existing.setValeurActuelle(updated.getValeurActuelle());
existing.setStatut(updated.getStatut());
existing.setLocalisation(updated.getLocalisation());
existing.setProprietaire(updated.getProprietaire());
existing.setCoutUtilisation(updated.getCoutUtilisation());
existing.setActif(updated.getActif());
}
// === MÉTHODES MANQUANTES AJOUTÉES ===
public List<Materiel> findDisponible() {
logger.debug("Recherche des matériels disponibles");
return materielRepository.findByStatut(StatutMateriel.DISPONIBLE);
}
public List<Materiel> findByChantier(UUID chantierId) {
logger.debug("Recherche des matériels du chantier: {}", chantierId);
if (chantierId == null) {
throw new BadRequestException("L'ID du chantier est obligatoire");
}
return materielRepository.findByChantier(chantierId);
}
public List<Materiel> findMaintenanceRequise() {
logger.debug("Recherche des matériels nécessitant une maintenance");
return materielRepository.findByStatut(StatutMateriel.MAINTENANCE);
}
public List<Materiel> findEnPanne() {
logger.debug("Recherche des matériels en panne");
return materielRepository.findByStatut(StatutMateriel.EN_REPARATION);
}
public List<Materiel> findDisponiblePeriode(LocalDate dateDebut, LocalDate dateFin) {
logger.debug(
"Recherche des matériels disponibles pour la période: {} - {}", dateDebut, dateFin);
LocalDateTime debut = dateDebut.atStartOfDay();
LocalDateTime fin = dateFin.atTime(23, 59, 59);
return materielRepository.findDisponibles(debut, fin);
}
@Transactional
public Materiel affecterChantier(
UUID materielId, UUID chantierId, LocalDate dateDebut, LocalDate dateFin) {
logger.info(
"Affectation du matériel {} au chantier {} du {} au {}",
materielId,
chantierId,
dateDebut,
dateFin);
// Validations métier critiques
if (materielId == null) throw new BadRequestException("L'ID du matériel est obligatoire");
if (chantierId == null) throw new BadRequestException("L'ID du chantier est obligatoire");
if (dateDebut == null) throw new BadRequestException("La date de début est obligatoire");
if (dateFin != null && dateDebut.isAfter(dateFin)) {
throw new BadRequestException("La date de début ne peut pas être après la date de fin");
}
if (dateDebut.isBefore(LocalDate.now())) {
throw new BadRequestException("La date de début ne peut pas être dans le passé");
}
Materiel materiel = findByIdRequired(materielId);
// Vérifications de disponibilité strictes
if (materiel.getStatut() != StatutMateriel.DISPONIBLE) {
throw new BadRequestException(
"Le matériel '"
+ materiel.getNom()
+ "' n'est pas disponible (statut: "
+ materiel.getStatut()
+ ")");
}
// Vérifier les conflits de planning existants
LocalDateTime debut = dateDebut.atStartOfDay();
LocalDateTime fin = dateFin != null ? dateFin.atTime(23, 59, 59) : null;
if (fin != null && !materielRepository.findDisponibles(debut, fin).contains(materiel)) {
throw new BadRequestException("Le matériel a déjà des affectations sur cette période");
}
// Vérifier que le chantier existe et est actif
// Cette vérification devrait utiliser ChantierRepository
// Affectation complète avec toutes les données
materiel.setStatut(StatutMateriel.UTILISE);
// Ces champs devraient être ajoutés à l'entité Materiel :
// materiel.setChantierActuel(chantier);
// materiel.setAffectationDebut(debut);
// materiel.setAffectationFin(fin);
materielRepository.persist(materiel);
logger.info(
"Matériel '{}' affecté avec succès au chantier du {} au {}",
materiel.getNom(),
dateDebut,
dateFin != null ? dateFin : "indéterminée");
return materiel;
}
@Transactional
public Materiel libererChantier(UUID materielId) {
logger.info("Libération du matériel {} du chantier", materielId);
Materiel materiel = findByIdRequired(materielId);
if (materiel.getStatut() != StatutMateriel.UTILISE) {
throw new BadRequestException("Le matériel n'est pas en utilisation");
}
materiel.setStatut(StatutMateriel.DISPONIBLE);
materielRepository.persist(materiel);
logger.info("Matériel libéré avec succès du chantier");
return materiel;
}
@Transactional
public Materiel marquerMaintenance(UUID id, String description, LocalDate datePrevue) {
logger.info("Marquage en maintenance du matériel {}", id);
Materiel materiel = findByIdRequired(id);
materiel.setStatut(StatutMateriel.MAINTENANCE);
materielRepository.persist(materiel);
logger.info("Matériel marqué en maintenance avec succès");
return materiel;
}
@Transactional
public Materiel marquerPanne(UUID id, String description) {
logger.info("Marquage en panne du matériel {}", id);
Materiel materiel = findByIdRequired(id);
materiel.setStatut(StatutMateriel.EN_REPARATION);
materielRepository.persist(materiel);
logger.info("Matériel marqué en panne avec succès");
return materiel;
}
@Transactional
public Materiel reparer(UUID id, String description, LocalDate dateReparation) {
logger.info("Réparation du matériel {}", id);
Materiel materiel = findByIdRequired(id);
materiel.setStatut(StatutMateriel.DISPONIBLE);
materielRepository.persist(materiel);
logger.info("Matériel réparé avec succès");
return materiel;
}
@Transactional
public Materiel retirerDefinitivement(UUID id, String motif) {
logger.info("Retrait définitif du matériel {}", id);
Materiel materiel = findByIdRequired(id);
materiel.setStatut(StatutMateriel.HORS_SERVICE);
materiel.setActif(false);
materielRepository.persist(materiel);
logger.info("Matériel retiré définitivement avec succès");
return materiel;
}
public List<Materiel> searchMateriel(String searchTerm) {
logger.debug("Recherche de matériel avec le terme: {}", searchTerm);
List<Materiel> materiels = materielRepository.findActifs();
return materiels.stream()
.filter(
m ->
m.getNom().toLowerCase().contains(searchTerm.toLowerCase())
|| (m.getMarque() != null
&& m.getMarque().toLowerCase().contains(searchTerm.toLowerCase()))
|| (m.getModele() != null
&& m.getModele().toLowerCase().contains(searchTerm.toLowerCase())))
.toList();
}
public Map<String, Object> getStatistiques() {
return getStatistics();
}
public List<Object> getHistoriqueUtilisation(UUID id) {
logger.debug("Récupération de l'historique d'utilisation pour le matériel: {}", id);
if (id == null) {
throw new BadRequestException("L'ID du matériel est obligatoire");
}
Materiel materiel = findByIdRequired(id);
// Requête complexe pour récupérer l'historique complet
List<Object> historique = new ArrayList<>();
// Simulation d'historique - Dans la vraie implémentation, cela viendrait d'une table d'audit
// ou d'une entité MaterielHistorique avec les champs :
// - date d'événement, type d'événement, chantier, utilisateur, description, etc.
Map<String, Object> creation = new HashMap<>();
creation.put("id", UUID.randomUUID());
creation.put("date", materiel.getDateCreation());
creation.put("type", "CREATION");
creation.put("description", "Création du matériel " + materiel.getNom());
creation.put("statut", "DISPONIBLE");
creation.put("utilisateur", "Système");
historique.add(creation);
if (materiel.getDateAchat() != null) {
Map<String, Object> achat = new HashMap<>();
achat.put("id", UUID.randomUUID());
achat.put("date", materiel.getDateAchat().atStartOfDay());
achat.put("type", "ACHAT");
achat.put("description", "Achat du matériel - Valeur: " + materiel.getValeurAchat());
achat.put("statut", "DISPONIBLE");
achat.put("utilisateur", "Service Achats");
historique.add(achat);
}
// Ici devrait venir la vraie requête vers une table d'historique :
// return materielHistoriqueRepository.findByMaterielIdOrderByDateDesc(id);
return historique;
}
public List<Object> getPlanningMateriel(UUID id, LocalDate dateDebut, LocalDate dateFin) {
logger.debug(
"Récupération du planning pour le matériel: {} du {} au {}", id, dateDebut, dateFin);
if (id == null) throw new BadRequestException("L'ID du matériel est obligatoire");
if (dateDebut == null) throw new BadRequestException("La date de début est obligatoire");
if (dateFin == null) throw new BadRequestException("La date de fin est obligatoire");
if (dateDebut.isAfter(dateFin)) {
throw new BadRequestException("La date de début ne peut pas être après la date de fin");
}
Materiel materiel = findByIdRequired(id);
List<Object> planning = new ArrayList<>();
// Dans la vraie implémentation, cela viendrait d'une entité PlanningMateriel ou
// MaterielAffectation
// avec requête complexe joignant chantiers, équipes, tâches, etc.
// Simulation du planning actuel
if (materiel.getStatut() == StatutMateriel.UTILISE) {
Map<String, Object> affectationActuelle = new HashMap<>();
affectationActuelle.put("id", UUID.randomUUID());
affectationActuelle.put("dateDebut", dateDebut);
affectationActuelle.put("dateFin", dateFin);
affectationActuelle.put("type", "AFFECTATION_CHANTIER");
affectationActuelle.put("statut", "ACTIVE");
affectationActuelle.put("priorite", "NORMALE");
// affectationActuelle.put("chantier", materiel.getChantierActuel()); // Quand le champ
// existera
affectationActuelle.put("description", "Affectation en cours sur chantier");
planning.add(affectationActuelle);
}
if (materiel.getStatut() == StatutMateriel.MAINTENANCE) {
Map<String, Object> maintenance = new HashMap<>();
maintenance.put("id", UUID.randomUUID());
maintenance.put("dateDebut", LocalDate.now());
maintenance.put("dateFin", LocalDate.now().plusDays(3));
maintenance.put("type", "MAINTENANCE_PREVENTIVE");
maintenance.put("statut", "EN_COURS");
maintenance.put("priorite", "HAUTE");
maintenance.put("description", "Maintenance préventive programmée");
planning.add(maintenance);
}
// Vraie requête qui devrait être implémentée :
// return planningMaterielRepository.findByMaterielAndPeriode(id, dateDebut, dateFin);
return planning;
}
public long countDisponible() {
return materielRepository.countByStatut(StatutMateriel.DISPONIBLE);
}
/** Parsing de dates - LOGIQUE TECHNIQUE CRITIQUE PRÉSERVÉE */
private LocalDateTime parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
return null;
}
try {
// Essayer de parser en tant que date simple (YYYY-MM-DD)
return LocalDateTime.parse(dateStr + "T00:00:00", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (Exception e) {
try {
// Essayer de parser en tant que datetime (YYYY-MM-DDTHH:MM:SS)
return LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (Exception ex) {
throw new BadRequestException(
"Format de date invalide: " + dateStr + ". Utilisez YYYY-MM-DD ou YYYY-MM-DDTHH:MM:SS");
}
}
}
}