|
|
|
@@ -46,7 +46,11 @@ import { |
|
|
|
fetchDoPickOrderDetail, // 必须添加 |
|
|
|
DoPickOrderDetail, // 必须添加 |
|
|
|
fetchFGPickOrdersByUserId , |
|
|
|
batchQrSubmit |
|
|
|
batchQrSubmit, |
|
|
|
batchSubmitList, // 添加:导入 batchSubmitList |
|
|
|
batchSubmitListRequest, // 添加:导入类型 |
|
|
|
batchSubmitListLineRequest |
|
|
|
|
|
|
|
} from "@/app/api/pickOrder/actions"; |
|
|
|
|
|
|
|
import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; |
|
|
|
@@ -327,7 +331,127 @@ const QrCodeModal: React.FC<{ |
|
|
|
</Modal> |
|
|
|
); |
|
|
|
}; |
|
|
|
const ManualLotConfirmationModal: React.FC<{ |
|
|
|
open: boolean; |
|
|
|
onClose: () => void; |
|
|
|
onConfirm: (expectedLotNo: string, scannedLotNo: string) => void; |
|
|
|
expectedLot: { |
|
|
|
lotNo: string; |
|
|
|
itemCode: string; |
|
|
|
itemName: string; |
|
|
|
} | null; |
|
|
|
scannedLot: { |
|
|
|
lotNo: string; |
|
|
|
itemCode: string; |
|
|
|
itemName: string; |
|
|
|
} | null; |
|
|
|
isLoading?: boolean; |
|
|
|
}> = ({ open, onClose, onConfirm, expectedLot, scannedLot, isLoading = false }) => { |
|
|
|
const { t } = useTranslation("pickOrder"); |
|
|
|
const [expectedLotInput, setExpectedLotInput] = useState<string>(''); |
|
|
|
const [scannedLotInput, setScannedLotInput] = useState<string>(''); |
|
|
|
const [error, setError] = useState<string>(''); |
|
|
|
|
|
|
|
// 当模态框打开时,预填充输入框 |
|
|
|
useEffect(() => { |
|
|
|
if (open) { |
|
|
|
setExpectedLotInput(expectedLot?.lotNo || ''); |
|
|
|
setScannedLotInput(scannedLot?.lotNo || ''); |
|
|
|
setError(''); |
|
|
|
} |
|
|
|
}, [open, expectedLot, scannedLot]); |
|
|
|
|
|
|
|
const handleConfirm = () => { |
|
|
|
if (!expectedLotInput.trim() || !scannedLotInput.trim()) { |
|
|
|
setError(t("Please enter both expected and scanned lot numbers.")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (expectedLotInput.trim() === scannedLotInput.trim()) { |
|
|
|
setError(t("Expected and scanned lot numbers cannot be the same.")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
onConfirm(expectedLotInput.trim(), scannedLotInput.trim()); |
|
|
|
}; |
|
|
|
|
|
|
|
return ( |
|
|
|
<Modal open={open} onClose={onClose}> |
|
|
|
<Box sx={{ |
|
|
|
position: 'absolute', |
|
|
|
top: '50%', |
|
|
|
left: '50%', |
|
|
|
transform: 'translate(-50%, -50%)', |
|
|
|
bgcolor: 'background.paper', |
|
|
|
p: 3, |
|
|
|
borderRadius: 2, |
|
|
|
minWidth: 500, |
|
|
|
}}> |
|
|
|
<Typography variant="h6" gutterBottom color="warning.main"> |
|
|
|
{t("Manual Lot Confirmation")} |
|
|
|
</Typography> |
|
|
|
|
|
|
|
<Box sx={{ mb: 2 }}> |
|
|
|
<Typography variant="body2" gutterBottom> |
|
|
|
<strong>{t("Expected Lot Number")}:</strong> |
|
|
|
</Typography> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
size="small" |
|
|
|
value={expectedLotInput} |
|
|
|
onChange={(e) => { |
|
|
|
setExpectedLotInput(e.target.value); |
|
|
|
setError(''); |
|
|
|
}} |
|
|
|
placeholder={expectedLot?.lotNo || t("Enter expected lot number")} |
|
|
|
sx={{ mb: 2 }} |
|
|
|
error={!!error && !expectedLotInput.trim()} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box sx={{ mb: 2 }}> |
|
|
|
<Typography variant="body2" gutterBottom> |
|
|
|
<strong>{t("Scanned Lot Number")}:</strong> |
|
|
|
</Typography> |
|
|
|
<TextField |
|
|
|
fullWidth |
|
|
|
size="small" |
|
|
|
value={scannedLotInput} |
|
|
|
onChange={(e) => { |
|
|
|
setScannedLotInput(e.target.value); |
|
|
|
setError(''); |
|
|
|
}} |
|
|
|
placeholder={scannedLot?.lotNo || t("Enter scanned lot number")} |
|
|
|
sx={{ mb: 2 }} |
|
|
|
error={!!error && !scannedLotInput.trim()} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
|
|
|
|
{error && ( |
|
|
|
<Box sx={{ mb: 2, p: 1, backgroundColor: '#ffebee', borderRadius: 1 }}> |
|
|
|
<Typography variant="body2" color="error"> |
|
|
|
{error} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
)} |
|
|
|
|
|
|
|
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end', gap: 2 }}> |
|
|
|
<Button onClick={onClose} variant="outlined" disabled={isLoading}> |
|
|
|
{t("Cancel")} |
|
|
|
</Button> |
|
|
|
<Button |
|
|
|
onClick={handleConfirm} |
|
|
|
variant="contained" |
|
|
|
color="warning" |
|
|
|
disabled={isLoading || !expectedLotInput.trim() || !scannedLotInput.trim()} |
|
|
|
> |
|
|
|
{isLoading ? t("Processing...") : t("Confirm")} |
|
|
|
</Button> |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
</Modal> |
|
|
|
); |
|
|
|
}; |
|
|
|
const PickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab, onRefreshReleasedOrderCount }) => { |
|
|
|
const { t } = useTranslation("pickOrder"); |
|
|
|
const router = useRouter(); |
|
|
|
@@ -346,7 +470,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); |
|
|
|
const [qrScanInput, setQrScanInput] = useState<string>(''); |
|
|
|
const [qrScanError, setQrScanError] = useState<boolean>(false); |
|
|
|
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false); |
|
|
|
|
|
|
|
const [manualLotConfirmationOpen, setManualLotConfirmationOpen] = useState(false); |
|
|
|
const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({}); |
|
|
|
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); |
|
|
|
|
|
|
|
@@ -640,6 +764,149 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
} |
|
|
|
}, [currentUserId, checkAllLotsCompleted]); // 移除 selectedPickOrderId 依赖 |
|
|
|
// Add effect to check completion when lot data changes |
|
|
|
const handleManualLotConfirmation = useCallback(async (currentLotNo: string, newLotNo: string) => { |
|
|
|
console.log(`🔍 Manual lot confirmation: Current=${currentLotNo}, New=${newLotNo}`); |
|
|
|
|
|
|
|
// 使用第一个输入框的 lot number 查找当前数据 |
|
|
|
const currentLot = combinedLotData.find(lot => |
|
|
|
lot.lotNo && lot.lotNo === currentLotNo |
|
|
|
); |
|
|
|
|
|
|
|
if (!currentLot) { |
|
|
|
console.error(`❌ Current lot not found: ${currentLotNo}`); |
|
|
|
alert(t("Current lot number not found. Please verify and try again.")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!currentLot.stockOutLineId) { |
|
|
|
console.error("❌ No stockOutLineId found for current lot"); |
|
|
|
alert(t("No stock out line found for current lot. Please contact administrator.")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setIsConfirmingLot(true); |
|
|
|
|
|
|
|
try { |
|
|
|
// 调用 updateStockOutLineStatusByQRCodeAndLotNo API |
|
|
|
// 第一个 lot 用于获取 pickOrderLineId, stockOutLineId, itemId |
|
|
|
// 第二个 lot 作为 inventoryLotNo |
|
|
|
const res = await updateStockOutLineStatusByQRCodeAndLotNo({ |
|
|
|
pickOrderLineId: currentLot.pickOrderLineId, |
|
|
|
inventoryLotNo: newLotNo, // 第二个输入框的值 |
|
|
|
stockOutLineId: currentLot.stockOutLineId, |
|
|
|
itemId: currentLot.itemId, |
|
|
|
status: "checked", |
|
|
|
}); |
|
|
|
|
|
|
|
console.log("📥 updateStockOutLineStatusByQRCodeAndLotNo result:", res); |
|
|
|
|
|
|
|
if (res.code === "checked" || res.code === "SUCCESS") { |
|
|
|
// ✅ 更新本地状态 |
|
|
|
const entity = res.entity as any; |
|
|
|
|
|
|
|
setCombinedLotData(prev => prev.map(lot => { |
|
|
|
if (lot.stockOutLineId === currentLot.stockOutLineId && |
|
|
|
lot.pickOrderLineId === currentLot.pickOrderLineId) { |
|
|
|
return { |
|
|
|
...lot, |
|
|
|
stockOutLineStatus: 'checked', |
|
|
|
stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty, |
|
|
|
}; |
|
|
|
} |
|
|
|
return lot; |
|
|
|
})); |
|
|
|
|
|
|
|
setOriginalCombinedData(prev => prev.map(lot => { |
|
|
|
if (lot.stockOutLineId === currentLot.stockOutLineId && |
|
|
|
lot.pickOrderLineId === currentLot.pickOrderLineId) { |
|
|
|
return { |
|
|
|
...lot, |
|
|
|
stockOutLineStatus: 'checked', |
|
|
|
stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty, |
|
|
|
}; |
|
|
|
} |
|
|
|
return lot; |
|
|
|
})); |
|
|
|
|
|
|
|
console.log("✅ Lot substitution completed successfully"); |
|
|
|
setQrScanSuccess(true); |
|
|
|
setQrScanError(false); |
|
|
|
|
|
|
|
// 关闭手动输入模态框 |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
|
|
|
|
// 刷新数据 |
|
|
|
await fetchAllCombinedLotData(); |
|
|
|
} else if (res.code === "LOT_NUMBER_MISMATCH") { |
|
|
|
console.warn("⚠️ Backend reported LOT_NUMBER_MISMATCH:", res.message); |
|
|
|
|
|
|
|
// ✅ 打开 lot confirmation modal 而不是显示 alert |
|
|
|
// 从响应消息中提取 expected lot number(如果可能) |
|
|
|
// 或者使用 currentLotNo 作为 expected lot |
|
|
|
const expectedLotNo = currentLotNo; // 当前 lot 是期望的 |
|
|
|
|
|
|
|
// 查找新 lot 的信息(如果存在于 combinedLotData 中) |
|
|
|
const newLot = combinedLotData.find(lot => |
|
|
|
lot.lotNo && lot.lotNo === newLotNo |
|
|
|
); |
|
|
|
|
|
|
|
// 设置 expected lot data |
|
|
|
setExpectedLotData({ |
|
|
|
lotNo: expectedLotNo, |
|
|
|
itemCode: currentLot.itemCode || '', |
|
|
|
itemName: currentLot.itemName || '' |
|
|
|
}); |
|
|
|
|
|
|
|
// 设置 scanned lot data |
|
|
|
setScannedLotData({ |
|
|
|
lotNo: newLotNo, |
|
|
|
itemCode: newLot?.itemCode || currentLot.itemCode || '', |
|
|
|
itemName: newLot?.itemName || currentLot.itemName || '', |
|
|
|
inventoryLotLineId: newLot?.lotId || null, |
|
|
|
stockInLineId: null // 手动输入时可能没有 stockInLineId |
|
|
|
}); |
|
|
|
|
|
|
|
// 设置 selectedLotForQr 为当前 lot |
|
|
|
setSelectedLotForQr(currentLot); |
|
|
|
|
|
|
|
// 关闭手动输入模态框 |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
|
|
|
|
// 打开 lot confirmation modal |
|
|
|
setLotConfirmationOpen(true); |
|
|
|
|
|
|
|
setQrScanError(false); // 不显示错误,因为会打开确认模态框 |
|
|
|
setQrScanSuccess(false); |
|
|
|
} else if (res.code === "ITEM_MISMATCH") { |
|
|
|
console.warn("⚠️ Backend reported ITEM_MISMATCH:", res.message); |
|
|
|
alert(t("Item mismatch: {message}", { message: res.message || "" })); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
|
|
|
|
// 关闭手动输入模态框 |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
} else { |
|
|
|
console.warn("⚠️ Unexpected response code:", res.code); |
|
|
|
alert(t("Failed to update lot status. Response: {code}", { code: res.code })); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
|
|
|
|
// 关闭手动输入模态框 |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
} |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error("❌ Error in manual lot confirmation:", error); |
|
|
|
alert(t("Failed to confirm lot substitution. Please try again.")); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
|
|
|
|
// 关闭手动输入模态框 |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
} finally { |
|
|
|
setIsConfirmingLot(false); |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchAllCombinedLotData, t]); |
|
|
|
useEffect(() => { |
|
|
|
if (combinedLotData.length > 0) { |
|
|
|
checkAllLotsCompleted(combinedLotData); |
|
|
|
@@ -666,13 +933,11 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; |
|
|
|
setIsConfirmingLot(true); |
|
|
|
try { |
|
|
|
let newLotLineId = scannedLotData?.inventoryLotLineId; |
|
|
|
if (!newLotLineId && scannedLotData?.stockInLineId) { |
|
|
|
const ld = await fetchLotDetail(scannedLotData.stockInLineId); |
|
|
|
newLotLineId = ld.inventoryLotLineId; |
|
|
|
} |
|
|
|
if (!newLotLineId) { |
|
|
|
console.error("No inventory lot line id for scanned lot"); |
|
|
|
const newLotNo = scannedLotData?.lotNo; |
|
|
|
if (!newLotNo) { |
|
|
|
console.error("No lot number for scanned lot"); |
|
|
|
alert(t("Cannot find lot number for scanned lot. Please verify the lot number is correct.")); |
|
|
|
setIsConfirmingLot(false); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -680,17 +945,21 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
pickOrderLineId: selectedLotForQr.pickOrderLineId, |
|
|
|
stockOutLineId: selectedLotForQr.stockOutLineId, |
|
|
|
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId, |
|
|
|
newInventoryLotLineId: newLotLineId |
|
|
|
newInventoryLotNo: newLotNo |
|
|
|
}); |
|
|
|
|
|
|
|
setQrScanError(false); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanInput(''); |
|
|
|
//setIsManualScanning(false); |
|
|
|
//stopScan(); |
|
|
|
//resetScan(); |
|
|
|
setProcessedQrCodes(new Set()); |
|
|
|
setLastProcessedQr(''); |
|
|
|
|
|
|
|
// ✅ 修复:在确认后重置扫描状态,避免重复处理 |
|
|
|
resetScan(); |
|
|
|
|
|
|
|
// ✅ 修复:不要清空 processedQrCodes,而是保留当前 QR code 的标记 |
|
|
|
// 或者如果确实需要清空,应该在重置扫描后再清空 |
|
|
|
// setProcessedQrCodes(new Set()); |
|
|
|
// setLastProcessedQr(''); |
|
|
|
|
|
|
|
setQrModalOpen(false); |
|
|
|
setPickExecutionFormOpen(false); |
|
|
|
if(selectedLotForQr?.stockOutLineId){ |
|
|
|
@@ -700,17 +969,23 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
qty: 0 |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ 修复:先关闭 modal 和清空状态,再刷新数据 |
|
|
|
setLotConfirmationOpen(false); |
|
|
|
setExpectedLotData(null); |
|
|
|
setScannedLotData(null); |
|
|
|
setSelectedLotForQr(null); |
|
|
|
|
|
|
|
// ✅ 修复:刷新数据前设置刷新标志,避免在刷新期间处理新的 QR code |
|
|
|
setIsRefreshingData(true); |
|
|
|
await fetchAllCombinedLotData(); |
|
|
|
setIsRefreshingData(false); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error confirming lot substitution:", error); |
|
|
|
} finally { |
|
|
|
setIsConfirmingLot(false); |
|
|
|
} |
|
|
|
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]); |
|
|
|
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData, resetScan]); |
|
|
|
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { |
|
|
|
console.log(` Processing QR Code for lot: ${lotNo}`); |
|
|
|
|
|
|
|
@@ -1147,7 +1422,14 @@ useEffect(() => { |
|
|
|
console.log("QR code already processed, skipping..."); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (latestQr === "{2fic}") { |
|
|
|
console.log("🔍 Detected {2fic} shortcut - opening manual lot confirmation form"); |
|
|
|
setManualLotConfirmationOpen(true); |
|
|
|
resetScan(); |
|
|
|
setLastProcessedQr(latestQr); |
|
|
|
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); |
|
|
|
return; // 直接返回,不继续处理其他逻辑 |
|
|
|
} |
|
|
|
if (latestQr && latestQr !== lastProcessedQr) { |
|
|
|
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); |
|
|
|
setLastProcessedQr(latestQr); |
|
|
|
@@ -1718,124 +2000,72 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
} |
|
|
|
}, [fetchAllCombinedLotData, session, currentUserId, fgPickOrders]); |
|
|
|
// ... existing code ... |
|
|
|
const handleSubmitAllScanned = useCallback(async () => { |
|
|
|
const startTime = performance.now(); |
|
|
|
console.log(`⏱️ [BATCH SUBMIT START]`); |
|
|
|
const handleSubmitAllScanned = useCallback(async () => { |
|
|
|
const startTime = performance.now(); |
|
|
|
console.log(`⏱️ [BATCH SUBMIT START]`); |
|
|
|
console.log(`⏰ Start time: ${new Date().toISOString()}`); |
|
|
|
const scannedLots = combinedLotData.filter(lot => { |
|
|
|
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete |
|
|
|
if (lot.noLot === true) { |
|
|
|
return lot.stockOutLineStatus === 'checked' || |
|
|
|
lot.stockOutLineStatus === 'pending' || |
|
|
|
lot.stockOutLineStatus === 'partially_completed' || |
|
|
|
lot.stockOutLineStatus === 'PARTIALLY_COMPLETE'; |
|
|
|
|
|
|
|
const scannedLots = combinedLotData.filter(lot => { |
|
|
|
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete |
|
|
|
if (lot.noLot === true) { |
|
|
|
return lot.stockOutLineStatus === 'checked' || |
|
|
|
lot.stockOutLineStatus === 'pending' || |
|
|
|
lot.stockOutLineStatus === 'partially_completed' || |
|
|
|
lot.stockOutLineStatus === 'PARTIALLY_COMPLETE'; |
|
|
|
} |
|
|
|
// 正常情况:只包含 checked 状态 |
|
|
|
return lot.stockOutLineStatus === 'checked'; |
|
|
|
}); |
|
|
|
|
|
|
|
if (scannedLots.length === 0) { |
|
|
|
console.log("No scanned items to submit"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setIsSubmittingAll(true); |
|
|
|
console.log(`📦 Submitting ${scannedLots.length} scanned items using batchSubmitList...`); |
|
|
|
|
|
|
|
try { |
|
|
|
// 转换为 batchSubmitList 所需的格式(与后端 QrPickBatchSubmitRequest 匹配) |
|
|
|
const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { |
|
|
|
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty || 0; |
|
|
|
const currentActualPickQty = lot.actualPickQty || 0; |
|
|
|
const cumulativeQty = currentActualPickQty + submitQty; |
|
|
|
|
|
|
|
let newStatus = 'partially_completed'; |
|
|
|
if (cumulativeQty >= (lot.requiredQty || 0)) { |
|
|
|
newStatus = 'completed'; |
|
|
|
} |
|
|
|
// 正常情况:只包含 checked 状态 |
|
|
|
return lot.stockOutLineStatus === 'checked'; |
|
|
|
|
|
|
|
return { |
|
|
|
stockOutLineId: Number(lot.stockOutLineId) || 0, |
|
|
|
pickOrderLineId: Number(lot.pickOrderLineId), |
|
|
|
inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, |
|
|
|
requiredQty: Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0), |
|
|
|
actualPickQty: Number(cumulativeQty), |
|
|
|
stockOutLineStatus: newStatus, |
|
|
|
pickOrderConsoCode: String(lot.pickOrderConsoCode || ''), |
|
|
|
noLot: Boolean(lot.noLot === true) |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
if (scannedLots.length === 0) { |
|
|
|
console.log("No scanned items to submit"); |
|
|
|
return; |
|
|
|
} |
|
|
|
const request: batchSubmitListRequest = { |
|
|
|
userId: currentUserId || 0, |
|
|
|
lines: lines |
|
|
|
}; |
|
|
|
|
|
|
|
setIsSubmittingAll(true); |
|
|
|
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); |
|
|
|
console.log(`📤 Sending batch submit request with ${lines.length} lines`); |
|
|
|
console.log(`📋 Request data:`, JSON.stringify(request, null, 2)); |
|
|
|
const submitStartTime = performance.now(); |
|
|
|
|
|
|
|
try { |
|
|
|
// Submit all items in parallel using Promise.all |
|
|
|
const submitPromises = scannedLots.map(async (lot) => { |
|
|
|
// 检查是否是 noLot 情况 |
|
|
|
if (lot.noLot === true) { |
|
|
|
// 使用 handlelotnull 处理无 lot 的情况 |
|
|
|
console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`); |
|
|
|
await updateStockOutLineStatus({ |
|
|
|
id: lot.stockOutLineId, |
|
|
|
status: 'completed', |
|
|
|
qty: 0 |
|
|
|
}); |
|
|
|
console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`); |
|
|
|
const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0; |
|
|
|
const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || ''; |
|
|
|
const issueData: PickExecutionIssueData = { |
|
|
|
type: "Do", // Delivery Order type |
|
|
|
pickOrderId: pickOrderId, |
|
|
|
pickOrderCode: pickOrderCode, |
|
|
|
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format |
|
|
|
pickExecutionDate: dayjs().format('YYYY-MM-DD'), |
|
|
|
pickOrderLineId: lot.pickOrderLineId, |
|
|
|
itemId: lot.itemId, |
|
|
|
itemCode: lot.itemCode || '', |
|
|
|
itemDescription: lot.itemName || '', |
|
|
|
lotId: null, // No lot available |
|
|
|
lotNo: null, // No lot number |
|
|
|
storeLocation: lot.location || '', |
|
|
|
requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, |
|
|
|
actualPickQty: 0, // No items picked (no lot available) |
|
|
|
missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, |
|
|
|
badItemQty: 0, |
|
|
|
issueRemark: `No lot available for this item. Handled via handlelotnull.`, |
|
|
|
pickerName: session?.user?.name || '', |
|
|
|
|
|
|
|
}; |
|
|
|
const result = await recordPickExecutionIssue(issueData); |
|
|
|
return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true }; |
|
|
|
} |
|
|
|
|
|
|
|
// 正常情况:有 lot 的处理逻辑 |
|
|
|
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; |
|
|
|
const currentActualPickQty = lot.actualPickQty || 0; |
|
|
|
const cumulativeQty = currentActualPickQty + submitQty; |
|
|
|
|
|
|
|
let newStatus = 'partially_completed'; |
|
|
|
if (cumulativeQty >= lot.requiredQty) { |
|
|
|
newStatus = 'completed'; |
|
|
|
} |
|
|
|
|
|
|
|
console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`); |
|
|
|
|
|
|
|
// Update stock out line |
|
|
|
await updateStockOutLineStatus({ |
|
|
|
id: lot.stockOutLineId, |
|
|
|
status: newStatus, |
|
|
|
qty: cumulativeQty |
|
|
|
}); |
|
|
|
|
|
|
|
// Update inventory |
|
|
|
if (submitQty > 0 && lot.lotId) { |
|
|
|
await updateInventoryLotLineQuantities({ |
|
|
|
inventoryLotLineId: lot.lotId, |
|
|
|
qty: submitQty, |
|
|
|
status: 'available', |
|
|
|
operation: 'pick' |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Check if pick order is completed |
|
|
|
if (newStatus === 'completed' && lot.pickOrderConsoCode) { |
|
|
|
await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); |
|
|
|
} |
|
|
|
|
|
|
|
return { success: true, lotNo: lot.lotNo }; |
|
|
|
}); |
|
|
|
|
|
|
|
// Wait for all submissions to complete |
|
|
|
const submitStartTime = performance.now(); |
|
|
|
const submitResults = await Promise.all(submitPromises); |
|
|
|
const submitSuccessCount = submitResults.filter(r => r.success).length; |
|
|
|
|
|
|
|
console.log(` Batch submit completed: ${submitSuccessCount}/${scannedLots.length} items submitted`); |
|
|
|
|
|
|
|
// Wait for all submissions to complete |
|
|
|
const results = await Promise.all(submitPromises); |
|
|
|
const submitTime = performance.now() - submitStartTime; |
|
|
|
const successCount = results.filter(r => r.success).length; |
|
|
|
// 使用 batchSubmitList API |
|
|
|
const result = await batchSubmitList(request); |
|
|
|
|
|
|
|
console.log(`⏱️ All submissions completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`); |
|
|
|
console.log(`⏱️ Average time per item: ${(submitTime / scannedLots.length).toFixed(2)}ms`); |
|
|
|
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); |
|
|
|
const submitTime = performance.now() - submitStartTime; |
|
|
|
console.log(`⏱️ Batch submit API call completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`); |
|
|
|
console.log(`📥 Batch submit result:`, result); |
|
|
|
|
|
|
|
// Refresh data once after all submissions |
|
|
|
// Refresh data once after batch submission |
|
|
|
const refreshStartTime = performance.now(); |
|
|
|
await fetchAllCombinedLotData(); |
|
|
|
const refreshTime = performance.now() - refreshStartTime; |
|
|
|
@@ -1845,27 +2075,31 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
console.log(`⏱️ [BATCH SUBMIT END]`); |
|
|
|
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`); |
|
|
|
console.log(`⏰ End time: ${new Date().toISOString()}`); |
|
|
|
if (successCount > 0) { |
|
|
|
setQrScanSuccess(true); |
|
|
|
setTimeout(() => { |
|
|
|
setQrScanSuccess(false); |
|
|
|
checkAndAutoAssignNext(); |
|
|
|
if (onSwitchToRecordTab) { |
|
|
|
onSwitchToRecordTab(); |
|
|
|
} |
|
|
|
if (onRefreshReleasedOrderCount) { |
|
|
|
onRefreshReleasedOrderCount(); |
|
|
|
} |
|
|
|
}, 2000); |
|
|
|
} |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error("Error submitting all scanned items:", error); |
|
|
|
|
|
|
|
if (result && result.code === "SUCCESS") { |
|
|
|
setQrScanSuccess(true); |
|
|
|
setTimeout(() => { |
|
|
|
setQrScanSuccess(false); |
|
|
|
checkAndAutoAssignNext(); |
|
|
|
if (onSwitchToRecordTab) { |
|
|
|
onSwitchToRecordTab(); |
|
|
|
} |
|
|
|
if (onRefreshReleasedOrderCount) { |
|
|
|
onRefreshReleasedOrderCount(); |
|
|
|
} |
|
|
|
}, 2000); |
|
|
|
} else { |
|
|
|
console.error("Batch submit failed:", result); |
|
|
|
setQrScanError(true); |
|
|
|
} finally { |
|
|
|
setIsSubmittingAll(false); |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull, onSwitchToRecordTab, onRefreshReleasedOrderCount]); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error("Error submitting all scanned items:", error); |
|
|
|
setQrScanError(true); |
|
|
|
} finally { |
|
|
|
setIsSubmittingAll(false); |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount]); |
|
|
|
|
|
|
|
// Calculate scanned items count |
|
|
|
// Calculate scanned items count (should match handleSubmitAllScanned filter logic) |
|
|
|
@@ -2290,10 +2524,10 @@ paginatedData.map((lot, index) => { |
|
|
|
onClick={() => handlePickExecutionForm(lot)} |
|
|
|
disabled={ |
|
|
|
lot.lotAvailability === 'expired' || |
|
|
|
lot.lotAvailability === 'status_unavailable' || |
|
|
|
lot.lotAvailability === 'rejected' || |
|
|
|
lot.stockOutLineStatus === 'completed' || |
|
|
|
lot.stockOutLineStatus === 'pending' |
|
|
|
//lot.lotAvailability === 'status_unavailable' || |
|
|
|
// lot.lotAvailability === 'rejected' || |
|
|
|
lot.stockOutLineStatus === 'completed' |
|
|
|
//lot.stockOutLineStatus === 'pending' |
|
|
|
} |
|
|
|
sx={{ |
|
|
|
fontSize: '0.7rem', |
|
|
|
@@ -2349,7 +2583,16 @@ paginatedData.map((lot, index) => { |
|
|
|
combinedLotData={combinedLotData} |
|
|
|
onQrCodeSubmit={handleQrCodeSubmitFromModal} |
|
|
|
/> |
|
|
|
|
|
|
|
<ManualLotConfirmationModal |
|
|
|
open={manualLotConfirmationOpen} |
|
|
|
onClose={() => { |
|
|
|
setManualLotConfirmationOpen(false); |
|
|
|
}} |
|
|
|
onConfirm={handleManualLotConfirmation} |
|
|
|
expectedLot={expectedLotData} |
|
|
|
scannedLot={scannedLotData} |
|
|
|
isLoading={isConfirmingLot} |
|
|
|
/> |
|
|
|
{/* 保留:Lot Confirmation Modal */} |
|
|
|
{lotConfirmationOpen && expectedLotData && scannedLotData && ( |
|
|
|
<LotConfirmationModal |
|
|
|
|