@@ -147,7 +147,7 @@ const QrCodeModal: React.FC<{
if (qrData.stockInLineId && qrData.itemId) {
// ✅ Check if we're already fetching this stockInLineId
if (fetchingRef.current.has(qrData.stockInLineId)) {
console.log(`⏱️ [QR MODAL] Already fetching stockInLineId: ${qrData.stockInLineId}, skipping duplicate call`);
console.log(` [QR MODAL] Already fetching stockInLineId: ${qrData.stockInLineId}, skipping duplicate call`);
return;
}
@@ -159,7 +159,7 @@ const QrCodeModal: React.FC<{
fetchingRef.current.add(qrData.stockInLineId);
const fetchStartTime = performance.now();
console.log(`⏱️ [QR MODAL] Starting fetchStockInLineInfo for stockInLineId: ${qrData.stockInLineId}`);
console.log(` [QR MODAL] Starting fetchStockInLineInfo for stockInLineId: ${qrData.stockInLineId}`);
fetchStockInLineInfo(qrData.stockInLineId)
.then((stockInLineInfo) => {
@@ -173,7 +173,7 @@ const QrCodeModal: React.FC<{
}
const fetchTime = performance.now() - fetchStartTime;
console.log(`⏱️ [QR MODAL] fetchStockInLineInfo time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`);
console.log(` [QR MODAL] fetchStockInLineInfo time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`);
console.log("Stock in line info:", stockInLineInfo);
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
@@ -655,7 +655,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
abortControllerRef.current = abortController;
try {
console.log(`⏱️ [CACHE MISS] Fetching stockInLineInfo for ${stockInLineId}`);
console.log(` [CACHE MISS] Fetching stockInLineInfo for ${stockInLineId}`);
const stockInLineInfo = await fetchStockInLineInfo(stockInLineId);
// Store in cache
@@ -675,7 +675,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
return { lotNo: stockInLineInfo.lotNo || null };
} catch (error: any) {
if (error.name === 'AbortError') {
console.log(`⏱️ [CACHE] Request aborted for ${stockInLineId}`);
console.log(` [CACHE] Request aborted for ${stockInLineId}`);
throw error;
}
console.error(`❌ [CACHE] Error fetching stockInLineInfo for ${stockInLineId}:`, error);
@@ -685,8 +685,8 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any, qrScanCountAtOpen?: number) => {
const mismatchStartTime = performance.now();
console.log(`⏱️ [HANDLE LOT MISMATCH START]`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
console.log(` [HANDLE LOT MISMATCH START]`);
console.log(` Start time: ${new Date().toISOString()}`);
console.log("Lot mismatch detected:", { expectedLot, scannedLot });
lotConfirmOpenedQrCountRef.current =
@@ -705,26 +705,26 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
setLotConfirmationOpen(true);
const setStateTime = performance.now() - setStateStartTime;
console.timeEnd('setLotConfirmationOpen');
console.log(`⏱️ [HANDLE LOT MISMATCH] Modal state set to open (setState time: ${setStateTime.toFixed(2)}ms)`);
console.log(` [HANDLE LOT MISMATCH] Modal state set to open (setState time: ${setStateTime.toFixed(2)}ms)`);
console.log(`✅ [HANDLE LOT MISMATCH] Modal state set to open`);
}, 0);
const setTimeoutTime = performance.now() - setTimeoutStartTime;
console.log(`⏱️ [PERF] setTimeout scheduling time: ${setTimeoutTime.toFixed(2)}ms`);
console.log(` [PERF] setTimeout scheduling time: ${setTimeoutTime.toFixed(2)}ms`);
// ✅ Fetch lotNo in background ONLY for display purposes (using cached version)
if (!scannedLot.lotNo && scannedLot.stockInLineId) {
const stockInLineId = scannedLot.stockInLineId;
if (typeof stockInLineId !== 'number') {
console.warn(`⏱️ [HANDLE LOT MISMATCH] Invalid stockInLineId: ${stockInLineId}`);
console.warn(` [HANDLE LOT MISMATCH] Invalid stockInLineId: ${stockInLineId}`);
return;
}
console.log(`⏱️ [HANDLE LOT MISMATCH] Fetching lotNo in background (stockInLineId: ${stockInLineId})`);
console.log(` [HANDLE LOT MISMATCH] Fetching lotNo in background (stockInLineId: ${stockInLineId})`);
const fetchStartTime = performance.now();
fetchStockInLineInfoCached(stockInLineId)
.then((stockInLineInfo) => {
const fetchTime = performance.now() - fetchStartTime;
console.log(`⏱️ [HANDLE LOT MISMATCH] fetchStockInLineInfoCached time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`);
console.log(` [HANDLE LOT MISMATCH] fetchStockInLineInfoCached time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`);
const updateStateStartTime = performance.now();
startTransition(() => {
@@ -734,10 +734,10 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
}));
});
const updateStateTime = performance.now() - updateStateStartTime;
console.log(`⏱️ [PERF] Update scanned lot data time: ${updateStateTime.toFixed(2)}ms`);
console.log(` [PERF] Update scanned lot data time: ${updateStateTime.toFixed(2)}ms`);
const totalTime = performance.now() - mismatchStartTime;
console.log(`⏱️ [HANDLE LOT MISMATCH] Background fetch completed: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(` [HANDLE LOT MISMATCH] Background fetch completed: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
})
.catch((error) => {
if (error.name !== 'AbortError') {
@@ -747,7 +747,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
});
} else {
const totalTime = performance.now() - mismatchStartTime;
console.log(`⏱️ [HANDLE LOT MISMATCH END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(` [HANDLE LOT MISMATCH END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
}
}, [fetchStockInLineInfoCached]);
const checkAllLotsCompleted = useCallback((lotData: any[]) => {
@@ -1187,7 +1187,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
setTimeout(() => {
lastProcessedQrRef.current = '';
processedQrCodesRef.current.clear();
console.log(`⏱️ [LOT CONFIRM MODAL] Cleared refs to allow reprocessing`);
console.log(` [LOT CONFIRM MODAL] Cleared refs to allow reprocessing`);
}, 100);
}
}, []);
@@ -1362,6 +1362,16 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
console.log(` QR Code "${lotNo}" does not match any expected lots. Available lots: ${availableLotNos}`);
return;
}
const hasExpiredLot = matchingLots.some(
(lot: any) => String(lot.lotAvailability || '').toLowerCase() === 'expired'
);
if (hasExpiredLot) {
console.warn(`⚠️ [QR PROCESS] Scanned lot ${lotNo} is expired`);
setQrScanError(true);
setQrScanSuccess(false);
return;
}
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
setQrScanError(false);
@@ -1485,8 +1495,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}, [combinedLotData]);
const handleFastQrScan = useCallback(async (lotNo: string) => {
const startTime = performance.now();
console.log(`⏱️ [FAST SCAN START] Lot: ${lotNo}`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
console.log(` [FAST SCAN START] Lot: ${lotNo}`);
console.log(` Start time: ${new Date().toISOString()}`);
// 从 combinedLotData 中找到对应的 lot
const findStartTime = performance.now();
@@ -1494,12 +1504,12 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
lot.lotNo && lot.lotNo === lotNo
);
const findTime = performance.now() - findStartTime;
console.log(`⏱️ Find lot time: ${findTime.toFixed(2)}ms`);
console.log(` Find lot time: ${findTime.toFixed(2)}ms`);
if (!matchingLot || !matchingLot.stockOutLineId) {
const totalTime = performance.now() - startTime;
console.warn(`⚠️ Fast scan: Lot ${lotNo} not found or no stockOutLineId`);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms`);
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
return;
}
@@ -1514,7 +1524,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
status: "checked",
});
const apiTime = performance.now() - apiStartTime;
console.log(`⏱️ API call time: ${apiTime.toFixed(2)}ms`);
console.log(` API call time: ${apiTime.toFixed(2)}ms`);
if (res.code === "checked" || res.code === "SUCCESS") {
// ✅ 只更新本地状态,不调用 fetchAllCombinedLotData
@@ -1545,27 +1555,27 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return lot;
}));
const updateTime = performance.now() - updateStartTime;
console.log(`⏱️ State update time: ${updateTime.toFixed(2)}ms`);
console.log(` State update time: ${updateTime.toFixed(2)}ms`);
const totalTime = performance.now() - startTime;
console.log(`✅ [FAST SCAN END] Lot: ${lotNo}`);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(`⏰ End time: ${new Date().toISOString()}`);
console.log(` Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(` End time: ${new Date().toISOString()}`);
} else {
const totalTime = performance.now() - startTime;
console.warn(`⚠️ Fast scan failed for ${lotNo}:`, res.code);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms`);
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
}
} catch (error) {
const totalTime = performance.now() - startTime;
console.error(` Fast scan error for ${lotNo}:`, error);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms`);
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
}
}, [combinedLotData, updateStockOutLineStatusByQRCodeAndLotNo]);
// Enhanced lotDataIndexes with cached active lots for better performance
const lotDataIndexes = useMemo(() => {
const indexStartTime = performance.now();
console.log(`⏱️ [PERF] lotDataIndexes calculation START, data length: ${combinedLotData.length}`);
console.log(` [PERF] lotDataIndexes calculation START, data length: ${combinedLotData.length}`);
const byItemId = new Map<number, any[]>();
const byItemCode = new Map<string, any[]>();
@@ -1622,7 +1632,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const indexTime = performance.now() - indexStartTime;
if (indexTime > 10) {
console.log(`⏱️ [PERF] lotDataIndexes calculation END: ${indexTime.toFixed(2)}ms (${(indexTime / 1000).toFixed(3)}s)`);
console.log(` [PERF] lotDataIndexes calculation END: ${indexTime.toFixed(2)}ms (${(indexTime / 1000).toFixed(3)}s)`);
}
return { byItemId, byItemCode, byLotId, byLotNo, byStockInLineId, activeLotsByItemId };
@@ -1633,14 +1643,14 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const processOutsideQrCode = useCallback(async (latestQr: string, qrScanCountAtInvoke?: number) => {
const totalStartTime = performance.now();
console.log(`⏱️ [PROCESS OUTSIDE QR START] QR: ${latestQr.substring(0, 50)}...`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
console.log(` [PROCESS OUTSIDE QR START] QR: ${latestQr.substring(0, 50)}...`);
console.log(` Start time: ${new Date().toISOString()}`);
// ✅ Measure index access time
const indexAccessStart = performance.now();
const indexes = lotDataIndexes; // Access the memoized indexes
const indexAccessTime = performance.now() - indexAccessStart;
console.log(`⏱️ [PERF] Index access time: ${indexAccessTime.toFixed(2)}ms`);
console.log(` [PERF] Index access time: ${indexAccessTime.toFixed(2)}ms`);
// 1) Parse JSON safely (parse once, reuse)
const parseStartTime = performance.now();
@@ -1649,7 +1659,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
try {
qrData = JSON.parse(latestQr);
parseTime = performance.now() - parseStartTime;
console.log(`⏱️ [PERF] JSON parse time: ${parseTime.toFixed(2)}ms`);
console.log(` [PERF] JSON parse time: ${parseTime.toFixed(2)}ms`);
} catch {
console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches.");
startTransition(() => {
@@ -1670,7 +1680,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return;
}
const validationTime = performance.now() - validationStartTime;
console.log(`⏱️ [PERF] Validation time: ${validationTime.toFixed(2)}ms`);
console.log(` [PERF] Validation time: ${validationTime.toFixed(2)}ms`);
const scannedItemId = qrData.itemId;
const scannedStockInLineId = qrData.stockInLineId;
@@ -1680,11 +1690,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const itemProcessedSet = processedQrCombinations.get(scannedItemId);
if (itemProcessedSet?.has(scannedStockInLineId)) {
const duplicateCheckTime = performance.now() - duplicateCheckStartTime;
console.log(`⏱️ [SKIP] Already processed combination: itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId} (check time: ${duplicateCheckTime.toFixed(2)}ms)`);
console.log(` [SKIP] Already processed combination: itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId} (check time: ${duplicateCheckTime.toFixed(2)}ms)`);
return;
}
const duplicateCheckTime = performance.now() - duplicateCheckStartTime;
console.log(`⏱️ [PERF] Duplicate check time: ${duplicateCheckTime.toFixed(2)}ms`);
console.log(` [PERF] Duplicate check time: ${duplicateCheckTime.toFixed(2)}ms`);
// ✅ OPTIMIZATION: Use cached active lots directly (no filtering needed)
const lookupStartTime = performance.now();
@@ -1692,7 +1702,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// ✅ 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;
console.log(`⏱️ [PERF] Index lookup time: ${lookupTime.toFixed(2)}ms, found ${activeSuggestedLots.length} active lots, ${allLotsForItem.length} total lots`);
console.log(` [PERF] Index lookup time: ${lookupTime.toFixed(2)}ms, found ${activeSuggestedLots.length} active lots, ${allLotsForItem.length} total lots`);
// ✅ Check if scanned lot is rejected BEFORE checking activeSuggestedLots
// This allows users to scan other lots even when all suggested lots are rejected
@@ -1724,6 +1734,27 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
});
return;
}
const isExpired =
String(scannedLot.lotAvailability || '').toLowerCase() === 'expired';
if (isExpired) {
console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is expired`);
startTransition(() => {
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(
`此批次(${scannedLot.lotNo || scannedStockInLineId})已过期,无法使用。请扫描其他批次。`
);
});
// Mark as processed to prevent re-processing the same expired QR repeatedly
setProcessedQrCombinations(prev => {
const newMap = new Map(prev);
if (!newMap.has(scannedItemId)) newMap.set(scannedItemId, new Set());
newMap.get(scannedItemId)!.add(scannedStockInLineId);
return newMap;
});
return;
}
}
// ✅ If no active suggested lots, but scanned lot is not rejected, allow lot switching
@@ -1793,7 +1824,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
}
const matchTime = performance.now() - matchStartTime;
console.log(`⏱️ [PERF] Find exact match time: ${matchTime.toFixed(2)}ms, found: ${exactMatch ? 'yes' : 'no'}`);
console.log(` [PERF] Find exact match time: ${matchTime.toFixed(2)}ms, found: ${exactMatch ? 'yes' : 'no'}`);
// ✅ Check if scanned lot exists in allLotsForItem but not in activeSuggestedLots
// This handles the case where Lot A is rejected and user scans Lot B
@@ -1843,8 +1874,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
try {
const apiStartTime = performance.now();
console.log(`⏱️ [API CALL START] Calling updateStockOutLineStatusByQRCodeAndLotNo`);
console.log(`⏰ [API CALL] API start time: ${new Date().toISOString()}`);
console.log(` [API CALL START] Calling updateStockOutLineStatusByQRCodeAndLotNo`);
console.log(` [API CALL] API start time: ${new Date().toISOString()}`);
const res = await updateStockOutLineStatusByQRCodeAndLotNo({
pickOrderLineId: exactMatch.pickOrderLineId,
inventoryLotNo: exactMatch.lotNo,
@@ -1853,8 +1884,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
status: "checked",
});
const apiTime = performance.now() - apiStartTime;
console.log(`⏱️ [API CALL END] Total API time: ${apiTime.toFixed(2)}ms (${(apiTime / 1000).toFixed(3)}s)`);
console.log(`⏰ [API CALL] API end time: ${new Date().toISOString()}`);
console.log(` [API CALL END] Total API time: ${apiTime.toFixed(2)}ms (${(apiTime / 1000).toFixed(3)}s)`);
console.log(` [API CALL] API end time: ${new Date().toISOString()}`);
if (res.code === "checked" || res.code === "SUCCESS") {
const entity = res.entity as any;
@@ -1890,7 +1921,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}));
});
const stateUpdateTime = performance.now() - stateUpdateStartTime;
console.log(`⏱️ [PERF] State update time: ${stateUpdateTime.toFixed(2)}ms`);
console.log(` [PERF] State update time: ${stateUpdateTime.toFixed(2)}ms`);
// Mark this combination as processed
const markProcessedStartTime = performance.now();
@@ -1903,11 +1934,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return newMap;
});
const markProcessedTime = performance.now() - markProcessedStartTime;
console.log(`⏱️ [PERF] Mark processed time: ${markProcessedTime.toFixed(2)}ms`);
console.log(` [PERF] Mark processed time: ${markProcessedTime.toFixed(2)}ms`);
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(` 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("✅ Status updated locally, no full data refresh needed");
} else {
@@ -1936,11 +1967,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const itemProcessedSet2 = processedQrCombinations.get(scannedItemId);
if (itemProcessedSet2?.has(scannedStockInLineId)) {
const mismatchCheckTime = performance.now() - mismatchCheckStartTime;
console.log(`⏱️ [SKIP] Already processed this exact combination (check time: ${mismatchCheckTime.toFixed(2)}ms)`);
console.log(` [SKIP] Already processed this exact combination (check time: ${mismatchCheckTime.toFixed(2)}ms)`);
return;
}
const mismatchCheckTime = performance.now() - mismatchCheckStartTime;
console.log(`⏱️ [PERF] Mismatch check time: ${mismatchCheckTime.toFixed(2)}ms`);
console.log(` [PERF] Mismatch check time: ${mismatchCheckTime.toFixed(2)}ms`);
// 取应被替换的活跃行(同物料多行时优先有建议批次的行)
const expectedLotStartTime = performance.now();
@@ -1954,7 +1985,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return;
}
const expectedLotTime = performance.now() - expectedLotStartTime;
console.log(`⏱️ [PERF] Get expected lot time: ${expectedLotTime.toFixed(2)}ms`);
console.log(` [PERF] Get expected lot time: ${expectedLotTime.toFixed(2)}ms`);
// ✅ 立即打开确认模态框,不等待其他操作
console.log(`⚠️ Lot mismatch: Expected stockInLineId=${expectedLot.stockInLineId}, Scanned stockInLineId=${scannedStockInLineId}`);
@@ -1963,7 +1994,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const setSelectedLotStartTime = performance.now();
setSelectedLotForQr(expectedLot);
const setSelectedLotTime = performance.now() - setSelectedLotStartTime;
console.log(`⏱️ [PERF] Set selected lot time: ${setSelectedLotTime.toFixed(2)}ms`);
console.log(` [PERF] Set selected lot time: ${setSelectedLotTime.toFixed(2)}ms`);
// ✅ 获取扫描的 lot 信息(从 QR 数据中提取,或使用默认值)
// Call handleLotMismatch immediately - it will open the modal
@@ -1984,11 +2015,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
qrScanCountAtInvoke
);
const handleMismatchTime = performance.now() - handleMismatchStartTime;
console.log(`⏱️ [PERF] Handle mismatch call time: ${handleMismatchTime.toFixed(2)}ms`);
console.log(` [PERF] Handle mismatch call time: ${handleMismatchTime.toFixed(2)}ms`);
const totalTime = performance.now() - totalStartTime;
console.log(`⚠️ [PROCESS OUTSIDE QR MISMATCH] Total time before modal: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(`⏰ End time: ${new Date().toISOString()}`);
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, setSelectedLot=${setSelectedLotTime.toFixed(2)}ms, handleMismatch=${handleMismatchTime.toFixed(2)}ms`);
} catch (error) {
const totalTime = performance.now() - totalStartTime;
@@ -2011,13 +2042,13 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
const qrValuesChangeStartTime = performance.now();
console.log(`⏱️ [QR VALUES EFFECT] Triggered at: ${new Date().toISOString()}`);
console.log(`⏱️ [QR VALUES EFFECT] qrValues.length: ${qrValues.length}`);
console.log(`⏱️ [QR VALUES EFFECT] qrValues:`, qrValues);
console.log(` [QR VALUES EFFECT] Triggered at: ${new Date().toISOString()}`);
console.log(` [QR VALUES EFFECT] qrValues.length: ${qrValues.length}`);
console.log(` [QR VALUES EFFECT] qrValues:`, qrValues);
const latestQr = qrValues[qrValues.length - 1];
console.log(`⏱️ [QR VALUES EFFECT] Latest QR: ${latestQr}`);
console.log(`⏰ [QR VALUES EFFECT] Latest QR detected at: ${new Date().toISOString()}`);
console.log(` [QR VALUES EFFECT] Latest QR: ${latestQr}`);
console.log(` [QR VALUES EFFECT] Latest QR detected at: ${new Date().toISOString()}`);
// ✅ FIXED: Handle test shortcut {2fitestx,y} or {2fittestx,y} where x=itemId, y=stockInLineId
// Support both formats: {2fitest (2 t's) and {2fittest (3 t's)
@@ -2048,8 +2079,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
stockInLineId: stockInLineId
});
console.log(`⏱️ [TEST QR] Simulated QR content: ${simulatedQr}`);
console.log(`⏱️ [TEST QR] Start time: ${new Date().toISOString()}`);
console.log(` [TEST QR] Simulated QR content: ${simulatedQr}`);
console.log(` [TEST QR] Start time: ${new Date().toISOString()}`);
const testStartTime = performance.now();
// ✅ Mark as processed FIRST to avoid duplicate processing
@@ -2068,8 +2099,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
if (processOutsideQrCodeRef.current) {
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()}`);
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()}`);
}).catch((error) => {
const testTime = performance.now() - testStartTime;
console.error(`❌ [TEST QR] Error after ${testTime.toFixed(2)}ms:`, error);
@@ -2082,13 +2113,13 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
const qrValuesChangeTime = performance.now() - qrValuesChangeStartTime;
console.log(`⏱️ [QR VALUES EFFECT] Test QR handling time: ${qrValuesChangeTime.toFixed(2)}ms`);
console.log(` [QR VALUES EFFECT] Test QR handling time: ${qrValuesChangeTime.toFixed(2)}ms`);
return; // ✅ IMPORTANT: Return early to prevent normal processing
} else {
console.warn(`⏱️ [TEST QR] Invalid itemId or stockInLineId: itemId=${parts[0]}, stockInLineId=${parts[1]}`);
console.warn(` [TEST QR] Invalid itemId or stockInLineId: itemId=${parts[0]}, stockInLineId=${parts[1]}`);
}
} else {
console.warn(`⏱️ [TEST QR] Invalid format. Expected {2fitestx,y} or {2fittestx,y}, got: ${latestQr}`);
console.warn(` [TEST QR] Invalid format. Expected {2fitestx,y} or {2fittestx,y}, got: ${latestQr}`);
}
}
@@ -2118,23 +2149,23 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// 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 - manual modal open for same QR`);
console.log(` [QR PROCESS] Skipping - manual modal open for same QR`);
return;
}
// If it's a different QR, allow processing
console.log(`⏱️ [QR PROCESS] Different QR detected while manual modal open, allowing processing`);
console.log(` [QR PROCESS] Different QR detected while manual modal open, allowing processing`);
}
const qrDetectionStartTime = performance.now();
console.log(`⏱️ [QR DETECTION] Latest QR detected: ${latestQr?.substring(0, 50)}...`);
console.log(`⏰ [QR DETECTION] Detection time: ${new Date().toISOString()}`);
console.log(`⏱️ [QR DETECTION] Time since QR scanner set value: ${(qrDetectionStartTime - qrValuesChangeStartTime).toFixed(2)}ms`);
console.log(` [QR DETECTION] Latest QR detected: ${latestQr?.substring(0, 50)}...`);
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 (use refs to avoid dependency issues and delays)
const checkProcessedStartTime = performance.now();
if (processedQrCodesRef.current.has(latestQr) || lastProcessedQrRef.current === latestQr) {
const checkTime = performance.now() - checkProcessedStartTime;
console.log(`⏱️ [QR PROCESS] Already processed check time: ${checkTime.toFixed(2)}ms`);
console.log(` [QR PROCESS] Already processed check time: ${checkTime.toFixed(2)}ms`);
return;
}
const checkTime = performance.now() - checkProcessedStartTime;
@@ -2174,8 +2205,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// Check against refs to avoid state update delays
if (latestQr && latestQr !== lastProcessedQrRef.current) {
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`);
console.log(` [QR PROCESS] Starting processing at: ${new Date().toISOString()}`);
console.log(` [QR PROCESS] Time since detection: ${(processingStartTime - qrDetectionStartTime).toFixed(2)}ms`);
// ✅ Process immediately for better responsiveness
// Clear any pending debounced processing
@@ -2185,7 +2216,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
// Log immediately (console.log is synchronous)
console.log(`⏱️ [QR PROCESS] Processing new QR code with enhanced validation: ${latestQr}`);
console.log(` [QR PROCESS] Processing new QR code with enhanced validation: ${latestQr}`);
// Update refs immediately (no state update delay) - do this FIRST
const refUpdateStartTime = performance.now();
@@ -2198,7 +2229,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
}
const refUpdateTime = performance.now() - refUpdateStartTime;
console.log(`⏱️ [QR PROCESS] Ref update time: ${refUpdateTime.toFixed(2)}ms`);
console.log(` [QR PROCESS] Ref update time: ${refUpdateTime.toFixed(2)}ms`);
// Process immediately in background - no modal/form needed, no delays
// Use ref to avoid dependency issues
@@ -2207,8 +2238,8 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
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)`);
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)`);
}).catch((error) => {
const processCallTime = performance.now() - processCallStartTime;
const totalProcessingTime = performance.now() - processingStartTime;
@@ -2222,12 +2253,12 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
setLastProcessedQr(latestQr);
setProcessedQrCodes(new Set(processedQrCodesRef.current));
const stateUpdateTime = performance.now() - stateUpdateStartTime;
console.log(`⏱️ [QR PROCESS] State update time: ${stateUpdateTime.toFixed(2)}ms`);
console.log(` [QR PROCESS] State update time: ${stateUpdateTime.toFixed(2)}ms`);
const detectionTime = performance.now() - qrDetectionStartTime;
const totalEffectTime = performance.now() - qrValuesChangeStartTime;
console.log(`⏱️ [QR DETECTION] Total detection time: ${detectionTime.toFixed(2)}ms`);
console.log(`⏱️ [QR VALUES EFFECT] Total effect time: ${totalEffectTime.toFixed(2)}ms`);
console.log(` [QR DETECTION] Total detection time: ${detectionTime.toFixed(2)}ms`);
console.log(` [QR VALUES EFFECT] Total effect time: ${totalEffectTime.toFixed(2)}ms`);
}
return () => {
@@ -2248,7 +2279,7 @@ useEffect(() => {
if (renderStartTimeRef.current !== null) {
const renderTime = now - renderStartTimeRef.current;
if (renderTime > 100) { // Only log slow renders (>100ms)
console.log(`⏱️ [PERF] Render #${renderCountRef.current} took ${renderTime.toFixed(2)}ms, combinedLotData length: ${combinedLotData.length}`);
console.log(` [PERF] Render #${renderCountRef.current} took ${renderTime.toFixed(2)}ms, combinedLotData length: ${combinedLotData.length}`);
}
renderStartTimeRef.current = null;
}
@@ -2256,7 +2287,7 @@ useEffect(() => {
// Track when lotConfirmationOpen changes
if (lotConfirmationOpen) {
renderStartTimeRef.current = performance.now();
console.log(`⏱️ [PERF] Render triggered by lotConfirmationOpen=true`);
console.log(` [PERF] Render triggered by lotConfirmationOpen=true`);
}
}, [combinedLotData.length, lotConfirmationOpen]);
// Auto-start scanner only once on mount
@@ -2719,12 +2750,12 @@ useEffect(() => {
}, [hasPendingBatchSubmit]);
const handleStartScan = useCallback(() => {
const startTime = performance.now();
console.log(`⏱️ [START SCAN] Called at: ${new Date().toISOString()}`);
console.log(`⏱️ [START SCAN] Starting manual QR scan...`);
console.log(` [START SCAN] Called at: ${new Date().toISOString()}`);
console.log(` [START SCAN] Starting manual QR scan...`);
setIsManualScanning(true);
const setManualScanningTime = performance.now() - startTime;
console.log(`⏱️ [START SCAN] setManualScanning time: ${setManualScanningTime.toFixed(2)}ms`);
console.log(` [START SCAN] setManualScanning time: ${setManualScanningTime.toFixed(2)}ms`);
setProcessedQrCodes(new Set());
setLastProcessedQr('');
@@ -2734,11 +2765,11 @@ const handleStartScan = useCallback(() => {
const beforeStartScanTime = performance.now();
startScan();
const startScanTime = performance.now() - beforeStartScanTime;
console.log(`⏱️ [START SCAN] startScan() call time: ${startScanTime.toFixed(2)}ms`);
console.log(` [START SCAN] startScan() call time: ${startScanTime.toFixed(2)}ms`);
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()}`);
console.log(` [START SCAN] Total start scan time: ${totalTime.toFixed(2)}ms`);
console.log(` [START SCAN] Start scan completed at: ${new Date().toISOString()}`);
}, [startScan]);
const handlePickOrderSwitch = useCallback(async (pickOrderId: number) => {
if (pickOrderSwitching) return;
@@ -2835,8 +2866,8 @@ const handleStartScan = useCallback(() => {
}, [fetchAllCombinedLotData, session, currentUserId, fgPickOrders, actionBusyBySolId]);
const handleBatchScan = useCallback(async () => {
const startTime = performance.now();
console.log(`⏱️ [BATCH SCAN START]`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
console.log(` [BATCH SCAN START]`);
console.log(` Start time: ${new Date().toISOString()}`);
// 获取所有活跃批次(未扫描的)
const activeLots = combinedLotData.filter(lot => {
@@ -2884,19 +2915,19 @@ const handleStartScan = useCallback(() => {
const result = await batchScan(request);
const scanTime = performance.now() - scanStartTime;
console.log(`⏱️ Batch scan API call completed in ${scanTime.toFixed(2)}ms (${(scanTime / 1000).toFixed(3)}s)`);
console.log(` Batch scan API call completed in ${scanTime.toFixed(2)}ms (${(scanTime / 1000).toFixed(3)}s)`);
console.log(`📥 Batch scan result:`, result);
// ✅ 刷新数据以获取最新的状态
const refreshStartTime = performance.now();
await fetchAllCombinedLotData();
const refreshTime = performance.now() - refreshStartTime;
console.log(`⏱️ Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
console.log(` Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
const totalTime = performance.now() - startTime;
console.log(`⏱️ [BATCH SCAN END]`);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(`⏰ End time: ${new Date().toISOString()}`);
console.log(` [BATCH SCAN END]`);
console.log(` Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(` End time: ${new Date().toISOString()}`);
if (result && result.code === "SUCCESS") {
setQrScanSuccess(true);
@@ -2915,8 +2946,8 @@ const handleStartScan = useCallback(() => {
}, [combinedLotData, fetchAllCombinedLotData, currentUserId]);
const handleSubmitAllScanned = useCallback(async () => {
const startTime = performance.now();
console.log(`⏱️ [BATCH SUBMIT START]`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
console.log(` [BATCH SUBMIT START]`);
console.log(` Start time: ${new Date().toISOString()}`);
const scannedLots = combinedLotData.filter(lot => {
const status = lot.stockOutLineStatus;
@@ -3011,19 +3042,19 @@ const handleSubmitAllScanned = useCallback(async () => {
const result = await batchSubmitList(request);
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 API call completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`);
console.log(`📥 Batch submit result:`, result);
// Refresh data once after batch submission
const refreshStartTime = performance.now();
await fetchAllCombinedLotData();
const refreshTime = performance.now() - refreshStartTime;
console.log(`⏱️ Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
console.log(` Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
const totalTime = performance.now() - startTime;
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()}`);
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 (result && result.code === "SUCCESS") {
setQrScanSuccess(true);
@@ -3380,16 +3411,31 @@ paginatedData.map((lot, index) => {
<TableCell>{lot.itemCode}</TableCell>
<TableCell>{lot.itemName + '(' + lot.stockUnit + ')'}</TableCell>
<TableCell>
<Box>
<Typography
sx={{
// color: isIssueLot ? 'warning.main' : lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit',
}}
>
{lot.lotNo ||
t('Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.')}
</Typography>
</Box>
<Box>
<Typography
sx={{
color:
lot.lotAvailability === 'expired'
? 'warning.main'
: /* isIssueLot ? 'warning.main' : lot.lotAvailability === 'rejected' ? 'text.disabled' : */ 'inherit',
}}
>
{lot.lotNo ? (
lot.lotAvailability === 'expired' ? (
<>
{lot.lotNo}{' '}
{t('is expired. Please check around have available QR code or not.')}
</>
) : (
lot.lotNo
)
) : (
t(
'Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.'
)
)}
</Typography>
</Box>
</TableCell>
<TableCell align="right">
{(() => {
@@ -3568,6 +3614,7 @@ paginatedData.map((lot, index) => {
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'checked' ||
lot.stockOutLineStatus === 'partially_completed' ||
lot.lotAvailability === 'expired' ||
// 使用 issue form 後,禁用「Just Completed」(避免再次点击造成重复提交)
(Number(lot.stockOutLineId) > 0 && issuePickedQtyBySolId[Number(lot.stockOutLineId)] !== undefined) ||
@@ -3622,7 +3669,7 @@ paginatedData.map((lot, index) => {
<LotConfirmationModal
open={lotConfirmationOpen}
onClose={() => {
console.log(`⏱️ [LOT CONFIRM MODAL] Closing modal, clearing state`);
console.log(` [LOT CONFIRM MODAL] Closing modal, clearing state`);
clearLotConfirmationState(true);
}}
onConfirm={handleLotConfirmation}