From 045f9a6bd5479c8dbb3e63cff438ec600d39180a Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 27 Mar 2026 23:09:08 +0800 Subject: [PATCH] update --- .../GoodPickExecutiondetail.tsx | 267 ++++++++++-------- src/components/PoDetail/PoDetail.tsx | 8 +- src/i18n/zh/pickOrder.json | 3 +- 3 files changed, 161 insertions(+), 117 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 0639b44..2e2a3bf 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -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(); const byItemCode = new Map(); @@ -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) => { {lot.itemCode} {lot.itemName + '(' + lot.stockUnit + ')'} - - - {lot.lotNo || - t('Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.')} - - + + + {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.' + ) + )} + + {(() => { @@ -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) => { { - console.log(`⏱️ [LOT CONFIRM MODAL] Closing modal, clearing state`); + console.log(` [LOT CONFIRM MODAL] Closing modal, clearing state`); clearLotConfirmationState(true); }} onConfirm={handleLotConfirmation} diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 7ef386e..dd8910f 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -456,11 +456,6 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { alert("來貨數量必須大於0!"); return; } - if (!Number.isInteger(acceptedQty)) { - alert("來貨數量必須是整數(不能有小數)!"); - return; - } - const doSubmit = () => { setTimeout(async () => { const currentDnNo = dnFormProps.watch("dnNo"); @@ -622,7 +617,8 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { InputProps={{ inputProps: { min: 0, // Optional: set a minimum value - step: 1 // Optional: set the step for the number input + step: "any", + inputMode: "decimal", } }} /> diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 8dac361..9903481 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -50,7 +50,8 @@ "Delivery Order": "送貨單", "items": "項目", "Select Pick Order:": "選擇提料單:", - "⚠️ No Stock Available": "⚠️ 沒有庫存", + "No Stock Available": "沒有庫存", + "is expired. Please check around have available QR code or not.": "已過期。請檢查周圍是否有可用的 QR 碼。", "Start Fail": "開始失敗", "Start PO": "開始採購訂單", "Do you want to complete?": "確定完成嗎?",