From 8b2ab939e846b8bb5e628a9b31163f57d7e98e5c Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 20 Mar 2026 13:27:34 +0800 Subject: [PATCH] update switch lot --- .../GoodPickExecutiondetail.tsx | 182 +++++++++++++++--- .../LotConfirmationModal.tsx | 3 + 2 files changed, 158 insertions(+), 27 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 66f0c36..f07576e 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -573,6 +573,10 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); // Store callbacks in refs to avoid useEffect dependency issues const processOutsideQrCodeRef = useRef<((latestQr: string) => Promise) | null>(null); const resetScanRef = useRef<(() => void) | null>(null); + const lotConfirmOpenedQrCountRef = useRef(0); + const lotConfirmOpenedQrValueRef = useRef(''); + const lotConfirmInitialSameQrSkippedRef = useRef(false); + const autoConfirmInProgressRef = useRef(false); @@ -1137,6 +1141,55 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO }); window.dispatchEvent(event); }, [allLotsCompleted]); + + const clearLotConfirmationState = useCallback((clearProcessedRefs: boolean = false) => { + setLotConfirmationOpen(false); + setExpectedLotData(null); + setScannedLotData(null); + setSelectedLotForQr(null); + + if (clearProcessedRefs) { + setTimeout(() => { + lastProcessedQrRef.current = ''; + processedQrCodesRef.current.clear(); + console.log(`⏱️ [LOT CONFIRM MODAL] Cleared refs to allow reprocessing`); + }, 100); + } + }, []); + + const parseQrPayload = useCallback((rawQr: string): { itemId: number; stockInLineId: number } | null => { + if (!rawQr) return null; + + if ((rawQr.startsWith("{2fitest") || rawQr.startsWith("{2fittest")) && rawQr.endsWith("}")) { + let content = ''; + if (rawQr.startsWith("{2fittest")) { + content = rawQr.substring(9, rawQr.length - 1); + } else { + content = rawQr.substring(8, rawQr.length - 1); + } + + const parts = content.split(','); + if (parts.length === 2) { + const itemId = parseInt(parts[0].trim(), 10); + const stockInLineId = parseInt(parts[1].trim(), 10); + if (!isNaN(itemId) && !isNaN(stockInLineId)) { + return { itemId, stockInLineId }; + } + } + return null; + } + + try { + const parsed = JSON.parse(rawQr); + if (parsed?.itemId && parsed?.stockInLineId) { + return { itemId: parsed.itemId, stockInLineId: parsed.stockInLineId }; + } + return null; + } catch { + return null; + } + }, []); + const handleLotConfirmation = useCallback(async () => { if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; setIsConfirmingLot(true); @@ -1175,10 +1228,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO } // ✅ 修复:先关闭 modal 和清空状态,再刷新数据 - setLotConfirmationOpen(false); - setExpectedLotData(null); - setScannedLotData(null); - setSelectedLotForQr(null); + clearLotConfirmationState(false); // ✅ 修复:刷新数据前设置刷新标志,避免在刷新期间处理新的 QR code setIsRefreshingData(true); @@ -1189,7 +1239,94 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO } finally { setIsConfirmingLot(false); } - }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData, resetScan]); + }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData, resetScan, clearLotConfirmationState]); + + const handleLotConfirmationByRescan = useCallback(async (rawQr: string): Promise => { + if (!lotConfirmationOpen || !selectedLotForQr || !expectedLotData || !scannedLotData) { + return false; + } + + const payload = parseQrPayload(rawQr); + const expectedStockInLineId = Number(selectedLotForQr.stockInLineId); + const mismatchedStockInLineId = Number(scannedLotData?.stockInLineId); + + if (payload) { + const rescannedStockInLineId = Number(payload.stockInLineId); + + // 再扫“差异 lot” => 直接执行切换 + if ( + Number.isFinite(mismatchedStockInLineId) && + rescannedStockInLineId === mismatchedStockInLineId + ) { + await handleLotConfirmation(); + return true; + } + + // 再扫“原建议 lot” => 关闭弹窗并按原 lot 正常记一次扫描 + if ( + Number.isFinite(expectedStockInLineId) && + rescannedStockInLineId === expectedStockInLineId + ) { + clearLotConfirmationState(false); + if (processOutsideQrCodeRef.current) { + await processOutsideQrCodeRef.current(JSON.stringify(payload)); + } + return true; + } + } else { + // 兼容纯 lotNo 文本扫码 + const scannedText = rawQr?.trim(); + const expectedLotNo = expectedLotData?.lotNo?.trim(); + const mismatchedLotNo = scannedLotData?.lotNo?.trim(); + + if (mismatchedLotNo && scannedText === mismatchedLotNo) { + await handleLotConfirmation(); + return true; + } + + if (expectedLotNo && scannedText === expectedLotNo) { + clearLotConfirmationState(false); + if (processOutsideQrCodeRef.current) { + await processOutsideQrCodeRef.current(JSON.stringify({ + itemId: selectedLotForQr.itemId, + stockInLineId: selectedLotForQr.stockInLineId, + })); + } + return true; + } + } + + return false; + }, [lotConfirmationOpen, selectedLotForQr, expectedLotData, scannedLotData, parseQrPayload, handleLotConfirmation, clearLotConfirmationState]); + + useEffect(() => { + if (!lotConfirmationOpen || !expectedLotData || !scannedLotData || !selectedLotForQr) { + autoConfirmInProgressRef.current = false; + return; + } + + if (autoConfirmInProgressRef.current || isConfirmingLot) { + return; + } + + autoConfirmInProgressRef.current = true; + handleLotConfirmation() + .catch((error) => { + console.error("Auto confirm lot substitution failed:", error); + }) + .finally(() => { + autoConfirmInProgressRef.current = false; + }); + }, [lotConfirmationOpen, expectedLotData, scannedLotData, selectedLotForQr, isConfirmingLot, handleLotConfirmation]); + + useEffect(() => { + if (lotConfirmationOpen) { + // 记录弹窗打开时的扫码数量,避免把“触发弹窗的同一次扫码”当作二次确认 + lotConfirmOpenedQrCountRef.current = qrValues.length; + lotConfirmOpenedQrValueRef.current = qrValues[qrValues.length - 1] || ''; + lotConfirmInitialSameQrSkippedRef.current = true; + } + }, [lotConfirmationOpen, qrValues.length]); const handleQrCodeSubmit = useCallback(async (lotNo: string) => { console.log(` Processing QR Code for lot: ${lotNo}`); @@ -1937,17 +2074,22 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO } } - // Skip processing if confirmation modals are open - // BUT: Allow processing if modal was just closed (to allow reopening for different stockInLineId) - if (lotConfirmationOpen || manualLotConfirmationOpen) { + // lot confirm 弹窗打开时,允许通过“再次扫码”决定走向(切换或继续原 lot) + if (lotConfirmationOpen) { + // 已改回自动确认:弹窗打开时不再等待二次扫码 + return; + } + + // Skip processing if manual confirmation modal is open + if (manualLotConfirmationOpen) { // Check if this is a different QR code than what triggered the modal const modalTriggerQr = lastProcessedQrRef.current; if (latestQr === modalTriggerQr) { - console.log(`⏱️ [QR PROCESS] Skipping - modal open for same QR: lotConfirmation=${lotConfirmationOpen}, manual=${manualLotConfirmationOpen}`); + console.log(`⏱️ [QR PROCESS] Skipping - manual modal open for same QR`); return; } - // If it's a different QR, allow processing (user might have canceled and scanned different lot) - console.log(`⏱️ [QR PROCESS] Different QR detected while modal open, allowing processing`); + // If it's a different QR, allow processing + console.log(`⏱️ [QR PROCESS] Different QR detected while manual modal open, allowing processing`); } const qrDetectionStartTime = performance.now(); @@ -2061,7 +2203,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO qrProcessingTimeoutRef.current = null; } }; - }, [qrValues.length, isManualScanning, isRefreshingData, combinedLotData.length, lotConfirmationOpen, manualLotConfirmationOpen]); + }, [qrValues, isManualScanning, isRefreshingData, combinedLotData.length, lotConfirmationOpen, manualLotConfirmationOpen, handleLotConfirmationByRescan]); const renderCountRef = useRef(0); const renderStartTimeRef = useRef(null); @@ -3440,21 +3582,7 @@ paginatedData.map((lot, index) => { open={lotConfirmationOpen} onClose={() => { console.log(`⏱️ [LOT CONFIRM MODAL] Closing modal, clearing state`); - setLotConfirmationOpen(false); - setExpectedLotData(null); - setScannedLotData(null); - setSelectedLotForQr(null); - - // ✅ IMPORTANT: Clear refs to allow reprocessing the same QR code if user cancels and scans again - // This allows the modal to reopen for the same itemId with a different stockInLineId - setTimeout(() => { - lastProcessedQrRef.current = ''; - processedQrCodesRef.current.clear(); - console.log(`⏱️ [LOT CONFIRM MODAL] Cleared refs to allow reprocessing`); - }, 100); - - // ✅ Don't clear processedQrCombinations - it tracks by itemId+stockInLineId, - // so reopening for same itemId but different stockInLineId is allowed + clearLotConfirmationState(true); }} onConfirm={handleLotConfirmation} expectedLot={expectedLotData} diff --git a/src/components/FinishedGoodSearch/LotConfirmationModal.tsx b/src/components/FinishedGoodSearch/LotConfirmationModal.tsx index de48da7..d5c60eb 100644 --- a/src/components/FinishedGoodSearch/LotConfirmationModal.tsx +++ b/src/components/FinishedGoodSearch/LotConfirmationModal.tsx @@ -97,6 +97,9 @@ const LotConfirmationModal: React.FC = ({
  • {t("Update your suggested lot to the this scanned lot")}
  • + + {t("You can also scan again to confirm: scan the scanned lot again to switch, or scan the expected lot to continue with current lot.")} +