feat: add current events section for equipment with dynamic status calculation

This commit is contained in:
ElPoyo
2026-01-06 12:13:09 +01:00
parent 25d395b41a
commit fb6a271f66
12 changed files with 773 additions and 124 deletions

View File

@@ -0,0 +1,235 @@
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<EventModel>? _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<EquipmentStatus> 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<Map<String, EquipmentStatus>> calculateMultipleStatuses(
List<EquipmentModel> equipments,
) async {
await _loadEventsIfNeeded();
final statuses = <String, EquipmentStatus>{};
// Trouver tous les équipements en cours d'utilisation
final equipmentIdsInUse = <String>{};
final containerIdsInUse = <String>{};
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<String>.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<bool> _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<String>.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<void> _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<EventModel>()
.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();
}
}