import 'package:cloud_firestore/cloud_firestore.dart'; /// Type d'alerte enum AlertType { lowStock, // Stock faible maintenanceDue, // Maintenance à venir conflict, // Conflit disponibilité lost, // Équipement perdu eventCreated, // Événement créé eventModified, // Événement modifié eventCancelled, // Événement annulé eventAssigned, // Assigné à un événement maintenanceReminder, // Rappel maintenance périodique equipmentMissing, // Équipement manquant à une étape quantityMismatch, // Quantité incorrecte damaged, // Équipement endommagé workforceAdded, // Ajouté à la workforce d'un événement } /// Gravité de l'alerte enum AlertSeverity { info, // Information (bleu) warning, // Avertissement (orange) critical, // Critique (rouge) } String alertTypeToString(AlertType type) { switch (type) { case AlertType.lowStock: return 'LOW_STOCK'; case AlertType.maintenanceDue: return 'MAINTENANCE_DUE'; case AlertType.conflict: return 'CONFLICT'; case AlertType.lost: return 'LOST'; case AlertType.eventCreated: return 'EVENT_CREATED'; case AlertType.eventModified: return 'EVENT_MODIFIED'; case AlertType.eventCancelled: return 'EVENT_CANCELLED'; case AlertType.eventAssigned: return 'EVENT_ASSIGNED'; case AlertType.maintenanceReminder: return 'MAINTENANCE_REMINDER'; case AlertType.equipmentMissing: return 'EQUIPMENT_MISSING'; case AlertType.quantityMismatch: return 'QUANTITY_MISMATCH'; case AlertType.damaged: return 'DAMAGED'; case AlertType.workforceAdded: return 'WORKFORCE_ADDED'; } } AlertType alertTypeFromString(String? type) { switch (type) { case 'LOW_STOCK': return AlertType.lowStock; case 'MAINTENANCE_DUE': return AlertType.maintenanceDue; case 'CONFLICT': return AlertType.conflict; case 'LOST': return AlertType.lost; case 'EVENT_CREATED': return AlertType.eventCreated; case 'EVENT_MODIFIED': return AlertType.eventModified; case 'EVENT_CANCELLED': return AlertType.eventCancelled; case 'EVENT_ASSIGNED': return AlertType.eventAssigned; case 'MAINTENANCE_REMINDER': return AlertType.maintenanceReminder; case 'EQUIPMENT_MISSING': return AlertType.equipmentMissing; case 'QUANTITY_MISMATCH': return AlertType.quantityMismatch; case 'DAMAGED': return AlertType.damaged; case 'WORKFORCE_ADDED': return AlertType.workforceAdded; default: return AlertType.conflict; } } String alertSeverityToString(AlertSeverity severity) { switch (severity) { case AlertSeverity.info: return 'INFO'; case AlertSeverity.warning: return 'WARNING'; case AlertSeverity.critical: return 'CRITICAL'; } } AlertSeverity alertSeverityFromString(String? severity) { switch (severity) { case 'INFO': return AlertSeverity.info; case 'WARNING': return AlertSeverity.warning; case 'CRITICAL': return AlertSeverity.critical; default: return AlertSeverity.info; } } class AlertModel { final String id; // ID généré automatiquement final AlertType type; // Type d'alerte final AlertSeverity severity; // Gravité de l'alerte final String message; // Message de l'alerte final List assignedToUserIds; // Utilisateurs concernés final String? eventId; // ID de l'événement concerné (optionnel) final String? equipmentId; // ID de l'équipement concerné (optionnel) final String? createdByUserId; // Qui a déclenché l'alerte final DateTime createdAt; // Date de création final DateTime? dueDate; // Date d'échéance (pour maintenance) final String? actionUrl; // URL de redirection (deep link) final bool isRead; // Statut lu/non lu final bool isResolved; // Résolue ou non final String? resolution; // Message de résolution final DateTime? resolvedAt; // Date de résolution final String? resolvedByUserId; // Qui a résolu AlertModel({ required this.id, required this.type, this.severity = AlertSeverity.info, required this.message, this.assignedToUserIds = const [], this.eventId, this.equipmentId, this.createdByUserId, required this.createdAt, this.dueDate, this.actionUrl, this.isRead = false, this.isResolved = false, this.resolution, this.resolvedAt, this.resolvedByUserId, }); factory AlertModel.fromMap(Map map, String id) { // Fonction helper pour convertir Timestamp ou String ISO en DateTime DateTime _parseDate(dynamic value) { if (value == null) return DateTime.now(); if (value is Timestamp) return value.toDate(); if (value is String) return DateTime.tryParse(value) ?? DateTime.now(); return DateTime.now(); } // Parser les assignedToUserIds (peut être List ou null) List parseUserIds(dynamic value) { if (value == null) return []; if (value is List) return value.map((e) => e.toString()).toList(); return []; } return AlertModel( id: id, type: alertTypeFromString(map['type']), severity: alertSeverityFromString(map['severity']), message: map['message'] ?? '', assignedToUserIds: parseUserIds(map['assignedToUserIds'] ?? map['assignedTo']), eventId: map['eventId'], equipmentId: map['equipmentId'], createdByUserId: map['createdByUserId'] ?? map['createdBy'], createdAt: _parseDate(map['createdAt']), dueDate: map['dueDate'] != null ? _parseDate(map['dueDate']) : null, actionUrl: map['actionUrl'], isRead: map['isRead'] ?? false, isResolved: map['isResolved'] ?? false, resolution: map['resolution'], resolvedAt: map['resolvedAt'] != null ? _parseDate(map['resolvedAt']) : null, resolvedByUserId: map['resolvedByUserId'], ); } /// Factory depuis un document Firestore factory AlertModel.fromFirestore(DocumentSnapshot doc) { final data = doc.data() as Map?; if (data == null) { throw Exception('Document vide: ${doc.id}'); } return AlertModel.fromMap(data, doc.id); } Map toMap() { return { 'type': alertTypeToString(type), 'severity': alertSeverityToString(severity), 'message': message, 'assignedToUserIds': assignedToUserIds, if (eventId != null) 'eventId': eventId, if (equipmentId != null) 'equipmentId': equipmentId, if (createdByUserId != null) 'createdByUserId': createdByUserId, 'createdAt': Timestamp.fromDate(createdAt), if (dueDate != null) 'dueDate': Timestamp.fromDate(dueDate!), if (actionUrl != null) 'actionUrl': actionUrl, 'isRead': isRead, 'isResolved': isResolved, if (resolution != null) 'resolution': resolution, if (resolvedAt != null) 'resolvedAt': Timestamp.fromDate(resolvedAt!), if (resolvedByUserId != null) 'resolvedByUserId': resolvedByUserId, }; } AlertModel copyWith({ String? id, AlertType? type, AlertSeverity? severity, String? message, List? assignedToUserIds, String? eventId, String? equipmentId, String? createdByUserId, DateTime? createdAt, DateTime? dueDate, String? actionUrl, bool? isRead, bool? isResolved, String? resolution, DateTime? resolvedAt, String? resolvedByUserId, }) { return AlertModel( id: id ?? this.id, type: type ?? this.type, severity: severity ?? this.severity, message: message ?? this.message, assignedToUserIds: assignedToUserIds ?? this.assignedToUserIds, eventId: eventId ?? this.eventId, equipmentId: equipmentId ?? this.equipmentId, createdByUserId: createdByUserId ?? this.createdByUserId, createdAt: createdAt ?? this.createdAt, dueDate: dueDate ?? this.dueDate, actionUrl: actionUrl ?? this.actionUrl, isRead: isRead ?? this.isRead, isResolved: isResolved ?? this.isResolved, resolution: resolution ?? this.resolution, resolvedAt: resolvedAt ?? this.resolvedAt, resolvedByUserId: resolvedByUserId ?? this.resolvedByUserId, ); } /// Helper : Retourne true si l'alerte est pour un événement bool get isEventAlert => type == AlertType.eventCreated || type == AlertType.eventModified || type == AlertType.eventCancelled || type == AlertType.eventAssigned; /// Helper : Retourne true si l'alerte est pour la maintenance bool get isMaintenanceAlert => type == AlertType.maintenanceDue || type == AlertType.maintenanceReminder; /// Helper : Retourne true si l'alerte est pour un équipement bool get isEquipmentAlert => type == AlertType.lost || type == AlertType.equipmentMissing || type == AlertType.lowStock; }