import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:em2rp/models/event_model.dart'; import 'package:em2rp/models/equipment_model.dart'; /// Informations sur un conflit de disponibilité class AvailabilityConflict { final String equipmentId; final String equipmentName; final EventModel conflictingEvent; final int overlapDays; AvailabilityConflict({ required this.equipmentId, required this.equipmentName, required this.conflictingEvent, required this.overlapDays, }); } /// Service pour vérifier la disponibilité du matériel class EventAvailabilityService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; /// Vérifie si un équipement est disponible pour une plage de dates Future> checkEquipmentAvailability({ required String equipmentId, required String equipmentName, required DateTime startDate, required DateTime endDate, String? excludeEventId, // Pour exclure l'événement en cours d'édition }) async { final conflicts = []; try { print('[EventAvailabilityService] Checking availability for equipment: $equipmentId'); print('[EventAvailabilityService] Date range: $startDate - $endDate'); // Récupérer TOUS les événements (on filtre côté client car arrayContains avec objet ne marche pas) final eventsSnapshot = await _firestore.collection('events').get(); print('[EventAvailabilityService] Found ${eventsSnapshot.docs.length} total events'); for (var doc in eventsSnapshot.docs) { if (excludeEventId != null && doc.id == excludeEventId) { continue; // Ignorer l'événement en cours d'édition } try { final event = EventModel.fromMap(doc.data(), doc.id); // Vérifier si cet événement contient l'équipement recherché final assignedEquipment = event.assignedEquipment.firstWhere( (eq) => eq.equipmentId == equipmentId, orElse: () => EventEquipment(equipmentId: ''), ); // Si l'équipement est assigné et non retourné if (assignedEquipment.equipmentId.isNotEmpty && !assignedEquipment.isReturned) { print('[EventAvailabilityService] Equipment $equipmentId found in event: ${event.name}'); // Vérifier le chevauchement des dates if (_datesOverlap(startDate, endDate, event.startDateTime, event.endDateTime)) { final overlapDays = _calculateOverlapDays( startDate, endDate, event.startDateTime, event.endDateTime, ); print('[EventAvailabilityService] CONFLICT detected! Overlap: $overlapDays days'); conflicts.add(AvailabilityConflict( equipmentId: equipmentId, equipmentName: equipmentName, conflictingEvent: event, overlapDays: overlapDays, )); } } } catch (e) { print('[EventAvailabilityService] Error processing event ${doc.id}: $e'); } } print('[EventAvailabilityService] Total conflicts found for $equipmentId: ${conflicts.length}'); } catch (e) { print('[EventAvailabilityService] Error checking equipment availability: $e'); } return conflicts; } /// Vérifie la disponibilité pour une liste d'équipements Future>> checkMultipleEquipmentAvailability({ required List equipmentIds, required Map equipmentNames, required DateTime startDate, required DateTime endDate, String? excludeEventId, }) async { final allConflicts = >{}; for (var equipmentId in equipmentIds) { final conflicts = await checkEquipmentAvailability( equipmentId: equipmentId, equipmentName: equipmentNames[equipmentId] ?? equipmentId, startDate: startDate, endDate: endDate, excludeEventId: excludeEventId, ); if (conflicts.isNotEmpty) { allConflicts[equipmentId] = conflicts; } } return allConflicts; } /// Vérifie si deux plages de dates se chevauchent bool _datesOverlap(DateTime start1, DateTime end1, DateTime start2, DateTime end2) { // Deux plages se chevauchent si elles ne sont PAS complètement séparées // Elles sont séparées si : end1 < start2 OU end2 < start1 // Donc elles se chevauchent si : NOT (end1 < start2 OU end2 < start1) // Équivalent à : end1 >= start2 ET end2 >= start1 return !end1.isBefore(start2) && !end2.isBefore(start1); } /// Calcule le nombre de jours de chevauchement int _calculateOverlapDays(DateTime start1, DateTime end1, DateTime start2, DateTime end2) { final overlapStart = start1.isAfter(start2) ? start1 : start2; final overlapEnd = end1.isBefore(end2) ? end1 : end2; return overlapEnd.difference(overlapStart).inDays + 1; } /// Récupère la quantité disponible pour un consommable/câble Future getAvailableQuantity({ required EquipmentModel equipment, required DateTime startDate, required DateTime endDate, String? excludeEventId, }) async { if (!equipment.hasQuantity) { return 1; // Équipement non consommable } final totalQuantity = equipment.totalQuantity ?? 0; int reservedQuantity = 0; try { // Récupérer tous les événements (on filtre côté client) final eventsSnapshot = await _firestore.collection('events').get(); for (var doc in eventsSnapshot.docs) { if (excludeEventId != null && doc.id == excludeEventId) { continue; } try { final event = EventModel.fromMap(doc.data(), doc.id); // Vérifier le chevauchement des dates if (_datesOverlap(startDate, endDate, event.startDateTime, event.endDateTime)) { final assignedEquipment = event.assignedEquipment.firstWhere( (eq) => eq.equipmentId == equipment.id, orElse: () => EventEquipment(equipmentId: ''), ); if (assignedEquipment.equipmentId.isNotEmpty && !assignedEquipment.isReturned) { reservedQuantity += assignedEquipment.quantity; } } } catch (e) { print('[EventAvailabilityService] Error processing event ${doc.id} for quantity: $e'); } } } catch (e) { print('[EventAvailabilityService] Error getting available quantity: $e'); } return totalQuantity - reservedQuantity; } }