diff --git a/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx b/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx index 935ef13d..efb50a4b 100644 --- a/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx +++ b/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx @@ -43,7 +43,6 @@ import { PickOrderCompletionResponse, updateSuggestedLotLineId, updateStockOutLineStatusByQRCodeAndLotNo, - confirmLotSubstitution, fetchDoPickOrderDetail, // 必须添加 DoPickOrderDetail, // 必须添加 batchScan, @@ -68,7 +67,6 @@ import QrCodeIcon from '@mui/icons-material/QrCode'; import { useQrCodeScannerContext } from "@/components/QrCodeScannerProvider/QrCodeScannerProvider"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; -import { fetchStockInLineInfo } from "@/app/api/po/actions"; import GoodPickExecutionForm from "@/components/FinishedGoodSearch/GoodPickExecutionForm"; import WorkbenchLotLabelPrintModal from "@/components/DoWorkbench/WorkbenchLotLabelPrintModal"; import FGPickOrderCard from "@/components/FinishedGoodSearch/FGPickOrderCard"; @@ -211,7 +209,7 @@ function hasPendingSwitchableRowForItem( ); } -/** Allow scan when scanned SIL still needed, or item has any pending/noLot row for auto-switch. */ +/** Allow scan when the scanned lot is still needed, or the item still has a pending line to reject against. */ function canProcessWorkbenchScanPick( indexes: { byStockInLineId: Map; @@ -237,42 +235,43 @@ function findExactActiveMatchForStockInLine( scannedItemId: number, activeSuggestedLots: any[], processedByItemId: ProcessedStockOutLinesByItemId, + scannedStockInLineId?: number, ): any | null { const activeSet = new Set(activeSuggestedLots); + const activeIdSet = new Set( + activeSuggestedLots + .map((lot) => Number(lot?.stockInLineId)) + .filter((id) => Number.isFinite(id) && id > 0), + ); + const isSuggested = (lot: any) => + activeSet.has(lot) || activeIdSet.has(Number(lot?.stockInLineId)); const candidates = stockInLineLots.filter( (lot) => - lot.itemId === scannedItemId && - activeSet.has(lot) && + Number(lot.itemId) === Number(scannedItemId) && + isSuggested(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() !== "", + if (candidates.length > 0) { + return candidates.find((lot) => isLotRowPending(lot)) ?? candidates[0]; + } + const scannedId = Number(scannedStockInLineId); + if (!(scannedId > 0)) return null; + return ( + activeSuggestedLots.find( + (lot) => + Number(lot?.itemId) === Number(scannedItemId) && + Number(lot?.stockInLineId) === scannedId && + !isStockOutLineAlreadyProcessed( + processedByItemId, + scannedItemId, + lot.stockOutLineId, + ), + ) ?? null ); - 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<{ @@ -777,10 +776,6 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const [isRefreshingData, setIsRefreshingData] = useState(false); const [isSubmittingAll, setIsSubmittingAll] = useState(false); - // Cache for fetchStockInLineInfo API calls to avoid redundant requests - const stockInLineInfoCache = useRef>(new Map()); - const CACHE_TTL = 60000; // 60 seconds cache TTL - const abortControllerRef = useRef(null); const qrProcessingTimeoutRef = useRef(null); // Use refs for processed QR tracking to avoid useEffect dependency issues and delays @@ -834,56 +829,6 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); }; }, [combinedLotData]); - - // Cached version of fetchStockInLineInfo to avoid redundant API calls - const fetchStockInLineInfoCached = useCallback(async (stockInLineId: number): Promise<{ lotNo: string | null }> => { - const now = Date.now(); - const cached = stockInLineInfoCache.current.get(stockInLineId); - - // Return cached value if still valid - if (cached && (now - cached.timestamp) < CACHE_TTL) { - console.log(`✅ [CACHE HIT] Using cached stockInLineInfo for ${stockInLineId}`); - return { lotNo: cached.lotNo }; - } - - // Cancel previous request if exists - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - - // Create new abort controller for this request - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - try { - console.log(` [CACHE MISS] Fetching stockInLineInfo for ${stockInLineId}`); - const stockInLineInfo = await fetchStockInLineInfo(stockInLineId); - - // Store in cache - stockInLineInfoCache.current.set(stockInLineId, { - lotNo: stockInLineInfo.lotNo || null, - timestamp: now - }); - - // Limit cache size to prevent memory leaks - if (stockInLineInfoCache.current.size > 100) { - const firstKey = stockInLineInfoCache.current.keys().next().value; - if (firstKey !== undefined) { - stockInLineInfoCache.current.delete(firstKey); - } - } - - return { lotNo: stockInLineInfo.lotNo || null }; - } catch (error: any) { - if (error.name === 'AbortError') { - console.log(` [CACHE] Request aborted for ${stockInLineId}`); - throw error; - } - console.error(`❌ [CACHE] Error fetching stockInLineInfo for ${stockInLineId}:`, error); - throw error; - } - }, []); - const checkAllLotsCompleted = useCallback((lotData: any[]) => { if (lotData.length === 0) { setAllLotsCompleted(false); @@ -1805,6 +1750,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO scannedItemId, activeSuggestedLots, processedQrCombinations, + scannedStockInLineId, ) ?? allLotsForItem.find( (lot: any) => lot.stockInLineId === scannedStockInLineId, @@ -1869,7 +1815,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO } } - // ✅ If no active suggested lots, auto-switch to scanned lot (no modal) + // No active suggested lot: reject the scan. Do not bind another lot. if (activeSuggestedLots.length === 0) { // Check if there are any lots for this item (even if all are rejected) if (allLotsForItem.length === 0) { @@ -1882,184 +1828,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO return; } - console.log(`⚠️ [QR PROCESS] No active suggested lots, auto-switching to scanned lot.`); - - // Prefer pending noLot / rejected / unavailable rows (not completed ones). - const switchableLot = allLotsForItem.find( - (lot: any) => - isLotRowPending(lot) && - !isStockOutLineAlreadyProcessed( - processedQrCombinations, - scannedItemId, - lot.stockOutLineId, - ) && - (lot.noLot === true || - lot.stockOutLineStatus?.toLowerCase() === "rejected" || - lot.lotAvailability === "rejected" || - isInventoryLotLineUnavailable(lot) || - isLotAvailabilityExpired(lot)), + const notSuggestedMsg = t( + "Scanned lot is not the suggested lot. Please scan the suggested lot.", + ); + console.warn( + `⚠️ [QR PROCESS] No active suggested lot for scanned stockInLineId=${scannedStockInLineId}`, ); - const expectedLot = - switchableLot || - pickExpectedLotForSubstitution( - allLotsForItem.filter( - (l: any) => l.lotNo != null && String(l.lotNo).trim() !== "" - ), - processedQrCombinations, - ) || - allLotsForItem.find( - (lot: any) => - isLotRowPending(lot) && - !isStockOutLineAlreadyProcessed( - processedQrCombinations, - scannedItemId, - lot.stockOutLineId, - ), - ) || - allLotsForItem[0]; - - let scannedLotNo: string | null = scannedLot?.lotNo || null; - if (!scannedLotNo) { - try { - const info = await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e); - } - } - if (!scannedLotNo) { - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg( - t("Cannot resolve lot number from QR. Please rescan or use manual confirmation.") - ); - }); - return; - } - - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE"; - if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) { - const errMsg = - substitutionResult?.message || - t("Lot switch failed; pick line was not updated."); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - }); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg); - } - return; - } - } - - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: fgPickOrders?.[0]?.storeId ?? null, - userId: currentUserId ?? 1, - }); - const ok = res.code === "SUCCESS"; - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - t("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { - markAsScanIssue: true, - }); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg); - } - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - }); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - const entity = res.entity as any; - const nextStatus = String(entity?.status ?? "completed").toLowerCase(); - const nextQty = entity?.qty != null ? Number(entity.qty) : undefined; - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - setCombinedLotData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); - setOriginalCombinedData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); + setQrScanError(true); + setQrScanSuccess(false); + setQrScanErrorMsg(notSuggestedMsg); }); - - const nextProcessedNoActive = markProcessedStockOutLine( - processedQrCombinations, - scannedItemId, - expectedLot.stockOutLineId, - ); - setProcessedQrCombinations(nextProcessedNoActive); - maybeReleaseQrForNextSol( - scannedItemId, - scannedStockInLineId, - nextProcessedNoActive, - ); - - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - return; } @@ -2071,170 +1850,24 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO scannedItemId, activeSuggestedLots, processedQrCombinations, + scannedStockInLineId, ); const matchTime = performance.now() - matchStartTime; 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 - // Also handle case where scanned lot is not in allLotsForItem (scannedLot is undefined) if (!exactMatch) { - const expectedLot = - pickExpectedLotForSubstitution( - activeSuggestedLots, - processedQrCombinations, - ) || allLotsForItem[0]; - if (expectedLot) { - const shouldAutoSwitch = - !scannedLot || (scannedLot.stockInLineId !== expectedLot.stockInLineId); - if (shouldAutoSwitch) { - console.log( - `⚠️ [QR PROCESS] Auto-switching (scanned lot ${scannedLot?.lotNo || 'not in data'} is not in active suggested lots)`, - ); - - let scannedLotNo: string | null = scannedLot?.lotNo || null; - if (!scannedLotNo) { - try { - const info = await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e); - } - } - if (!scannedLotNo) { - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg( - t("Cannot resolve lot number from QR. Please rescan or use manual confirmation.") - ); - }); - return; - } - - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE"; - if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) { - const errMsg = - substitutionResult?.message || - t("Lot switch failed; pick line was not updated."); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - }); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg); - } - return; - } - } - - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: fgPickOrders?.[0]?.storeId ?? null, - userId: currentUserId ?? 1, - }); - const ok = res.code === "SUCCESS"; - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - t("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { - markAsScanIssue: true, - }); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg); - } - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - }); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - const entity = res.entity as any; - const nextStatus = String(entity?.status ?? "completed").toLowerCase(); - const nextQty = entity?.qty != null ? Number(entity.qty) : undefined; - - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - setCombinedLotData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); - setOriginalCombinedData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); - }); - - const nextProcessedAuto = markProcessedStockOutLine( - processedQrCombinations, - scannedItemId, - expectedLot.stockOutLineId, - ); - setProcessedQrCombinations(nextProcessedAuto); - maybeReleaseQrForNextSol( - scannedItemId, - scannedStockInLineId, - nextProcessedAuto, - ); - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - return; - } - } + const notSuggestedMsg = t( + "Scanned lot is not the suggested lot. Please scan the suggested lot.", + ); + console.warn( + `⚠️ [QR PROCESS] Reject scanned lot ${scannedLot?.lotNo || scannedStockInLineId}: not a suggested lot`, + ); + startTransition(() => { + setQrScanError(true); + setQrScanSuccess(false); + setQrScanErrorMsg(notSuggestedMsg); + }); + return; } if (exactMatch) { @@ -2384,213 +2017,6 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO return; // ✅ 直接返回,不需要确认表单 } - // ✅ Case 2: itemId 匹配但 stockInLineId 不匹配 - // Workbench 策略:不彈窗,直接切換到掃到的批次並提交一次掃描 - const mismatchCheckStartTime = performance.now(); - if ( - !canProcessWorkbenchScanPick( - indexes, - scannedItemId, - scannedStockInLineId, - processedQrCombinations, - ) - ) { - const mismatchCheckTime = performance.now() - mismatchCheckStartTime; - const itemRows = indexes.byItemId.get(scannedItemId) ?? []; - const errMsg = - itemRows.length === 0 - ? t("QR code does not match any item in current orders.") - : t( - "No pending pick line left for this item. It may already be completed or fully processed.", - ); - console.log( - ` [SKIP] No pending stock-out line left for mismatch path (check time: ${mismatchCheckTime.toFixed(2)}ms)`, - ); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - }); - return; - } - const mismatchCheckTime = performance.now() - mismatchCheckStartTime; - console.log(` [PERF] Mismatch check time: ${mismatchCheckTime.toFixed(2)}ms`); - - const expectedLotStartTime = performance.now(); - const expectedLot = pickExpectedLotForSubstitution( - activeSuggestedLots, - processedQrCombinations, - ); - if (!expectedLot) { - console.error("Could not determine expected lot for auto-switch"); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - }); - return; - } - const expectedLotTime = performance.now() - expectedLotStartTime; - console.log(` [PERF] Get expected lot time: ${expectedLotTime.toFixed(2)}ms`); - - console.log( - `⚠️ Lot mismatch (auto): Expected stockInLineId=${expectedLot.stockInLineId}, Scanned stockInLineId=${scannedStockInLineId}`, - ); - - // 1) 先把掃到的 stockInLineId 轉成 lotNo(workbenchScanPick 需要 lotNo) - let scannedLotNo: string | null = null; - try { - const info = await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e); - } - if (!scannedLotNo) { - const msg = t("Cannot resolve lot number from QR. Please rescan or use manual confirmation."); - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(msg); - return; - } - - // 2) 非 workbench:先 confirmLotSubstitution;workbench 僅依 scan-pick 規則與錯誤訊息 - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE"; - if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) { - const errMsg = - substitutionResult?.message || - t("Lot switch failed; pick line was not updated."); - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg); - } - return; - } - } - - // 3) 提交掃描(workbench:直接 workbenchScanPick) - try { - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: fgPickOrders?.[0]?.storeId ?? null, - userId: currentUserId ?? 1, - }); - - const ok = res.code === "SUCCESS"; - - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - t("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { - markAsScanIssue: true, - }); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg); - } - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - const entity = res.entity as any; - const nextStatus = String(entity?.status ?? "completed").toLowerCase(); - const nextQty = entity?.qty != null ? Number(entity.qty) : undefined; - - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - - setCombinedLotData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); - - setOriginalCombinedData((prev) => - prev.map((lot) => { - if ( - lot.stockOutLineId === expectedLot.stockOutLineId && - lot.pickOrderLineId === expectedLot.pickOrderLineId - ) { - return { - ...lot, - lotNo: scannedLotNo, - stockOutLineStatus: nextStatus, - stockOutLineQty: nextQty ?? lot.stockOutLineQty, - }; - } - return lot; - }), - ); - }); - - const nextProcessedMismatch = markProcessedStockOutLine( - processedQrCombinations, - scannedItemId, - expectedLot.stockOutLineId, - ); - setProcessedQrCombinations(nextProcessedMismatch); - maybeReleaseQrForNextSol( - scannedItemId, - scannedStockInLineId, - nextProcessedMismatch, - ); - - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - } catch (e) { - console.error("Auto-switch scanPick failed:", e); - setQrScanError(true); - setQrScanSuccess(false); - return; - } - - const totalTime = performance.now() - totalStartTime; - console.log( - `✅ [PROCESS OUTSIDE QR AUTO-SWITCH] 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, 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; console.error(`❌ [PROCESS OUTSIDE QR ERROR] Total time: ${totalTime.toFixed(2)}ms`); @@ -2608,7 +2034,6 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO lotDataIndexes, processedQrCombinations, combinedLotData, - fetchStockInLineInfoCached, workbenchMode, currentUserId, clearWorkbenchScanReject, @@ -4346,23 +3771,6 @@ paginatedData.map((row, index) => { })()} - {/* Lot QR Code Button */} - {Number(lot.stockOutLineId) > 0 && Number(lot.itemId) > 0 && ( - - )} diff --git a/src/components/JoWorkbench/newJobPickExecution.tsx b/src/components/JoWorkbench/newJobPickExecution.tsx index 3754bafb..e263d064 100644 --- a/src/components/JoWorkbench/newJobPickExecution.tsx +++ b/src/components/JoWorkbench/newJobPickExecution.tsx @@ -105,24 +105,6 @@ function isInventoryLotLineUnavailable(lot: any): boolean { return String(lot.lotStatus || "").toLowerCase() === "unavailable"; } -/** 同 DO Workbench:多行時優先替換有建議批號的行 */ -function pickExpectedLotForSubstitution( - activeSuggestedLots: any[], -): any | null { - if (!activeSuggestedLots?.length) return null; - const withLotNo = activeSuggestedLots.filter( - (l) => l.lotNo != null && String(l.lotNo).trim() !== "", - ); - if (withLotNo.length === 1) return withLotNo[0]; - if (withLotNo.length > 1) { - const pending = withLotNo.find( - (l) => String(l.stockOutLineStatus || "").toLowerCase() === "pending", - ); - return pending || withLotNo[0]; - } - return activeSuggestedLots[0]; -} - type PickOrderT = (key: string, options?: Record) => string; /** 與 DO Workbench:優先顯示 scan-pick 暫存拒絕訊息 */ @@ -867,12 +849,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo Map> >(new Map()); - // Cache for fetchStockInLineInfo API calls to avoid redundant requests - const stockInLineInfoCache = useRef< - Map - >(new Map()); - const CACHE_TTL = 60000; // 60 seconds cache TTL - const abortControllerRef = useRef(null); const qrProcessingTimeoutRef = useRef(null); // Use refs for processed QR tracking to avoid useEffect dependency issues and delays @@ -1366,33 +1342,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo }; }, [combinedLotData]); - // Cached version of fetchStockInLineInfo to avoid redundant API calls - const fetchStockInLineInfoCached = useCallback( - async (stockInLineId: number): Promise<{ lotNo: string | null }> => { - const now = Date.now(); - const cached = stockInLineInfoCache.current.get(stockInLineId); - if (cached && now - cached.timestamp < CACHE_TTL) { - return { lotNo: cached.lotNo }; - } - - if (abortControllerRef.current) abortControllerRef.current.abort(); - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - const stockInLineInfo = await fetchStockInLineInfo(stockInLineId); - stockInLineInfoCache.current.set(stockInLineId, { - lotNo: stockInLineInfo.lotNo || null, - timestamp: now, - }); - if (stockInLineInfoCache.current.size > 100) { - const firstKey = stockInLineInfoCache.current.keys().next().value; - if (firstKey !== undefined) - stockInLineInfoCache.current.delete(firstKey); - } - return { lotNo: stockInLineInfo.lotNo || null }; - }, - [], - ); // 修改:加载未分配的 Job Order 订单 const loadUnassignedOrders = useCallback(async () => { setIsLoadingUnassigned(true); @@ -2165,7 +2114,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo } } - // ✅ If no active suggested lots, auto-switch to scanned lot (no modal) + // No active suggested lot: reject the scan. Do not bind another lot. if (activeSuggestedLots.length === 0) { // Check if there are any lots for this item (even if all are rejected) if (allLotsForItem.length === 0) { @@ -2178,144 +2127,18 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo return; } - console.log( - `⚠️ [QR PROCESS] No active suggested lots, auto-switching to scanned lot.`, + const notSuggestedMsg = L( + "此批次不是建議批次,已拒絕。請掃描建議批次。", + "Scanned lot is not the suggested lot. Please scan the suggested lot.", ); - - // Find a rejected lot as expected lot (the one that was rejected) - const rejectedLot = allLotsForItem.find( - (lot: any) => - lot.stockOutLineStatus?.toLowerCase() === "rejected" || - lot.lotAvailability === "rejected" || - isInventoryLotLineUnavailable(lot), + console.warn( + `⚠️ [QR PROCESS] No active suggested lot for scanned stockInLineId=${scannedStockInLineId}`, ); - const expectedLot = - rejectedLot || - pickExpectedLotForSubstitution( - allLotsForItem.filter( - (l: any) => l.lotNo != null && String(l.lotNo).trim() !== "", - ), - ) || - allLotsForItem[0]; - - let scannedLotNo: string | null = scannedLot?.lotNo || null; - if (!scannedLotNo) { - try { - const info = - await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn( - "Failed to fetch lotNo for stockInLineId:", - scannedStockInLineId, - e, - ); - } - } - if (!scannedLotNo) { - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg( - tPick( - "Cannot resolve lot number from QR. Please rescan or use manual confirmation.", - ), - ); - }); - return; - } - - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || - substitutionCode === "BOUND_UNAVAILABLE"; - if ( - !substitutionResult || - (substitutionCode !== "SUCCESS" && !switchedToUnavailable) - ) { - const errMsg = - substitutionResult?.message || - tPick("Lot switch failed; pick line was not updated."); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - }); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - errMsg, - ); - } - return; - } - } - - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: workbenchStoreId, - userId: currentUserId ?? 1, - }); - const ok = res.code === "SUCCESS"; - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - tPick("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - failMsg, - ); - } - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - }); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - }); - - setProcessedQrCombinations((prev) => { - const newMap = new Map(prev); - if (!newMap.has(scannedItemId)) - newMap.set(scannedItemId, new Set()); - newMap.get(scannedItemId)!.add(scannedStockInLineId); - return newMap; + setQrScanError(true); + setQrScanSuccess(false); + setQrScanErrorMsg(notSuggestedMsg); }); - - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - return; } @@ -2342,143 +2165,32 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo }`, ); - // ✅ 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 - // Also handle case where scanned lot is not in allLotsForItem (scannedLot is undefined) if (!exactMatch) { - const expectedLot = - pickExpectedLotForSubstitution(activeSuggestedLots) || - allLotsForItem[0]; - if (expectedLot) { - const shouldAutoSwitch = - !scannedLot || - scannedLot.stockInLineId !== expectedLot.stockInLineId; - if (shouldAutoSwitch) { - console.log( - `⚠️ [QR PROCESS] Auto-switching (scanned lot ${ - scannedLot?.lotNo || "not in data" - } is not in active suggested lots)`, - ); - - let scannedLotNo: string | null = scannedLot?.lotNo || null; - if (!scannedLotNo) { - try { - const info = - await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn( - "Failed to fetch lotNo for stockInLineId:", - scannedStockInLineId, - e, - ); - } - } - if (!scannedLotNo) { - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg( - tPick( - "Cannot resolve lot number from QR. Please rescan or use manual confirmation.", - ), - ); - }); - return; - } - - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || - substitutionCode === "BOUND_UNAVAILABLE"; - if ( - !substitutionResult || - (substitutionCode !== "SUCCESS" && !switchedToUnavailable) - ) { - const errMsg = - substitutionResult?.message || - tPick("Lot switch failed; pick line was not updated."); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - }); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - errMsg, - ); - } - return; - } - } - - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: workbenchStoreId, - userId: currentUserId ?? 1, - }); - const ok = res.code === "SUCCESS"; - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - tPick("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - failMsg, - ); - } - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - }); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - }); + exactMatch = + activeSuggestedLots.find( + (lot: any) => + Number(lot?.itemId) === Number(scannedItemId) && + Number(lot?.stockInLineId) === Number(scannedStockInLineId) && + Number(scannedStockInLineId) > 0, + ) ?? null; + } - setProcessedQrCombinations((prev) => { - const newMap = new Map(prev); - if (!newMap.has(scannedItemId)) - newMap.set(scannedItemId, new Set()); - newMap.get(scannedItemId)!.add(scannedStockInLineId); - return newMap; - }); - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - return; - } - } + if (!exactMatch) { + const notSuggestedMsg = L( + "此批次不是建議批次,已拒絕。請掃描建議批次。", + "Scanned lot is not the suggested lot. Please scan the suggested lot.", + ); + console.warn( + `⚠️ [QR PROCESS] Reject scanned lot ${ + scannedLot?.lotNo || scannedStockInLineId + }: not a suggested lot`, + ); + startTransition(() => { + setQrScanError(true); + setQrScanSuccess(false); + setQrScanErrorMsg(notSuggestedMsg); + }); + return; } if (exactMatch) { @@ -2642,183 +2354,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo return; // ✅ 直接返回,不需要确认表单 } - // ✅ Case 2: itemId 匹配但 stockInLineId 不匹配 - // Workbench 策略:不彈窗,直接切換到掃到的批次並提交一次掃描 - const mismatchCheckStartTime = performance.now(); - 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)`, - ); - return; - } - const mismatchCheckTime = performance.now() - mismatchCheckStartTime; - console.log( - ` [PERF] Mismatch check time: ${mismatchCheckTime.toFixed(2)}ms`, - ); - - const expectedLotStartTime = performance.now(); - const expectedLot = pickExpectedLotForSubstitution(activeSuggestedLots); - if (!expectedLot) { - console.error("Could not determine expected lot for auto-switch"); - startTransition(() => { - setQrScanError(true); - setQrScanSuccess(false); - }); - return; - } - const expectedLotTime = performance.now() - expectedLotStartTime; - console.log( - ` [PERF] Get expected lot time: ${expectedLotTime.toFixed(2)}ms`, - ); - - console.log( - `⚠️ Lot mismatch (auto): Expected stockInLineId=${expectedLot.stockInLineId}, Scanned stockInLineId=${scannedStockInLineId}`, - ); - - // 1) 先把掃到的 stockInLineId 轉成 lotNo(workbenchScanPick 需要 lotNo) - let scannedLotNo: string | null = null; - try { - const info = await fetchStockInLineInfoCached(scannedStockInLineId); - scannedLotNo = info?.lotNo || null; - } catch (e) { - console.warn( - "Failed to fetch lotNo for stockInLineId:", - scannedStockInLineId, - e, - ); - } - if (!scannedLotNo) { - const msg = tPick( - "Cannot resolve lot number from QR. Please rescan or use manual confirmation.", - ); - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(msg); - return; - } - - // 2) 非 workbench:先 confirmLotSubstitution;workbench 僅依 scan-pick 規則與錯誤訊息 - if (!workbenchMode) { - const substitutionResult = await confirmLotSubstitution({ - pickOrderLineId: expectedLot.pickOrderLineId, - stockOutLineId: expectedLot.stockOutLineId, - originalSuggestedPickLotId: expectedLot.suggestedPickLotId, - newInventoryLotNo: "", - newStockInLineId: scannedStockInLineId, - }); - const substitutionCode = (substitutionResult as any)?.code; - const switchedToUnavailable = - substitutionCode === "SUCCESS_UNAVAILABLE" || - substitutionCode === "BOUND_UNAVAILABLE"; - if ( - !substitutionResult || - (substitutionCode !== "SUCCESS" && !switchedToUnavailable) - ) { - const errMsg = - substitutionResult?.message || - tPick("Lot switch failed; pick line was not updated."); - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(errMsg); - if (expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - errMsg, - ); - } - return; - } - } - - // 3) 提交掃描(workbench:直接 workbenchScanPick) - try { - const res = await workbenchScanPick({ - stockOutLineId: expectedLot.stockOutLineId, - lotNo: scannedLotNo, - ...(typeof scannedStockInLineId === "number" && - Number.isFinite(scannedStockInLineId) && - scannedStockInLineId > 0 - ? { stockInLineId: scannedStockInLineId } - : {}), - ...workbenchScanPickQtyFromLot(expectedLot), - storeId: workbenchStoreId, - userId: currentUserId ?? 1, - }); - - const ok = res.code === "SUCCESS"; - - if (!ok) { - const failMsg = - (res as { message?: string })?.message || - tPick("Workbench scan-pick failed."); - if ( - shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && - expectedLot - ) { - openWorkbenchLotLabelModalForLot(expectedLot, failMsg); - return; - } - if (workbenchMode && expectedLot.stockOutLineId != null) { - rememberWorkbenchScanReject( - Number(expectedLot.stockOutLineId), - failMsg, - ); - } - setQrScanError(true); - setQrScanSuccess(false); - setQrScanErrorMsg(failMsg); - return; - } - - clearWorkbenchScanReject(Number(expectedLot.stockOutLineId)); - - startTransition(() => { - setQrScanError(false); - setQrScanSuccess(true); - }); - - setProcessedQrCombinations((prev) => { - const newMap = new Map(prev); - if (!newMap.has(scannedItemId)) - newMap.set(scannedItemId, new Set()); - newMap.get(scannedItemId)!.add(scannedStockInLineId); - return newMap; - }); - - if (workbenchMode) { - await refreshWorkbenchAfterScanPick(); - } - } catch (e) { - console.error("Auto-switch scanPick failed:", e); - setQrScanError(true); - setQrScanSuccess(false); - return; - } - - const totalTime = performance.now() - totalStartTime; - console.log( - `✅ [PROCESS OUTSIDE QR AUTO-SWITCH] 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, mismatchCheck=${mismatchCheckTime.toFixed( - 2, - )}ms, expectedLot=${expectedLotTime.toFixed(2)}ms`, - ); } catch (error) { const totalTime = performance.now() - totalStartTime; console.error( @@ -2836,7 +2371,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo lotDataIndexes, processedQrCombinations, combinedLotData, - fetchStockInLineInfoCached, workbenchMode, currentUserId, clearWorkbenchScanReject, @@ -2845,7 +2379,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo workbenchScanPickQtyFromLot, tPick, workbenchStoreId, - confirmLotSubstitution, openWorkbenchLotLabelModalForLot, shouldOpenWorkbenchLotLabelModalForFailure, ], @@ -4213,33 +3746,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo )} - {Number(lot.stockOutLineId) > 0 && - Number(lot.itemId) > 0 ? ( - - ) : null} diff --git a/src/components/PickOrderSearch/WorkbenchPickExecution.tsx b/src/components/PickOrderSearch/WorkbenchPickExecution.tsx index da9a2b0a..947fab88 100644 --- a/src/components/PickOrderSearch/WorkbenchPickExecution.tsx +++ b/src/components/PickOrderSearch/WorkbenchPickExecution.tsx @@ -1405,16 +1405,27 @@ const WorkbenchPickExecution: React.FC = ({ filterArgs }) => { } } - if ( - Number(expectedRow.stockInLineId) > 0 && - Number(scannedState.stockInLineId) > 0 && - Number(expectedRow.stockInLineId) === Number(scannedState.stockInLineId) - ) { - await submitRow(expectedRow); + const scannedSil = Number(scannedState.stockInLineId); + const suggestedRow = + scannedSil > 0 + ? activeForUom.find((r) => Number(r.stockInLineId) === scannedSil) + : undefined; + if (suggestedRow) { + await submitRow(suggestedRow); return; } - await handleLotConfirmation(scannedState, toConfirmLotState(expectedRow)); + const notSuggestedMsg = t( + "Scanned lot is not the suggested lot. Please scan the suggested lot.", + ); + setError(notSuggestedMsg); + startTransition(() => { + setQrScanError(true); + setQrScanSuccess(false); + setQrScanErrorMsg(notSuggestedMsg); + }); + releaseProcessedQr(latest); + return; }, [ handleLotConfirmation, @@ -1707,16 +1718,6 @@ const WorkbenchPickExecution: React.FC = ({ filterArgs }) => { )} - {r.stockOutLineId > 0 ? ( - - ) : null} {`${r.requiredQty.toLocaleString()}(${r.uomDesc || ""})`} diff --git a/src/i18n/en/pickOrder.json b/src/i18n/en/pickOrder.json index 195125e4..ab43d149 100644 --- a/src/i18n/en/pickOrder.json +++ b/src/i18n/en/pickOrder.json @@ -16,6 +16,8 @@ "Loading...": "Loading...", "Suggestion success": "Suggestion success", "Scan pick success": "Scan pick success", + "Scanned lot is not the suggested lot. Please scan the suggested lot.": "Scanned lot is not the suggested lot. Please scan the suggested lot.", + "remaining quantity allocated to next stock-out line": "remaining quantity allocated to next stock-out line", "Remark": "Remark", "Available Qty": "Available Qty", "Picked Qty": "Picked Qty", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index bdde2cf7..2ab4e8ec 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -17,6 +17,8 @@ "Loading...": "載入中...", "Suggestion success": "建議成功", "Scan pick success": "掃描提料成功", + "Scanned lot is not the suggested lot. Please scan the suggested lot.": "此批次不是建議批次,已拒絕。請掃描建議批次。", + "remaining quantity allocated to next stock-out line": "剩餘數量分配到下一個出庫行", "Remark": "備註", "Available Qty": "可用數量", "Picked Qty": "已提料數量",