package dev.lions.btpxpress.application.service; import dev.lions.btpxpress.domain.core.entity.*; import dev.lions.btpxpress.domain.infrastructure.repository.*; 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.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Service de gestion des notifications - Architecture 2025 COMMUNICATION: Logique métier complète * pour les notifications BTP */ @ApplicationScoped public class NotificationService { private static final Logger logger = LoggerFactory.getLogger(NotificationService.class); @Inject NotificationRepository notificationRepository; @Inject UserRepository userRepository; @Inject ChantierRepository chantierRepository; @Inject MaterielRepository materielRepository; @Inject MaintenanceService maintenanceService; @Inject ChantierService chantierService; // === MÉTHODES DE CONSULTATION === public List findAll() { logger.debug("Recherche de toutes les notifications"); return notificationRepository.findActives(); } public List findAll(int page, int size) { logger.debug("Recherche des notifications - page: {}, taille: {}", page, size); return notificationRepository.findActives(page, size); } public Optional findById(UUID id) { logger.debug("Recherche de la notification avec l'ID: {}", id); return notificationRepository.findByIdOptional(id); } public Notification findByIdRequired(UUID id) { return findById(id) .orElseThrow(() -> new NotFoundException("Notification non trouvée avec l'ID: " + id)); } public List findByUser(UUID userId) { logger.debug("Recherche des notifications pour l'utilisateur: {}", userId); return notificationRepository.findByUser(userId); } public List findByType(TypeNotification type) { logger.debug("Recherche des notifications par type: {}", type); return notificationRepository.findByType(type); } public List findByPriorite(PrioriteNotification priorite) { logger.debug("Recherche des notifications par priorité: {}", priorite); return notificationRepository.findByPriorite(priorite); } public List findNonLues() { logger.debug("Recherche des notifications non lues"); return notificationRepository.findNonLues(); } public List findNonLuesByUser(UUID userId) { logger.debug("Recherche des notifications non lues pour l'utilisateur: {}", userId); return notificationRepository.findNonLuesByUser(userId); } public List findRecentes(int limite) { logger.debug("Recherche des {} notifications les plus récentes", limite); return notificationRepository.findRecentes(limite); } public List findRecentsByUser(UUID userId, int limite) { logger.debug( "Recherche des {} notifications les plus récentes pour l'utilisateur: {}", limite, userId); return notificationRepository.findRecentsByUser(userId, limite); } // === CRÉATION DE NOTIFICATIONS === @Transactional public Notification createNotification( String titre, String message, String typeStr, String prioriteStr, UUID userId, UUID chantierId, String lienAction, String donnees) { logger.info("Création d'une notification: {} pour l'utilisateur: {}", titre, userId); // Validation des données validateNotificationData(titre, message, typeStr, userId); TypeNotification type = parseTypeRequired(typeStr); PrioriteNotification priorite = parsePriorite(prioriteStr, PrioriteNotification.NORMALE); // Récupération des entités liées User user = getUserById(userId); Chantier chantier = chantierId != null ? getChantierById(chantierId) : null; // Création de la notification Notification notification = Notification.builder() .titre(titre) .message(message) .type(type) .priorite(priorite) .user(user) .chantier(chantier) .lienAction(lienAction) .donnees(donnees) .actif(true) .build(); notificationRepository.persist(notification); logger.info( "Notification créée avec succès: {} (ID: {})", notification.getTitre(), notification.getId()); return notification; } @Transactional public List broadcastNotification( String titre, String message, String typeStr, String prioriteStr, List userIds, String roleTarget, String lienAction, String donnees) { logger.info("Diffusion d'une notification: {}", titre); // Validation des données validateNotificationData(titre, message, typeStr, null); TypeNotification type = parseTypeRequired(typeStr); PrioriteNotification priorite = parsePriorite(prioriteStr, PrioriteNotification.NORMALE); // Détermination des destinataires List destinataires; if (userIds != null && !userIds.isEmpty()) { destinataires = userIds.stream().map(this::getUserById).collect(Collectors.toList()); } else if (roleTarget != null) { destinataires = getUsersByRole(roleTarget); } else { throw new BadRequestException("Aucun destinataire spécifié pour la diffusion"); } // Création des notifications pour chaque destinataire List notifications = destinataires.stream() .map( user -> { Notification notification = Notification.builder() .titre(titre) .message(message) .type(type) .priorite(priorite) .user(user) .lienAction(lienAction) .donnees(donnees) .actif(true) .build(); notificationRepository.persist(notification); return notification; }) .collect(Collectors.toList()); logger.info("Notification diffusée à {} utilisateurs", notifications.size()); return notifications; } @Transactional public List generateMaintenanceNotifications() { logger.info("Génération des notifications de maintenance automatiques"); List maintenancesEnRetard = maintenanceService.findEnRetard(); List prochainesMaintenances = maintenanceService.findProchainesMaintenances(7); List notifications = new ArrayList<>(); // Notifications pour maintenances en retard for (MaintenanceMateriel maintenance : maintenancesEnRetard) { String titre = "⚠️ Maintenance en retard: " + maintenance.getMateriel().getNom(); String message = String.format( "La maintenance %s du matériel %s était prévue le %s et est maintenant en retard.", maintenance.getType().toString(), maintenance.getMateriel().getNom(), maintenance.getDatePrevue()); // Notification pour le technicien responsable et les superviseurs List destinataires = getUsersForMaintenance(maintenance); for (User user : destinataires) { Notification notification = Notification.builder() .titre(titre) .message(message) .type(TypeNotification.MAINTENANCE) .priorite(PrioriteNotification.CRITIQUE) .user(user) .materiel(maintenance.getMateriel()) .maintenance(maintenance) .lienAction("/maintenance/" + maintenance.getId()) .actif(true) .build(); notificationRepository.persist(notification); notifications.add(notification); } } // Notifications pour prochaines maintenances for (MaintenanceMateriel maintenance : prochainesMaintenances) { String titre = "📅 Maintenance programmée: " + maintenance.getMateriel().getNom(); String message = String.format( "La maintenance %s du matériel %s est programmée pour le %s.", maintenance.getType().toString(), maintenance.getMateriel().getNom(), maintenance.getDatePrevue()); List destinataires = getUsersForMaintenance(maintenance); for (User user : destinataires) { Notification notification = Notification.builder() .titre(titre) .message(message) .type(TypeNotification.MAINTENANCE) .priorite(PrioriteNotification.HAUTE) .user(user) .materiel(maintenance.getMateriel()) .maintenance(maintenance) .lienAction("/maintenance/" + maintenance.getId()) .actif(true) .build(); notificationRepository.persist(notification); notifications.add(notification); } } logger.info("Générées {} notifications de maintenance", notifications.size()); return notifications; } @Transactional public List generateChantierNotifications() { logger.info("Génération des notifications de chantiers automatiques"); List chantiersEnRetard = chantierService.findByStatut(StatutChantier.EN_COURS).stream() .filter( c -> c.getDateFinPrevue() != null && c.getDateFinPrevue().isBefore(LocalDate.now())) .collect(Collectors.toList()); List notifications = new ArrayList<>(); for (Chantier chantier : chantiersEnRetard) { String titre = "🚧 Chantier en retard: " + chantier.getNom(); String message = String.format( "Le chantier %s devait se terminer le %s et accuse maintenant un retard.", chantier.getNom(), chantier.getDateFinPrevue()); // Notification pour le client et les responsables List destinataires = getUsersForChantier(chantier); for (User user : destinataires) { Notification notification = Notification.builder() .titre(titre) .message(message) .type(TypeNotification.CHANTIER) .priorite(PrioriteNotification.HAUTE) .user(user) .chantier(chantier) .lienAction("/chantiers/" + chantier.getId()) .actif(true) .build(); notificationRepository.persist(notification); notifications.add(notification); } } logger.info("Générées {} notifications de chantiers", notifications.size()); return notifications; } // === GESTION DES NOTIFICATIONS === @Transactional public Notification marquerCommeLue(UUID id) { logger.info("Marquage de la notification comme lue: {}", id); Notification notification = findByIdRequired(id); notification.marquerCommeLue(); notificationRepository.persist(notification); return notification; } @Transactional public Notification marquerCommeNonLue(UUID id) { logger.info("Marquage de la notification comme non lue: {}", id); Notification notification = findByIdRequired(id); notification.marquerCommeNonLue(); notificationRepository.persist(notification); return notification; } @Transactional public int marquerToutesCommeLues(UUID userId) { logger.info("Marquage de toutes les notifications comme lues pour l'utilisateur: {}", userId); return notificationRepository.marquerToutesCommeLues(userId); } @Transactional public void deleteNotification(UUID id) { logger.info("Suppression de la notification: {}", id); Notification notification = findByIdRequired(id); notificationRepository.softDelete(id); logger.info("Notification supprimée avec succès: {}", notification.getTitre()); } @Transactional public int deleteAnciennesNotifications(UUID userId, int jours) { logger.info( "Suppression des anciennes notifications (plus de {} jours) pour l'utilisateur: {}", jours, userId); return notificationRepository.deleteAnciennesByUser(userId, jours); } // === STATISTIQUES === public Object getStatistiques() { logger.debug("Génération des statistiques globales des notifications"); return new Object() { public final long totalNotifications = notificationRepository.count("actif = true"); public final long notificationsNonLues = notificationRepository.countNonLues(); public final long notificationsCritiques = notificationRepository.countCritiques(); public final long notificationsRecentes = notificationRepository.countRecentes(24); public final List parType = notificationRepository.getStatsByType(); public final List parPriorite = notificationRepository.getStatsByPriorite(); public final LocalDateTime genereA = LocalDateTime.now(); }; } public Object getStatistiquesUser(UUID userId) { logger.debug("Génération des statistiques des notifications pour l'utilisateur: {}", userId); return new Object() { public final long totalNotifications = notificationRepository.countByUser(userId); public final long notificationsNonLues = notificationRepository.countNonLuesByUser(userId); public final long notificationsCritiques = notificationRepository.countCritiquesByUser(userId); public final List dernieresNonLues = notificationRepository.findNonLuesByUser(userId).stream() .limit(5) .collect(Collectors.toList()); public final LocalDateTime genereA = LocalDateTime.now(); }; } public Object getTableauBordGlobal() { logger.debug("Génération du tableau de bord global des notifications"); List alertesCritiques = notificationRepository.findCritiques(); List notificationsRecentes = notificationRepository.findRecentes(10); return new Object() { public final String titre = "Tableau de Bord Global des Notifications"; public final Object resume = new Object() { public final long total = notificationRepository.count("actif = true"); public final long nonLues = notificationRepository.countNonLues(); public final long critiques = alertesCritiques.size(); public final boolean alerteCritique = !alertesCritiques.isEmpty(); }; public final List alertesCritiquesDetail = alertesCritiques.stream() .limit(5) .map( notification -> new Object() { public final String titre = notification.getTitre(); public final String type = notification.getType().toString(); public final String destinataire = notification.getUser().getEmail(); public final LocalDateTime dateCreation = notification.getDateCreation(); }) .collect(Collectors.toList()); public final List activiteRecente = notificationsRecentes.stream() .map( notif -> new Object() { public final String titre = notif.getTitre(); public final String type = notif.getType().toString(); public final String priorite = notif.getPriorite().toString(); public final LocalDateTime dateCreation = notif.getDateCreation(); }) .collect(Collectors.toList()); public final LocalDateTime genereA = LocalDateTime.now(); }; } public Object getTableauBordUser(UUID userId) { logger.debug("Génération du tableau de bord des notifications pour l'utilisateur: {}", userId); List notificationsNonLues = notificationRepository.findNonLuesByUser(userId); List notificationsRecentes = notificationRepository.findRecentsByUser(userId, 5); final UUID userIdFinal = userId; return new Object() { public final String titre = "Mes Notifications"; public final UUID userId = userIdFinal; public final Object resume = new Object() { public final long total = notificationRepository.countByUser(userIdFinal); public final long nonLues = notificationsNonLues.size(); public final long critiques = notificationsNonLues.stream().filter(Notification::estCritique).count(); public final boolean alerteCritique = notificationsNonLues.stream().anyMatch(Notification::estCritique); }; public final List nonLues = notificationsNonLues.stream() .limit(10) .map( n -> new Object() { public final UUID id = n.getId(); public final String titre = n.getTitre(); public final String message = n.getMessage(); public final String type = n.getType().toString(); public final String priorite = n.getPriorite().toString(); public final LocalDateTime dateCreation = n.getDateCreation(); public final String lienAction = n.getLienAction(); }) .collect(Collectors.toList()); public final List recentes = notificationsRecentes.stream() .map( n -> new Object() { public final UUID id = n.getId(); public final String titre = n.getTitre(); public final String type = n.getType().toString(); public final String priorite = n.getPriorite().toString(); public final boolean lue = n.getLue(); public final LocalDateTime dateCreation = n.getDateCreation(); }) .collect(Collectors.toList()); public final LocalDateTime genereA = LocalDateTime.now(); }; } // === MÉTHODES PRIVÉES === private void validateNotificationData(String titre, String message, String type, UUID userId) { if (titre == null || titre.trim().isEmpty()) { throw new BadRequestException("Le titre de la notification est obligatoire"); } if (message == null || message.trim().isEmpty()) { throw new BadRequestException("Le message de la notification est obligatoire"); } if (type == null || type.trim().isEmpty()) { throw new BadRequestException("Le type de notification est obligatoire"); } if (userId != null && userRepository.findByIdOptional(userId).isEmpty()) { throw new BadRequestException("Utilisateur non trouvé: " + userId); } } private TypeNotification parseTypeRequired(String typeStr) { try { return TypeNotification.valueOf(typeStr.toUpperCase()); } catch (IllegalArgumentException e) { throw new BadRequestException("Type de notification invalide: " + typeStr); } } private PrioriteNotification parsePriorite( String prioriteStr, PrioriteNotification defaultValue) { if (prioriteStr == null || prioriteStr.trim().isEmpty()) { return defaultValue; } try { return PrioriteNotification.valueOf(prioriteStr.toUpperCase()); } catch (IllegalArgumentException e) { logger.warn( "Priorité de notification invalide: {}, utilisation de la valeur par défaut", prioriteStr); return defaultValue; } } private User getUserById(UUID userId) { return userRepository .findByIdOptional(userId) .orElseThrow(() -> new BadRequestException("Utilisateur non trouvé: " + userId)); } private Chantier getChantierById(UUID chantierId) { return chantierRepository .findByIdOptional(chantierId) .orElseThrow(() -> new BadRequestException("Chantier non trouvé: " + chantierId)); } private List getUsersByRole(String role) { logger.debug("Recherche des utilisateurs par rôle: {}", role); try { UserRole roleEnum = UserRole.valueOf(role.toUpperCase()); return userRepository.findByRole(roleEnum); } catch (IllegalArgumentException e) { logger.warn("Rôle invalide: {}, retour de liste vide", role); return List.of(); } } private List getUsersForMaintenance(MaintenanceMateriel maintenance) { logger.debug( "Récupération des utilisateurs concernés par la maintenance: {}", maintenance.getId()); List users = new ArrayList<>(); // Récupérer les techniciens de maintenance List techniciens = userRepository.findByRole(UserRole.OUVRIER); users.addAll(techniciens); // Récupérer les chefs de chantier et responsables List responsables = userRepository.findByRole(UserRole.CHEF_CHANTIER); users.addAll(responsables); // Récupérer les managers List managers = userRepository.findByRole(UserRole.MANAGER); users.addAll(managers); return users.stream().distinct().collect(Collectors.toList()); } private List getUsersForChantier(Chantier chantier) { logger.debug("Récupération des utilisateurs concernés par le chantier: {}", chantier.getId()); List users = new ArrayList<>(); // Ajouter le client du chantier si disponible if (chantier.getClient() != null && chantier.getClient().getCompteUtilisateur() != null) { users.add(chantier.getClient().getCompteUtilisateur()); } // Ajouter le chef de chantier assigné if (chantier.getChefChantier() != null) { users.add(chantier.getChefChantier()); } // Ajouter les gestionnaires de projet List gestionnaires = userRepository.findByRole(UserRole.GESTIONNAIRE_PROJET); users.addAll(gestionnaires); // Ajouter les managers List managers = userRepository.findByRole(UserRole.MANAGER); users.addAll(managers); return users.stream().distinct().collect(Collectors.toList()); } }