| @@ -74,6 +74,8 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||||
| urlTargetDateRaw && urlTargetDateRaw.trim() !== "" | urlTargetDateRaw && urlTargetDateRaw.trim() !== "" | ||||
| ? decodeURIComponent(urlTargetDateRaw.trim()) | ? decodeURIComponent(urlTargetDateRaw.trim()) | ||||
| : null; | : null; | ||||
| /** Item Tracing deep-link only; DO finish redirect must not set this. */ | |||||
| const urlOpenDetail = searchParams.get("openDetail") === "1"; | |||||
| const [tab, setTab] = React.useState<number>(defaultTabIndex); | const [tab, setTab] = React.useState<number>(defaultTabIndex); | ||||
| const [lanePanelPrefs, setLanePanelPrefs] = React.useState<WorkbenchLanePanelPrefs>( | const [lanePanelPrefs, setLanePanelPrefs] = React.useState<WorkbenchLanePanelPrefs>( | ||||
| @@ -147,10 +149,11 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||||
| setTab(newTab); | setTab(newTab); | ||||
| const params = new URLSearchParams(searchParams.toString()); | const params = new URLSearchParams(searchParams.toString()); | ||||
| params.set("tab", String(newTab)); | params.set("tab", String(newTab)); | ||||
| /* ticketNo / targetDate deep-link for Finished Good Record tabs */ | |||||
| /* ticketNo / targetDate / openDetail deep-link for Finished Good Record tabs */ | |||||
| if (newTab !== 2 && newTab !== 3) { | if (newTab !== 2 && newTab !== 3) { | ||||
| params.delete("ticketNo"); | params.delete("ticketNo"); | ||||
| params.delete("targetDate"); | params.delete("targetDate"); | ||||
| params.delete("openDetail"); | |||||
| } | } | ||||
| const qs = params.toString(); | const qs = params.toString(); | ||||
| router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false }); | router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false }); | ||||
| @@ -380,24 +383,26 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tab} index={2}> | <TabPanel value={tab} index={2}> | ||||
| <GoodPickExecutionWorkbenchRecord | <GoodPickExecutionWorkbenchRecord | ||||
| key={`workbench-record-mine-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}`} | |||||
| key={`workbench-record-mine-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}-${urlOpenDetail ? "1" : "0"}`} | |||||
| printerCombo={printerCombo} | printerCombo={printerCombo} | ||||
| listScope="mine" | listScope="mine" | ||||
| a4Printer={a4Printer} | a4Printer={a4Printer} | ||||
| labelPrinter={labelPrinter} | labelPrinter={labelPrinter} | ||||
| initialTicketNo={urlTicketNo} | initialTicketNo={urlTicketNo} | ||||
| initialTargetDate={urlTargetDate} | initialTargetDate={urlTargetDate} | ||||
| openDetail={urlOpenDetail} | |||||
| /> | /> | ||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tab} index={3}> | <TabPanel value={tab} index={3}> | ||||
| <GoodPickExecutionWorkbenchRecord | <GoodPickExecutionWorkbenchRecord | ||||
| key={`workbench-record-all-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}`} | |||||
| key={`workbench-record-all-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}-${urlOpenDetail ? "1" : "0"}`} | |||||
| printerCombo={printerCombo} | printerCombo={printerCombo} | ||||
| listScope="all" | listScope="all" | ||||
| a4Printer={a4Printer} | a4Printer={a4Printer} | ||||
| labelPrinter={labelPrinter} | labelPrinter={labelPrinter} | ||||
| initialTicketNo={urlTicketNo} | initialTicketNo={urlTicketNo} | ||||
| initialTargetDate={urlTargetDate} | initialTargetDate={urlTargetDate} | ||||
| openDetail={urlOpenDetail} | |||||
| /> | /> | ||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tab} index={4}> | <TabPanel value={tab} index={4}> | ||||
| @@ -48,6 +48,8 @@ type Props = { | |||||
| labelPrinter: PrinterCombo | null; | labelPrinter: PrinterCombo | null; | ||||
| initialTicketNo?: string | null; | initialTicketNo?: string | null; | ||||
| initialTargetDate?: string | null; | initialTargetDate?: string | null; | ||||
| /** When true (Item Tracing `openDetail=1`), auto-open matching ticket detail. */ | |||||
| openDetail?: boolean; | |||||
| }; | }; | ||||
| const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | ||||
| @@ -57,6 +59,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||||
| labelPrinter, | labelPrinter, | ||||
| initialTicketNo, | initialTicketNo, | ||||
| initialTargetDate, | initialTargetDate, | ||||
| openDetail = false, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -509,13 +512,14 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||||
| const initialDetailOpenedRef = useRef(false); | const initialDetailOpenedRef = useRef(false); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!openDetail) return; | |||||
| if (initialDetailOpenedRef.current || loading || !initialTicketNo?.trim()) return; | if (initialDetailOpenedRef.current || loading || !initialTicketNo?.trim()) return; | ||||
| const tn = initialTicketNo.trim(); | const tn = initialTicketNo.trim(); | ||||
| const match = records.find((r) => r.ticketNo?.trim() === tn); | const match = records.find((r) => r.ticketNo?.trim() === tn); | ||||
| if (!match) return; | if (!match) return; | ||||
| initialDetailOpenedRef.current = true; | initialDetailOpenedRef.current = true; | ||||
| void handleDetailClick(match); | void handleDetailClick(match); | ||||
| }, [records, loading, initialTicketNo, handleDetailClick]); | |||||
| }, [openDetail, records, loading, initialTicketNo, handleDetailClick]); | |||||
| const handleBackToList = useCallback(() => { | const handleBackToList = useCallback(() => { | ||||
| setShowDetailView(false); | setShowDetailView(false); | ||||
| @@ -47,6 +47,8 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||||
| params.set("tab", String(workbenchTab)); | params.set("tab", String(workbenchTab)); | ||||
| params.set("ticketNo", ticketNo.trim()); | params.set("ticketNo", ticketNo.trim()); | ||||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | if (linkTargetDate) params.set("targetDate", linkTargetDate); | ||||
| // Only Item Tracing deep-links should auto-open detail (not DO finish redirect). | |||||
| params.set("openDetail", "1"); | |||||
| href = `/doworkbench?${params.toString()}`; | href = `/doworkbench?${params.toString()}`; | ||||
| } else if (kind === "jodetail" && (consoCode || code)) { | } else if (kind === "jodetail" && (consoCode || code)) { | ||||
| const params = new URLSearchParams(); | const params = new URLSearchParams(); | ||||
| @@ -54,6 +56,7 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||||
| // Prefer PI-* pick order code for jodetail search; consoCode may be PICK-*. | // Prefer PI-* pick order code for jodetail search; consoCode may be PICK-*. | ||||
| params.set("pickOrderCode", (code || consoCode || "").trim()); | params.set("pickOrderCode", (code || consoCode || "").trim()); | ||||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | if (linkTargetDate) params.set("targetDate", linkTargetDate); | ||||
| params.set("openDetail", "1"); | |||||
| href = `/jodetail?${params.toString()}`; | href = `/jodetail?${params.toString()}`; | ||||
| } else if (kind === "pick") { | } else if (kind === "pick") { | ||||
| // /pickOrder/detail?consoCode=… is retired — only link when we have a TI-* workbench ticket. | // /pickOrder/detail?consoCode=… is retired — only link when we have a TI-* workbench ticket. | ||||
| @@ -65,6 +68,7 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||||
| params.set("tab", String(workbenchTab)); | params.set("tab", String(workbenchTab)); | ||||
| params.set("ticketNo", workbenchTicket); | params.set("ticketNo", workbenchTicket); | ||||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | if (linkTargetDate) params.set("targetDate", linkTargetDate); | ||||
| params.set("openDetail", "1"); | |||||
| href = `/doworkbench?${params.toString()}`; | href = `/doworkbench?${params.toString()}`; | ||||
| } else { | } else { | ||||
| return <>{code}</>; | return <>{code}</>; | ||||
| @@ -62,6 +62,8 @@ const JodetailSearch: React.FC<Props> = ({ printerCombo }) => { | |||||
| const match = urlTargetDateRaw.match(/(\d{4}-\d{2}-\d{2})/); | const match = urlTargetDateRaw.match(/(\d{4}-\d{2}-\d{2})/); | ||||
| return match ? match[1] : undefined; | return match ? match[1] : undefined; | ||||
| })(); | })(); | ||||
| /** Item Tracing deep-link only; other navigations must not set this. */ | |||||
| const urlOpenDetail = searchParams.get("openDetail") === "1"; | |||||
| const urlTabIndex = useMemo(() => { | const urlTabIndex = useMemo(() => { | ||||
| if (urlTabRaw == null) return undefined; | if (urlTabRaw == null) return undefined; | ||||
| const n = parseInt(urlTabRaw, 10); | const n = parseInt(urlTabRaw, 10); | ||||
| @@ -513,6 +515,7 @@ const JodetailSearch: React.FC<Props> = ({ printerCombo }) => { | |||||
| printQty={printQty} | printQty={printQty} | ||||
| initialPickOrderCode={urlPickOrderCode} | initialPickOrderCode={urlPickOrderCode} | ||||
| initialTargetDate={urlTargetDate} | initialTargetDate={urlTargetDate} | ||||
| openDetail={urlOpenDetail} | |||||
| /> | /> | ||||
| )} | )} | ||||
| {tabIndex === 2 && <MaterialPickStatusTable />} | {tabIndex === 2 && <MaterialPickStatusTable />} | ||||
| @@ -60,6 +60,8 @@ interface Props { | |||||
| printQty?: number; | printQty?: number; | ||||
| initialPickOrderCode?: string; | initialPickOrderCode?: string; | ||||
| initialTargetDate?: string; | initialTargetDate?: string; | ||||
| /** When true (Item Tracing `openDetail=1`), auto-open matching pick-order detail. */ | |||||
| openDetail?: boolean; | |||||
| } | } | ||||
| // 修改:已完成的 Job Order Pick Order 接口 | // 修改:已完成的 Job Order Pick Order 接口 | ||||
| @@ -121,6 +123,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| printQty: printQtyProp, | printQty: printQtyProp, | ||||
| initialPickOrderCode, | initialPickOrderCode, | ||||
| initialTargetDate, | initialTargetDate, | ||||
| openDetail = false, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("jo"); | const { t } = useTranslation("jo"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| @@ -383,6 +386,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| }, [fetchLotDetailsData]); | }, [fetchLotDetailsData]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!openDetail) return; | |||||
| if (initialDetailOpenedRef.current || completedJobOrderPickOrdersLoading) return; | if (initialDetailOpenedRef.current || completedJobOrderPickOrdersLoading) return; | ||||
| const code = initialPickOrderCode?.trim(); | const code = initialPickOrderCode?.trim(); | ||||
| if (!code) return; | if (!code) return; | ||||
| @@ -395,6 +399,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| initialDetailOpenedRef.current = true; | initialDetailOpenedRef.current = true; | ||||
| void handleDetailClick(match); | void handleDetailClick(match); | ||||
| }, [ | }, [ | ||||
| openDetail, | |||||
| completedJobOrderPickOrders, | completedJobOrderPickOrders, | ||||
| completedJobOrderPickOrdersLoading, | completedJobOrderPickOrdersLoading, | ||||
| initialPickOrderCode, | initialPickOrderCode, | ||||