feat: implement equipment and container loading rollback functionality with corresponding backend cloud functions
This commit is contained in:
@@ -88,22 +88,22 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
|
||||
// Logique stricte : on avance étape par étape
|
||||
// 1. Préparation dépôt
|
||||
if (prep != PreparationStatus.completed) {
|
||||
if (prep != PreparationStatus.completed && prep != PreparationStatus.completedWithMissing) {
|
||||
return PreparationStep.preparation;
|
||||
}
|
||||
|
||||
// 2. Chargement aller (après préparation complète)
|
||||
if (loading != LoadingStatus.completed) {
|
||||
if (loading != LoadingStatus.completed && loading != LoadingStatus.completedWithMissing) {
|
||||
return PreparationStep.loadingOutbound;
|
||||
}
|
||||
|
||||
// 3. Chargement retour (après chargement aller complet)
|
||||
if (unloading != UnloadingStatus.completed) {
|
||||
if (unloading != UnloadingStatus.completed && unloading != UnloadingStatus.completedWithMissing) {
|
||||
return PreparationStep.unloadingReturn;
|
||||
}
|
||||
|
||||
// 4. Retour dépôt (après déchargement complet)
|
||||
if (returnStatus != ReturnStatus.completed) {
|
||||
if (returnStatus != ReturnStatus.completed && returnStatus != ReturnStatus.completedWithMissing) {
|
||||
return PreparationStep.return_;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_isCurrentStepCompleted()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Cette étape est déjà terminée'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
@@ -141,6 +141,17 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isPreviousStepCompleted()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('L\'étape précédente n\'est pas terminée. Impossible d\'accéder à cette étape.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Charger les équipements après le premier frame pour éviter setState pendant build
|
||||
_loadEquipmentAndContainers();
|
||||
});
|
||||
@@ -150,13 +161,34 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
bool _isCurrentStepCompleted() {
|
||||
switch (_currentStep) {
|
||||
case PreparationStep.preparation:
|
||||
return (_currentEvent.preparationStatus ?? PreparationStatus.notStarted) == PreparationStatus.completed;
|
||||
final status = _currentEvent.preparationStatus ?? PreparationStatus.notStarted;
|
||||
return status == PreparationStatus.completed || status == PreparationStatus.completedWithMissing;
|
||||
case PreparationStep.loadingOutbound:
|
||||
return (_currentEvent.loadingStatus ?? LoadingStatus.notStarted) == LoadingStatus.completed;
|
||||
final status = _currentEvent.loadingStatus ?? LoadingStatus.notStarted;
|
||||
return status == LoadingStatus.completed || status == LoadingStatus.completedWithMissing;
|
||||
case PreparationStep.unloadingReturn:
|
||||
return (_currentEvent.unloadingStatus ?? UnloadingStatus.notStarted) == UnloadingStatus.completed;
|
||||
final status = _currentEvent.unloadingStatus ?? UnloadingStatus.notStarted;
|
||||
return status == UnloadingStatus.completed || status == UnloadingStatus.completedWithMissing;
|
||||
case PreparationStep.return_:
|
||||
return (_currentEvent.returnStatus ?? ReturnStatus.notStarted) == ReturnStatus.completed;
|
||||
final status = _currentEvent.returnStatus ?? ReturnStatus.notStarted;
|
||||
return status == ReturnStatus.completed || status == ReturnStatus.completedWithMissing;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'étape précédente est bien complétée
|
||||
bool _isPreviousStepCompleted() {
|
||||
switch (_currentStep) {
|
||||
case PreparationStep.preparation:
|
||||
return true; // Première étape, toujours OK
|
||||
case PreparationStep.loadingOutbound:
|
||||
final prep = _currentEvent.preparationStatus ?? PreparationStatus.notStarted;
|
||||
return prep == PreparationStatus.completed || prep == PreparationStatus.completedWithMissing;
|
||||
case PreparationStep.unloadingReturn:
|
||||
final loading = _currentEvent.loadingStatus ?? LoadingStatus.notStarted;
|
||||
return loading == LoadingStatus.completed || loading == LoadingStatus.completedWithMissing;
|
||||
case PreparationStep.return_:
|
||||
final unloading = _currentEvent.unloadingStatus ?? UnloadingStatus.notStarted;
|
||||
return unloading == UnloadingStatus.completed || unloading == UnloadingStatus.completedWithMissing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,10 +271,15 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
break;
|
||||
}
|
||||
|
||||
_quantitiesAtPreparation[eq.equipmentId] = eq.quantityAtPreparation ?? eq.quantity;
|
||||
_quantitiesAtLoading[eq.equipmentId] = eq.quantityAtLoading ?? eq.quantityAtPreparation ?? eq.quantity;
|
||||
_quantitiesAtUnloading[eq.equipmentId] = eq.quantityAtUnloading ?? eq.quantityAtLoading ?? eq.quantityAtPreparation ?? eq.quantity;
|
||||
_quantitiesAtReturn[eq.equipmentId] = eq.quantityAtReturn ?? eq.quantityAtUnloading ?? eq.quantityAtLoading ?? eq.quantityAtPreparation ?? eq.quantity;
|
||||
|
||||
if ((_currentStep == PreparationStep.return_ ||
|
||||
_currentStep == PreparationStep.unloadingReturn) &&
|
||||
(equipmentItem?.hasQuantity ?? false)) {
|
||||
_returnedQuantities[eq.equipmentId] = eq.quantityAtReturn ?? eq.quantity;
|
||||
_returnedQuantities[eq.equipmentId] = eq.quantityAtReturn ?? eq.quantityAtUnloading ?? eq.quantityAtLoading ?? eq.quantityAtPreparation ?? eq.quantity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,9 +455,8 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
returnStatus: updateData['returnStatus'],
|
||||
);
|
||||
|
||||
// Mettre à jour les statuts des équipements si nécessaire
|
||||
if (_currentStep == PreparationStep.preparation ||
|
||||
(_currentStep == PreparationStep.unloadingReturn && _loadSimultaneously)) {
|
||||
// Mettre à jour les statuts des équipements si nécessaire (uniquement pour la préparation, le retour étant géré par le trigger Firestore Cloud Function)
|
||||
if (_currentStep == PreparationStep.preparation) {
|
||||
await _updateEquipmentStatuses(updatedEquipment);
|
||||
}
|
||||
|
||||
@@ -505,6 +541,8 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
}
|
||||
|
||||
Future<void> _updateEquipmentStatuses(List<EventEquipment> equipment) async {
|
||||
final List<String> failedUpdates = [];
|
||||
|
||||
for (var eq in equipment) {
|
||||
try {
|
||||
final equipmentData = _equipmentCache[eq.equipmentId];
|
||||
@@ -513,7 +551,9 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
// Déterminer le nouveau statut
|
||||
EquipmentStatus newStatus;
|
||||
if (eq.isReturned) {
|
||||
newStatus = EquipmentStatus.available;
|
||||
// Note : Le retour est géré par le trigger Firestore Cloud Function en tâche de fond.
|
||||
// On évite les conflits d'écritures client/serveur et les double-restaurations de stock.
|
||||
continue;
|
||||
} else if (eq.isPrepared || eq.isLoaded) {
|
||||
newStatus = EquipmentStatus.inUse;
|
||||
} else {
|
||||
@@ -527,19 +567,22 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
status: equipmentStatusToString(newStatus),
|
||||
);
|
||||
}
|
||||
|
||||
// Gérer les stocks pour les consommables
|
||||
if (equipmentData.hasQuantity && eq.isReturned && eq.quantityAtReturn != null) {
|
||||
final currentAvailable = equipmentData.availableQuantity ?? 0;
|
||||
await _dataService.updateEquipmentStatusOnly(
|
||||
equipmentId: eq.equipmentId,
|
||||
availableQuantity: currentAvailable + eq.quantityAtReturn!,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Erreur silencieuse pour ne pas bloquer le processus
|
||||
DebugLog.error('[EventPreparationPage] Échec de la mise à jour du statut pour l\'équipement ${eq.equipmentId}', e);
|
||||
failedUpdates.add(eq.equipmentId);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedUpdates.isNotEmpty && mounted) {
|
||||
final names = failedUpdates.map((id) => _equipmentCache[id]?.name ?? id).join(', ');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Attention : Échec de mise à jour du statut en base pour : $names. Le matériel a tout de même été validé pour l\'événement.'),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 6),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _getSuccessMessage() {
|
||||
@@ -895,26 +938,28 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
_quantitiesAtReturn.addAll(quantities);
|
||||
break;
|
||||
}
|
||||
|
||||
// Mettre à jour `_currentEvent.assignedEquipment` pour que l'UI se reconstruise avec les bonnes valeurs
|
||||
final updatedList = _currentEvent.assignedEquipment.map((eq) {
|
||||
final qty = quantities[eq.equipmentId];
|
||||
if (qty != null) {
|
||||
switch (_currentStep) {
|
||||
case PreparationStep.preparation:
|
||||
return eq.copyWith(quantityAtPreparation: qty);
|
||||
case PreparationStep.loadingOutbound:
|
||||
return eq.copyWith(quantityAtLoading: qty);
|
||||
case PreparationStep.unloadingReturn:
|
||||
return eq.copyWith(quantityAtUnloading: qty);
|
||||
case PreparationStep.return_:
|
||||
return eq.copyWith(quantityAtReturn: qty);
|
||||
}
|
||||
}
|
||||
return eq;
|
||||
}).toList();
|
||||
|
||||
_currentEvent = _currentEvent.copyWith(assignedEquipment: updatedList);
|
||||
}
|
||||
|
||||
/// Obtenir la quantité requise selon l'étape (nouvelle logique)
|
||||
int _getTargetQuantity(EventEquipment eventEquipment) {
|
||||
switch (_currentStep) {
|
||||
case PreparationStep.preparation:
|
||||
return eventEquipment.quantity; // Quantité initiale
|
||||
case PreparationStep.loadingOutbound:
|
||||
return eventEquipment.quantityAtPreparation ?? eventEquipment.quantity;
|
||||
case PreparationStep.unloadingReturn:
|
||||
return eventEquipment.quantityAtLoading ??
|
||||
eventEquipment.quantityAtPreparation ??
|
||||
eventEquipment.quantity;
|
||||
case PreparationStep.return_:
|
||||
return eventEquipment.quantityAtUnloading ??
|
||||
eventEquipment.quantityAtLoading ??
|
||||
eventEquipment.quantityAtPreparation ??
|
||||
eventEquipment.quantity;
|
||||
}
|
||||
}
|
||||
|
||||
/// Afficher un message de succès
|
||||
void _showSuccessFeedback(String message) {
|
||||
@@ -1020,20 +1065,7 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
/// Mettre à jour la quantité d'un équipement à l'étape actuelle
|
||||
void _updateEquipmentQuantity(String equipmentId, int newQuantity) {
|
||||
setState(() {
|
||||
switch (_currentStep) {
|
||||
case PreparationStep.preparation:
|
||||
_quantitiesAtPreparation[equipmentId] = newQuantity;
|
||||
break;
|
||||
case PreparationStep.loadingOutbound:
|
||||
_quantitiesAtLoading[equipmentId] = newQuantity;
|
||||
break;
|
||||
case PreparationStep.unloadingReturn:
|
||||
_quantitiesAtUnloading[equipmentId] = newQuantity;
|
||||
break;
|
||||
case PreparationStep.return_:
|
||||
_quantitiesAtReturn[equipmentId] = newQuantity;
|
||||
break;
|
||||
}
|
||||
_updateQuantitiesMap({equipmentId: newQuantity});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user