Files
EM2_ERP/em2rp/lib/services/container_service.dart
ElPoyo 3fab69cb00 feat: Gestion complète des containers et refactorisation du matériel
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.).
2025-10-29 10:57:42 +01:00

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;
}
}
}