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.).
This commit is contained in:
251
em2rp/lib/models/container_model.dart
Normal file
251
em2rp/lib/models/container_model.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:em2rp/models/equipment_model.dart';
|
||||
|
||||
/// Type de container
|
||||
enum ContainerType {
|
||||
flightCase, // Flight case
|
||||
pelicase, // Pelicase
|
||||
bag, // Sac
|
||||
openCrate, // Caisse ouverte
|
||||
toolbox, // Boîte à outils
|
||||
}
|
||||
|
||||
String containerTypeToString(ContainerType type) {
|
||||
switch (type) {
|
||||
case ContainerType.flightCase:
|
||||
return 'FLIGHT_CASE';
|
||||
case ContainerType.pelicase:
|
||||
return 'PELICASE';
|
||||
case ContainerType.bag:
|
||||
return 'BAG';
|
||||
case ContainerType.openCrate:
|
||||
return 'OPEN_CRATE';
|
||||
case ContainerType.toolbox:
|
||||
return 'TOOLBOX';
|
||||
}
|
||||
}
|
||||
|
||||
ContainerType containerTypeFromString(String? type) {
|
||||
switch (type) {
|
||||
case 'FLIGHT_CASE':
|
||||
return ContainerType.flightCase;
|
||||
case 'PELICASE':
|
||||
return ContainerType.pelicase;
|
||||
case 'BAG':
|
||||
return ContainerType.bag;
|
||||
case 'OPEN_CRATE':
|
||||
return ContainerType.openCrate;
|
||||
case 'TOOLBOX':
|
||||
return ContainerType.toolbox;
|
||||
default:
|
||||
return ContainerType.flightCase;
|
||||
}
|
||||
}
|
||||
|
||||
String containerTypeLabel(ContainerType type) {
|
||||
switch (type) {
|
||||
case ContainerType.flightCase:
|
||||
return 'Flight Case';
|
||||
case ContainerType.pelicase:
|
||||
return 'Pelicase';
|
||||
case ContainerType.bag:
|
||||
return 'Sac';
|
||||
case ContainerType.openCrate:
|
||||
return 'Caisse Ouverte';
|
||||
case ContainerType.toolbox:
|
||||
return 'Boîte à Outils';
|
||||
}
|
||||
}
|
||||
|
||||
/// Modèle de container/boîte pour le matériel
|
||||
class ContainerModel {
|
||||
final String id; // Identifiant unique (généré comme pour équipement)
|
||||
final String name; // Nom du container
|
||||
final ContainerType type; // Type de container
|
||||
final EquipmentStatus status; // Statut actuel (même que équipement)
|
||||
|
||||
// Caractéristiques physiques
|
||||
final double? weight; // Poids à vide (kg)
|
||||
final double? length; // Longueur (cm)
|
||||
final double? width; // Largeur (cm)
|
||||
final double? height; // Hauteur (cm)
|
||||
|
||||
// Contenu
|
||||
final List<String> equipmentIds; // IDs des équipements contenus
|
||||
|
||||
// Événement
|
||||
final String? eventId; // ID de l'événement actuel (si en prestation)
|
||||
|
||||
// Métadonnées
|
||||
final String? notes; // Notes additionnelles
|
||||
final DateTime createdAt; // Date de création
|
||||
final DateTime updatedAt; // Date de mise à jour
|
||||
|
||||
// Historique simple (optionnel)
|
||||
final List<ContainerHistoryEntry> history; // Historique des modifications
|
||||
|
||||
ContainerModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
this.status = EquipmentStatus.available,
|
||||
this.weight,
|
||||
this.length,
|
||||
this.width,
|
||||
this.height,
|
||||
this.equipmentIds = const [],
|
||||
this.eventId,
|
||||
this.notes,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.history = const [],
|
||||
});
|
||||
|
||||
/// Vérifier si le container est vide
|
||||
bool get isEmpty => equipmentIds.isEmpty;
|
||||
|
||||
/// Nombre d'équipements dans le container
|
||||
int get itemCount => equipmentIds.length;
|
||||
|
||||
/// Calculer le volume (m³)
|
||||
double? get volume {
|
||||
if (length == null || width == null || height == null) return null;
|
||||
return (length! * width! * height!) / 1000000; // cm³ to m³
|
||||
}
|
||||
|
||||
/// Calculer le poids total (poids vide + équipements)
|
||||
/// Nécessite la liste des équipements
|
||||
double calculateTotalWeight(List<EquipmentModel> equipment) {
|
||||
double total = weight ?? 0.0;
|
||||
for (final eq in equipment) {
|
||||
if (equipmentIds.contains(eq.id) && eq.weight != null) {
|
||||
total += eq.weight!;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Factory depuis Firestore
|
||||
factory ContainerModel.fromMap(Map<String, dynamic> map, String id) {
|
||||
final List<dynamic> equipmentIdsRaw = map['equipmentIds'] ?? [];
|
||||
final List<String> equipmentIds = equipmentIdsRaw.map((e) => e.toString()).toList();
|
||||
|
||||
final List<dynamic> historyRaw = map['history'] ?? [];
|
||||
final List<ContainerHistoryEntry> history = historyRaw
|
||||
.map((e) => ContainerHistoryEntry.fromMap(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
return ContainerModel(
|
||||
id: id,
|
||||
name: map['name'] ?? '',
|
||||
type: containerTypeFromString(map['type']),
|
||||
status: equipmentStatusFromString(map['status']),
|
||||
weight: map['weight']?.toDouble(),
|
||||
length: map['length']?.toDouble(),
|
||||
width: map['width']?.toDouble(),
|
||||
height: map['height']?.toDouble(),
|
||||
equipmentIds: equipmentIds,
|
||||
eventId: map['eventId'],
|
||||
notes: map['notes'],
|
||||
createdAt: (map['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
|
||||
updatedAt: (map['updatedAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
|
||||
history: history,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convertir en Map pour Firestore
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'type': containerTypeToString(type),
|
||||
'status': equipmentStatusToString(status),
|
||||
'weight': weight,
|
||||
'length': length,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'equipmentIds': equipmentIds,
|
||||
'eventId': eventId,
|
||||
'notes': notes,
|
||||
'createdAt': Timestamp.fromDate(createdAt),
|
||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||
'history': history.map((e) => e.toMap()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Copier avec modifications
|
||||
ContainerModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
ContainerType? type,
|
||||
EquipmentStatus? status,
|
||||
double? weight,
|
||||
double? length,
|
||||
double? width,
|
||||
double? height,
|
||||
List<String>? equipmentIds,
|
||||
String? eventId,
|
||||
String? notes,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
List<ContainerHistoryEntry>? history,
|
||||
}) {
|
||||
return ContainerModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
status: status ?? this.status,
|
||||
weight: weight ?? this.weight,
|
||||
length: length ?? this.length,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
equipmentIds: equipmentIds ?? this.equipmentIds,
|
||||
eventId: eventId ?? this.eventId,
|
||||
notes: notes ?? this.notes,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
history: history ?? this.history,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Entrée d'historique pour un container
|
||||
class ContainerHistoryEntry {
|
||||
final DateTime timestamp;
|
||||
final String action; // 'added', 'removed', 'status_change', etc.
|
||||
final String? equipmentId; // ID de l'équipement concerné (si applicable)
|
||||
final String? previousValue; // Valeur précédente
|
||||
final String? newValue; // Nouvelle valeur
|
||||
final String? userId; // ID de l'utilisateur ayant fait la modification
|
||||
|
||||
ContainerHistoryEntry({
|
||||
required this.timestamp,
|
||||
required this.action,
|
||||
this.equipmentId,
|
||||
this.previousValue,
|
||||
this.newValue,
|
||||
this.userId,
|
||||
});
|
||||
|
||||
factory ContainerHistoryEntry.fromMap(Map<String, dynamic> map) {
|
||||
return ContainerHistoryEntry(
|
||||
timestamp: (map['timestamp'] as Timestamp?)?.toDate() ?? DateTime.now(),
|
||||
action: map['action'] ?? '',
|
||||
equipmentId: map['equipmentId'],
|
||||
previousValue: map['previousValue'],
|
||||
newValue: map['newValue'],
|
||||
userId: map['userId'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'timestamp': Timestamp.fromDate(timestamp),
|
||||
'action': action,
|
||||
'equipmentId': equipmentId,
|
||||
'previousValue': previousValue,
|
||||
'newValue': newValue,
|
||||
'userId': userId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,12 @@ class EquipmentModel {
|
||||
// Boîtes parentes (plusieurs possibles)
|
||||
final List<String> parentBoxIds; // IDs des boîtes contenant cet équipement
|
||||
|
||||
// Caractéristiques physiques
|
||||
final double? weight; // Poids (kg)
|
||||
final double? length; // Longueur (cm)
|
||||
final double? width; // Largeur (cm)
|
||||
final double? height; // Hauteur (cm)
|
||||
|
||||
// Dates & maintenance
|
||||
final DateTime? purchaseDate; // Date d'achat
|
||||
final DateTime? lastMaintenanceDate; // Dernière maintenance
|
||||
@@ -148,6 +154,10 @@ class EquipmentModel {
|
||||
this.availableQuantity,
|
||||
this.criticalThreshold,
|
||||
this.parentBoxIds = const [],
|
||||
this.weight,
|
||||
this.length,
|
||||
this.width,
|
||||
this.height,
|
||||
this.purchaseDate,
|
||||
this.lastMaintenanceDate,
|
||||
this.nextMaintenanceDate,
|
||||
@@ -179,6 +189,10 @@ class EquipmentModel {
|
||||
availableQuantity: map['availableQuantity']?.toInt(),
|
||||
criticalThreshold: map['criticalThreshold']?.toInt(),
|
||||
parentBoxIds: parentBoxIds,
|
||||
weight: map['weight']?.toDouble(),
|
||||
length: map['length']?.toDouble(),
|
||||
width: map['width']?.toDouble(),
|
||||
height: map['height']?.toDouble(),
|
||||
purchaseDate: (map['purchaseDate'] as Timestamp?)?.toDate(),
|
||||
nextMaintenanceDate: (map['nextMaintenanceDate'] as Timestamp?)?.toDate(),
|
||||
maintenanceIds: maintenanceIds,
|
||||
@@ -202,6 +216,10 @@ class EquipmentModel {
|
||||
'availableQuantity': availableQuantity,
|
||||
'criticalThreshold': criticalThreshold,
|
||||
'parentBoxIds': parentBoxIds,
|
||||
'weight': weight,
|
||||
'length': length,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'lastMaintenanceDate': lastMaintenanceDate != null ? Timestamp.fromDate(lastMaintenanceDate!) : null,
|
||||
'purchaseDate': purchaseDate != null ? Timestamp.fromDate(purchaseDate!) : null,
|
||||
'nextMaintenanceDate': nextMaintenanceDate != null ? Timestamp.fromDate(nextMaintenanceDate!) : null,
|
||||
@@ -226,6 +244,10 @@ class EquipmentModel {
|
||||
int? availableQuantity,
|
||||
int? criticalThreshold,
|
||||
List<String>? parentBoxIds,
|
||||
double? weight,
|
||||
double? length,
|
||||
double? width,
|
||||
double? height,
|
||||
DateTime? purchaseDate,
|
||||
DateTime? lastMaintenanceDate,
|
||||
DateTime? nextMaintenanceDate,
|
||||
@@ -248,6 +270,10 @@ class EquipmentModel {
|
||||
availableQuantity: availableQuantity ?? this.availableQuantity,
|
||||
criticalThreshold: criticalThreshold ?? this.criticalThreshold,
|
||||
parentBoxIds: parentBoxIds ?? this.parentBoxIds,
|
||||
weight: weight ?? this.weight,
|
||||
length: length ?? this.length,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
lastMaintenanceDate: lastMaintenanceDate ?? this.lastMaintenanceDate,
|
||||
purchaseDate: purchaseDate ?? this.purchaseDate,
|
||||
nextMaintenanceDate: nextMaintenanceDate ?? this.nextMaintenanceDate,
|
||||
|
||||
Reference in New Issue
Block a user