diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index bc8f79e..3de7cdc 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -210,6 +210,8 @@ export interface PickExecutionIssueData { issueRemark: string; pickerName: string; handledBy?: number; + badReason?: string; + reason?: string; } export type AutoAssignReleaseResponse = { id: number | null; diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 5e3f9cd..9ab4802 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -1,4 +1,4 @@ -// FPSMS-frontend/src/components/PickOrderSearch/PickExecutionForm.tsx +// FPSMS-frontend/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx "use client"; import { @@ -53,16 +53,13 @@ interface PickExecutionFormProps { selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; pickOrderId?: number; pickOrderCreateDate: any; - // Remove these props since we're not handling normal cases - // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise; - // selectedRowId?: number | null; } -// 定义错误类型 interface FormErrors { actualPickQty?: string; missQty?: string; badItemQty?: string; + badReason?: string; issueRemark?: string; handledBy?: string; } @@ -75,38 +72,21 @@ const PickExecutionForm: React.FC = ({ selectedPickOrderLine, pickOrderId, pickOrderCreateDate, - // Remove these props - // onNormalPickSubmit, - // selectedRowId, }) => { const { t } = useTranslation("pickOrder"); const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); const [handlers, setHandlers] = useState>([]); - // 计算剩余可用数量 + const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { - // 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty return lot.availableQty || 0; }, []); + const calculateRequiredQty = useCallback((lot: LotPickData) => { - // Use the original required quantity, not subtracting actualPickQty - // The actualPickQty in the form should be independent of the database value return lot.requiredQty || 0; }, []); - const remaining = selectedLot ? calculateRemainingAvailableQty(selectedLot) : 0; - const req = selectedLot ? calculateRequiredQty(selectedLot) : 0; - - const ap = Number(formData.actualPickQty) || 0; - const miss = Number(formData.missQty) || 0; - const bad = Number(formData.badItemQty) || 0; - - // Max the user can type - const maxPick = Math.min(remaining, req); - const maxIssueTotal = Math.max(0, req - ap); // remaining room for miss+bad - const clamp0 = (v: any) => Math.max(0, Number(v) || 0); - // 获取处理人员列表 useEffect(() => { const fetchHandlers = async () => { try { @@ -120,12 +100,11 @@ const PickExecutionForm: React.FC = ({ fetchHandlers(); }, []); - const initKeyRef = useRef(null); + const initKeyRef = useRef(null); useEffect(() => { if (!open || !selectedLot || !selectedPickOrderLine || !pickOrderId) return; - // Only initialize once per (pickOrderLineId + lotId) while dialog open const key = `${selectedPickOrderLine.id}-${selectedLot.lotId}`; if (initKeyRef.current === key) return; @@ -161,86 +140,75 @@ const PickExecutionForm: React.FC = ({ issueRemark: '', pickerName: '', handledBy: undefined, + reason: '', + badReason: '', }); initKeyRef.current = key; }, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]); - // Mutually exclusive inputs: picking vs reporting issues const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); - // 清除错误 if (errors[field as keyof FormErrors]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }, [errors]); - // Update form validation to require either missQty > 0 OR badItemQty > 0 -const validateForm = (): boolean => { - const newErrors: FormErrors = {}; - const req = selectedLot?.requiredQty || 0; - const ap = Number(formData.actualPickQty) || 0; - const miss = Number(formData.missQty) || 0; - const bad = Number(formData.badItemQty) || 0; - const total = ap + miss + bad; + // Updated validation logic + const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + const req = selectedLot?.requiredQty || 0; + const ap = Number(formData.actualPickQty) || 0; + const miss = Number(formData.missQty) || 0; + const bad = Number(formData.badItemQty) || 0; + const total = ap + miss + bad; + const availableQty = selectedLot?.availableQty || 0; - // 1. 检查 actualPickQty 不能为负数 - if (ap < 0) { - newErrors.actualPickQty = t('Qty cannot be negative'); - } - - // 2. 检查 actualPickQty 不能超过可用数量或需求数量 - if (ap > Math.min(req)) { - newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); - } - - // 3. 检查 missQty 和 badItemQty 不能为负数 - if (miss < 0) { - newErrors.missQty = t('Invalid qty'); - } - if (bad < 0) { - newErrors.badItemQty = t('Invalid qty'); - } - - // 4. 🔥 关键验证:总和必须等于 Required Qty(不能多也不能少) - 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) }); - newErrors.actualPickQty = errorMsg; - newErrors.missQty = errorMsg; - newErrors.badItemQty = errorMsg; - } - - // 5. 🔥 关键验证:如果只有 actualPickQty 有值,而 missQty 和 badItemQty 都为 0,不允许提交 - // 这意味着如果 actualPickQty < requiredQty,必须报告问题(missQty 或 badItemQty > 0) - if (ap > 0 && miss === 0 && bad === 0 && ap < req) { - newErrors.missQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty'); - newErrors.badItemQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty'); - } - - // 6. 如果所有值都为 0,不允许提交 - if (ap === 0 && miss === 0 && bad === 0) { - newErrors.actualPickQty = t('Enter pick qty or issue qty'); - newErrors.missQty = t('Enter pick qty or issue qty'); - } - - // 7. 如果 actualPickQty = requiredQty,missQty 和 badItemQty 必须都为 0 - if (ap === req && (miss > 0 || bad > 0)) { - newErrors.missQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0'); - newErrors.badItemQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0'); - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; -}; + // 1. Check actualPickQty cannot be negative + if (ap < 0) { + newErrors.actualPickQty = t('Qty cannot be negative'); + } + + // 2. Check actualPickQty cannot exceed available quantity + if (ap > availableQty) { + newErrors.actualPickQty = t('Actual pick qty cannot exceed available qty'); + } + + // 3. Check missQty and badItemQty cannot be negative + if (miss < 0) { + newErrors.missQty = t('Invalid qty'); + } + if (bad < 0) { + newErrors.badItemQty = t('Invalid qty'); + } + + // 4. NEW: Total (actualPickQty + missQty + badItemQty) cannot exceed lot available qty + if (total > availableQty) { + const errorMsg = t('Total qty (actual pick + miss + bad) cannot exceed available qty: {available}', { available: availableQty }); + newErrors.actualPickQty = errorMsg; + newErrors.missQty = errorMsg; + newErrors.badItemQty = errorMsg; + } + + // 5. If badItemQty > 0, badReason is required + if (bad > 0 && !formData.badReason) { + newErrors.badReason = t('Bad reason is required when bad item qty > 0'); + newErrors.badItemQty = t('Bad reason is required'); + } + + // 6. At least one field must have a value + if (ap === 0 && miss === 0 && bad === 0) { + newErrors.actualPickQty = t('Enter pick qty or issue qty'); + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; const handleSubmit = async () => { - // First validate the form if (!validateForm()) { console.error('Form validation failed:', errors); - return; // Prevent submission, show validation errors + return; } if (!formData.pickOrderId) { @@ -251,11 +219,8 @@ const validateForm = (): boolean => { setLoading(true); try { await onSubmit(formData as PickExecutionIssueData); - // Automatically closed when successful (handled by onClose) } catch (error: any) { console.error('Error submitting pick execution issue:', error); - // Show error message (can be passed to parent component via props or state) - // 或者在这里显示 toast/alert alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : '')); } finally { setLoading(false); @@ -278,21 +243,11 @@ const validateForm = (): boolean => { return ( - {t('Pick Execution Issue Form')} {/* Always show issue form title */} + {t('Pick Execution Issue Form')} - {/* Add instruction text */} - - - - {t('Note:')} {t('This form is for reporting issues only. You must report either missing items or bad items.')} - - - - - {/* Keep the existing form fields */} { value={selectedLot?.requiredQty || 0} disabled variant="outlined" - // helperText={t('Still need to pick')} /> @@ -311,7 +265,6 @@ const validateForm = (): boolean => { value={remainingAvailableQty} disabled variant="outlined" - // helperText={t('Available in warehouse')} /> @@ -320,88 +273,81 @@ const validateForm = (): boolean => { fullWidth label={t('Actual Pick Qty')} type="number" - - inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} + inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 0 }} value={formData.actualPickQty ?? ''} onChange={(e) => handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} error={!!errors.actualPickQty} - helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`} + helperText={errors.actualPickQty || `${t('Max')}: ${remainingAvailableQty}`} variant="outlined" /> + + + + + {t('Reason')} + + - handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} - error={!!errors.missQty} - variant="outlined" - //disabled={(formData.actualPickQty || 0) > 0} - /> + handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + error={!!errors.missQty} + variant="outlined" + /> - handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} - error={!!errors.badItemQty} - variant="outlined" - //disabled={(formData.actualPickQty || 0) > 0} - /> + handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + error={!!errors.badItemQty} + variant="outlined" + /> - - {/* Show issue description and handler fields when bad items > 0 */} + + {/* Show bad reason dropdown when badItemQty > 0 */} {(formData.badItemQty && formData.badItemQty > 0) ? ( - <> - - handleInputChange('issueRemark', e.target.value)} - error={!!errors.issueRemark} - helperText={errors.issueRemark} - //placeholder={t('Describe the issue with bad items')} - variant="outlined" - /> - - - - - {t('handler')} - - {errors.handledBy && ( - - {errors.handledBy} - - )} - - - - ) : (<>)} + + + {t('Bad Reason')} + + {errors.badReason && ( + + {errors.badReason} + + )} + + + ) : null} + + diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 1f52d6c..1f3a3f7 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -690,7 +690,7 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); stockOutLineId: lot.stockOutLineId, stockOutLineStatus: lot.stockOutLineStatus, stockOutLineQty: lot.stockOutLineQty, - + stockInLineId: lot.stockInLineId, routerId: lot.router?.id, routerIndex: lot.router?.index, routerRoute: lot.router?.route, @@ -1217,7 +1217,50 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms`); } }, [combinedLotData, updateStockOutLineStatusByQRCodeAndLotNo]); - + const lotDataIndexes = useMemo(() => { + const byItemId = new Map(); + const byItemCode = new Map(); + const byLotId = new Map(); + const byLotNo = new Map(); + const byStockInLineId = new Map(); // ✅ 新增:按 stockInLineId 索引 + + combinedLotData.forEach(lot => { + if (lot.itemId) { + if (!byItemId.has(lot.itemId)) { + byItemId.set(lot.itemId, []); + } + byItemId.get(lot.itemId)!.push(lot); + } + + if (lot.itemCode) { + if (!byItemCode.has(lot.itemCode)) { + byItemCode.set(lot.itemCode, []); + } + byItemCode.get(lot.itemCode)!.push(lot); + } + + if (lot.lotId) { + byLotId.set(lot.lotId, lot); + } + + if (lot.lotNo) { + if (!byLotNo.has(lot.lotNo)) { + byLotNo.set(lot.lotNo, []); + } + byLotNo.get(lot.lotNo)!.push(lot); + } + + // ✅ 新增:按 stockInLineId 索引 + if (lot.stockInLineId) { + if (!byStockInLineId.has(lot.stockInLineId)) { + byStockInLineId.set(lot.stockInLineId, []); + } + byStockInLineId.get(lot.stockInLineId)!.push(lot); + } + }); + + return { byItemId, byItemCode, byLotId, byLotNo, byStockInLineId }; // ✅ 添加 byStockInLineId + }, [combinedLotData]); const processOutsideQrCode = useCallback(async (latestQr: string) => { // 1) Parse JSON safely @@ -1232,7 +1275,7 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } try { - // Only use the new API when we have JSON with stockInLineId + itemId + // ✅ OPTIMIZATION: 直接使用 QR 数据,不需要调用 analyzeQrCode API if (!(qrData?.stockInLineId && qrData?.itemId)) { console.log("QR JSON missing required fields (itemId, stockInLineId)."); setQrScanError(true); @@ -1240,45 +1283,30 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - // Call new analyze-qr-code API - const analysis = await analyzeQrCode({ - itemId: qrData.itemId, - stockInLineId: qrData.stockInLineId - }); + const scannedItemId = qrData.itemId; + const scannedStockInLineId = qrData.stockInLineId; - if (!analysis) { - console.error("analyzeQrCode returned no data"); - setQrScanError(true); - setQrScanSuccess(false); - return; + // ✅ OPTIMIZATION: 使用索引快速查找相同 item 的 lots + const sameItemLots: any[] = []; + + // 使用索引快速查找 + if (lotDataIndexes.byItemId.has(scannedItemId)) { + sameItemLots.push(...lotDataIndexes.byItemId.get(scannedItemId)!); } - - const { - itemId: analyzedItemId, - itemCode: analyzedItemCode, - itemName: analyzedItemName, - scanned, - } = analysis || {}; - - // 1) Find all lots for the same item from current expected list - const sameItemLotsInExpected = combinedLotData.filter(l => - (l.itemId && analyzedItemId && l.itemId === analyzedItemId) || - (l.itemCode && analyzedItemCode && l.itemCode === analyzedItemCode) - ); - - if (!sameItemLotsInExpected || sameItemLotsInExpected.length === 0) { - // Case 3: No item code match + + if (sameItemLots.length === 0) { console.error("No item match in expected lots for scanned code"); setQrScanError(true); setQrScanSuccess(false); return; } - // FIXED: Find the ACTIVE suggested lot (not rejected lots) - const activeSuggestedLots = sameItemLotsInExpected.filter(lot => - lot.lotAvailability !== 'rejected' && - lot.stockOutLineStatus !== 'rejected' && - lot.processingStatus !== 'rejected' + // ✅ OPTIMIZATION: 过滤出活跃的 lots(非 rejected) + const rejectedStatuses = new Set(['rejected']); + const activeSuggestedLots = sameItemLots.filter(lot => + !rejectedStatuses.has(lot.lotAvailability) && + !rejectedStatuses.has(lot.stockOutLineStatus) && + !rejectedStatuses.has(lot.processingStatus) ); if (activeSuggestedLots.length === 0) { @@ -1288,77 +1316,63 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - // 2) Check if scanned lot is exactly in active suggested lots - const exactLotMatch = activeSuggestedLots.find(l => - (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || - (scanned?.lotNo && l.lotNo === scanned.lotNo) + // ✅ OPTIMIZATION: 按优先级查找匹配的 lot + // 1. 首先查找 stockInLineId 完全匹配的(正确的 lot) + let exactMatch = activeSuggestedLots.find(lot => + lot.stockInLineId === scannedStockInLineId ); - - if (exactLotMatch && scanned?.lotNo) { - // ✅ Case 1: 使用 updateStockOutLineStatusByQRCodeAndLotNo API(更快) - console.log(`✅ Exact lot match found for ${scanned.lotNo}, using fast API`); + + if (exactMatch) { + // ✅ Case 1: stockInLineId 匹配 - 直接处理,不需要确认 + console.log(`✅ Exact stockInLineId match found for lot: ${exactMatch.lotNo}`); - if (!exactLotMatch.stockOutLineId) { - console.warn("No stockOutLineId on exactLotMatch, cannot update status by QR."); + if (!exactMatch.stockOutLineId) { + console.warn("No stockOutLineId on exactMatch, cannot update status by QR."); setQrScanError(true); setQrScanSuccess(false); return; } try { - // ✅ 直接调用后端 API,后端会处理所有匹配逻辑 const res = await updateStockOutLineStatusByQRCodeAndLotNo({ - pickOrderLineId: exactLotMatch.pickOrderLineId, - inventoryLotNo: scanned.lotNo, - stockOutLineId: exactLotMatch.stockOutLineId, - itemId: exactLotMatch.itemId, + pickOrderLineId: exactMatch.pickOrderLineId, + inventoryLotNo: exactMatch.lotNo, + stockOutLineId: exactMatch.stockOutLineId, + itemId: exactMatch.itemId, status: "checked", }); - console.log("updateStockOutLineStatusByQRCodeAndLotNo result:", res); - - // 后端返回三种 code:checked / LOT_NUMBER_MISMATCH / ITEM_MISMATCH if (res.code === "checked" || res.code === "SUCCESS") { - // ✅ 完全匹配 - 只更新本地状态,不调用 fetchAllCombinedLotData setQrScanError(false); setQrScanSuccess(true); - // ✅ 更新本地状态 const entity = res.entity as any; setCombinedLotData(prev => prev.map(lot => { - if (lot.stockOutLineId === exactLotMatch.stockOutLineId && - lot.pickOrderLineId === exactLotMatch.pickOrderLineId) { + if (lot.stockOutLineId === exactMatch.stockOutLineId && + lot.pickOrderLineId === exactMatch.pickOrderLineId) { return { ...lot, stockOutLineStatus: 'checked', - stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty, + stockOutLineQty: entity?.qty ?? lot.stockOutLineQty, }; } return lot; })); setOriginalCombinedData(prev => prev.map(lot => { - if (lot.stockOutLineId === exactLotMatch.stockOutLineId && - lot.pickOrderLineId === exactLotMatch.pickOrderLineId) { + if (lot.stockOutLineId === exactMatch.stockOutLineId && + lot.pickOrderLineId === exactMatch.pickOrderLineId) { return { ...lot, stockOutLineStatus: 'checked', - stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty, + stockOutLineQty: entity?.qty ?? lot.stockOutLineQty, }; } return lot; })); console.log("✅ Status updated locally, no full data refresh needed"); - } else if (res.code === "LOT_NUMBER_MISMATCH") { - console.warn("Backend reported LOT_NUMBER_MISMATCH:", res.message); - setQrScanError(true); - setQrScanSuccess(false); - } else if (res.code === "ITEM_MISMATCH") { - console.warn("Backend reported ITEM_MISMATCH:", res.message); - setQrScanError(true); - setQrScanSuccess(false); } else { console.warn("Unexpected response code from backend:", res.code); setQrScanError(true); @@ -1370,10 +1384,11 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setQrScanSuccess(false); } - return; // ✅ 直接返回,不再调用 handleQrCodeSubmit + return; // ✅ 直接返回,不需要确认表单 } - - // Case 2: Item matches but lot number differs -> open confirmation modal + + // ✅ Case 2: itemId 匹配但 stockInLineId 不匹配 - 显示确认表单 + // 取第一个活跃的 lot 作为期望的 lot const expectedLot = activeSuggestedLots[0]; if (!expectedLot) { console.error("Could not determine expected lot for confirmation"); @@ -1382,39 +1397,38 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - // Check if the expected lot is already the scanned lot (after substitution) - if (expectedLot.lotNo === scanned?.lotNo) { - console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); - handleQrCodeSubmit(scanned.lotNo); - return; - } - - console.log(` Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); + // ✅ 立即打开确认模态框,不等待其他操作 + console.log(`⚠️ Lot mismatch: Expected stockInLineId=${expectedLot.stockInLineId}, Scanned stockInLineId=${scannedStockInLineId}`); setSelectedLotForQr(expectedLot); + + // ✅ 获取扫描的 lot 信息(从 QR 数据中提取,或使用默认值) handleLotMismatch( { lotNo: expectedLot.lotNo, - itemCode: analyzedItemCode || expectedLot.itemCode, - itemName: analyzedItemName || expectedLot.itemName + itemCode: expectedLot.itemCode, + itemName: expectedLot.itemName }, { - lotNo: scanned?.lotNo || '', - itemCode: analyzedItemCode || expectedLot.itemCode, - itemName: analyzedItemName || expectedLot.itemName, - inventoryLotLineId: scanned?.inventoryLotLineId, - stockInLineId: qrData.stockInLineId + lotNo: null, // 扫描的 lotNo 未知,需要从后端获取或显示为未知 + itemCode: expectedLot.itemCode, + itemName: expectedLot.itemName, + inventoryLotLineId: null, + stockInLineId: scannedStockInLineId // ✅ 传递 stockInLineId } ); } catch (error) { - console.error("Error during analyzeQrCode flow:", error); + console.error("Error during QR code processing:", error); setQrScanError(true); setQrScanSuccess(false); return; } - }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); - // Update the outside QR scanning effect to use enhanced processing -// Update the outside QR scanning effect to use enhanced processing + }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotDataIndexes, updateStockOutLineStatusByQRCodeAndLotNo]); useEffect(() => { + if (lotConfirmationOpen || manualLotConfirmationOpen) { + console.log("Confirmation modal is open, skipping QR processing..."); + return; + } + if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { return; } @@ -1965,8 +1979,8 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe const handleSkip = useCallback(async (lot: any) => { try { - console.log("Skip clicked, submit 0 qty for lot:", lot.lotNo); - await handleSubmitPickQtyWithQty(lot, 0); + console.log("Skip clicked, submit lot required qty for lot:", lot.lotNo); + await handleSubmitPickQtyWithQty(lot, lot.requiredQty); } catch (err) { console.error("Error in Skip:", err); } @@ -2749,27 +2763,27 @@ paginatedData.map((lot, index) => { /> {/* 保留:Lot Confirmation Modal */} {lotConfirmationOpen && expectedLotData && scannedLotData && ( - { - setLotConfirmationOpen(false); - setExpectedLotData(null); - setScannedLotData(null); - if (lastProcessedQr) { - setProcessedQrCodes(prev => { - const newSet = new Set(prev); - newSet.delete(lastProcessedQr); - return newSet; - }); - setLastProcessedQr(''); - } - }} - onConfirm={handleLotConfirmation} - expectedLot={expectedLotData} - scannedLot={scannedLotData} - isLoading={isConfirmingLot} - /> - )} + { + setLotConfirmationOpen(false); + setExpectedLotData(null); + setScannedLotData(null); + setSelectedLotForQr(null); // ✅ 新增:清除选中的 lot + + // ✅ 修复:不要清除 processedQrCodes,而是保留它,避免重复处理 + // 或者,如果确实需要清除,应该在清除后立即重新标记为已处理 + if (lastProcessedQr) { + + setLastProcessedQr(''); + } + }} + onConfirm={handleLotConfirmation} + expectedLot={expectedLotData} + scannedLot={scannedLotData} + isLoading={isConfirmingLot} + /> +)} {/* 保留:Good Pick Execution Form Modal */} {pickExecutionFormOpen && selectedLotForExecutionForm && (