|
|
|
@@ -516,8 +516,28 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
const allLots: any[] = []; |
|
|
|
|
|
|
|
data.pickOrderLines.forEach((line) => { |
|
|
|
// 用来记录这一行已经通过 lots 出现过的 lotId(避免 stockouts 再渲染一次) |
|
|
|
const lotIdSet = new Set<number>(); |
|
|
|
|
|
|
|
// lots:按 lotId 去重并合并 requiredQty(对齐 GoodPickExecutiondetail) |
|
|
|
if (line.lots && line.lots.length > 0) { |
|
|
|
line.lots.forEach((lot) => { |
|
|
|
const lotMap = new Map<number, any>(); |
|
|
|
|
|
|
|
line.lots.forEach((lot: any) => { |
|
|
|
const lotId = lot.lotId; |
|
|
|
if (lotId == null) return; |
|
|
|
if (lotMap.has(lotId)) { |
|
|
|
const existingLot = lotMap.get(lotId); |
|
|
|
existingLot.requiredQty = |
|
|
|
(existingLot.requiredQty || 0) + (lot.requiredQty || 0); |
|
|
|
} else { |
|
|
|
lotMap.set(lotId, { ...lot }); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
lotMap.forEach((lot: any) => { |
|
|
|
if (lot.lotId != null) lotIdSet.add(lot.lotId); |
|
|
|
|
|
|
|
allLots.push({ |
|
|
|
...lot, |
|
|
|
pickOrderLineId: line.id, |
|
|
|
@@ -530,7 +550,6 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
pickOrderLineStatus: line.status, |
|
|
|
jobOrderId: data.pickOrder.jobOrder.id, |
|
|
|
jobOrderCode: data.pickOrder.jobOrder.code, |
|
|
|
// 添加 pickOrder 信息(如果需要) |
|
|
|
pickOrderId: data.pickOrder.id, |
|
|
|
pickOrderCode: data.pickOrder.code, |
|
|
|
pickOrderConsoCode: data.pickOrder.consoCode, |
|
|
|
@@ -539,6 +558,60 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
pickOrderStatus: data.pickOrder.status, |
|
|
|
pickOrderAssignTo: data.pickOrder.assignTo, |
|
|
|
handler: line.handler, |
|
|
|
noLot: false, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// stockouts:用于“无 suggested lot / noLot”场景也显示并可 submit 0 闭环 |
|
|
|
if (line.stockouts && line.stockouts.length > 0) { |
|
|
|
line.stockouts.forEach((stockout: any) => { |
|
|
|
const hasLot = stockout.lotId != null; |
|
|
|
const lotAlreadyInLots = hasLot && lotIdSet.has(stockout.lotId as number); |
|
|
|
|
|
|
|
// 有批次 & 已经通过 lots 渲染过 → 跳过,避免一条变两行 |
|
|
|
if (!stockout.noLot && lotAlreadyInLots) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
allLots.push({ |
|
|
|
pickOrderLineId: line.id, |
|
|
|
itemId: line.itemId, |
|
|
|
itemCode: line.itemCode, |
|
|
|
itemName: line.itemName, |
|
|
|
uomCode: line.uomCode, |
|
|
|
uomDesc: line.uomDesc, |
|
|
|
pickOrderLineRequiredQty: line.requiredQty, |
|
|
|
pickOrderLineStatus: line.status, |
|
|
|
jobOrderId: data.pickOrder.jobOrder.id, |
|
|
|
jobOrderCode: data.pickOrder.jobOrder.code, |
|
|
|
pickOrderId: data.pickOrder.id, |
|
|
|
pickOrderCode: data.pickOrder.code, |
|
|
|
pickOrderConsoCode: data.pickOrder.consoCode, |
|
|
|
pickOrderTargetDate: data.pickOrder.targetDate, |
|
|
|
pickOrderType: data.pickOrder.type, |
|
|
|
pickOrderStatus: data.pickOrder.status, |
|
|
|
pickOrderAssignTo: data.pickOrder.assignTo, |
|
|
|
handler: line.handler, |
|
|
|
|
|
|
|
lotId: stockout.lotId || null, |
|
|
|
lotNo: stockout.lotNo || null, |
|
|
|
expiryDate: null, |
|
|
|
location: stockout.location || null, |
|
|
|
availableQty: stockout.availableQty ?? 0, |
|
|
|
requiredQty: line.requiredQty ?? 0, |
|
|
|
actualPickQty: stockout.qty ?? 0, |
|
|
|
processingStatus: stockout.status || "pending", |
|
|
|
lotAvailability: stockout.noLot ? "insufficient_stock" : "available", |
|
|
|
suggestedPickLotId: null, |
|
|
|
stockOutLineId: stockout.id || null, |
|
|
|
stockOutLineQty: stockout.qty ?? 0, |
|
|
|
stockOutLineStatus: stockout.status || null, |
|
|
|
stockInLineId: null, |
|
|
|
routerIndex: stockout.noLot ? 999999 : null, |
|
|
|
routerArea: null, |
|
|
|
routerRoute: null, |
|
|
|
noLot: !!stockout.noLot, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
@@ -1011,30 +1084,57 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
console.log("Scanned Lot No:", scannedLotData.lotNo); |
|
|
|
console.log("Scanned StockInLineId:", scannedLotData.stockInLineId); |
|
|
|
|
|
|
|
// Call confirmLotSubstitution to update the suggested lot |
|
|
|
console.log("🔄 [LOT CONFIRM] Calling confirmLotSubstitution..."); |
|
|
|
const substitutionResult = await confirmLotSubstitution({ |
|
|
|
pickOrderLineId: selectedLotForQr.pickOrderLineId, |
|
|
|
stockOutLineId: selectedLotForQr.stockOutLineId, |
|
|
|
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId, |
|
|
|
newInventoryLotNo: scannedLotData.lotNo || '', |
|
|
|
// ✅ required by LotSubstitutionConfirmRequest |
|
|
|
newStockInLineId: scannedLotData?.stockInLineId ?? null, |
|
|
|
}); |
|
|
|
const originalSuggestedPickLotId = |
|
|
|
selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId; |
|
|
|
|
|
|
|
// noLot / missing suggestedPickLotId 场景:没有 originalSuggestedPickLotId,改用 updateStockOutLineStatusByQRCodeAndLotNo |
|
|
|
if (!originalSuggestedPickLotId) { |
|
|
|
if (!selectedLotForQr?.stockOutLineId) { |
|
|
|
throw new Error("Missing stockOutLineId for noLot line"); |
|
|
|
} |
|
|
|
console.log("🔄 [LOT CONFIRM] No originalSuggestedPickLotId, using updateStockOutLineStatusByQRCodeAndLotNo..."); |
|
|
|
const res = await updateStockOutLineStatusByQRCodeAndLotNo({ |
|
|
|
pickOrderLineId: selectedLotForQr.pickOrderLineId, |
|
|
|
inventoryLotNo: scannedLotData.lotNo || '', |
|
|
|
stockInLineId: scannedLotData?.stockInLineId ?? null, |
|
|
|
stockOutLineId: selectedLotForQr.stockOutLineId, |
|
|
|
itemId: selectedLotForQr.itemId, |
|
|
|
status: "checked", |
|
|
|
}); |
|
|
|
console.log("✅ [LOT CONFIRM] updateStockOutLineStatusByQRCodeAndLotNo result:", res); |
|
|
|
const ok = res?.code === "checked" || res?.code === "SUCCESS"; |
|
|
|
if (!ok) { |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg(res?.message || "换批失败:无法更新 stock out line"); |
|
|
|
return; |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Call confirmLotSubstitution to update the suggested lot |
|
|
|
console.log("🔄 [LOT CONFIRM] Calling confirmLotSubstitution..."); |
|
|
|
const substitutionResult = await confirmLotSubstitution({ |
|
|
|
pickOrderLineId: selectedLotForQr.pickOrderLineId, |
|
|
|
stockOutLineId: selectedLotForQr.stockOutLineId, |
|
|
|
originalSuggestedPickLotId, |
|
|
|
newInventoryLotNo: scannedLotData.lotNo || '', |
|
|
|
// ✅ required by LotSubstitutionConfirmRequest |
|
|
|
newStockInLineId: scannedLotData?.stockInLineId ?? null, |
|
|
|
}); |
|
|
|
|
|
|
|
console.log("✅ [LOT CONFIRM] Lot substitution result:", substitutionResult); |
|
|
|
console.log("✅ [LOT CONFIRM] Lot substitution result:", substitutionResult); |
|
|
|
|
|
|
|
// ✅ CRITICAL: substitution failed => DO NOT mark original stockOutLine as checked. |
|
|
|
// Keep modal open so user can cancel/rescan. |
|
|
|
if (!substitutionResult || substitutionResult.code !== "SUCCESS") { |
|
|
|
console.error("❌ [LOT CONFIRM] Lot substitution failed. Will NOT update stockOutLine status."); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg( |
|
|
|
substitutionResult?.message || |
|
|
|
`换批失败:stockInLineId ${scannedLotData?.stockInLineId ?? ""} 不存在或无法匹配` |
|
|
|
); |
|
|
|
return; |
|
|
|
// ✅ CRITICAL: substitution failed => DO NOT mark original stockOutLine as checked. |
|
|
|
// Keep modal open so user can cancel/rescan. |
|
|
|
if (!substitutionResult || substitutionResult.code !== "SUCCESS") { |
|
|
|
console.error("❌ [LOT CONFIRM] Lot substitution failed. Will NOT update stockOutLine status."); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg( |
|
|
|
substitutionResult?.message || |
|
|
|
`换批失败:stockInLineId ${scannedLotData?.stockInLineId ?? ""} 不存在或无法匹配` |
|
|
|
); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Update stock out line status to 'checked' after substitution |
|
|
|
@@ -1280,6 +1380,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
const res = await updateStockOutLineStatusByQRCodeAndLotNo({ |
|
|
|
pickOrderLineId: exactMatch.pickOrderLineId, |
|
|
|
inventoryLotNo: exactMatch.lotNo, |
|
|
|
stockInLineId: exactMatch.stockInLineId ?? null, |
|
|
|
stockOutLineId: exactMatch.stockOutLineId, |
|
|
|
itemId: exactMatch.itemId, |
|
|
|
status: "checked", |
|
|
|
@@ -2152,6 +2253,9 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
}; |
|
|
|
}, [isManualScanning, stopScan, resetScan]); |
|
|
|
const getStatusMessage = useCallback((lot: any) => { |
|
|
|
if (lot?.noLot === true || lot?.lotAvailability === 'insufficient_stock') { |
|
|
|
return t("This order is insufficient, please pick another lot."); |
|
|
|
} |
|
|
|
switch (lot.stockOutLineStatus?.toLowerCase()) { |
|
|
|
case 'pending': |
|
|
|
return t("Please finish QR code scan and pick order."); |
|
|
|
|