From 845b6e91d2dc4b8c1391c053ace1ac56b50c9f60 Mon Sep 17 00:00:00 2001 From: ElPoyo Date: Tue, 26 May 2026 13:50:35 +0200 Subject: [PATCH] =?UTF-8?q?perf:=20suppression=20du=20t=C3=A9l=C3=A9charge?= =?UTF-8?q?ment=20massif=20d'=C3=A9v=C3=A9nements=20c=C3=B4t=C3=A9=20clien?= =?UTF-8?q?t=20(appel=20de=20la=20CF=20checkContainerAvailability=20et=20l?= =?UTF-8?q?ecture=20synchrone=20des=20quantit=C3=A9s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- em2rp/lib/services/container_service.dart | 53 +--- em2rp/lib/services/data_service.dart | 20 ++ .../services/event_availability_service.dart | 295 ++---------------- .../event/equipment_selection_dialog.dart | 19 +- 4 files changed, 78 insertions(+), 309 deletions(-) diff --git a/em2rp/lib/services/container_service.dart b/em2rp/lib/services/container_service.dart index 462e2ac..b0db340 100644 --- a/em2rp/lib/services/container_service.dart +++ b/em2rp/lib/services/container_service.dart @@ -141,7 +141,6 @@ class ContainerService { } } - /// Vérifier la disponibilité d'un container et de son contenu pour un événement Future> checkContainerAvailability({ required String containerId, required DateTime startDate, @@ -149,43 +148,21 @@ class ContainerService { String? excludeEventId, }) async { try { - final container = await getContainerById(containerId); - if (container == null) { - return {'available': false, 'message': 'Container non trouvé'}; - } - - // Vérifier le statut du container - if (container.status != EquipmentStatus.available) { - return { - 'available': false, - 'message': 'Container ${container.name} n\'est pas disponible (statut: ${container.status})', - }; - } - - // Vérifier la disponibilité de chaque équipement dans le container - List unavailableEquipment = []; - - if (container.equipmentIds.isNotEmpty) { - final equipmentsData = await _dataService.getEquipmentsByIds(container.equipmentIds); - - for (var data in equipmentsData) { - final id = data['id'] as String; - final equipment = EquipmentModel.fromMap(data, id); - if (equipment.status != EquipmentStatus.available) { - unavailableEquipment.add('${equipment.name} (${equipment.status})'); - } - } - } - - if (unavailableEquipment.isNotEmpty) { - return { - 'available': false, - 'message': 'Certains équipements ne sont pas disponibles', - 'unavailableItems': unavailableEquipment, - }; - } - - return {'available': true, 'message': 'Container et tout son contenu disponibles'}; + final result = await _dataService.checkContainerAvailability( + containerId: containerId, + startDate: startDate, + endDate: endDate, + excludeEventId: excludeEventId, + ); + return { + 'available': result['isAvailable'] ?? false, + 'message': result['isAvailable'] == true + ? 'Container et tout son contenu disponibles' + : 'Container non disponible ou en conflit', + 'conflictType': result['conflictType'], + 'containerConflicts': result['containerConflicts'], + 'equipmentConflicts': result['equipmentConflicts'], + }; } catch (e) { print('Error checking container availability: $e'); return {'available': false, 'message': 'Erreur: $e'}; diff --git a/em2rp/lib/services/data_service.dart b/em2rp/lib/services/data_service.dart index db6c5ec..bd25e52 100644 --- a/em2rp/lib/services/data_service.dart +++ b/em2rp/lib/services/data_service.dart @@ -779,6 +779,26 @@ class DataService { } } + /// Vérifie la disponibilité d'un container + Future> checkContainerAvailability({ + required String containerId, + required DateTime startDate, + required DateTime endDate, + String? excludeEventId, + }) async { + try { + final result = await _apiService.call('checkContainerAvailability', { + 'containerId': containerId, + 'startDate': startDate.toIso8601String(), + 'endDate': endDate.toIso8601String(), + if (excludeEventId != null) 'excludeEventId': excludeEventId, + }); + return result; + } catch (e) { + throw Exception('Erreur lors de la vérification de disponibilité du container: $e'); + } + } + /// Récupère tous les IDs d'équipements et conteneurs en conflit pour une période /// Optimisé : une seule requête au lieu d'une par équipement Future> getConflictingEquipmentIds({ diff --git a/em2rp/lib/services/event_availability_service.dart b/em2rp/lib/services/event_availability_service.dart index 664d8cc..7d149a0 100644 --- a/em2rp/lib/services/event_availability_service.dart +++ b/em2rp/lib/services/event_availability_service.dart @@ -67,27 +67,19 @@ class AvailabilityConflict { class EventAvailabilityService { final DataService _dataService = DataService(apiService); - /// Helper pour récupérer uniquement la liste d'événements - Future>> _getEventsList() async { - final result = await _dataService.getEvents(); - final events = result['events'] as List? ?? []; - return events.map((e) => e as Map).toList(); - } - /// Vérifie si un équipement est disponible pour une plage de dates via Cloud Function 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 + String? excludeEventId, }) async { final conflicts = []; try { if (kDebugMode) debugPrint('[EventAvailabilityService] Checking availability for equipment $equipmentId ($equipmentName)'); - // Utiliser la Cloud Function pour vérifier la disponibilité final result = await _dataService.checkEquipmentAvailability( equipmentId: equipmentId, startDate: startDate, @@ -95,20 +87,12 @@ class EventAvailabilityService { excludeEventId: excludeEventId, ); - if (kDebugMode) debugPrint('[EventAvailabilityService] Result for $equipmentId: $result'); - final available = result['available'] as bool? ?? true; - if (kDebugMode) debugPrint('[EventAvailabilityService] Equipment $equipmentId available: $available'); - if (!available) { final conflictsData = result['conflicts'] as List? ?? []; - if (kDebugMode) debugPrint('[EventAvailabilityService] Found ${conflictsData.length} conflicts for equipment $equipmentId'); - for (final conflictData in conflictsData) { final conflict = conflictData as Map; final eventId = conflict['eventId'] as String; - - // Le backend retourne déjà eventData final eventData = conflict['eventData'] as Map?; if (eventData != null && eventData.isNotEmpty) { @@ -120,10 +104,8 @@ class EventAvailabilityService { conflictingEvent: event, overlapDays: conflict['overlapDays'] as int? ?? 0, )); - if (kDebugMode) debugPrint('[EventAvailabilityService] Added conflict with event ${event.name}'); } catch (e) { if (kDebugMode) debugPrint('[EventAvailabilityService] Error creating EventModel: $e'); - if (kDebugMode) debugPrint('[EventAvailabilityService] EventData: $eventData'); } } } @@ -132,7 +114,6 @@ class EventAvailabilityService { if (kDebugMode) debugPrint('[EventAvailabilityService] Error checking availability: $e'); } - if (kDebugMode) debugPrint('[EventAvailabilityService] Returning ${conflicts.length} conflicts for equipment $equipmentId'); return conflicts; } @@ -160,164 +141,10 @@ class EventAvailabilityService { } } - 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 via Cloud Function - final eventsData = await _getEventsList(); - - for (var eventData in eventsData) { - final eventId = eventData['id'] as String; - if (excludeEventId != null && eventId == excludeEventId) { - continue; - } - - try { - final event = EventModel.fromMap(eventData, eventId); - - // Ignorer les événements annulés - if (event.status == EventStatus.canceled) { - continue; - } - - // Calculer les dates réelles avec temps d'installation et démontage - final eventRealStartDate = event.startDateTime.subtract( - Duration(hours: event.installationTime), - ); - final eventRealEndDate = event.endDateTime.add( - Duration(hours: event.disassemblyTime), - ); - - // Vérifier le chevauchement des dates - if (_datesOverlap(startDate, endDate, eventRealStartDate, eventRealEndDate)) { - final assignedEquipment = event.assignedEquipment.firstWhere( - (eq) => eq.equipmentId == equipment.id, - orElse: () => EventEquipment(equipmentId: ''), - ); - - // Si l'équipement est assigné, réserver la quantité - // (peu importe le statut de préparation/retour) - if (assignedEquipment.equipmentId.isNotEmpty) { - reservedQuantity += assignedEquipment.quantity; - } - } - } catch (e) { - if (kDebugMode) debugPrint('[EventAvailabilityService] Error processing event $eventId for quantity: $e'); - } - } - } catch (e) { - if (kDebugMode) debugPrint('[EventAvailabilityService] Error getting available quantity: $e'); - } - - return totalQuantity - reservedQuantity; - } - - /// Vérifie la disponibilité d'un équipement avec gestion des quantités - Future> checkEquipmentAvailabilityWithQuantity({ - required EquipmentModel equipment, - required int requestedQuantity, - required DateTime startDate, - required DateTime endDate, - String? excludeEventId, - }) async { - final conflicts = []; - - // Si équipement quantifiable (consommable/câble) - if (equipment.hasQuantity) { - final totalQuantity = equipment.totalQuantity ?? 0; - final availableQty = await getAvailableQuantity( - equipment: equipment, - startDate: startDate, - endDate: endDate, - excludeEventId: excludeEventId, - ); - final reservedQty = totalQuantity - availableQty; - - // ✅ Ne créer un conflit que si la quantité est VRAIMENT insuffisante - if (availableQty < requestedQuantity) { - // Trouver les événements qui réservent cette quantité - final eventsData = await _getEventsList(); - - for (var eventData in eventsData) { - final eventId = eventData['id'] as String; - if (excludeEventId != null && eventId == excludeEventId) continue; - - try { - final event = EventModel.fromMap(eventData, eventId); - - 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) { - conflicts.add(AvailabilityConflict( - equipmentId: equipment.id, - equipmentName: equipment.name, - conflictingEvent: event, - overlapDays: _calculateOverlapDays(startDate, endDate, event.startDateTime, event.endDateTime), - type: ConflictType.insufficientQuantity, - totalQuantity: totalQuantity, - availableQuantity: availableQty, - requestedQuantity: requestedQuantity, - reservedQuantity: reservedQty, - )); - } - } - } catch (e) { - if (kDebugMode) debugPrint('[EventAvailabilityService] Error processing event $eventId: $e'); - } - } - } - } else { - // Équipement non quantifiable : vérification classique - return await checkEquipmentAvailability( - equipmentId: equipment.id, - equipmentName: equipment.name, - startDate: startDate, - endDate: endDate, - excludeEventId: excludeEventId, - ); - } - - return conflicts; - } - - /// Vérifie la disponibilité d'une boîte et de son contenu + /// Vérifie la disponibilité d'une boîte et de son contenu via le backend Future> checkContainerAvailability({ required ContainerModel container, required List containerEquipment, @@ -326,99 +153,45 @@ class EventAvailabilityService { String? excludeEventId, }) async { final conflicts = []; - final conflictingChildrenIds = []; - // Vérifier d'abord si la boîte complète est utilisée - final eventsData = await _getEventsList(); - bool isContainerFullyUsed = false; - EventModel? containerConflictingEvent; + try { + final result = await _dataService.checkContainerAvailability( + containerId: container.id, + startDate: startDate, + endDate: endDate, + excludeEventId: excludeEventId, + ); - for (var eventData in eventsData) { - final eventId = eventData['id'] as String; - if (excludeEventId != null && eventId == excludeEventId) continue; - - try { - final event = EventModel.fromMap(eventData, eventId); - - // Ignorer les événements annulés - if (event.status == EventStatus.canceled) { - continue; - } - - // Calculer les dates réelles avec temps d'installation et démontage - final eventRealStartDate = event.startDateTime.subtract( - Duration(hours: event.installationTime), - ); - final eventRealEndDate = event.endDateTime.add( - Duration(hours: event.disassemblyTime), - ); - - // Vérifier si cette boîte est assignée - if (event.assignedContainers.contains(container.id)) { - if (_datesOverlap(startDate, endDate, eventRealStartDate, eventRealEndDate)) { - isContainerFullyUsed = true; - containerConflictingEvent = event; - break; + final isAvailable = result['isAvailable'] as bool? ?? true; + if (!isAvailable) { + final conflictType = result['conflictType'] as String?; + + if (conflictType == 'complete') { + final containerConflicts = result['containerConflicts'] as List? ?? []; + for (var conflictData in containerConflicts) { + final conflict = conflictData as Map; + final eventId = conflict['eventId'] as String; + final eventDoc = await _dataService.getEvents(); + final eventData = (eventDoc['events'] as List).firstWhere((e) => e['id'] == eventId, orElse: () => null); + if (eventData != null) { + final event = EventModel.fromMap(eventData as Map, eventId); + conflicts.add(AvailabilityConflict( + equipmentId: container.id, + equipmentName: container.name, + conflictingEvent: event, + overlapDays: conflict['overlapDays'] as int? ?? 0, + type: ConflictType.containerFullyUsed, + containerId: container.id, + containerName: container.name, + )); + } } } - } catch (e) { - if (kDebugMode) debugPrint('[EventAvailabilityService] Error processing event $eventId: $e'); - } - } - - if (isContainerFullyUsed && containerConflictingEvent != null) { - // Boîte complète utilisée - conflicts.add(AvailabilityConflict( - equipmentId: container.id, - equipmentName: container.name, - conflictingEvent: containerConflictingEvent, - overlapDays: _calculateOverlapDays( - startDate, - endDate, - containerConflictingEvent.startDateTime, - containerConflictingEvent.endDateTime, - ), - type: ConflictType.containerFullyUsed, - containerId: container.id, - containerName: container.name, - )); - } else { - // Vérifier chaque équipement enfant individuellement - for (var equipment in containerEquipment) { - final equipmentConflicts = await checkEquipmentAvailability( - equipmentId: equipment.id, - equipmentName: equipment.name, - startDate: startDate, - endDate: endDate, - excludeEventId: excludeEventId, - ); - - if (equipmentConflicts.isNotEmpty) { - conflictingChildrenIds.add(equipment.id); - conflicts.addAll(equipmentConflicts); - } - } - - // Si au moins un enfant en conflit, ajouter un conflit pour la boîte - if (conflictingChildrenIds.isNotEmpty && conflicts.isNotEmpty) { - conflicts.insert( - 0, - AvailabilityConflict( - equipmentId: container.id, - equipmentName: container.name, - conflictingEvent: conflicts.first.conflictingEvent, - overlapDays: conflicts.first.overlapDays, - type: ConflictType.containerPartiallyUsed, - containerId: container.id, - containerName: container.name, - conflictingChildrenIds: conflictingChildrenIds, - ), - ); } + } catch (e) { + if (kDebugMode) debugPrint('[EventAvailabilityService] Error checking container availability: $e'); } return conflicts; } } - - diff --git a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart index 91f917f..7b8434a 100644 --- a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart +++ b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/models/event_model.dart'; -import 'package:em2rp/services/event_availability_service.dart'; import 'package:em2rp/services/data_service.dart'; import 'package:em2rp/services/api_service.dart'; import 'package:em2rp/utils/colors.dart'; @@ -88,7 +87,6 @@ class EquipmentSelectionDialog extends StatefulWidget { class _EquipmentSelectionDialogState extends State { final TextEditingController _searchController = TextEditingController(); final ScrollController _scrollController = ScrollController(); // Pr├®serve la position de scroll - final EventAvailabilityService _availabilityService = EventAvailabilityService(); final DataService _dataService = DataService(apiService); EquipmentCategory? _selectedCategory; @@ -279,7 +277,7 @@ class _EquipmentSelectionDialogState extends State { DebugLog.info('[EquipmentSelectionDialog] Loaded ${newEquipments.length} equipments, total: ${_paginatedEquipments.length}, hasMore: $_hasMoreEquipments'); // Charger les quantites pour les consommables/cbles de cette page - await _loadAvailableQuantities(newEquipments); + _loadAvailableQuantities(newEquipments); // Vrifier si on doit charger d'autres lments (ex: tout a t filtr) WidgetsBinding.instance.addPostFrameCallback((_) { @@ -442,7 +440,7 @@ class _EquipmentSelectionDialogState extends State { } /// Charge les quantit├®s disponibles pour les consommables/c├óbles d'une liste d'├®quipements - Future _loadAvailableQuantities(List equipments) async { + void _loadAvailableQuantities(List equipments) { if (!mounted) return; try { @@ -453,12 +451,13 @@ class _EquipmentSelectionDialogState extends State { for (var eq in consumables) { // Ne recharger que si on n'a pas d├®j├á la quantit├® if (!_availableQuantities.containsKey(eq.id)) { - final available = await _availabilityService.getAvailableQuantity( - equipment: eq, - startDate: widget.startDate, - endDate: widget.endDate, - excludeEventId: widget.excludeEventId, - ); + int available = eq.totalQuantity ?? 0; + if (_equipmentQuantities.containsKey(eq.id)) { + final qtyInfo = _equipmentQuantities[eq.id]; + if (qtyInfo != null && qtyInfo['availableQuantity'] != null) { + available = (qtyInfo['availableQuantity'] as num).toInt(); + } + } _availableQuantities[eq.id] = available; } }