Migration complète du backend pour utiliser des Cloud Functions comme couche API sécurisée, en remplacement des appels directs à Firestore depuis le client.
**Backend (Cloud Functions):**
- **Centralisation CORS :** Ajout d'un middleware `withCors` et d'une configuration `httpOptions` pour gérer uniformément les en-têtes CORS et les requêtes `OPTIONS` sur toutes les fonctions.
- **Nouvelles Fonctions de Lecture (GET) :**
- `getEquipments`, `getContainers`, `getEvents`, `getUsers`, `getOptions`, `getEventTypes`, `getRoles`, `getMaintenances`, `getAlerts`.
- Ces fonctions gèrent les permissions côté serveur, masquant les données sensibles (ex: prix des équipements) pour les utilisateurs non-autorisés.
- `getEvents` retourne également une map des utilisateurs (`usersMap`) pour optimiser le chargement des données de la main d'œuvre.
- **Nouvelle Fonction de Recherche :**
- `getContainersByEquipment` : Endpoint dédié pour trouver efficacement tous les containers qui contiennent un équipement spécifique.
- **Nouvelles Fonctions d'Écriture (CRUD) :**
- Fonctions CRUD complètes pour `eventTypes` (`create`, `update`, `delete`), incluant la validation (unicité du nom, vérification des événements futurs avant suppression).
- **Mise à jour de Fonctions Existantes :**
- Toutes les fonctions CRUD existantes (`create/update/deleteEquipment`, `create/update/deleteContainer`, etc.) sont wrappées avec le nouveau gestionnaire CORS.
**Frontend (Flutter):**
- **Introduction du `DataService` :** Nouveau service centralisant tous les appels aux Cloud Functions, servant d'intermédiaire entre l'UI/Providers et l'API.
- **Refactorisation des Providers :**
- `EquipmentProvider`, `ContainerProvider`, `EventProvider`, `UsersProvider`, `MaintenanceProvider` et `AlertProvider` ont été refactorisés pour utiliser le `DataService` au lieu d'accéder directement à Firestore.
- Les `Stream` Firestore sont remplacés par des chargements de données via des méthodes `Future` (`loadEquipments`, `loadEvents`, etc.).
- **Gestion des Relations Équipement-Container :**
- Le modèle `EquipmentModel` ne stocke plus `parentBoxIds`.
- La relation est maintenant gérée par le `ContainerModel` qui contient `equipmentIds`.
- Le `ContainerEquipmentService` est introduit pour utiliser la nouvelle fonction `getContainersByEquipment`.
- L'affichage des boîtes parentes (`EquipmentParentContainers`) et le formulaire d'équipement (`EquipmentFormPage`) ont été mis à jour pour refléter ce nouveau modèle de données, synchronisant les ajouts/suppressions d'équipements dans les containers.
- **Amélioration de l'UI :**
- Nouveau widget `ParentBoxesSelector` pour une sélection améliorée et visuelle des boîtes parentes dans le formulaire d'équipement.
- Refonte visuelle de `EquipmentParentContainers` pour une meilleure présentation.
578 lines
18 KiB
Dart
578 lines
18 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:
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Statut de chargement (loading)
|
|
enum LoadingStatus {
|
|
notStarted,
|
|
inProgress,
|
|
completed,
|
|
completedWithMissing
|
|
}
|
|
|
|
String loadingStatusToString(LoadingStatus status) {
|
|
switch (status) {
|
|
case LoadingStatus.notStarted:
|
|
return 'NOT_STARTED';
|
|
case LoadingStatus.inProgress:
|
|
return 'IN_PROGRESS';
|
|
case LoadingStatus.completed:
|
|
return 'COMPLETED';
|
|
case LoadingStatus.completedWithMissing:
|
|
return 'COMPLETED_WITH_MISSING';
|
|
}
|
|
}
|
|
|
|
LoadingStatus loadingStatusFromString(String? status) {
|
|
switch (status) {
|
|
case 'NOT_STARTED':
|
|
return LoadingStatus.notStarted;
|
|
case 'IN_PROGRESS':
|
|
return LoadingStatus.inProgress;
|
|
case 'COMPLETED':
|
|
return LoadingStatus.completed;
|
|
case 'COMPLETED_WITH_MISSING':
|
|
return LoadingStatus.completedWithMissing;
|
|
default:
|
|
return LoadingStatus.notStarted;
|
|
}
|
|
}
|
|
|
|
// Statut de déchargement (unloading)
|
|
enum UnloadingStatus {
|
|
notStarted,
|
|
inProgress,
|
|
completed,
|
|
completedWithMissing
|
|
}
|
|
|
|
String unloadingStatusToString(UnloadingStatus status) {
|
|
switch (status) {
|
|
case UnloadingStatus.notStarted:
|
|
return 'NOT_STARTED';
|
|
case UnloadingStatus.inProgress:
|
|
return 'IN_PROGRESS';
|
|
case UnloadingStatus.completed:
|
|
return 'COMPLETED';
|
|
case UnloadingStatus.completedWithMissing:
|
|
return 'COMPLETED_WITH_MISSING';
|
|
}
|
|
}
|
|
|
|
UnloadingStatus unloadingStatusFromString(String? status) {
|
|
switch (status) {
|
|
case 'NOT_STARTED':
|
|
return UnloadingStatus.notStarted;
|
|
case 'IN_PROGRESS':
|
|
return UnloadingStatus.inProgress;
|
|
case 'COMPLETED':
|
|
return UnloadingStatus.completed;
|
|
case 'COMPLETED_WITH_MISSING':
|
|
return UnloadingStatus.completedWithMissing;
|
|
default:
|
|
return UnloadingStatus.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 isLoaded; // Validé au chargement
|
|
final bool isUnloaded; // Validé au déchargement
|
|
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.isLoaded = false,
|
|
this.isUnloaded = 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,
|
|
isLoaded: map['isLoaded'] ?? false,
|
|
isUnloaded: map['isUnloaded'] ?? false,
|
|
isReturned: map['isReturned'] ?? false,
|
|
returnedQuantity: map['returnedQuantity'],
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toMap() {
|
|
return {
|
|
'equipmentId': equipmentId,
|
|
'quantity': quantity,
|
|
'isPrepared': isPrepared,
|
|
'isLoaded': isLoaded,
|
|
'isUnloaded': isUnloaded,
|
|
'isReturned': isReturned,
|
|
'returnedQuantity': returnedQuantity,
|
|
};
|
|
}
|
|
|
|
EventEquipment copyWith({
|
|
String? equipmentId,
|
|
int? quantity,
|
|
bool? isPrepared,
|
|
bool? isLoaded,
|
|
bool? isUnloaded,
|
|
bool? isReturned,
|
|
int? returnedQuantity,
|
|
}) {
|
|
return EventEquipment(
|
|
equipmentId: equipmentId ?? this.equipmentId,
|
|
quantity: quantity ?? this.quantity,
|
|
isPrepared: isPrepared ?? this.isPrepared,
|
|
isLoaded: isLoaded ?? this.isLoaded,
|
|
isUnloaded: isUnloaded ?? this.isUnloaded,
|
|
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<dynamic> workforce; // Peut contenir DocumentReference OU String (UIDs)
|
|
final List<Map<String, String>> documents;
|
|
final List<Map<String, dynamic>> options;
|
|
final EventStatus status;
|
|
|
|
// Champs de contact
|
|
final int? jauge;
|
|
final String? contactEmail;
|
|
final String? contactPhone;
|
|
|
|
// Nouveaux champs pour la gestion du matériel
|
|
final List<EventEquipment> assignedEquipment;
|
|
final List<String> assignedContainers; // IDs des conteneurs assignés
|
|
final PreparationStatus? preparationStatus;
|
|
final LoadingStatus? loadingStatus;
|
|
final UnloadingStatus? unloadingStatus;
|
|
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.jauge,
|
|
this.contactEmail,
|
|
this.contactPhone,
|
|
this.assignedEquipment = const [],
|
|
this.assignedContainers = const [],
|
|
this.preparationStatus,
|
|
this.loadingStatus,
|
|
this.unloadingStatus,
|
|
this.returnStatus,
|
|
});
|
|
|
|
factory EventModel.fromMap(Map<String, dynamic> map, String id) {
|
|
try {
|
|
// Fonction helper pour convertir Timestamp ou String ISO en DateTime
|
|
DateTime _parseDate(dynamic value, DateTime defaultValue) {
|
|
if (value == null) return defaultValue;
|
|
if (value is Timestamp) return value.toDate();
|
|
if (value is String) return DateTime.tryParse(value) ?? defaultValue;
|
|
return defaultValue;
|
|
}
|
|
|
|
// Gestion sécurisée des références workforce
|
|
final List<dynamic> workforceRefs = map['workforce'] ?? [];
|
|
final List<dynamic> safeWorkforce = [];
|
|
|
|
for (var ref in workforceRefs) {
|
|
if (ref is DocumentReference) {
|
|
safeWorkforce.add(ref);
|
|
} else if (ref is String) {
|
|
// Accepter directement les UIDs (envoyés par le backend)
|
|
safeWorkforce.add(ref);
|
|
} else {
|
|
print('Warning: Invalid workforce reference in event $id: $ref');
|
|
}
|
|
}
|
|
|
|
// Gestion sécurisée des timestamps avec support ISO string
|
|
final DateTime startDate = _parseDate(map['StartDateTime'], DateTime.now());
|
|
final DateTime endDate = _parseDate(map['EndDateTime'], 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) {
|
|
final eventTypeString = map['EventType'] as String;
|
|
// Si c'est un path (ex: "eventTypes/Mariage"), extraire juste l'ID
|
|
if (eventTypeString.contains('/')) {
|
|
eventTypeId = eventTypeString.split('/').last;
|
|
} else {
|
|
eventTypeId = eventTypeString;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
final customerString = map['customer'] as String;
|
|
// Si c'est un path (ex: "clients/abc123"), extraire juste l'ID
|
|
if (customerString.contains('/')) {
|
|
customerId = customerString.split('/').last;
|
|
} else {
|
|
customerId = customerString;
|
|
}
|
|
}
|
|
|
|
// 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');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gestion des conteneurs assignés
|
|
final assignedContainersRaw = map['assignedContainers'] ?? [];
|
|
final List<String> assignedContainers = [];
|
|
|
|
if (assignedContainersRaw is List) {
|
|
for (var e in assignedContainersRaw) {
|
|
if (e is String) {
|
|
assignedContainers.add(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
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),
|
|
assignedContainers: assignedContainers,
|
|
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?),
|
|
jauge: map['jauge'] != null ? _parseInt(map['jauge']) : null,
|
|
contactEmail: map['contactEmail']?.toString(),
|
|
contactPhone: map['contactPhone']?.toString(),
|
|
assignedEquipment: assignedEquipment,
|
|
preparationStatus: preparationStatusFromString(map['preparationStatus'] as String?),
|
|
loadingStatus: loadingStatusFromString(map['loadingStatus'] as String?),
|
|
unloadingStatus: unloadingStatusFromString(map['unloadingStatus'] 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),
|
|
'jauge': jauge,
|
|
'contactEmail': contactEmail,
|
|
'contactPhone': contactPhone,
|
|
'assignedEquipment': assignedEquipment.map((e) => e.toMap()).toList(),
|
|
'assignedContainers': assignedContainers,
|
|
'preparationStatus': preparationStatus != null ? preparationStatusToString(preparationStatus!) : null,
|
|
'loadingStatus': loadingStatus != null ? loadingStatusToString(loadingStatus!) : null,
|
|
'unloadingStatus': unloadingStatus != null ? unloadingStatusToString(unloadingStatus!) : null,
|
|
'returnStatus': returnStatus != null ? returnStatusToString(returnStatus!) : null,
|
|
};
|
|
}
|
|
|
|
EventModel copyWith({
|
|
String? id,
|
|
String? name,
|
|
String? description,
|
|
DateTime? startDateTime,
|
|
DateTime? endDateTime,
|
|
double? basePrice,
|
|
int? installationTime,
|
|
int? disassemblyTime,
|
|
String? eventTypeId,
|
|
DocumentReference? eventTypeRef,
|
|
String? customerId,
|
|
String? address,
|
|
double? latitude,
|
|
double? longitude,
|
|
List<dynamic>? workforce,
|
|
List<Map<String, String>>? documents,
|
|
List<Map<String, dynamic>>? options,
|
|
EventStatus? status,
|
|
int? jauge,
|
|
String? contactEmail,
|
|
String? contactPhone,
|
|
List<EventEquipment>? assignedEquipment,
|
|
List<String>? assignedContainers,
|
|
PreparationStatus? preparationStatus,
|
|
LoadingStatus? loadingStatus,
|
|
UnloadingStatus? unloadingStatus,
|
|
ReturnStatus? returnStatus,
|
|
}) {
|
|
return EventModel(
|
|
id: id ?? this.id,
|
|
name: name ?? this.name,
|
|
description: description ?? this.description,
|
|
startDateTime: startDateTime ?? this.startDateTime,
|
|
endDateTime: endDateTime ?? this.endDateTime,
|
|
basePrice: basePrice ?? this.basePrice,
|
|
installationTime: installationTime ?? this.installationTime,
|
|
disassemblyTime: disassemblyTime ?? this.disassemblyTime,
|
|
eventTypeId: eventTypeId ?? this.eventTypeId,
|
|
eventTypeRef: eventTypeRef ?? this.eventTypeRef,
|
|
customerId: customerId ?? this.customerId,
|
|
address: address ?? this.address,
|
|
latitude: latitude ?? this.latitude,
|
|
longitude: longitude ?? this.longitude,
|
|
workforce: workforce ?? this.workforce,
|
|
documents: documents ?? this.documents,
|
|
options: options ?? this.options,
|
|
status: status ?? this.status,
|
|
jauge: jauge ?? this.jauge,
|
|
contactEmail: contactEmail ?? this.contactEmail,
|
|
contactPhone: contactPhone ?? this.contactPhone,
|
|
assignedEquipment: assignedEquipment ?? this.assignedEquipment,
|
|
assignedContainers: assignedContainers ?? this.assignedContainers,
|
|
preparationStatus: preparationStatus ?? this.preparationStatus,
|
|
loadingStatus: loadingStatus ?? this.loadingStatus,
|
|
unloadingStatus: unloadingStatus ?? this.unloadingStatus,
|
|
returnStatus: returnStatus ?? this.returnStatus,
|
|
);
|
|
}
|
|
}
|