import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/alert_model.dart'; class EquipmentService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; // Collection references CollectionReference get _equipmentCollection => _firestore.collection('equipments'); CollectionReference get _alertsCollection => _firestore.collection('alerts'); CollectionReference get _eventsCollection => _firestore.collection('events'); // CRUD Operations /// Créer un nouvel équipement Future createEquipment(EquipmentModel equipment) async { try { await _equipmentCollection.doc(equipment.id).set(equipment.toMap()); } catch (e) { print('Error creating equipment: $e'); rethrow; } } /// Mettre à jour un équipement Future updateEquipment(String id, Map data) async { try { data['updatedAt'] = Timestamp.fromDate(DateTime.now()); await _equipmentCollection.doc(id).update(data); } catch (e) { print('Error updating equipment: $e'); rethrow; } } /// Supprimer un équipement Future deleteEquipment(String id) async { try { await _equipmentCollection.doc(id).delete(); } catch (e) { print('Error deleting equipment: $e'); rethrow; } } /// Récupérer un équipement par ID Future getEquipmentById(String id) async { try { final doc = await _equipmentCollection.doc(id).get(); if (doc.exists) { return EquipmentModel.fromMap(doc.data() as Map, doc.id); } return null; } catch (e) { print('Error getting equipment: $e'); rethrow; } } /// Récupérer les équipements avec filtres Stream> getEquipment({ EquipmentCategory? category, EquipmentStatus? status, String? model, String? searchQuery, }) { try { Query query = _equipmentCollection; // Filtre par catégorie if (category != null) { query = query.where('category', isEqualTo: equipmentCategoryToString(category)); } // Filtre par statut if (status != null) { query = query.where('status', isEqualTo: equipmentStatusToString(status)); } // Filtre par modèle if (model != null && model.isNotEmpty) { query = query.where('model', isEqualTo: model); } return query.snapshots().map((snapshot) { List equipmentList = snapshot.docs .map((doc) => EquipmentModel.fromMap(doc.data() as Map, doc.id)) .toList(); // Filtre par recherche texte (côté client car Firestore ne supporte pas les recherches texte complexes) 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 streaming equipment: $e'); rethrow; } } /// Vérifier la disponibilité d'un équipement pour une période donnée Future> checkAvailability( String equipmentId, DateTime startDate, DateTime endDate, ) async { try { final conflicts = []; // Récupérer tous les événements qui chevauchent la période final eventsQuery = await _eventsCollection .where('StartDateTime', isLessThanOrEqualTo: Timestamp.fromDate(endDate)) .where('EndDateTime', isGreaterThanOrEqualTo: Timestamp.fromDate(startDate)) .get(); for (var eventDoc in eventsQuery.docs) { final eventData = eventDoc.data() as Map; final assignedEquipmentRaw = eventData['assignedEquipment'] ?? []; if (assignedEquipmentRaw is List) { for (var eq in assignedEquipmentRaw) { if (eq is Map && eq['equipmentId'] == equipmentId) { conflicts.add(eventDoc.id); break; } } } } 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 { // Récupérer tous les équipements du même modèle final equipmentQuery = await _equipmentCollection .where('model', isEqualTo: model) .get(); final alternatives = []; for (var doc in equipmentQuery.docs) { final equipment = EquipmentModel.fromMap( doc.data() as Map, doc.id, ); // Vérifier la disponibilité final conflicts = await checkAvailability(equipment.id, startDate, endDate); if (conflicts.isEmpty && equipment.status == EquipmentStatus.available) { alternatives.add(equipment); } } 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 equipmentQuery = await _equipmentCollection .where('category', whereIn: [ equipmentCategoryToString(EquipmentCategory.consumable), equipmentCategoryToString(EquipmentCategory.cable), ]) .get(); for (var doc in equipmentQuery.docs) { final equipment = EquipmentModel.fromMap( doc.data() as Map, doc.id, ); if (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 { // Vérifier si une alerte existe déjà pour cet équipement final existingAlerts = await _alertsCollection .where('equipmentId', isEqualTo: equipment.id) .where('type', isEqualTo: alertTypeToString(AlertType.lowStock)) .where('isRead', isEqualTo: false) .get(); if (existingAlerts.docs.isEmpty) { final alert = AlertModel( id: _alertsCollection.doc().id, type: AlertType.lowStock, message: 'Stock critique pour ${equipment.name} (${equipment.model ?? ""}): ${equipment.availableQuantity}/${equipment.criticalThreshold}', equipmentId: equipment.id, createdAt: DateTime.now(), ); await _alertsCollection.doc(alert.id).set(alert.toMap()); } } catch (e) { print('Error creating low stock alert: $e'); rethrow; } } /// 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 equipmentQuery = await _equipmentCollection.get(); final models = {}; for (var doc in equipmentQuery.docs) { final data = doc.data() as Map; 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 equipmentQuery = await _equipmentCollection.get(); final brands = {}; for (var doc in equipmentQuery.docs) { final data = doc.data() as Map; 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 equipmentQuery = await _equipmentCollection .where('brand', isEqualTo: brand) .get(); final models = {}; for (var doc in equipmentQuery.docs) { final data = doc.data() as Map; 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 doc = await _equipmentCollection.doc(id).get(); return !doc.exists; } catch (e) { print('Error checking ID uniqueness: $e'); rethrow; } } /// Récupérer toutes les boîtes (équipements qui peuvent contenir d'autres équipements) Future> getBoxes() async { try { // Les boîtes sont généralement des équipements de catégorie "structure" ou "other" // On pourrait aussi ajouter un champ spécifique "isBox" dans le modèle final equipmentQuery = await _equipmentCollection .where('category', whereIn: [ equipmentCategoryToString(EquipmentCategory.structure), equipmentCategoryToString(EquipmentCategory.other), ]) .get(); final boxes = []; for (var doc in equipmentQuery.docs) { final equipment = EquipmentModel.fromMap( doc.data() as Map, doc.id, ); // On pourrait ajouter un filtre supplémentaire ici si besoin boxes.add(equipment); } return boxes; } catch (e) { print('Error getting boxes: $e'); rethrow; } } }