| @@ -381,6 +381,7 @@ export interface CompletedDoPickOrderSearchParams { | |||
| deliveryNoteCode?: string; | |||
| /** 卡車/車道(後端 truckLanceCode 模糊匹配) */ | |||
| truckLanceCode?: string; | |||
| ticketNo?: string; | |||
| } | |||
| export interface PickExecutionIssue { | |||
| id: number; | |||
| @@ -713,6 +714,9 @@ export const fetchCompletedDoPickOrdersWorkbench = async ( | |||
| if (searchParams?.truckLanceCode) { | |||
| params.append("truckLanceCode", searchParams.truckLanceCode); | |||
| } | |||
| if (searchParams?.ticketNo) { | |||
| params.append("ticketNo", searchParams.ticketNo); | |||
| } | |||
| const queryString = params.toString(); | |||
| const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-workbench/${userId}${ | |||
| @@ -742,7 +746,9 @@ export const fetchCompletedDoPickOrdersWorkbenchAll = async ( | |||
| if (searchParams?.truckLanceCode) { | |||
| params.append("truckLanceCode", searchParams.truckLanceCode); | |||
| } | |||
| if (searchParams?.ticketNo) { | |||
| params.append("ticketNo", searchParams.ticketNo); | |||
| } | |||
| const queryString = params.toString(); | |||
| const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-workbench-all${ | |||
| queryString ? `?${queryString}` : "" | |||
| @@ -1,7 +1,8 @@ | |||
| "use client"; | |||
| import { Autocomplete, Box, Tab, Tabs, TextField, Typography } from "@mui/material"; | |||
| import React from "react"; | |||
| import { Autocomplete, Box, CircularProgress, Tab, Tabs, TextField, Typography } from "@mui/material"; | |||
| import React, { Suspense } from "react"; | |||
| import { usePathname, useRouter, useSearchParams } from "next/navigation"; | |||
| import DoWorkbenchPickShell from "./DoWorkbenchPickShell"; | |||
| import type { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import GoodPickExecutionWorkbenchRecord from "./GoodPickExecutionWorkbenchRecord"; | |||
| @@ -14,6 +15,9 @@ import { fetchWorkbenchReleasedDoPickOrdersForSelectionToday } from "@/app/api/d | |||
| import { Button } from "@mui/material"; | |||
| import FinishedGoodCartonDashboardTab from "../FinishedGoodSearch/FinishedGoodCartonDashboardTab"; | |||
| import TruckRoutingSummaryTabWorkbench from "./TruckRoutingSummaryTabWorkbench"; | |||
| const ALLOWED_WORKBENCH_TABS = new Set([0, 1, 2, 3, 5, 6]); | |||
| type Props = { | |||
| defaultTabIndex?: 0 | 1; | |||
| printerCombo?: PrinterCombo[]; | |||
| @@ -25,7 +29,18 @@ function TabPanel(props: { value: number; index: number; children: React.ReactNo | |||
| return <Box sx={{ pt: 2 }}>{children}</Box>; | |||
| } | |||
| const DoWorkbenchTabs: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = [] }) => { | |||
| const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = [] }) => { | |||
| const searchParams = useSearchParams(); | |||
| const router = useRouter(); | |||
| const pathname = usePathname(); | |||
| const urlTabStr = searchParams.get("tab"); | |||
| const urlTicketRaw = searchParams.get("ticketNo"); | |||
| const urlTicketNo = | |||
| urlTicketRaw && urlTicketRaw.trim() !== "" | |||
| ? decodeURIComponent(urlTicketRaw.trim()) | |||
| : null; | |||
| const [tab, setTab] = React.useState<number>(defaultTabIndex); | |||
| const [a4Printer, setA4Printer] = React.useState<PrinterCombo | null>(null); | |||
| const [labelPrinter, setLabelPrinter] = React.useState<PrinterCombo | null>(null); | |||
| @@ -54,6 +69,28 @@ const DoWorkbenchTabs: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = | |||
| void fetchReleasedOrderCount(); | |||
| }, [fetchReleasedOrderCount]); | |||
| React.useEffect(() => { | |||
| if (urlTabStr == null || urlTabStr === "") return; | |||
| const n = parseInt(urlTabStr, 10); | |||
| if (!Number.isNaN(n) && ALLOWED_WORKBENCH_TABS.has(n)) { | |||
| setTab(n); | |||
| } | |||
| }, [urlTabStr]); | |||
| const handleTabChange = React.useCallback( | |||
| (_: React.SyntheticEvent, newTab: number) => { | |||
| setTab(newTab); | |||
| const params = new URLSearchParams(searchParams.toString()); | |||
| params.set("tab", String(newTab)); | |||
| if (newTab !== 1) { | |||
| params.delete("ticketNo"); | |||
| } | |||
| const qs = params.toString(); | |||
| router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false }); | |||
| }, | |||
| [pathname, router, searchParams], | |||
| ); | |||
| const handleAllDraft = React.useCallback(async () => { | |||
| try { | |||
| if (!a4Printer) { | |||
| @@ -186,7 +223,7 @@ const DoWorkbenchTabs: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = | |||
| {`${t("Print All Draft")} (${releasedOrderCount})`} | |||
| </Button> | |||
| </Stack> | |||
| <Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ borderBottom: 1, borderColor: "divider" }}> | |||
| <Tabs value={tab} onChange={handleTabChange} sx={{ borderBottom: 1, borderColor: "divider" }}> | |||
| <Tab label={t("Pick Order Detail")} value={0} /> | |||
| <Tab label={t("Finished Good Record")} value={1} /> | |||
| <Tab label={t("Finished Good Record (All)")} value={2} /> | |||
| @@ -200,18 +237,22 @@ const DoWorkbenchTabs: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={1}> | |||
| <GoodPickExecutionWorkbenchRecord | |||
| key={`workbench-record-mine-${urlTicketNo ?? ""}`} | |||
| printerCombo={printerCombo} | |||
| listScope="mine" | |||
| a4Printer={a4Printer} | |||
| labelPrinter={labelPrinter} | |||
| initialTicketNo={urlTicketNo} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={2}> | |||
| <GoodPickExecutionWorkbenchRecord | |||
| //key={`workbench-record-all-${urlTicketNo ?? ""}`} | |||
| printerCombo={printerCombo} | |||
| listScope="all" | |||
| a4Printer={a4Printer} | |||
| labelPrinter={labelPrinter} | |||
| //initialTicketNo={urlTicketNo} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={3}> | |||
| @@ -228,5 +269,17 @@ const DoWorkbenchTabs: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = | |||
| ); | |||
| }; | |||
| const DoWorkbenchTabs: React.FC<Props> = (props) => ( | |||
| <Suspense | |||
| fallback={ | |||
| <Box sx={{ display: "flex", justifyContent: "center", p: 4 }}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| } | |||
| > | |||
| <DoWorkbenchTabsInner {...props} /> | |||
| </Suspense> | |||
| ); | |||
| export default DoWorkbenchTabs; | |||
| @@ -44,6 +44,7 @@ type Props = { | |||
| listScope?: "mine" | "all"; | |||
| a4Printer: PrinterCombo | null; | |||
| labelPrinter: PrinterCombo | null; | |||
| initialTicketNo?: string | null; | |||
| }; | |||
| const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| @@ -51,6 +52,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| listScope = "mine", | |||
| a4Printer, | |||
| labelPrinter, | |||
| initialTicketNo, | |||
| }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| @@ -70,6 +72,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| shopName?: string; | |||
| deliveryNoteCode?: string; | |||
| truckLanceCode?: string; | |||
| ticketNo?: string; | |||
| }) => { | |||
| setLoading(true); | |||
| try { | |||
| @@ -89,8 +92,13 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| }, [currentUserId, listScope]); | |||
| useEffect(() => { | |||
| void loadData({ targetDate: dayjs().format("YYYY-MM-DD") }); | |||
| }, [loadData]); | |||
| const today = dayjs().format("YYYY-MM-DD"); | |||
| const tn = initialTicketNo?.trim() || undefined; | |||
| void loadData({ | |||
| targetDate: today, | |||
| ...(tn ? { ticketNo: tn } : {}), | |||
| }); | |||
| }, [loadData, initialTicketNo]); | |||
| const searchCriteria: Criterion<any>[] = useMemo( | |||
| () => [ | |||
| @@ -115,8 +123,16 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| type: "date", | |||
| defaultValue: dayjs().format("YYYY-MM-DD"), | |||
| }, | |||
| { | |||
| label: t("Ticket No"), | |||
| paramName: "ticketNo", | |||
| type: "text", | |||
| ...(initialTicketNo?.trim() | |||
| ? { preFilledValue: initialTicketNo.trim() } | |||
| : {}), | |||
| }, | |||
| ], | |||
| [t], | |||
| [t, initialTicketNo], | |||
| ); | |||
| const handleSearch = useCallback((query: Record<string, any>) => { | |||
| @@ -126,6 +142,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| shopName: query.shopName || undefined, | |||
| deliveryNoteCode: query.deliveryNoteCode || undefined, | |||
| truckLanceCode: query.truckLanceCode || undefined, | |||
| ticketNo: query.ticketNo || undefined, | |||
| }); | |||
| }, [loadData]); | |||
| @@ -582,10 +599,10 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| <Box> | |||
| <Box sx={{ mb: 2 }}> | |||
| <SearchBox | |||
| key={`workbench-search-${listScope}-${initialTicketNo ?? ""}`} | |||
| criteria={searchCriteria} | |||
| onSearch={handleSearch} | |||
| onReset={handleSearchReset} | |||
| // searchQuery={searchQuery} | |||
| /> | |||
| </Box> | |||
| <Stack | |||
| @@ -617,6 +634,9 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| <Stack direction="row" justifyContent="space-between" alignItems="center"> | |||
| <Box> | |||
| <Typography variant="h6">{row.deliveryNoteCode || "-"}</Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {row.ticketNo || "-"} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {row.shopName} | |||
| </Typography> | |||
| @@ -25,7 +25,7 @@ import TestQrCodeProvider from "@/components/QrCodeScannerProvider/TestQrCodePro | |||
| import { fetchLotDetail } from "@/app/api/inventory/actions"; | |||
| import React, { useCallback, useEffect, useState, useRef, useMemo, startTransition } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useRouter } from "next/navigation"; | |||
| import { usePathname, useRouter } from "next/navigation"; | |||
| import { | |||
| updateStockOutLineStatus, | |||
| createStockOutLine, | |||
| @@ -383,6 +383,7 @@ const WorkbenchGoodPickExecutionDetail: React.FC<Props> = ({ | |||
| const workbenchMode = true; | |||
| const { t } = useTranslation("pickOrder"); | |||
| const router = useRouter(); | |||
| const pathname = usePathname(); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null); | |||
| const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | |||
| @@ -519,6 +520,10 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | |||
| const autoAssignRef = useRef(false); | |||
| /** 曾成功載入過 workbench 階層資料;避免「列表仍有單但階層暫空」時對外層重複觸發造成迴圈 */ | |||
| const workbenchHierarchicalReadyRef = useRef(false); | |||
| /** 最後一筆 workbench 票號(階層清空或完成後仍可用於導向完成紀錄) */ | |||
| const lastWorkbenchTicketNoRef = useRef<string | null>(null); | |||
| /** 同一筆揀貨完成後只導向「完成紀錄」分頁一次 */ | |||
| const workbenchFinishNavigateDoneRef = useRef(false); | |||
| const formProps = useForm(); | |||
| const errors = formProps.formState.errors; | |||
| @@ -718,15 +723,31 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||
| // 检查数据结构 | |||
| if (!hierarchicalData?.fgInfo || !hierarchicalData.pickOrders?.length) { | |||
| console.warn("⚠️ No FG info or pick orders found"); | |||
| const hadWorkbenchData = workbenchHierarchicalReadyRef.current; | |||
| const ticketForRedirect = | |||
| String(hierarchicalData?.fgInfo?.ticketNo ?? "").trim() || | |||
| lastWorkbenchTicketNoRef.current || | |||
| ""; | |||
| setCombinedLotData([]); | |||
| setOriginalCombinedData([]); | |||
| setAllLotsCompleted(false); | |||
| setIssuePickedQtyBySolId({}); | |||
| setFgPickOrders([]); | |||
| if (workbenchHierarchicalReadyRef.current) { | |||
| workbenchHierarchicalReadyRef.current = false; | |||
| workbenchHierarchicalReadyRef.current = false; | |||
| if (hadWorkbenchData) { | |||
| onWorkbenchHierarchyEmpty?.(); | |||
| } | |||
| if ( | |||
| hadWorkbenchData && | |||
| ticketForRedirect && | |||
| !workbenchFinishNavigateDoneRef.current | |||
| ) { | |||
| workbenchFinishNavigateDoneRef.current = true; | |||
| router.replace( | |||
| `${pathname}?tab=1&ticketNo=${encodeURIComponent(ticketForRedirect)}`, | |||
| { scroll: false }, | |||
| ); | |||
| } | |||
| return; | |||
| } | |||
| @@ -779,6 +800,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||
| setFgPickOrders([fgOrder]); | |||
| workbenchHierarchicalReadyRef.current = true; | |||
| lastWorkbenchTicketNoRef.current = | |||
| String(fgOrder.ticketNo ?? "").trim() || null; | |||
| workbenchFinishNavigateDoneRef.current = false; | |||
| console.log(" DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder); | |||
| console.log(" DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes); | |||
| console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); | |||
| @@ -967,7 +991,13 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||
| } finally { | |||
| setCombinedDataLoading(false); | |||
| } | |||
| }, [currentUserId, checkAllLotsCompleted, onWorkbenchHierarchyEmpty]); // 移除 selectedPickOrderId 依赖 | |||
| }, [ | |||
| currentUserId, | |||
| checkAllLotsCompleted, | |||
| onWorkbenchHierarchyEmpty, | |||
| router, | |||
| pathname, | |||
| ]); // 移除 selectedPickOrderId 依赖 | |||
| /** After workbench scan-pick (incl. split → new stock_out_line), reload hierarchical rows. */ | |||
| const refreshWorkbenchAfterScanPick = useCallback(async () => { | |||
| @@ -45,7 +45,7 @@ const JoPickOrderList: React.FC<Props> = () => { | |||
| paramName: "BOM Description", | |||
| type: "select-labelled", | |||
| options: [ | |||
| { label: t("All"), value: "All" }, | |||
| //{ label: t("All"), value: "All" }, | |||
| { label: t("FG"), value: "FG" }, | |||
| { label: t("WIP"), value: "WIP" }, | |||
| ], | |||
| @@ -56,7 +56,7 @@ const JoPickOrderList: React.FC<Props> = () => { | |||
| paramName: "bomType", | |||
| type: "select-labelled", | |||
| options: [ | |||
| { label: t("All"), value: "All" }, | |||
| //{ label: t("All"), value: "All" }, | |||
| { label: t("Drink"), value: "drink" }, | |||
| { label: t("Powder Mixture"), value: "Powder_Mixture" }, | |||
| { label: t("Other"), value: "other" }, | |||
| @@ -67,7 +67,7 @@ const JoPickOrderList: React.FC<Props> = () => { | |||
| paramName: "floor", | |||
| type: "select-labelled", | |||
| options: [ | |||
| { label: t("All"), value: "ALL" }, | |||
| //{ label: t("All"), value: "ALL" }, | |||
| { label: "2F", value: "2F" }, | |||
| { label: "3F", value: "3F" }, | |||
| { label: "4F", value: "4F" }, | |||
| @@ -3002,6 +3002,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| const canPostScanPick = | |||
| // unavailable lot: Just Completed must always submit qty=0, even without lotNo | |||
| isUnavailableForJustComplete || | |||
| isLotAvailabilityExpired(canonicalLotForSol) || | |||
| // noLot row: Just Completed always submit qty=0 | |||
| isNoLotForJustComplete || | |||
| (canonicalLotForSol.lotNo && | |||
| @@ -3014,6 +3015,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| if (canPostScanPick) { | |||
| const qtyToSend = | |||
| isUnavailableForJustComplete | |||
| ? 0 | |||
| : isLotAvailabilityExpired(canonicalLotForSol) | |||
| ? 0 | |||
| : isNoLotForJustComplete | |||
| ? 0 | |||
| @@ -3060,7 +3063,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| ); | |||
| return; | |||
| } | |||
| const justCompleteErr = tPick( | |||
| const justCompleteErr = t( | |||
| "Just Completed (workbench): requires valid quantity; expired rows must not use this button.", | |||
| ); | |||
| if (solId > 0) { | |||
| @@ -3964,7 +3967,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| !Number.isNaN(Number(fromPickRow)) | |||
| ? Number(fromPickRow) | |||
| : lockedSubmitQtyDisplay; | |||
| const totalAvail = Number(lot.itemTotalAvailableQty ?? 0); | |||
| const isLastLotUnavailable = Number.isFinite(totalAvail) && totalAvail === 0; | |||
| return ( | |||
| <TableRow | |||
| key={`${lot.pickOrderLineId}-${lot.lotId}`} | |||
| @@ -3982,14 +3986,14 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| </Typography> | |||
| </TableCell> | |||
| <TableCell> | |||
| {row.isGroupFirst ? ( | |||
| <> | |||
| {lot.itemCode} <br /> | |||
| {lot.itemName} <br /> | |||
| {lot.uomDesc} | |||
| </> | |||
| ) : ""} | |||
| </TableCell> | |||
| {row.isGroupFirst ? ( | |||
| <> | |||
| {lot.itemCode} <br /> | |||
| {lot.itemName} <br /> | |||
| {lot.uomDesc} | |||
| </> | |||
| ) : ""} | |||
| </TableCell> | |||
| <TableCell> | |||
| <Typography variant="body2"> | |||
| @@ -4043,7 +4047,16 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| {t( | |||
| "is expired. Please check around have available QR code or not.", | |||
| )} | |||
| </Box> | |||
| {isLastLotUnavailable && ( | |||
| <Box | |||
| component="span" | |||
| sx={{ fontSize: "0.85rem", lineHeight: 1.4 }} | |||
| > | |||
| {t("This is last lot, so no available lot.")} | |||
| </Box> | |||
| )} | |||
| </> | |||
| ) : isInventoryLotLineUnavailable(lot) && | |||
| !( | |||
| @@ -4093,7 +4106,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| openWorkbenchLotLabelModalForLot(lot) | |||
| } | |||
| disabled={ | |||
| lot.lotAvailability === "expired" || | |||
| (Number(lot.stockOutLineId) > 0 && | |||
| actionBusyBySolId[ | |||
| Number(lot.stockOutLineId) | |||
| @@ -4457,7 +4470,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| "partially_completed" || | |||
| lot.stockOutLineStatus === | |||
| "partially_complete" || | |||
| isUnavailableLot || | |||
| // isUnavailableLot || | |||
| (Number(lot.stockOutLineId) > 0 && | |||
| issuePickedQtyBySolId[ | |||
| Number(lot.stockOutLineId) | |||
| @@ -23,7 +23,7 @@ const JodetailSearchWrapper: React.FC & SubComponents = async () => { | |||
| */ | |||
| fetchPrinterCombo(), | |||
| ]); | |||
| console.log("%c printerCombo:", "color:green", printerCombo); | |||
| //console.log("%c printerCombo:", "color:green", printerCombo); | |||
| return <JodetailSearch printerCombo={printerCombo} />; | |||
| }; | |||
| @@ -770,7 +770,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| ) : isPaused ? ( | |||
| <Chip label={t("Paused")} color="warning" size="small" /> | |||
| ) : isPass ? ( | |||
| <Chip label={t("Pass")} color="success" size="small" /> | |||
| <Chip label={t("Just Pass")} color="success" size="small" /> | |||
| ) : ( | |||
| <Chip label={t("Unknown")} color="error" size="small" /> | |||
| ) | |||
| @@ -284,8 +284,9 @@ | |||
| "Finished Good Management": "成品出倉管理", | |||
| "提料順序": "提料順序", | |||
| "Filter": "過濾", | |||
| "Item Code": "材料編號", | |||
| "Item Name": "材料名稱", | |||
| "Item Code": "物料編號", | |||
| "Item Name": "物料名稱", | |||
| "Just Completed (workbench): requires valid quantity; expired rows must not use this button.": "工單對料:需要有效數量;過期項目不能使用此按鈕。", | |||
| "Search & Jump": "搜尋並跳轉", | |||
| "Enter to jump to item": "按 Enter 直接跳到品項位置", | |||
| "Jump": "跳轉", | |||
| @@ -533,6 +534,7 @@ | |||
| "Edit departure time": "編輯出發時間", | |||
| "Failed to load truck lane detail": "載入車線詳情失敗", | |||
| "Shop Detail": "店鋪詳情", | |||
| "Just Pass": "已完成", | |||
| "Truck Lane Detail": "車線詳情", | |||
| "Filter by Status": "按狀態篩選", | |||
| "All": "全部", | |||
| @@ -151,6 +151,8 @@ | |||
| "Issue": "問題", | |||
| "Location": "位置", | |||
| "Scan Result": "掃碼結果", | |||
| "Just Completed (workbench): requires valid quantity; expired rows must not use this button.": "工單對料:需要有效數量;過期項目不能使用此按鈕。", | |||
| "This is last lot, so no available lot.": "這是最後一個批次,所以沒有可用批次。", | |||
| "Expiry Date": "有效期", | |||
| "Target Date": "需求日期", | |||
| "Lot Required Pick Qty": "批號需求數", | |||
| @@ -501,6 +501,7 @@ | |||
| "label Printer" : "標籤打印機", | |||
| "A4 Printer" : "A4 打印機", | |||
| "Loading Sequence": "裝載序", | |||
| "Ticket No": "提票號碼", | |||
| "The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated.": "掃描的庫存批行為「不可用」,無法換批或綁定;揀貨行未更新。", | |||
| "is unavable. Please check around have available QR code or not.": "此批號不可用,請檢查周圍是否有可用的 QR 碼。", | |||
| "Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。", | |||