Files
EM2_ERP/em2rp/lib/models/equipment_model.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

306 lines
10 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
enum EquipmentStatus {
available, // Disponible
inUse, // En prestation
rented, // Loué
lost, // Perdu
outOfService, // HS
maintenance, // En maintenance
}
String equipmentStatusToString(EquipmentStatus status) {
switch (status) {
case EquipmentStatus.available:
return 'AVAILABLE';
case EquipmentStatus.inUse:
return 'IN_USE';
case EquipmentStatus.rented:
return 'RENTED';
case EquipmentStatus.lost:
return 'LOST';
case EquipmentStatus.outOfService:
return 'OUT_OF_SERVICE';
case EquipmentStatus.maintenance:
return 'MAINTENANCE';
}
}
EquipmentStatus equipmentStatusFromString(String? status) {
switch (status) {
case 'AVAILABLE':
return EquipmentStatus.available;
case 'IN_USE':
return EquipmentStatus.inUse;
case 'RENTED':
return EquipmentStatus.rented;
case 'LOST':
return EquipmentStatus.lost;
case 'OUT_OF_SERVICE':
return EquipmentStatus.outOfService;
case 'MAINTENANCE':
return EquipmentStatus.maintenance;
default:
return EquipmentStatus.available;
}
}
enum EquipmentCategory {
lighting, // Lumière
sound, // Son
video, // Vidéo
effect, // Effets spéciaux
structure, // Structure
consumable, // Consommable
cable, // Câble
other // Autre
}
String equipmentCategoryToString(EquipmentCategory category) {
switch (category) {
case EquipmentCategory.lighting:
return 'LIGHTING';
case EquipmentCategory.sound:
return 'SOUND';
case EquipmentCategory.video:
return 'VIDEO';
case EquipmentCategory.structure:
return 'STRUCTURE';
case EquipmentCategory.consumable:
return 'CONSUMABLE';
case EquipmentCategory.cable:
return 'CABLE';
case EquipmentCategory.other:
return 'OTHER';
case EquipmentCategory.effect:
return 'EFFECT';
}
}
EquipmentCategory equipmentCategoryFromString(String? category) {
switch (category) {
case 'LIGHTING':
return EquipmentCategory.lighting;
case 'SOUND':
return EquipmentCategory.sound;
case 'VIDEO':
return EquipmentCategory.video;
case 'STRUCTURE':
return EquipmentCategory.structure;
case 'CONSUMABLE':
return EquipmentCategory.consumable;
case 'CABLE':
return EquipmentCategory.cable;
case 'EFFECT':
return EquipmentCategory.effect;
case 'OTHER':
default:
return EquipmentCategory.other;
}
}
class EquipmentModel {
final String id; // Identifiant unique (clé)
final String name; // Nom de l'équipement
final String? brand; // Marque (indexé)
final String? model; // Modèle (indexé)
final EquipmentCategory category; // Catégorie
final EquipmentStatus status; // Statut actuel
// Prix (visible uniquement avec manage_equipment)
final double? purchasePrice; // Prix d'achat
final double? rentalPrice; // Prix de location
// Quantité (pour consommables/câbles)
final int? totalQuantity; // Quantité totale
final int? availableQuantity; // Quantité disponible
final int? criticalThreshold; // Seuil critique pour alerte
// 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
final DateTime? nextMaintenanceDate; // Prochaine maintenance prévue
// Maintenances (références)
final List<String> maintenanceIds; // IDs des opérations de maintenance
// Image
final String? imageUrl; // URL de l'image (Storage /materiel)
// Métadonnées
final String? notes; // Notes additionnelles
final DateTime createdAt; // Date de création
final DateTime updatedAt; // Date de mise à jour
EquipmentModel({
required this.id,
required this.name,
this.brand,
this.model,
required this.category,
this.status = EquipmentStatus.available,
this.purchasePrice,
this.rentalPrice,
this.totalQuantity,
this.availableQuantity,
this.criticalThreshold,
this.parentBoxIds = const [],
this.weight,
this.length,
this.width,
this.height,
this.purchaseDate,
this.lastMaintenanceDate,
this.nextMaintenanceDate,
this.maintenanceIds = const [],
this.imageUrl,
this.notes,
required this.createdAt,
required this.updatedAt,
});
factory EquipmentModel.fromMap(Map<String, dynamic> map, String id) {
// Gestion des listes
final List<dynamic> parentBoxIdsRaw = map['parentBoxIds'] ?? [];
final List<String> parentBoxIds = parentBoxIdsRaw.map((e) => e.toString()).toList();
final List<dynamic> maintenanceIdsRaw = map['maintenanceIds'] ?? [];
final List<String> maintenanceIds = maintenanceIdsRaw.map((e) => e.toString()).toList();
return EquipmentModel(
id: id,
name: map['name'] ?? '',
brand: map['brand'],
model: map['model'],
category: equipmentCategoryFromString(map['category']),
status: equipmentStatusFromString(map['status']),
purchasePrice: map['purchasePrice']?.toDouble(),
rentalPrice: map['rentalPrice']?.toDouble(),
totalQuantity: map['totalQuantity']?.toInt(),
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,
imageUrl: map['imageUrl'],
notes: map['notes'],
createdAt: (map['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
updatedAt: (map['updatedAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'brand': brand,
'model': model,
'category': equipmentCategoryToString(category),
'status': equipmentStatusToString(status),
'purchasePrice': purchasePrice,
'rentalPrice': rentalPrice,
'totalQuantity': totalQuantity,
'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,
'maintenanceIds': maintenanceIds,
'imageUrl': imageUrl,
'notes': notes,
'createdAt': Timestamp.fromDate(createdAt),
'updatedAt': Timestamp.fromDate(updatedAt),
};
}
EquipmentModel copyWith({
String? id,
String? brand,
String? name,
String? model,
EquipmentCategory? category,
EquipmentStatus? status,
double? purchasePrice,
double? rentalPrice,
int? totalQuantity,
int? availableQuantity,
int? criticalThreshold,
List<String>? parentBoxIds,
double? weight,
double? length,
double? width,
double? height,
DateTime? purchaseDate,
DateTime? lastMaintenanceDate,
DateTime? nextMaintenanceDate,
List<String>? maintenanceIds,
String? imageUrl,
String? notes,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return EquipmentModel(
id: id ?? this.id,
brand: brand ?? this.brand,
name: name ?? this.name,
model: model ?? this.model,
category: category ?? this.category,
status: status ?? this.status,
purchasePrice: purchasePrice ?? this.purchasePrice,
rentalPrice: rentalPrice ?? this.rentalPrice,
totalQuantity: totalQuantity ?? this.totalQuantity,
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,
maintenanceIds: maintenanceIds ?? this.maintenanceIds,
imageUrl: imageUrl ?? this.imageUrl,
notes: notes ?? this.notes,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
// Helper pour vérifier si c'est un consommable/câble avec quantité
bool get hasQuantity => category == EquipmentCategory.consumable || category == EquipmentCategory.cable;
// Helper pour vérifier si le stock est critique
bool get isCriticalStock {
if (!hasQuantity || criticalThreshold == null || availableQuantity == null) {
return false;
}
return availableQuantity! <= criticalThreshold!;
}
// Helper pour vérifier si la maintenance est à venir
bool get isMaintenanceDue {
if (nextMaintenanceDate == null) return false;
return nextMaintenanceDate!.isBefore(DateTime.now().add(const Duration(days: 7)));
}
}