Ajout de la gestion des containers (création, édition, suppression, affichage des détails).
Introduction d'un système de génération de QR codes unifié et d'un mode de sélection multiple.
**Features:**
- **Gestion des Containers :**
- Nouvelle page de gestion des containers (`container_management_page.dart`) avec recherche et filtres.
- Formulaire de création/édition de containers (`container_form_page.dart`) avec génération d'ID automatique.
- Page de détails d'un container (`container_detail_page.dart`) affichant son contenu et ses caractéristiques.
- Ajout des routes et du provider (`ContainerProvider`) nécessaires.
- **Modèle de Données :**
- Ajout du `ContainerModel` pour représenter les boîtes, flight cases, etc.
- Le modèle `EquipmentModel` a été enrichi avec des caractéristiques physiques (poids, dimensions).
- **QR Codes :**
- Nouveau service unifié (`UnifiedPDFGeneratorService`) pour générer des PDFs de QR codes pour n'importe quelle entité.
- Services `PDFGeneratorService` et `ContainerPDFGeneratorService` transformés en wrappers pour maintenir la compatibilité.
- Amélioration de la performance de la génération de QR codes en masse.
- **Interface Utilisateur (UI/UX) :**
- Nouvelle page de détails pour le matériel (`equipment_detail_page.dart`).
- Ajout d'un `SelectionModeMixin` pour gérer la sélection multiple dans les pages de gestion.
- Dialogues réutilisables pour l'affichage de QR codes (`QRCodeDialog`) et la sélection de format d'impression (`QRCodeFormatSelectorDialog`).
- Ajout d'un bouton "Gérer les boîtes" sur la page de gestion du matériel.
**Refactorisation :**
- L' `IdGenerator` a été déplacé dans le répertoire `utils` et étendu pour gérer les containers.
- Mise à jour de nombreuses dépendances `pubspec.yaml` vers des versions plus récentes.
- Séparation de la logique d'affichage des containers et du matériel dans des widgets dédiés (`ContainerHeaderCard`, `EquipmentParentContainers`, etc.).
379 lines
12 KiB
Dart
379 lines
12 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:em2rp/models/container_model.dart';
|
|
import 'package:em2rp/models/equipment_model.dart';
|
|
|
|
class ContainerService {
|
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
|
|
|
// Collection references
|
|
CollectionReference get _containersCollection => _firestore.collection('containers');
|
|
CollectionReference get _equipmentCollection => _firestore.collection('equipments');
|
|
|
|
// CRUD Operations
|
|
|
|
/// Créer un nouveau container
|
|
Future<void> createContainer(ContainerModel container) async {
|
|
try {
|
|
await _containersCollection.doc(container.id).set(container.toMap());
|
|
} catch (e) {
|
|
print('Error creating container: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Mettre à jour un container
|
|
Future<void> updateContainer(String id, Map<String, dynamic> data) async {
|
|
try {
|
|
data['updatedAt'] = Timestamp.fromDate(DateTime.now());
|
|
await _containersCollection.doc(id).update(data);
|
|
} catch (e) {
|
|
print('Error updating container: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Supprimer un container
|
|
Future<void> deleteContainer(String id) async {
|
|
try {
|
|
// Récupérer le container pour obtenir les équipements
|
|
final container = await getContainerById(id);
|
|
if (container != null && container.equipmentIds.isNotEmpty) {
|
|
// Retirer le container des parentBoxIds de chaque équipement
|
|
for (final equipmentId in container.equipmentIds) {
|
|
final equipmentDoc = await _equipmentCollection.doc(equipmentId).get();
|
|
if (equipmentDoc.exists) {
|
|
final equipment = EquipmentModel.fromMap(
|
|
equipmentDoc.data() as Map<String, dynamic>,
|
|
equipmentDoc.id,
|
|
);
|
|
final updatedParents = equipment.parentBoxIds.where((boxId) => boxId != id).toList();
|
|
await _equipmentCollection.doc(equipmentId).update({
|
|
'parentBoxIds': updatedParents,
|
|
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
await _containersCollection.doc(id).delete();
|
|
} catch (e) {
|
|
print('Error deleting container: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Récupérer un container par ID
|
|
Future<ContainerModel?> getContainerById(String id) async {
|
|
try {
|
|
final doc = await _containersCollection.doc(id).get();
|
|
if (doc.exists) {
|
|
return ContainerModel.fromMap(doc.data() as Map<String, dynamic>, doc.id);
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
print('Error getting container: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Récupérer tous les containers
|
|
Stream<List<ContainerModel>> getContainers({
|
|
ContainerType? type,
|
|
EquipmentStatus? status,
|
|
String? searchQuery,
|
|
}) {
|
|
try {
|
|
Query query = _containersCollection;
|
|
|
|
// Filtre par type
|
|
if (type != null) {
|
|
query = query.where('type', isEqualTo: containerTypeToString(type));
|
|
}
|
|
|
|
// Filtre par statut
|
|
if (status != null) {
|
|
query = query.where('status', isEqualTo: equipmentStatusToString(status));
|
|
}
|
|
|
|
return query.snapshots().map((snapshot) {
|
|
List<ContainerModel> containerList = snapshot.docs
|
|
.map((doc) => ContainerModel.fromMap(doc.data() as Map<String, dynamic>, doc.id))
|
|
.toList();
|
|
|
|
// Filtre par recherche texte (côté client)
|
|
if (searchQuery != null && searchQuery.isNotEmpty) {
|
|
final lowerSearch = searchQuery.toLowerCase();
|
|
containerList = containerList.where((container) {
|
|
return container.name.toLowerCase().contains(lowerSearch) ||
|
|
container.id.toLowerCase().contains(lowerSearch);
|
|
}).toList();
|
|
}
|
|
|
|
return containerList;
|
|
});
|
|
} catch (e) {
|
|
print('Error getting containers: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Ajouter un équipement à un container
|
|
Future<Map<String, dynamic>> addEquipmentToContainer({
|
|
required String containerId,
|
|
required String equipmentId,
|
|
String? userId,
|
|
}) async {
|
|
try {
|
|
// Récupérer le container
|
|
final container = await getContainerById(containerId);
|
|
if (container == null) {
|
|
return {'success': false, 'message': 'Container non trouvé'};
|
|
}
|
|
|
|
// Vérifier si l'équipement n'est pas déjà dans ce container
|
|
if (container.equipmentIds.contains(equipmentId)) {
|
|
return {'success': false, 'message': 'Cet équipement est déjà dans ce container'};
|
|
}
|
|
|
|
// Récupérer l'équipement pour vérifier s'il est déjà dans d'autres containers
|
|
final equipmentDoc = await _equipmentCollection.doc(equipmentId).get();
|
|
if (!equipmentDoc.exists) {
|
|
return {'success': false, 'message': 'Équipement non trouvé'};
|
|
}
|
|
|
|
final equipment = EquipmentModel.fromMap(
|
|
equipmentDoc.data() as Map<String, dynamic>,
|
|
equipmentDoc.id,
|
|
);
|
|
|
|
// Avertir si l'équipement est déjà dans d'autres containers
|
|
List<String> otherContainers = [];
|
|
if (equipment.parentBoxIds.isNotEmpty) {
|
|
for (final boxId in equipment.parentBoxIds) {
|
|
final box = await getContainerById(boxId);
|
|
if (box != null) {
|
|
otherContainers.add(box.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mettre à jour le container
|
|
final updatedEquipmentIds = [...container.equipmentIds, equipmentId];
|
|
await updateContainer(containerId, {
|
|
'equipmentIds': updatedEquipmentIds,
|
|
});
|
|
|
|
// Mettre à jour l'équipement
|
|
final updatedParentBoxIds = [...equipment.parentBoxIds, containerId];
|
|
await _equipmentCollection.doc(equipmentId).update({
|
|
'parentBoxIds': updatedParentBoxIds,
|
|
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
|
});
|
|
|
|
// Ajouter une entrée dans l'historique
|
|
await _addHistoryEntry(
|
|
containerId: containerId,
|
|
action: 'equipment_added',
|
|
equipmentId: equipmentId,
|
|
newValue: equipmentId,
|
|
userId: userId,
|
|
);
|
|
|
|
return {
|
|
'success': true,
|
|
'message': 'Équipement ajouté avec succès',
|
|
'warnings': otherContainers.isNotEmpty
|
|
? 'Attention : cet équipement est également dans les containers suivants : ${otherContainers.join(", ")}'
|
|
: null,
|
|
};
|
|
} catch (e) {
|
|
print('Error adding equipment to container: $e');
|
|
return {'success': false, 'message': 'Erreur: $e'};
|
|
}
|
|
}
|
|
|
|
/// Retirer un équipement d'un container
|
|
Future<void> removeEquipmentFromContainer({
|
|
required String containerId,
|
|
required String equipmentId,
|
|
String? userId,
|
|
}) async {
|
|
try {
|
|
// Récupérer le container
|
|
final container = await getContainerById(containerId);
|
|
if (container == null) throw Exception('Container non trouvé');
|
|
|
|
// Mettre à jour le container
|
|
final updatedEquipmentIds = container.equipmentIds.where((id) => id != equipmentId).toList();
|
|
await updateContainer(containerId, {
|
|
'equipmentIds': updatedEquipmentIds,
|
|
});
|
|
|
|
// Mettre à jour l'équipement
|
|
final equipmentDoc = await _equipmentCollection.doc(equipmentId).get();
|
|
if (equipmentDoc.exists) {
|
|
final equipment = EquipmentModel.fromMap(
|
|
equipmentDoc.data() as Map<String, dynamic>,
|
|
equipmentDoc.id,
|
|
);
|
|
final updatedParentBoxIds = equipment.parentBoxIds.where((id) => id != containerId).toList();
|
|
await _equipmentCollection.doc(equipmentId).update({
|
|
'parentBoxIds': updatedParentBoxIds,
|
|
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
|
});
|
|
}
|
|
|
|
// Ajouter une entrée dans l'historique
|
|
await _addHistoryEntry(
|
|
containerId: containerId,
|
|
action: 'equipment_removed',
|
|
equipmentId: equipmentId,
|
|
previousValue: equipmentId,
|
|
userId: userId,
|
|
);
|
|
} catch (e) {
|
|
print('Error removing equipment from container: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Vérifier la disponibilité d'un container et de son contenu pour un événement
|
|
Future<Map<String, dynamic>> checkContainerAvailability({
|
|
required String containerId,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
String? excludeEventId,
|
|
}) async {
|
|
try {
|
|
final container = await getContainerById(containerId);
|
|
if (container == null) {
|
|
return {'available': false, 'message': 'Container non trouvé'};
|
|
}
|
|
|
|
// Vérifier le statut du container
|
|
if (container.status != EquipmentStatus.available) {
|
|
return {
|
|
'available': false,
|
|
'message': 'Container ${container.name} n\'est pas disponible (statut: ${container.status})',
|
|
};
|
|
}
|
|
|
|
// Vérifier la disponibilité de chaque équipement dans le container
|
|
List<String> unavailableEquipment = [];
|
|
for (final equipmentId in container.equipmentIds) {
|
|
final equipmentDoc = await _equipmentCollection.doc(equipmentId).get();
|
|
if (equipmentDoc.exists) {
|
|
final equipment = EquipmentModel.fromMap(
|
|
equipmentDoc.data() as Map<String, dynamic>,
|
|
equipmentDoc.id,
|
|
);
|
|
|
|
if (equipment.status != EquipmentStatus.available) {
|
|
unavailableEquipment.add('${equipment.name} (${equipment.status})');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unavailableEquipment.isNotEmpty) {
|
|
return {
|
|
'available': false,
|
|
'message': 'Certains équipements ne sont pas disponibles',
|
|
'unavailableItems': unavailableEquipment,
|
|
};
|
|
}
|
|
|
|
return {'available': true, 'message': 'Container et tout son contenu disponibles'};
|
|
} catch (e) {
|
|
print('Error checking container availability: $e');
|
|
return {'available': false, 'message': 'Erreur: $e'};
|
|
}
|
|
}
|
|
|
|
/// Récupérer les équipements d'un container
|
|
Future<List<EquipmentModel>> getContainerEquipment(String containerId) async {
|
|
try {
|
|
final container = await getContainerById(containerId);
|
|
if (container == null) return [];
|
|
|
|
List<EquipmentModel> equipment = [];
|
|
for (final equipmentId in container.equipmentIds) {
|
|
final doc = await _equipmentCollection.doc(equipmentId).get();
|
|
if (doc.exists) {
|
|
equipment.add(EquipmentModel.fromMap(doc.data() as Map<String, dynamic>, doc.id));
|
|
}
|
|
}
|
|
|
|
return equipment;
|
|
} catch (e) {
|
|
print('Error getting container equipment: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Trouver tous les containers contenant un équipement spécifique
|
|
Future<List<ContainerModel>> findContainersWithEquipment(String equipmentId) async {
|
|
try {
|
|
final snapshot = await _containersCollection
|
|
.where('equipmentIds', arrayContains: equipmentId)
|
|
.get();
|
|
|
|
return snapshot.docs
|
|
.map((doc) => ContainerModel.fromMap(doc.data() as Map<String, dynamic>, doc.id))
|
|
.toList();
|
|
} catch (e) {
|
|
print('Error finding containers with equipment: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Ajouter une entrée d'historique
|
|
Future<void> _addHistoryEntry({
|
|
required String containerId,
|
|
required String action,
|
|
String? equipmentId,
|
|
String? previousValue,
|
|
String? newValue,
|
|
String? userId,
|
|
}) async {
|
|
try {
|
|
final container = await getContainerById(containerId);
|
|
if (container == null) return;
|
|
|
|
final entry = ContainerHistoryEntry(
|
|
timestamp: DateTime.now(),
|
|
action: action,
|
|
equipmentId: equipmentId,
|
|
previousValue: previousValue,
|
|
newValue: newValue,
|
|
userId: userId,
|
|
);
|
|
|
|
final updatedHistory = [...container.history, entry];
|
|
|
|
// Limiter l'historique aux 100 dernières entrées
|
|
final limitedHistory = updatedHistory.length > 100
|
|
? updatedHistory.sublist(updatedHistory.length - 100)
|
|
: updatedHistory;
|
|
|
|
await updateContainer(containerId, {
|
|
'history': limitedHistory.map((e) => e.toMap()).toList(),
|
|
});
|
|
} catch (e) {
|
|
print('Error adding history entry: $e');
|
|
// Ne pas throw pour éviter de bloquer l'opération principale
|
|
}
|
|
}
|
|
|
|
/// Vérifier si un ID de container existe déjà
|
|
Future<bool> checkContainerIdExists(String id) async {
|
|
try {
|
|
final doc = await _containersCollection.doc(id).get();
|
|
return doc.exists;
|
|
} catch (e) {
|
|
print('Error checking container ID: $e');
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|