feat: implement equipment and container loading rollback functionality with corresponding backend cloud functions
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
const {onCall} = require("firebase-functions/v2/https");
|
||||
const admin = require("firebase-admin");
|
||||
const logger = require("firebase-functions/logger");
|
||||
|
||||
/**
|
||||
* Reverts the validation progress of an event to a target step.
|
||||
* Resets subsequent step statuses and validation flags of assigned equipment.
|
||||
* If rolling back from a completed return, it decrements the consumable stock quantities
|
||||
* that were restored during return validation.
|
||||
*/
|
||||
const handler = async (request) => {
|
||||
try {
|
||||
const {auth, data} = request;
|
||||
if (!auth) {
|
||||
throw new Error("L'utilisateur doit être authentifié");
|
||||
}
|
||||
|
||||
const {eventId, targetStep} = data;
|
||||
if (!eventId || !targetStep) {
|
||||
throw new Error("eventId et targetStep sont requis");
|
||||
}
|
||||
|
||||
const db = admin.firestore();
|
||||
const eventRef = db.collection("events").doc(eventId);
|
||||
|
||||
await db.runTransaction(async (transaction) => {
|
||||
const eventDoc = await transaction.get(eventRef);
|
||||
if (!eventDoc.exists) {
|
||||
throw new Error("Événement introuvable");
|
||||
}
|
||||
|
||||
const event = eventDoc.data();
|
||||
const assignedEquipment = event.assignedEquipment || [];
|
||||
|
||||
// Si le retour était complété et qu'on revient en arrière, on doit annuler la restauration des stocks
|
||||
const shouldRevertStocks = event.stocksRestored === true;
|
||||
|
||||
if (shouldRevertStocks) {
|
||||
// Charger tous les équipements uniques de l'événement pour ajuster leur stock
|
||||
const equipmentIds = Array.from(new Set(assignedEquipment.map((eq) => eq.equipmentId).filter(Boolean)));
|
||||
const equipmentDocsMap = {};
|
||||
|
||||
for (const eqId of equipmentIds) {
|
||||
const eqRef = db.collection("equipments").doc(eqId);
|
||||
const eqDoc = await transaction.get(eqRef);
|
||||
if (eqDoc.exists) {
|
||||
equipmentDocsMap[eqId] = eqDoc.data();
|
||||
}
|
||||
}
|
||||
|
||||
for (const eq of assignedEquipment) {
|
||||
const eqId = eq.equipmentId;
|
||||
const equipmentData = equipmentDocsMap[eqId];
|
||||
if (!equipmentData) continue;
|
||||
|
||||
const hasQuantity = equipmentData.hasQuantity === true ||
|
||||
equipmentData.category === "CABLE" ||
|
||||
equipmentData.category === "CONSUMABLE";
|
||||
|
||||
if (hasQuantity) {
|
||||
// C'est un consommable, on doit déduire la quantité qui avait été restaurée
|
||||
const qtyAtRet = Number(eq.quantityAtReturn) || 0;
|
||||
if (qtyAtRet > 0) {
|
||||
const eqRef = db.collection("equipments").doc(eqId);
|
||||
const currentAvailable = Number(equipmentData.availableQuantity) || 0;
|
||||
// S'assurer de ne pas descendre en dessous de 0 (ou autoriser le négatif si stock virtuel)
|
||||
const newAvailable = Math.max(0, currentAvailable - qtyAtRet);
|
||||
transaction.update(eqRef, {
|
||||
availableQuantity: newAvailable,
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
logger.info(`[rollbackEventStep] Annulé la restauration de ${qtyAtRet} pour ${eqId}. Ancien stock: ${currentAvailable}, Nouveau stock: ${newAvailable}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Préparer les nouvelles valeurs des étapes
|
||||
let prepStatus = event.preparationStatus;
|
||||
let loadStatus = event.loadingStatus;
|
||||
let unloadStatus = event.unloadingStatus;
|
||||
let retStatus = event.returnStatus;
|
||||
|
||||
if (targetStep === 'PREPARATION') {
|
||||
prepStatus = 'IN_PROGRESS';
|
||||
loadStatus = 'NOT_STARTED';
|
||||
unloadStatus = 'NOT_STARTED';
|
||||
retStatus = 'NOT_STARTED';
|
||||
} else if (targetStep === 'LOADING') {
|
||||
loadStatus = 'IN_PROGRESS';
|
||||
unloadStatus = 'NOT_STARTED';
|
||||
retStatus = 'NOT_STARTED';
|
||||
} else if (targetStep === 'UNLOADING') {
|
||||
unloadStatus = 'IN_PROGRESS';
|
||||
retStatus = 'NOT_STARTED';
|
||||
} else if (targetStep === 'RETURN') {
|
||||
retStatus = 'IN_PROGRESS';
|
||||
} else {
|
||||
throw new Error("targetStep invalide. Doit être PREPARATION, LOADING, UNLOADING ou RETURN");
|
||||
}
|
||||
|
||||
// Nettoyer les champs de validation des équipements pour les étapes annulées
|
||||
const updatedEquipment = assignedEquipment.map((eq) => {
|
||||
let isPrepared = eq.isPrepared;
|
||||
let isMissingAtPreparation = eq.isMissingAtPreparation;
|
||||
let quantityAtPreparation = eq.quantityAtPreparation;
|
||||
|
||||
let isLoaded = eq.isLoaded;
|
||||
let isMissingAtLoading = eq.isMissingAtLoading;
|
||||
let quantityAtLoading = eq.quantityAtLoading;
|
||||
|
||||
let isUnloaded = eq.isUnloaded;
|
||||
let isMissingAtUnloading = eq.isMissingAtUnloading;
|
||||
let quantityAtUnloading = eq.quantityAtUnloading;
|
||||
|
||||
let isReturned = eq.isReturned;
|
||||
let isMissingAtReturn = eq.isMissingAtReturn;
|
||||
let quantityAtReturn = eq.quantityAtReturn;
|
||||
|
||||
if (targetStep === 'PREPARATION') {
|
||||
isLoaded = false;
|
||||
isMissingAtLoading = false;
|
||||
quantityAtLoading = null;
|
||||
isUnloaded = false;
|
||||
isMissingAtUnloading = false;
|
||||
quantityAtUnloading = null;
|
||||
isReturned = false;
|
||||
isMissingAtReturn = false;
|
||||
quantityAtReturn = null;
|
||||
} else if (targetStep === 'LOADING') {
|
||||
isUnloaded = false;
|
||||
isMissingAtUnloading = false;
|
||||
quantityAtUnloading = null;
|
||||
isReturned = false;
|
||||
isMissingAtReturn = false;
|
||||
quantityAtReturn = null;
|
||||
} else if (targetStep === 'UNLOADING') {
|
||||
isReturned = false;
|
||||
isMissingAtReturn = false;
|
||||
quantityAtReturn = null;
|
||||
}
|
||||
|
||||
return {
|
||||
...eq,
|
||||
isPrepared,
|
||||
isMissingAtPreparation,
|
||||
quantityAtPreparation,
|
||||
isLoaded,
|
||||
isMissingAtLoading,
|
||||
quantityAtLoading,
|
||||
isUnloaded,
|
||||
isMissingAtUnloading,
|
||||
quantityAtUnloading,
|
||||
isReturned,
|
||||
isMissingAtReturn,
|
||||
quantityAtReturn,
|
||||
};
|
||||
});
|
||||
|
||||
// Mettre à jour le document de l'événement
|
||||
transaction.update(eventRef, {
|
||||
preparationStatus: prepStatus,
|
||||
loadingStatus: loadStatus,
|
||||
unloadingStatus: unloadStatus,
|
||||
returnStatus: retStatus,
|
||||
assignedEquipment: updatedEquipment,
|
||||
stocksRestored: false,
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
});
|
||||
|
||||
logger.info(`[rollbackEventStep] Événement ${eventId} réinitialisé avec succès à l'étape ${targetStep}`);
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
logger.error("[rollbackEventStep] Erreur:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
exports.rollbackEventStep = onCall({
|
||||
cors: true,
|
||||
region: "europe-west9",
|
||||
}, handler);
|
||||
|
||||
exports.handler = handler;
|
||||
Reference in New Issue
Block a user