Explorar el Código

do scan same lot fix

production
CANCERYS\kw093 hace 2 días
padre
commit
1e0e1f0431
Se han modificado 1 ficheros con 270 adiciones y 172 borrados
  1. +270
    -172
      src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx

+ 270
- 172
src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx Ver fichero

@@ -86,19 +86,40 @@ interface Props {
}

/** 同物料多行时,优先对「有建议批次号」的行做替换,避免误选「无批次/不足」行 */
function pickExpectedLotForSubstitution(activeSuggestedLots: any[]): any | null {
if (!activeSuggestedLots?.length) return null;
const withLotNo = activeSuggestedLots.filter(
(l) => l.lotNo != null && String(l.lotNo).trim() !== ""
/** itemId → Set of processed stockOutLineId (allow same physical lot QR for next SOL). */
type ProcessedStockOutLinesByItemId = Map<number, Set<number>>;

function isLotRowPending(lot: any): boolean {
const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
return (
st === "pending" ||
st === "partially_completed" ||
st === "partially_complete" ||
st === ""
);
if (withLotNo.length === 1) return withLotNo[0];
if (withLotNo.length > 1) {
const pending = withLotNo.find(
(l) => (l.stockOutLineStatus || "").toLowerCase() === "pending"
);
return pending || withLotNo[0];
}
return activeSuggestedLots[0];
}

function isStockOutLineAlreadyProcessed(
processedByItemId: ProcessedStockOutLinesByItemId,
itemId: number,
stockOutLineId: number | null | undefined,
): boolean {
const solId = Number(stockOutLineId);
if (!Number.isFinite(solId) || solId <= 0) return false;
return processedByItemId.get(itemId)?.has(solId) ?? false;
}

function markProcessedStockOutLine(
prev: ProcessedStockOutLinesByItemId,
itemId: number,
stockOutLineId: number | null | undefined,
): ProcessedStockOutLinesByItemId {
const solId = Number(stockOutLineId);
if (!Number.isFinite(solId) || solId <= 0) return prev;
const newMap = new Map(prev);
if (!newMap.has(itemId)) newMap.set(itemId, new Set());
newMap.get(itemId)!.add(solId);
return newMap;
}

/** Parse workbench QR / test shortcut into itemId + stockInLineId. */
@@ -150,15 +171,67 @@ function parseWorkbenchScanQrPayload(
return null;
}

/** True when another pending/partial SOL still suggests this stockInLineId (same lot across lines). */
function activeLotsStillNeedStockInLine(
activeLotsForItem: any[] | undefined,
function hasPendingActiveRowForStockInLine(
indexes: {
byStockInLineId: Map<number, any[]>;
activeLotsByItemId: Map<number, any[]>;
},
itemId: number,
stockInLineId: number,
processedByItemId: ProcessedStockOutLinesByItemId,
): boolean {
if (!activeLotsForItem?.length) return false;
return activeLotsForItem.some(
(lot) => Number(lot?.stockInLineId) === Number(stockInLineId),
const rows = indexes.byStockInLineId.get(stockInLineId) ?? [];
const activeSet = new Set(indexes.activeLotsByItemId.get(itemId) ?? []);
return rows.some(
(lot) =>
lot.itemId === itemId &&
activeSet.has(lot) &&
isLotRowPending(lot) &&
!isStockOutLineAlreadyProcessed(processedByItemId, itemId, lot.stockOutLineId),
);
}

function findExactActiveMatchForStockInLine(
stockInLineLots: any[],
scannedItemId: number,
activeSuggestedLots: any[],
processedByItemId: ProcessedStockOutLinesByItemId,
): any | null {
const activeSet = new Set(activeSuggestedLots);
const candidates = stockInLineLots.filter(
(lot) =>
lot.itemId === scannedItemId &&
activeSet.has(lot) &&
!isStockOutLineAlreadyProcessed(
processedByItemId,
scannedItemId,
lot.stockOutLineId,
),
);
if (candidates.length === 0) return null;
return candidates.find((lot) => isLotRowPending(lot)) ?? candidates[0];
}

function pickExpectedLotForSubstitution(
activeSuggestedLots: any[],
processedByItemId?: ProcessedStockOutLinesByItemId,
): any | null {
if (!activeSuggestedLots?.length) return null;
const itemId = activeSuggestedLots[0]?.itemId;
const processed = processedByItemId ?? new Map();
const unprocessed = activeSuggestedLots.filter(
(l) =>
!itemId ||
!isStockOutLineAlreadyProcessed(processed, itemId, l.stockOutLineId),
);
const pool = unprocessed.length > 0 ? unprocessed : activeSuggestedLots;
const withLotNo = pool.filter(
(l) => l.lotNo != null && String(l.lotNo).trim() !== "",
);
const searchPool = withLotNo.length > 0 ? withLotNo : pool;
if (searchPool.length === 1) return searchPool[0];
const pending = searchPool.find((l) => isLotRowPending(l));
return pending ?? searchPool[0];
}

const ManualLotConfirmationModal: React.FC<{
@@ -645,8 +718,9 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);

// Add these missing state variables after line 352
const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
// Track processed QR codes by itemId+stockInLineId combination for better lot confirmation handling
const [processedQrCombinations, setProcessedQrCombinations] = useState<Map<number, Set<number>>>(new Map());
// Track processed stock-out lines per item (allow same physical lot QR for next SOL)
const [processedQrCombinations, setProcessedQrCombinations] =
useState<ProcessedStockOutLinesByItemId>(new Map());
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
@@ -662,9 +736,10 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const processedQrCodesRef = useRef<Set<string>>(new Set());
const lastProcessedQrRef = useRef<string>('');
/** Prevent double-submit while a scan-pick API is in flight (scanner bounce). */
const qrProcessInFlightRef = useRef(false);
const qrPickInFlightRef = useRef(false);
/** Latest lot indexes for QR re-scan allow checks outside processOutsideQrCode. */
const lotDataIndexesRef = useRef<{
byStockInLineId: Map<number, any[]>;
activeLotsByItemId: Map<number, any[]>;
} | null>(null);
@@ -1467,6 +1542,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}, [combinedLotData.length, combinedLotData]);

lotDataIndexesRef.current = {
byStockInLineId: lotDataIndexes.byStockInLineId,
activeLotsByItemId: lotDataIndexes.activeLotsByItemId,
};
@@ -1474,6 +1550,12 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
resetScanRef.current = resetScan;
const processOutsideQrCode = useCallback(async (latestQr: string, qrScanCountAtInvoke?: number) => {
if (qrPickInFlightRef.current) {
console.log(" [SKIP] QR pick already in flight");
return;
}
qrPickInFlightRef.current = true;

const totalStartTime = performance.now();
console.log(` [PROCESS OUTSIDE QR START] QR: ${latestQr.substring(0, 50)}...`);
console.log(` Start time: ${new Date().toISOString()}`);
@@ -1483,7 +1565,26 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const indexes = lotDataIndexes; // Access the memoized indexes
const indexAccessTime = performance.now() - indexAccessStart;
console.log(` [PERF] Index access time: ${indexAccessTime.toFixed(2)}ms`);

const maybeReleaseQrForNextSol = (
itemId: number,
stockInLineId: number,
processedAfterMark: ProcessedStockOutLinesByItemId,
) => {
if (
hasPendingActiveRowForStockInLine(
indexes,
itemId,
stockInLineId,
processedAfterMark,
)
) {
lastProcessedQrRef.current = "";
processedQrCodesRef.current.delete(latestQr);
}
};

try {
// 1) Parse JSON safely (parse once, reuse)
const parseStartTime = performance.now();
let qrData: any = null;
@@ -1516,30 +1617,24 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const scannedItemId = qrData.itemId;
const scannedStockInLineId = qrData.stockInLineId;
// ✅ Skip only when this lot was already processed AND no other active line still needs it.
// Same item + same stockInLineId across different pick-order lines must be re-scannable.
const duplicateCheckStartTime = performance.now();
const itemProcessedSet = processedQrCombinations.get(scannedItemId);
const activeSuggestedLotsEarly =
indexes.activeLotsByItemId.get(scannedItemId) || [];

if (
itemProcessedSet?.has(scannedStockInLineId) &&
!activeLotsStillNeedStockInLine(
activeSuggestedLotsEarly,
!hasPendingActiveRowForStockInLine(
indexes,
scannedItemId,
scannedStockInLineId,
processedQrCombinations,
)
) {
const duplicateCheckTime = performance.now() - duplicateCheckStartTime;
console.log(` [SKIP] Already processed combination: itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId} (check time: ${duplicateCheckTime.toFixed(2)}ms)`);
console.log(
` [SKIP] No pending stock-out line left for itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId}`,
);
return;
}
const duplicateCheckTime = performance.now() - duplicateCheckStartTime;
console.log(` [PERF] Duplicate check time: ${duplicateCheckTime.toFixed(2)}ms`);
// ✅ OPTIMIZATION: Use cached active lots directly (no filtering needed)
const lookupStartTime = performance.now();
const activeSuggestedLots = activeSuggestedLotsEarly;
const activeSuggestedLots = indexes.activeLotsByItemId.get(scannedItemId) || [];
// ✅ Also get all lots for this item (not just active ones) to allow lot switching even when all lots are rejected
const allLotsForItem = indexes.byItemId.get(scannedItemId) || [];
const lookupTime = performance.now() - lookupStartTime;
@@ -1547,9 +1642,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// ✅ Check if scanned lot is rejected BEFORE checking activeSuggestedLots
// This allows users to scan other lots even when all suggested lots are rejected
const scannedLot = allLotsForItem.find(
(lot: any) => lot.stockInLineId === scannedStockInLineId
);
const stockInLineRows = indexes.byStockInLineId.get(scannedStockInLineId) ?? [];
const scannedLot =
findExactActiveMatchForStockInLine(
stockInLineRows,
scannedItemId,
activeSuggestedLots,
processedQrCombinations,
) ??
allLotsForItem.find(
(lot: any) => lot.stockInLineId === scannedStockInLineId,
);
if (scannedLot) {
const isRejected =
@@ -1566,13 +1669,14 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
`此批次(${scannedLot.lotNo || scannedStockInLineId})已被拒绝,无法使用。请扫描其他批次。`
);
});
// Mark as processed to prevent re-processing
setProcessedQrCombinations(prev => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) newMap.set(scannedItemId, new Set());
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
// Mark this SOL as processed to prevent re-processing
setProcessedQrCombinations((prev) =>
markProcessedStockOutLine(
prev,
scannedItemId,
scannedLot.stockOutLineId,
),
);
return;
}

@@ -1631,7 +1735,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
pickExpectedLotForSubstitution(
allLotsForItem.filter(
(l: any) => l.lotNo != null && String(l.lotNo).trim() !== ""
)
),
processedQrCombinations,
) ||
allLotsForItem[0];
@@ -1759,12 +1864,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
);
});

