378 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:cloud_firestore/cloud_firestore.dart';
 | |
| 
 | |
| enum EventStatus {
 | |
|   confirmed,
 | |
|   canceled,
 | |
|   waitingForApproval,
 | |
| }
 | |
| 
 | |
| String eventStatusToString(EventStatus status) {
 | |
|   switch (status) {
 | |
|     case EventStatus.confirmed:
 | |
|       return 'CONFIRMED';
 | |
|     case EventStatus.canceled:
 | |
|       return 'CANCELED';
 | |
|     case EventStatus.waitingForApproval:
 | |
|     default:
 | |
|       return 'WAITING_FOR_APPROVAL';
 | |
|   }
 | |
| }
 | |
| 
 | |
| EventStatus eventStatusFromString(String? status) {
 | |
|   switch (status) {
 | |
|     case 'CONFIRMED':
 | |
|       return EventStatus.confirmed;
 | |
|     case 'CANCELED':
 | |
|       return EventStatus.canceled;
 | |
|     case 'WAITING_FOR_APPROVAL':
 | |
|     default:
 | |
|       return EventStatus.waitingForApproval;
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum PreparationStatus {
 | |
|   notStarted,
 | |
|   inProgress,
 | |
|   completed,
 | |
|   completedWithMissing
 | |
| }
 | |
| 
 | |
| String preparationStatusToString(PreparationStatus status) {
 | |
|   switch (status) {
 | |
|     case PreparationStatus.notStarted:
 | |
|       return 'NOT_STARTED';
 | |
|     case PreparationStatus.inProgress:
 | |
|       return 'IN_PROGRESS';
 | |
|     case PreparationStatus.completed:
 | |
|       return 'COMPLETED';
 | |
|     case PreparationStatus.completedWithMissing:
 | |
|       return 'COMPLETED_WITH_MISSING';
 | |
|   }
 | |
| }
 | |
| 
 | |
| PreparationStatus preparationStatusFromString(String? status) {
 | |
|   switch (status) {
 | |
|     case 'NOT_STARTED':
 | |
|       return PreparationStatus.notStarted;
 | |
|     case 'IN_PROGRESS':
 | |
|       return PreparationStatus.inProgress;
 | |
|     case 'COMPLETED':
 | |
|       return PreparationStatus.completed;
 | |
|     case 'COMPLETED_WITH_MISSING':
 | |
|       return PreparationStatus.completedWithMissing;
 | |
|     default:
 | |
|       return PreparationStatus.notStarted;
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum ReturnStatus {
 | |
|   notStarted,
 | |
|   inProgress,
 | |
|   completed,
 | |
|   completedWithMissing
 | |
| }
 | |
| 
 | |
| String returnStatusToString(ReturnStatus status) {
 | |
|   switch (status) {
 | |
|     case ReturnStatus.notStarted:
 | |
|       return 'NOT_STARTED';
 | |
|     case ReturnStatus.inProgress:
 | |
|       return 'IN_PROGRESS';
 | |
|     case ReturnStatus.completed:
 | |
|       return 'COMPLETED';
 | |
|     case ReturnStatus.completedWithMissing:
 | |
|       return 'COMPLETED_WITH_MISSING';
 | |
|   }
 | |
| }
 | |
| 
 | |
| ReturnStatus returnStatusFromString(String? status) {
 | |
|   switch (status) {
 | |
|     case 'NOT_STARTED':
 | |
|       return ReturnStatus.notStarted;
 | |
|     case 'IN_PROGRESS':
 | |
|       return ReturnStatus.inProgress;
 | |
|     case 'COMPLETED':
 | |
|       return ReturnStatus.completed;
 | |
|     case 'COMPLETED_WITH_MISSING':
 | |
|       return ReturnStatus.completedWithMissing;
 | |
|     default:
 | |
|       return ReturnStatus.notStarted;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class EventEquipment {
 | |
|   final String equipmentId;         // ID de l'équipement
 | |
|   final int quantity;               // Quantité (pour consommables)
 | |
|   final bool isPrepared;            // Validé en préparation
 | |
|   final bool isReturned;            // Validé au retour
 | |
|   final int? returnedQuantity;      // Quantité retournée (pour consommables)
 | |
| 
 | |
|   EventEquipment({
 | |
|     required this.equipmentId,
 | |
|     this.quantity = 1,
 | |
|     this.isPrepared = false,
 | |
|     this.isReturned = false,
 | |
|     this.returnedQuantity,
 | |
|   });
 | |
| 
 | |
|   factory EventEquipment.fromMap(Map<String, dynamic> map) {
 | |
|     return EventEquipment(
 | |
|       equipmentId: map['equipmentId'] ?? '',
 | |
|       quantity: map['quantity'] ?? 1,
 | |
|       isPrepared: map['isPrepared'] ?? false,
 | |
|       isReturned: map['isReturned'] ?? false,
 | |
|       returnedQuantity: map['returnedQuantity'],
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Map<String, dynamic> toMap() {
 | |
|     return {
 | |
|       'equipmentId': equipmentId,
 | |
|       'quantity': quantity,
 | |
|       'isPrepared': isPrepared,
 | |
|       'isReturned': isReturned,
 | |
|       'returnedQuantity': returnedQuantity,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   EventEquipment copyWith({
 | |
|     String? equipmentId,
 | |
|     int? quantity,
 | |
|     bool? isPrepared,
 | |
|     bool? isReturned,
 | |
|     int? returnedQuantity,
 | |
|   }) {
 | |
|     return EventEquipment(
 | |
|       equipmentId: equipmentId ?? this.equipmentId,
 | |
|       quantity: quantity ?? this.quantity,
 | |
|       isPrepared: isPrepared ?? this.isPrepared,
 | |
|       isReturned: isReturned ?? this.isReturned,
 | |
|       returnedQuantity: returnedQuantity ?? this.returnedQuantity,
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class EventModel {
 | |
|   final String id;
 | |
|   final String name;
 | |
|   final String description;
 | |
|   final DateTime startDateTime;
 | |
|   final DateTime endDateTime;
 | |
|   final double basePrice;
 | |
|   final int installationTime;
 | |
|   final int disassemblyTime;
 | |
|   final String eventTypeId;
 | |
|   final DocumentReference? eventTypeRef;
 | |
|   final String customerId;
 | |
|   final String address;
 | |
|   final double latitude;
 | |
|   final double longitude;
 | |
|   final List<DocumentReference> workforce;
 | |
|   final List<Map<String, String>> documents;
 | |
|   final List<Map<String, dynamic>> options;
 | |
|   final EventStatus status;
 | |
| 
 | |
|   // Nouveaux champs pour la gestion du matériel
 | |
|   final List<EventEquipment> assignedEquipment;
 | |
|   final PreparationStatus? preparationStatus;
 | |
|   final ReturnStatus? returnStatus;
 | |
| 
 | |
|   EventModel({
 | |
|     required this.id,
 | |
|     required this.name,
 | |
|     required this.description,
 | |
|     required this.startDateTime,
 | |
|     required this.endDateTime,
 | |
|     required this.basePrice,
 | |
|     required this.installationTime,
 | |
|     required this.disassemblyTime,
 | |
|     required this.eventTypeId,
 | |
|     this.eventTypeRef,
 | |
|     required this.customerId,
 | |
|     required this.address,
 | |
|     required this.latitude,
 | |
|     required this.longitude,
 | |
|     required this.workforce,
 | |
|     required this.documents,
 | |
|     this.options = const [],
 | |
|     this.status = EventStatus.waitingForApproval,
 | |
|     this.assignedEquipment = const [],
 | |
|     this.preparationStatus,
 | |
|     this.returnStatus,
 | |
|   });
 | |
| 
 | |
|   factory EventModel.fromMap(Map<String, dynamic> map, String id) {
 | |
|     try {
 | |
|       // Gestion sécurisée des références workforce
 | |
|       final List<dynamic> workforceRefs = map['workforce'] ?? [];
 | |
|       final List<DocumentReference> safeWorkforce = [];
 | |
| 
 | |
|       for (var ref in workforceRefs) {
 | |
|         if (ref is DocumentReference) {
 | |
|           safeWorkforce.add(ref);
 | |
|         } else {
 | |
|           print('Warning: Invalid workforce reference in event $id: $ref');
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Gestion sécurisée des timestamps
 | |
|       final Timestamp? startTimestamp = map['StartDateTime'] as Timestamp?;
 | |
|       final Timestamp? endTimestamp = map['EndDateTime'] as Timestamp?;
 | |
| 
 | |
|       final DateTime startDate = startTimestamp?.toDate() ?? DateTime.now();
 | |
|       final DateTime endDate = endTimestamp?.toDate() ??
 | |
|           startDate.add(const Duration(hours: 1));
 | |
| 
 | |
|       // Gestion sécurisée des documents
 | |
|       final docsRaw = map['documents'] ?? [];
 | |
|       final List<Map<String, String>> docs = [];
 | |
| 
 | |
|       if (docsRaw is List) {
 | |
|         for (var e in docsRaw) {
 | |
|           try {
 | |
|             if (e is Map) {
 | |
|               docs.add(Map<String, String>.from(e));
 | |
|             } else if (e is String) {
 | |
|               final fileName = Uri.decodeComponent(
 | |
|                 e.split('/').last.split('?').first,
 | |
|               );
 | |
|               docs.add({'name': fileName, 'url': e});
 | |
|             }
 | |
|           } catch (docError) {
 | |
|             print('Warning: Failed to parse document in event $id: $docError');
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Gestion sécurisée des options
 | |
|       final optionsRaw = map['options'] ?? [];
 | |
|       final List<Map<String, dynamic>> options = [];
 | |
| 
 | |
|       if (optionsRaw is List) {
 | |
|         for (var e in optionsRaw) {
 | |
|           try {
 | |
|             if (e is Map) {
 | |
|               options.add(Map<String, dynamic>.from(e));
 | |
|             }
 | |
|           } catch (optionError) {
 | |
|             print('Warning: Failed to parse option in event $id: $optionError');
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Gestion sécurisée de l'EventType
 | |
|       String eventTypeId = '';
 | |
|       DocumentReference? eventTypeRef;
 | |
| 
 | |
|       if (map['EventType'] is DocumentReference) {
 | |
|         eventTypeRef = map['EventType'] as DocumentReference;
 | |
|         eventTypeId = eventTypeRef.id;
 | |
|       } else if (map['EventType'] is String) {
 | |
|         eventTypeId = map['EventType'] as String;
 | |
|       }
 | |
| 
 | |
|       // Gestion sécurisée du customer
 | |
|       String customerId = '';
 | |
|       if (map['customer'] is DocumentReference) {
 | |
|         customerId = (map['customer'] as DocumentReference).id;
 | |
|       } else if (map['customer'] is String) {
 | |
|         customerId = map['customer'] as String;
 | |
|       }
 | |
| 
 | |
|       // Gestion des équipements assignés
 | |
|       final assignedEquipmentRaw = map['assignedEquipment'] ?? [];
 | |
|       final List<EventEquipment> assignedEquipment = [];
 | |
| 
 | |
|       if (assignedEquipmentRaw is List) {
 | |
|         for (var e in assignedEquipmentRaw) {
 | |
|           try {
 | |
|             if (e is Map) {
 | |
|               assignedEquipment.add(EventEquipment.fromMap(Map<String, dynamic>.from(e)));
 | |
|             }
 | |
|           } catch (equipmentError) {
 | |
|             print('Warning: Failed to parse equipment in event $id: $equipmentError');
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return EventModel(
 | |
|         id: id,
 | |
|         name: (map['Name'] ?? '').toString().trim(),
 | |
|         description: (map['Description'] ?? '').toString(),
 | |
|         startDateTime: startDate,
 | |
|         endDateTime: endDate,
 | |
|         basePrice: _parseDouble(map['BasePrice'] ?? map['Price'] ?? 0.0),
 | |
|         installationTime: _parseInt(map['InstallationTime'] ?? 0),
 | |
|         disassemblyTime: _parseInt(map['DisassemblyTime'] ?? 0),
 | |
|         eventTypeId: eventTypeId,
 | |
|         eventTypeRef: eventTypeRef,
 | |
|         customerId: customerId,
 | |
|         address: (map['Address'] ?? '').toString(),
 | |
|         latitude: _parseDouble(map['Latitude'] ?? 0.0),
 | |
|         longitude: _parseDouble(map['Longitude'] ?? 0.0),
 | |
|         workforce: safeWorkforce,
 | |
|         documents: docs,
 | |
|         options: options,
 | |
|         status: eventStatusFromString(map['status'] as String?),
 | |
|         assignedEquipment: assignedEquipment,
 | |
|         preparationStatus: preparationStatusFromString(map['preparationStatus'] as String?),
 | |
|         returnStatus: returnStatusFromString(map['returnStatus'] as String?),
 | |
|       );
 | |
|     } catch (e) {
 | |
|       print('Error parsing event $id: $e');
 | |
|       print('Event data: $map');
 | |
|       rethrow;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Méthodes utilitaires pour le parsing sécurisé
 | |
|   static double _parseDouble(dynamic value) {
 | |
|     if (value is double) return value;
 | |
|     if (value is int) return value.toDouble();
 | |
|     if (value is String) {
 | |
|       final parsed = double.tryParse(value);
 | |
|       if (parsed != null) return parsed;
 | |
|     }
 | |
|     return 0.0;
 | |
|   }
 | |
| 
 | |
|   static int _parseInt(dynamic value) {
 | |
|     if (value is int) return value;
 | |
|     if (value is double) return value.toInt();
 | |
|     if (value is String) {
 | |
|       final parsed = int.tryParse(value);
 | |
|       if (parsed != null) return parsed;
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   Map<String, dynamic> toMap() {
 | |
|     return {
 | |
|       'Name': name,
 | |
|       'Description': description,
 | |
|       'StartDateTime': Timestamp.fromDate(startDateTime),
 | |
|       'EndDateTime': Timestamp.fromDate(endDateTime),
 | |
|       'BasePrice': basePrice,
 | |
|       'InstallationTime': installationTime,
 | |
|       'DisassemblyTime': disassemblyTime,
 | |
|       'EventType': eventTypeId.isNotEmpty
 | |
|           ? FirebaseFirestore.instance.collection('eventTypes').doc(eventTypeId)
 | |
|           : null,
 | |
|       'customer': customerId.isNotEmpty
 | |
|           ? FirebaseFirestore.instance.collection('customers').doc(customerId)
 | |
|           : null,
 | |
|       'Address': address,
 | |
|       'Position': GeoPoint(latitude, longitude),
 | |
|       'Latitude': latitude,
 | |
|       'Longitude': longitude,
 | |
|       'workforce': workforce,
 | |
|       'documents': documents,
 | |
|       'options': options,
 | |
|       'status': eventStatusToString(status),
 | |
|       'assignedEquipment': assignedEquipment.map((e) => e.toMap()).toList(),
 | |
|       'preparationStatus': preparationStatus != null ? preparationStatusToString(preparationStatus!) : null,
 | |
|       'returnStatus': returnStatus != null ? returnStatusToString(returnStatus!) : null,
 | |
|     };
 | |
|   }
 | |
| }
 | 
