import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/event_model.dart'; /// Service pour calculer dynamiquement le statut réel d'un équipement /// basé sur les événements en cours class EquipmentStatusCalculator { final FirebaseFirestore _firestore = FirebaseFirestore.instance; /// Cache des événements pour éviter de multiples requêtes List? _cachedEvents; DateTime? _cacheTime; static const _cacheDuration = Duration(minutes: 1); /// Instance statique pour permettre l'invalidation depuis n'importe où static final EquipmentStatusCalculator _instance = EquipmentStatusCalculator._internal(); factory EquipmentStatusCalculator() { return _instance; } EquipmentStatusCalculator._internal(); /// Calcule le statut réel d'un équipement basé sur les événements Future calculateRealStatus(EquipmentModel equipment) async { print('[StatusCalculator] Calculating status for: ${equipment.id}'); // Si l'équipement est marqué comme perdu ou HS, on garde ce statut // car c'est une information métier importante if (equipment.status == EquipmentStatus.lost || equipment.status == EquipmentStatus.outOfService) { print('[StatusCalculator] ${equipment.id} is lost/outOfService -> keeping status'); return equipment.status; } // Charger les événements (avec cache) await _loadEventsIfNeeded(); print('[StatusCalculator] Events loaded: ${_cachedEvents?.length ?? 0}'); // Vérifier si l'équipement est utilisé dans un événement en cours final isInUse = await _isEquipmentInUse(equipment.id); print('[StatusCalculator] ${equipment.id} isInUse: $isInUse'); if (isInUse) { return EquipmentStatus.inUse; } // Vérifier si l'équipement est en maintenance if (equipment.status == EquipmentStatus.maintenance) { // On pourrait vérifier si la maintenance est toujours valide // Pour l'instant on garde le statut return EquipmentStatus.maintenance; } // Vérifier si l'équipement est loué if (equipment.status == EquipmentStatus.rented) { // On pourrait vérifier une date de retour prévue // Pour l'instant on garde le statut return EquipmentStatus.rented; } // Par défaut, l'équipement est disponible print('[StatusCalculator] ${equipment.id} -> AVAILABLE'); return EquipmentStatus.available; } /// Calcule les statuts pour une liste d'équipements (optimisé) Future> calculateMultipleStatuses( List equipments, ) async { await _loadEventsIfNeeded(); final statuses = {}; // Trouver tous les équipements en cours d'utilisation final equipmentIdsInUse = {}; final containerIdsInUse = {}; for (var event in _cachedEvents ?? []) { // Un équipement est "en prestation" dès que la préparation est complétée // et jusqu'à ce que le retour soit complété final isPrepared = event.preparationStatus == PreparationStatus.completed || event.preparationStatus == PreparationStatus.completedWithMissing; final isReturned = event.returnStatus == ReturnStatus.completed || event.returnStatus == ReturnStatus.completedWithMissing; final isInProgress = isPrepared && !isReturned; if (isInProgress) { // Ajouter les équipements directs for (var eq in event.assignedEquipment) { equipmentIdsInUse.add(eq.equipmentId); } // Ajouter les conteneurs containerIdsInUse.addAll(event.assignedContainers); } } // Récupérer les équipements dans les conteneurs en cours d'utilisation if (containerIdsInUse.isNotEmpty) { final containersSnapshot = await _firestore .collection('containers') .where(FieldPath.documentId, whereIn: containerIdsInUse.toList()) .get(); for (var doc in containersSnapshot.docs) { final data = doc.data(); final equipmentIds = List.from(data['equipmentIds'] ?? []); equipmentIdsInUse.addAll(equipmentIds); } } // Calculer le statut pour chaque équipement for (var equipment in equipments) { // Si perdu ou HS, on garde le statut if (equipment.status == EquipmentStatus.lost || equipment.status == EquipmentStatus.outOfService) { statuses[equipment.id] = equipment.status; continue; } // Si en cours d'utilisation if (equipmentIdsInUse.contains(equipment.id)) { statuses[equipment.id] = EquipmentStatus.inUse; continue; } // Si en maintenance ou loué, on garde le statut if (equipment.status == EquipmentStatus.maintenance || equipment.status == EquipmentStatus.rented) { statuses[equipment.id] = equipment.status; continue; } // Par défaut, disponible statuses[equipment.id] = EquipmentStatus.available; } return statuses; } /// Vérifie si un équipement est actuellement en cours d'utilisation Future _isEquipmentInUse(String equipmentId) async { print('[StatusCalculator] Checking if $equipmentId is in use...'); // Vérifier dans les événements directs for (var event in _cachedEvents ?? []) { // Un équipement est "en prestation" dès que la préparation est complétée // et jusqu'à ce que le retour soit complété final isPrepared = event.preparationStatus == PreparationStatus.completed || event.preparationStatus == PreparationStatus.completedWithMissing; final isReturned = event.returnStatus == ReturnStatus.completed || event.returnStatus == ReturnStatus.completedWithMissing; final isInProgress = isPrepared && !isReturned; if (!isInProgress) continue; print('[StatusCalculator] Event ${event.name} is IN PROGRESS (prepared and not returned)'); // Vérifier si l'équipement est directement assigné if (event.assignedEquipment.any((eq) => eq.equipmentId == equipmentId)) { print('[StatusCalculator] $equipmentId found DIRECTLY in event ${event.name}'); return true; } // Vérifier si l'équipement est dans un conteneur assigné if (event.assignedContainers.isNotEmpty) { print('[StatusCalculator] Checking containers for event ${event.name}: ${event.assignedContainers}'); final containersSnapshot = await _firestore .collection('containers') .where(FieldPath.documentId, whereIn: event.assignedContainers) .get(); for (var doc in containersSnapshot.docs) { final data = doc.data(); final equipmentIds = List.from(data['equipmentIds'] ?? []); print('[StatusCalculator] Container ${doc.id} contains: $equipmentIds'); if (equipmentIds.contains(equipmentId)) { print('[StatusCalculator] $equipmentId found in CONTAINER ${doc.id}'); return true; } } } } print('[StatusCalculator] $equipmentId is NOT in use'); return false; } /// Charge les événements si le cache est expiré Future _loadEventsIfNeeded() async { if (_cachedEvents != null && _cacheTime != null && DateTime.now().difference(_cacheTime!) < _cacheDuration) { return; // Cache encore valide } try { final eventsSnapshot = await _firestore.collection('events').get(); _cachedEvents = eventsSnapshot.docs .map((doc) { try { return EventModel.fromMap(doc.data(), doc.id); } catch (e) { print('[EquipmentStatusCalculator] Error parsing event ${doc.id}: $e'); return null; } }) .whereType() .where((event) => event.status != EventStatus.canceled) // Ignorer les événements annulés .toList(); _cacheTime = DateTime.now(); } catch (e) { print('[EquipmentStatusCalculator] Error loading events: $e'); _cachedEvents = []; } } /// Invalide le cache (à appeler après une modification d'événement) void invalidateCache() { _cachedEvents = null; _cacheTime = null; } /// Invalide le cache de l'instance globale (méthode statique) static void invalidateGlobalCache() { _instance.invalidateCache(); } }