setProcessedQrCombinations((prev) => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) newMap.set(scannedItemId, new Set());
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
const nextProcessedNoActive = markProcessedStockOutLine(
processedQrCombinations,
scannedItemId,
expectedLot.stockOutLineId,
);
setProcessedQrCombinations(nextProcessedNoActive);
maybeReleaseQrForNextSol(
scannedItemId,
scannedStockInLineId,
nextProcessedNoActive,
);

if (workbenchMode) {
await refreshWorkbenchAfterScanPick();
@@ -1775,16 +1885,13 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// ✅ OPTIMIZATION: Direct Map lookup for stockInLineId match (O(1))
const matchStartTime = performance.now();
let exactMatch: any = null;
const stockInLineLots = indexes.byStockInLineId.get(scannedStockInLineId) || [];
// Find exact match from stockInLineId index, then verify it's in active lots
for (let i = 0; i < stockInLineLots.length; i++) {
const lot = stockInLineLots[i];
if (lot.itemId === scannedItemId && activeSuggestedLots.includes(lot)) {
exactMatch = lot;
break;
}
}
const exactMatch = findExactActiveMatchForStockInLine(
stockInLineLots,
scannedItemId,
activeSuggestedLots,
processedQrCombinations,
);
const matchTime = performance.now() - matchStartTime;
console.log(` [PERF] Find exact match time: ${matchTime.toFixed(2)}ms, found: ${exactMatch ? 'yes' : 'no'}`);
@@ -1793,7 +1900,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// Also handle case where scanned lot is not in allLotsForItem (scannedLot is undefined)
if (!exactMatch) {
const expectedLot =
pickExpectedLotForSubstitution(activeSuggestedLots) || allLotsForItem[0];
pickExpectedLotForSubstitution(
activeSuggestedLots,
processedQrCombinations,
) || allLotsForItem[0];
if (expectedLot) {
const shouldAutoSwitch =
!scannedLot || (scannedLot.stockInLineId !== expectedLot.stockInLineId);
@@ -1926,12 +2036,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
);
});

