diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 7c96290..5c39506 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -474,8 +474,44 @@ export interface UpdateStockOutLineStatusByQRCodeAndLotNoRequest { itemId: number, status: string } + +export interface batchSubmitListRequest { + userId: number; + lines: batchSubmitListLineRequest[]; +} +export interface batchSubmitListLineRequest { + stockOutLineId: number; // 修复:改为 stockOutLineId(不是 stockInLineId) + pickOrderLineId: number; + inventoryLotLineId: number | null; // 添加:后端需要的字段 + requiredQty: number; + actualPickQty: number; + stockOutLineStatus: string; + pickOrderConsoCode: string; + noLot: boolean; + // 移除:lotNo 和 stockInLineId(后端不需要) +} + +export const batchSubmitList = async (data: batchSubmitListRequest) => { + // ✅ 确保发送的是对象,不是数组 + const requestBody = Array.isArray(data) ? data[0] : data; + + console.log("📤 batchSubmitList - Request body type:", Array.isArray(requestBody) ? "array" : "object"); + console.log("📤 batchSubmitList - Request body:", JSON.stringify(requestBody, null, 2)); + + const response = await serverFetchJson>( + `${BASE_API_URL}/stockOutLine/batchSubmitList`, + { + method: "POST", + body: JSON.stringify(requestBody), // ✅ 确保是对象 + headers: { + "Content-Type": "application/json", // ✅ 明确指定 Content-Type + }, + }, + ); + return response; +}; export const updateStockOutLineStatusByQRCodeAndLotNo = async (data: UpdateStockOutLineStatusByQRCodeAndLotNoRequest) => { - console.log("🚀 Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data); + console.log(" Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data); try { const response = await serverFetchJson>( @@ -535,9 +571,9 @@ export const updatePickExecutionIssueStatus = async ( revalidateTag("pickExecutionIssues"); return result; }; -export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string): Promise { +export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise { const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD'); - const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}`; + const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`; const response = await serverFetchJson( url, { @@ -927,7 +963,7 @@ export interface LotSubstitutionConfirmRequest { pickOrderLineId: number; stockOutLineId: number; originalSuggestedPickLotId: number; - newInventoryLotLineId: number; + newInventoryLotNo: string; } export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => { const response = await serverFetchJson( diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 55eed14..5e3f9cd 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -79,7 +79,7 @@ const PickExecutionForm: React.FC = ({ // onNormalPickSubmit, // selectedRowId, }) => { - const { t } = useTranslation(); + const { t } = useTranslation("pickOrder"); const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); @@ -206,8 +206,8 @@ const validateForm = (): boolean => { if (total !== req) { const diff = req - total; const errorMsg = diff > 0 - ? t('Total must equal Required Qty. Missing: {{diff}}', { diff }) - : t('Total must equal Required Qty. Exceeds by: {{diff}}', { diff: Math.abs(diff) }); + ? t('Total must equal Required Qty. Missing: {diff}', { diff }) + : t('Total must equal Required Qty. Exceeds by: {diff}', { diff: Math.abs(diff) }); newErrors.actualPickQty = errorMsg; newErrors.missQty = errorMsg; newErrors.badItemQty = errorMsg; @@ -320,6 +320,8 @@ const validateForm = (): boolean => { fullWidth label={t('Actual Pick Qty')} type="number" + + inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} value={formData.actualPickQty ?? ''} onChange={(e) => handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} error={!!errors.actualPickQty} @@ -333,6 +335,8 @@ const validateForm = (): boolean => { fullWidth label={t('Missing item Qty')} type="number" + + inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} value={formData.missQty || 0} onChange={(e) => handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} error={!!errors.missQty} @@ -346,6 +350,8 @@ const validateForm = (): boolean => { fullWidth label={t('Bad Item Qty')} type="number" + + inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} value={formData.badItemQty || 0} onChange={(e) => handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} error={!!errors.badItemQty} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 433832e..239529a 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -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<{ ); }; +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(''); + const [scannedLotInput, setScannedLotInput] = useState(''); + const [error, setError] = useState(''); + + // 当模态框打开时,预填充输入框 + 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 ( + + + + {t("Manual Lot Confirmation")} + + + + + {t("Expected Lot Number")}: + + { + setExpectedLotInput(e.target.value); + setError(''); + }} + placeholder={expectedLot?.lotNo || t("Enter expected lot number")} + sx={{ mb: 2 }} + error={!!error && !expectedLotInput.trim()} + /> + + + + + {t("Scanned Lot Number")}: + + { + setScannedLotInput(e.target.value); + setError(''); + }} + placeholder={scannedLot?.lotNo || t("Enter scanned lot number")} + sx={{ mb: 2 }} + error={!!error && !scannedLotInput.trim()} + /> + + + {error && ( + + + {error} + + + )} + + + + + + + + ); +}; const PickExecution: React.FC = ({ filterArgs, onSwitchToRecordTab, onRefreshReleasedOrderCount }) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); @@ -346,7 +470,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const [qrScanInput, setQrScanInput] = useState(''); const [qrScanError, setQrScanError] = useState(false); const [qrScanSuccess, setQrScanSuccess] = useState(false); - + const [manualLotConfirmationOpen, setManualLotConfirmationOpen] = useState(false); const [pickQtyData, setPickQtyData] = useState>({}); const [searchQuery, setSearchQuery] = useState>({}); @@ -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} /> - + { + setManualLotConfirmationOpen(false); + }} + onConfirm={handleManualLotConfirmation} + expectedLot={expectedLotData} + scannedLot={scannedLotData} + isLoading={isConfirmingLot} + /> {/* 保留:Lot Confirmation Modal */} {lotConfirmationOpen && expectedLotData && scannedLotData && ( = ({ filterArgs }) => { pickOrderLineId: selectedLotForQr.pickOrderLineId, stockOutLineId: selectedLotForQr.stockOutLineId, originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId, - newInventoryLotLineId: newLotLineId + newInventoryLotNo: scannedLotData.lotNo }); console.log(" Lot substitution result:", substitutionResult); diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 51558a0..e273501 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -109,10 +109,13 @@ "submit": "確認提交", "print": "列印", "bind": "綁定", - "Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}", - "Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}", - + "Total must equal Required Qty. Missing: {diff}": "總數量必須等於所需數量。缺少:{{diff}}", + "Total must equal Required Qty. Exceeds by: {diff}": "總數量必須等於所需數量。超出:{{diff}}", + "Batch": "批量", + "Single": "單量", + "Release Type": "放單類型", + "Pick Order": "提料單", "Type": "類型", "Product Type": "貨品類型",