import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/maintenance_model.dart'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/services/api_service.dart'; import 'package:em2rp/services/data_service.dart'; import 'package:em2rp/services/maintenance_service.dart'; class EquipmentService { final ApiService _apiService = apiService; final DataService _dataService = DataService(apiService); // ============================================================================ // CRUD Operations - Utilise le backend sécurisé // ============================================================================ /// Créer un nouvel équipement (via Cloud Function) Future createEquipment(EquipmentModel equipment) async { try { if (equipment.id.isEmpty) { throw Exception('L\'ID de l\'équipement est requis pour la création'); } final data = equipment.toMap(); data['id'] = equipment.id; // S'assurer que l'ID est inclus await _apiService.call('createEquipment', data); } catch (e) { print('Error creating equipment: $e'); rethrow; } } /// Mettre à jour un équipement (via Cloud Function) Future updateEquipment(String id, Map data) async { try { if (data.isEmpty) { throw Exception('Aucune donnée à mettre à jour'); } await _apiService.call('updateEquipment', { 'equipmentId': id, 'data': data, }); } catch (e) { print('Error updating equipment: $e'); rethrow; } } /// Supprimer un équipement (via Cloud Function) Future deleteEquipment(String id) async { try { await _apiService.call('deleteEquipment', {'equipmentId': id}); } catch (e) { print('Error deleting equipment: $e'); rethrow; } } // ============================================================================ // READ Operations - Utilise Firestore streams (temps réel) // ============================================================================ /// Récupérer un équipement par ID Future getEquipmentById(String id) async { try { final equipmentsData = await _dataService.getEquipmentsByIds([id]); if (equipmentsData.isEmpty) return null; return EquipmentModel.fromMap(equipmentsData.first, id); } catch (e) { print('Error getting equipment: $e'); rethrow; } } /// Récupérer les équipements avec filtres Future> getEquipment({ EquipmentCategory? category, EquipmentStatus? status, String? model, String? searchQuery, }) async { try { final equipmentsData = await _dataService.getEquipments(); var equipmentList = equipmentsData .map((data) => EquipmentModel.fromMap(data, data['id'] as String)) .toList(); // Filtres côté client if (category != null) { equipmentList = equipmentList .where((e) => e.category == category) .toList(); } if (status != null) { equipmentList = equipmentList .where((e) => e.status == status) .toList(); } if (model != null && model.isNotEmpty) { equipmentList = equipmentList .where((e) => e.model == model) .toList(); } if (searchQuery != null && searchQuery.isNotEmpty) { final lowerSearch = searchQuery.toLowerCase(); equipmentList = equipmentList.where((equipment) { return equipment.name.toLowerCase().contains(lowerSearch) || (equipment.model?.toLowerCase().contains(lowerSearch) ?? false) || equipment.id.toLowerCase().contains(lowerSearch); }).toList(); } return equipmentList; } catch (e) { print('Error getting equipment: $e'); rethrow; } } // ============================================================================ // Availability & Stock Management - Logique métier côté client // ============================================================================ /// Vérifier la disponibilité d'un équipement pour une période donnée Future>> checkAvailability( String equipmentId, DateTime startDate, DateTime endDate, ) async { try { final response = await _apiService.call('checkEquipmentAvailability', { 'equipmentId': equipmentId, 'startDate': startDate.toIso8601String(), 'endDate': endDate.toIso8601String(), }); final conflicts = (response['conflicts'] as List?) ?.map((c) => c as Map) .toList() ?? []; return conflicts; } catch (e) { print('Error checking availability: $e'); rethrow; } } /// Trouver des alternatives (même modèle) disponibles Future> findAlternatives( String model, DateTime startDate, DateTime endDate, ) async { try { final response = await _apiService.call('findAlternativeEquipment', { 'model': model, 'startDate': startDate.toIso8601String(), 'endDate': endDate.toIso8601String(), }); final alternatives = (response['alternatives'] as List?) ?.map((a) => EquipmentModel.fromMap(a as Map, a['id'] as String)) .toList() ?? []; return alternatives; } catch (e) { print('Error finding alternatives: $e'); rethrow; } } /// Mettre à jour le stock d'un consommable/câble Future updateStock(String id, int quantityChange) async { try { final equipment = await getEquipmentById(id); if (equipment == null) { throw Exception('Equipment not found'); } if (!equipment.hasQuantity) { throw Exception('Equipment does not have quantity tracking'); } final newAvailableQuantity = (equipment.availableQuantity ?? 0) + quantityChange; await updateEquipment(id, { 'availableQuantity': newAvailableQuantity, }); // Vérifier si le seuil critique est atteint if (equipment.criticalThreshold != null && newAvailableQuantity <= equipment.criticalThreshold!) { await _createLowStockAlert(equipment); } } catch (e) { print('Error updating stock: $e'); rethrow; } } /// Vérifier les stocks critiques et créer des alertes Future checkCriticalStock() async { try { final equipmentsData = await _dataService.getEquipments(); for (var data in equipmentsData) { final equipment = EquipmentModel.fromMap(data, data['id'] as String); // Filtrer uniquement les consommables et câbles if ((equipment.category == EquipmentCategory.consumable || equipment.category == EquipmentCategory.cable) && equipment.isCriticalStock) { await _createLowStockAlert(equipment); } } } catch (e) { print('Error checking critical stock: $e'); rethrow; } } /// Créer une alerte de stock faible Future _createLowStockAlert(EquipmentModel equipment) async { try { // Note: Cette fonction pourrait utiliser une Cloud Function dédiée dans le futur // Pour l'instant, on utilise l'API directement pour éviter de créer trop de fonctions // Cette méthode est appelée rarement et en arrière-plan await _apiService.call('createAlert', { 'type': 'LOW_STOCK', 'title': 'Stock critique', 'message': 'Stock critique pour ${equipment.name} (${equipment.model ?? ""}): ${equipment.availableQuantity}/${equipment.criticalThreshold}', 'severity': 'HIGH', 'equipmentId': equipment.id, }); } catch (e) { print('Error creating low stock alert: $e'); // Ne pas rethrow pour ne pas bloquer le processus } } /// Générer les données du QR code (ID de l'équipement) String generateQRCodeData(String equipmentId) { // Pour l'instant, on retourne simplement l'ID // On pourrait aussi générer une URL complète : https://app.em2events.fr/equipment/$equipmentId return equipmentId; } /// Récupérer tous les modèles uniques (pour l'indexation/autocomplete) Future> getAllModels() async { try { final equipmentsData = await _dataService.getEquipments(); final models = {}; for (var data in equipmentsData) { final model = data['model'] as String?; if (model != null && model.isNotEmpty) { models.add(model); } } return models.toList()..sort(); } catch (e) { print('Error getting all models: $e'); rethrow; } } /// Récupérer toutes les marques uniques (pour l'indexation/autocomplete) Future> getAllBrands() async { try { final equipmentsData = await _dataService.getEquipments(); final brands = {}; for (var data in equipmentsData) { final brand = data['brand'] as String?; if (brand != null && brand.isNotEmpty) { brands.add(brand); } } return brands.toList()..sort(); } catch (e) { print('Error getting all brands: $e'); rethrow; } } /// Récupérer les modèles filtrés par marque Future> getModelsByBrand(String brand) async { try { final equipmentsData = await _dataService.getEquipments(); final models = {}; for (var data in equipmentsData) { if (data['brand'] == brand) { final model = data['model'] as String?; if (model != null && model.isNotEmpty) { models.add(model); } } } return models.toList()..sort(); } catch (e) { print('Error getting models by brand: $e'); rethrow; } } /// Vérifier si un ID existe déjà Future isIdUnique(String id) async { try { final equipment = await getEquipmentById(id); return equipment == null; } catch (e) { print('Error checking ID uniqueness: $e'); rethrow; } } /// Récupérer toutes les boîtes/containers disponibles Future> getBoxes() async { try { final containersData = await _dataService.getContainers(); final boxes = []; for (var data in containersData) { final id = data['id'] as String; final container = ContainerModel.fromMap(data, id); boxes.add(container); } return boxes; } catch (e) { print('Error getting boxes: $e'); rethrow; } } /// Récupérer plusieurs équipements par leurs IDs Future> getEquipmentsByIds(List ids) async { try { if (ids.isEmpty) return []; final equipmentsData = await _dataService.getEquipmentsByIds(ids); return equipmentsData .map((data) => EquipmentModel.fromMap(data, data['id'] as String)) .toList(); } catch (e) { print('Error getting equipments by IDs: $e'); rethrow; } } /// Récupérer les maintenances pour un équipement /// Note: Cette méthode est maintenant déléguée au MaintenanceService /// pour éviter la duplication de code Future> getMaintenancesForEquipment(String equipmentId) async { try { // Déléguer au MaintenanceService qui utilise déjà les Cloud Functions final maintenanceService = MaintenanceService(); return await maintenanceService.getMaintenancesByEquipment(equipmentId); } catch (e) { print('Error getting maintenances for equipment: $e'); rethrow; } } }