setProcessedQrCombinations((prev) => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) newMap.set(scannedItemId, new Set());
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
const nextProcessedAuto = markProcessedStockOutLine(
processedQrCombinations,
scannedItemId,
expectedLot.stockOutLineId,
);
setProcessedQrCombinations(nextProcessedAuto);
maybeReleaseQrForNextSol(
scannedItemId,
scannedStockInLineId,
nextProcessedAuto,
);
if (workbenchMode) {
await refreshWorkbenchAfterScanPick();
}
@@ -2022,16 +2137,19 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const stateUpdateTime = performance.now() - stateUpdateStartTime;
console.log(` [PERF] State update time: ${stateUpdateTime.toFixed(2)}ms`);
// Mark this combination as processed
// Mark this SOL as processed (same physical lot may still be needed by another SOL)
const markProcessedStartTime = performance.now();
setProcessedQrCombinations(prev => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) {
newMap.set(scannedItemId, new Set());
}
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
const nextProcessedExact = markProcessedStockOutLine(
processedQrCombinations,
scannedItemId,
exactMatch.stockOutLineId,
);
setProcessedQrCombinations(nextProcessedExact);
maybeReleaseQrForNextSol(
scannedItemId,
scannedStockInLineId,
nextProcessedExact,
);
const markProcessedTime = performance.now() - markProcessedStartTime;
console.log(` [PERF] Mark processed time: ${markProcessedTime.toFixed(2)}ms`);

