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