| @@ -737,6 +737,12 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | |||
| const lastProcessedQrRef = useRef<string>(''); | |||
| /** Prevent double-submit while a scan-pick API is in flight (scanner bounce). */ | |||
| const qrPickInFlightRef = useRef(false); | |||
| /** | |||
| * Only process newly appended qrValues entries. | |||
| * Prevents refresh/deps re-running the effect from auto-picking the next SOL | |||
| * with the same stale QR still sitting at the end of qrValues. | |||
| */ | |||
| const lastConsumedQrValuesLengthRef = useRef(0); | |||
| /** Latest lot indexes for QR re-scan allow checks outside processOutsideQrCode. */ | |||
| const lotDataIndexesRef = useRef<{ | |||
| byStockInLineId: Map<number, any[]>; | |||
| @@ -2429,6 +2435,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||
| if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { | |||
| return; | |||
| } | |||
| // Only react to newly appended QR events — not effect re-runs after refresh / index updates. | |||
| if (qrValues.length <= lastConsumedQrValuesLengthRef.current) { | |||
| console.log( | |||
| ` [QR PROCESS] Skipping — no new QR append (len=${qrValues.length}, consumed=${lastConsumedQrValuesLengthRef.current})`, | |||
| ); | |||
| return; | |||
| } | |||
| // Don't consume yet if a pick is in flight; retry after refresh / inFlight clears. | |||
| if (qrPickInFlightRef.current) { | |||
| console.log( | |||
| ` [QR PROCESS] Deferring new QR — scan-pick still in flight (len=${qrValues.length})`, | |||
| ); | |||
| return; | |||
| } | |||
| const qrValuesChangeStartTime = performance.now(); | |||
| console.log(` [QR VALUES EFFECT] Triggered at: ${new Date().toISOString()}`); | |||
| @@ -2436,6 +2457,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||
| console.log(` [QR VALUES EFFECT] qrValues:`, qrValues); | |||
| const latestQr = qrValues[qrValues.length - 1]; | |||
| // Claim this append immediately so refresh/deps re-entry cannot re-process it. | |||
| lastConsumedQrValuesLengthRef.current = qrValues.length; | |||
| console.log(` [QR VALUES EFFECT] Latest QR: ${latestQr}`); | |||
| console.log(` [QR VALUES EFFECT] Latest QR detected at: ${new Date().toISOString()}`); | |||
| @@ -3311,6 +3334,8 @@ const handleStartScan = useCallback(() => { | |||
| processedQrCodesRef.current = new Set(); | |||
| lastProcessedQrRef.current = ''; | |||
| qrPickInFlightRef.current = false; | |||
| // Ignore any QR values already in the buffer; only new scans after Start count. | |||
| lastConsumedQrValuesLengthRef.current = qrValues.length; | |||
| setQrScanError(false); | |||
| setQrScanSuccess(false); | |||
| @@ -3322,7 +3347,7 @@ const handleStartScan = useCallback(() => { | |||
| const totalTime = performance.now() - startTime; | |||
| console.log(` [START SCAN] Total start scan time: ${totalTime.toFixed(2)}ms`); | |||
| console.log(` [START SCAN] Start scan completed at: ${new Date().toISOString()}`); | |||
| }, [startScan]); | |||
| }, [startScan, qrValues.length]); | |||
| const handlePickOrderSwitch = useCallback(async (pickOrderId: number) => { | |||
| if (pickOrderSwitching) return; | |||
| @@ -3347,6 +3372,7 @@ const handleStartScan = useCallback(() => { | |||
| setQrScanSuccess(false); | |||
| stopScan(); | |||
| resetScan(); | |||
| lastConsumedQrValuesLengthRef.current = 0; | |||
| }, [stopScan, resetScan]); | |||
| // ... existing code around line 1469 ... | |||
| const handlelotnull = useCallback(async (lot: any) => { | |||