@@ -2042,7 +2160,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const totalTime = performance.now() - totalStartTime;
console.log(`✅ [PROCESS OUTSIDE QR END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(` End time: ${new Date().toISOString()}`);
console.log(`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, duplicateCheck=${duplicateCheckTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, api=${apiTime.toFixed(2)}ms, stateUpdate=${stateUpdateTime.toFixed(2)}ms, markProcessed=${markProcessedTime.toFixed(2)}ms`);
console.log(`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, api=${apiTime.toFixed(2)}ms, stateUpdate=${stateUpdateTime.toFixed(2)}ms, markProcessed=${markProcessedTime.toFixed(2)}ms`);
console.log(
workbenchMode
? "✅ Workbench scan-pick: list refreshed from server"
@@ -2085,14 +2203,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// ✅ Case 2: itemId 匹配但 stockInLineId 不匹配
// Workbench 策略:不彈窗,直接切換到掃到的批次並提交一次掃描
const mismatchCheckStartTime = performance.now();
const itemProcessedSet2 = processedQrCombinations.get(scannedItemId);
if (
itemProcessedSet2?.has(scannedStockInLineId) &&
!activeLotsStillNeedStockInLine(activeSuggestedLots, scannedStockInLineId)
!hasPendingActiveRowForStockInLine(
indexes,
scannedItemId,
scannedStockInLineId,
processedQrCombinations,
)
) {
const mismatchCheckTime = performance.now() - mismatchCheckStartTime;
console.log(
` [SKIP] Already processed this exact combination (check time: ${mismatchCheckTime.toFixed(2)}ms)`,
` [SKIP] No pending stock-out line left for mismatch path (check time: ${mismatchCheckTime.toFixed(2)}ms)`,
);
return;
}
@@ -2100,7 +2221,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
console.log(` [PERF] Mismatch check time: ${mismatchCheckTime.toFixed(2)}ms`);

const expectedLotStartTime = performance.now();
const expectedLot = pickExpectedLotForSubstitution(activeSuggestedLots);
const expectedLot = pickExpectedLotForSubstitution(
activeSuggestedLots,
processedQrCombinations,
);
if (!expectedLot) {
console.error("Could not determine expected lot for auto-switch");
startTransition(() => {
@@ -2239,12 +2363,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
);
});

setProcessedQrCombinations((prev) => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) newMap.set(scannedItemId, new Set());
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
const nextProcessedMismatch = markProcessedStockOutLine(
processedQrCombinations,
scannedItemId,
expectedLot.stockOutLineId,
);
setProcessedQrCombinations(nextProcessedMismatch);
maybeReleaseQrForNextSol(
scannedItemId,
scannedStockInLineId,
nextProcessedMismatch,
);

if (workbenchMode) {
await refreshWorkbenchAfterScanPick();
@@ -2262,7 +2391,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
);
console.log(` End time: ${new Date().toISOString()}`);
console.log(
`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, duplicateCheck=${duplicateCheckTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, mismatchCheck=${mismatchCheckTime.toFixed(2)}ms, expectedLot=${expectedLotTime.toFixed(2)}ms`,
`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, mismatchCheck=${mismatchCheckTime.toFixed(2)}ms, expectedLot=${expectedLotTime.toFixed(2)}ms`,
);
} catch (error) {
const totalTime = performance.now() - totalStartTime;
@@ -2274,6 +2403,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
});
return;
}
} finally {
qrPickInFlightRef.current = false;
}
}, [
lotDataIndexes,
processedQrCombinations,
@@ -2354,39 +2486,29 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// ✅ Process immediately (bypass QR scanner delay)
if (processOutsideQrCodeRef.current) {
if (qrProcessInFlightRef.current) {
console.log(` [TEST QR] Skipping — scan-pick already in flight`);
return;
}
qrProcessInFlightRef.current = true;
processOutsideQrCodeRef.current(simulatedQr, qrValues.length).then(() => {
const testTime = performance.now() - testStartTime;
console.log(` [TEST QR] Total processing time: ${testTime.toFixed(2)}ms (${(testTime / 1000).toFixed(3)}s)`);
console.log(` [TEST QR] End time: ${new Date().toISOString()}`);
// Release test shortcut string if another SOL still needs this lot
if (
hasPendingActiveRowForStockInLine(
lotDataIndexes,
itemId,
stockInLineId,
processedQrCombinations,
)
) {
processedQrCodesRef.current.delete(latestQr);
if (lastProcessedQrRef.current === latestQr) {
lastProcessedQrRef.current = "";
}
setProcessedQrCodes(new Set(processedQrCodesRef.current));
setLastProcessedQr("");
}
}).catch((error) => {
const testTime = performance.now() - testStartTime;
console.error(`❌ [TEST QR] Error after ${testTime.toFixed(2)}ms:`, error);
}).finally(async () => {
await new Promise((r) => setTimeout(r, 50));
try {
const activeForItem =
lotDataIndexesRef.current?.activeLotsByItemId.get(itemId);
if (
activeLotsStillNeedStockInLine(activeForItem, stockInLineId)
) {
processedQrCodesRef.current.delete(latestQr);
if (lastProcessedQrRef.current === latestQr) {
lastProcessedQrRef.current = "";
}
setProcessedQrCodes(new Set(processedQrCodesRef.current));
setLastProcessedQr("");
console.log(
` [TEST QR] Cleared processed test QR so same lot can be scanned for remaining line(s)`,
);
}
} finally {
qrProcessInFlightRef.current = false;
}
});
}
@@ -2423,32 +2545,33 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
console.log(` [QR DETECTION] Detection time: ${new Date().toISOString()}`);
console.log(` [QR DETECTION] Time since QR scanner set value: ${(qrDetectionStartTime - qrValuesChangeStartTime).toFixed(2)}ms`);
// Skip if already processed — unless another active line still needs the same lot (cross-POL same lot).
const checkProcessedStartTime = performance.now();
const alreadySeenQr =
processedQrCodesRef.current.has(latestQr) ||
lastProcessedQrRef.current === latestQr;
if (alreadySeenQr) {
const payload = parseWorkbenchScanQrPayload(latestQr);
const activeForItem = payload
? lotDataIndexesRef.current?.activeLotsByItemId.get(payload.itemId)
: undefined;
const allowSameLotRescan =
!!payload &&
activeLotsStillNeedStockInLine(activeForItem, payload.stockInLineId);
if (!allowSameLotRescan) {
const checkTime = performance.now() - checkProcessedStartTime;
console.log(` [QR PROCESS] Already processed check time: ${checkTime.toFixed(2)}ms`);
return;
}
console.log(
` [QR PROCESS] Allowing re-scan of same lot QR for remaining active pick line(s)`,
const qrPayload = parseWorkbenchScanQrPayload(latestQr);
const canRetrySamePhysicalLot =
qrPayload != null &&
hasPendingActiveRowForStockInLine(
lotDataIndexes,
qrPayload.itemId,
qrPayload.stockInLineId,
processedQrCombinations,
);
}
if (qrProcessInFlightRef.current) {
console.log(` [QR PROCESS] Skipping — scan-pick already in flight`);

// Skip if already processed (allow same QR when another pending SOL needs this lot)
const checkProcessedStartTime = performance.now();
if (
(processedQrCodesRef.current.has(latestQr) ||
lastProcessedQrRef.current === latestQr) &&
!canRetrySamePhysicalLot
) {
const checkTime = performance.now() - checkProcessedStartTime;
console.log(` [QR PROCESS] Already processed check time: ${checkTime.toFixed(2)}ms`);
return;
}
if (canRetrySamePhysicalLot) {
processedQrCodesRef.current.delete(latestQr);
if (lastProcessedQrRef.current === latestQr) {
lastProcessedQrRef.current = "";
}
}
const checkTime = performance.now() - checkProcessedStartTime;
console.log(` [QR PROCESS] Not processed check time: ${checkTime.toFixed(2)}ms`);
@@ -2483,8 +2606,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
// Process new QR code immediately (background mode - no modal)
// Check against refs to avoid state update delays
if (latestQr && latestQr !== lastProcessedQrRef.current) {
if (latestQr && (latestQr !== lastProcessedQrRef.current || canRetrySamePhysicalLot)) {
const processingStartTime = performance.now();
console.log(` [QR PROCESS] Starting processing at: ${new Date().toISOString()}`);
console.log(` [QR PROCESS] Time since detection: ${(processingStartTime - qrDetectionStartTime).toFixed(2)}ms`);
@@ -2516,45 +2638,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// Use ref to avoid dependency issues
const processCallStartTime = performance.now();
if (processOutsideQrCodeRef.current) {
qrProcessInFlightRef.current = true;
processOutsideQrCodeRef.current(latestQr, qrValues.length)
.then(() => {
const processCallTime = performance.now() - processCallStartTime;
const totalProcessingTime = performance.now() - processingStartTime;
console.log(` [QR PROCESS] processOutsideQrCode call time: ${processCallTime.toFixed(2)}ms`);
console.log(` [QR PROCESS] Total processing time: ${totalProcessingTime.toFixed(2)}ms (${(totalProcessingTime / 1000).toFixed(3)}s)`);
// maybeReleaseQrForNextSol may have cleared refs; sync UI state
setProcessedQrCodes(new Set(processedQrCodesRef.current));
setLastProcessedQr(lastProcessedQrRef.current);
})
.catch((error) => {
const processCallTime = performance.now() - processCallStartTime;
const totalProcessingTime = performance.now() - processingStartTime;
console.error(`❌ [QR PROCESS] processOutsideQrCode error after ${processCallTime.toFixed(2)}ms:`, error);
console.error(`❌ [QR PROCESS] Total processing time before error: ${totalProcessingTime.toFixed(2)}ms`);
})
.finally(async () => {
// Wait a tick so refresh/local status updates commit into lotDataIndexesRef.
await new Promise((r) => setTimeout(r, 50));
try {
const payload = parseWorkbenchScanQrPayload(latestQr);
const activeForItem = payload
? lotDataIndexesRef.current?.activeLotsByItemId.get(payload.itemId)
: undefined;
if (
payload &&
activeLotsStillNeedStockInLine(activeForItem, payload.stockInLineId)
) {
processedQrCodesRef.current.delete(latestQr);
if (lastProcessedQrRef.current === latestQr) {
lastProcessedQrRef.current = "";
}
setProcessedQrCodes(new Set(processedQrCodesRef.current));
setLastProcessedQr("");
console.log(
` [QR PROCESS] Cleared processed QR so same lot can be scanned for remaining line(s)`,
);
}
} finally {
qrProcessInFlightRef.current = false;
}
});
}
@@ -2577,7 +2675,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
qrProcessingTimeoutRef.current = null;
}
};
}, [qrValues, isManualScanning, isRefreshingData, combinedLotData.length, manualLotConfirmationOpen]);
}, [qrValues, isManualScanning, isRefreshingData, combinedLotData.length, manualLotConfirmationOpen, lotDataIndexes, processedQrCombinations]);
const renderCountRef = useRef(0);
const renderStartTimeRef = useRef<number | null>(null);

@@ -3212,7 +3310,7 @@ const handleStartScan = useCallback(() => {
setProcessedQrCombinations(new Map());
processedQrCodesRef.current = new Set();
lastProcessedQrRef.current = '';
qrProcessInFlightRef.current = false;
qrPickInFlightRef.current = false;
setQrScanError(false);
setQrScanSuccess(false);


Cargando…
Cancelar
Guardar