diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts
index c2bce38..17f0401 100644
--- a/src/app/api/pickOrder/actions.ts
+++ b/src/app/api/pickOrder/actions.ts
@@ -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}` : ""
diff --git a/src/components/DoWorkbench/DoWorkbenchTabs.tsx b/src/components/DoWorkbench/DoWorkbenchTabs.tsx
index 83ef702..ae44f60 100644
--- a/src/components/DoWorkbench/DoWorkbenchTabs.tsx
+++ b/src/components/DoWorkbench/DoWorkbenchTabs.tsx
@@ -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 {children};
}
-const DoWorkbenchTabs: React.FC = ({ defaultTabIndex = 0, printerCombo = [] }) => {
+const DoWorkbenchTabsInner: React.FC = ({ 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(defaultTabIndex);
const [a4Printer, setA4Printer] = React.useState(null);
const [labelPrinter, setLabelPrinter] = React.useState(null);
@@ -54,6 +69,28 @@ const DoWorkbenchTabs: React.FC = ({ 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 = ({ defaultTabIndex = 0, printerCombo =
{`${t("Print All Draft")} (${releasedOrderCount})`}
- setTab(v)} sx={{ borderBottom: 1, borderColor: "divider" }}>
+
@@ -200,18 +237,22 @@ const DoWorkbenchTabs: React.FC = ({ defaultTabIndex = 0, printerCombo =
@@ -228,5 +269,17 @@ const DoWorkbenchTabs: React.FC = ({ defaultTabIndex = 0, printerCombo =
);
};
+const DoWorkbenchTabs: React.FC = (props) => (
+
+
+
+ }
+ >
+
+
+);
+
export default DoWorkbenchTabs;
diff --git a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx
index 7c2e9fd..ec5eb8b 100644
--- a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx
+++ b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx
@@ -44,6 +44,7 @@ type Props = {
listScope?: "mine" | "all";
a4Printer: PrinterCombo | null;
labelPrinter: PrinterCombo | null;
+ initialTicketNo?: string | null;
};
const GoodPickExecutionWorkbenchRecord: React.FC = ({
@@ -51,6 +52,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({
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 = ({
shopName?: string;
deliveryNoteCode?: string;
truckLanceCode?: string;
+ ticketNo?: string;
}) => {
setLoading(true);
try {
@@ -89,8 +92,13 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({
}, [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[] = useMemo(
() => [
@@ -115,8 +123,16 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({
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) => {
@@ -126,6 +142,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({
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 = ({
= ({
{row.deliveryNoteCode || "-"}
+
+ {row.ticketNo || "-"}
+
{row.shopName}
diff --git a/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx b/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx
index 178edbd..0d7a1a5 100644
--- a/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx
+++ b/src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx
@@ -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 = ({
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(null);
const [selectedPickOrderId, setSelectedPickOrderId] = useState(null);
@@ -519,6 +520,10 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const autoAssignRef = useRef(false);
/** 曾成功載入過 workbench 階層資料;避免「列表仍有單但階層暫空」時對外層重複觸發造成迴圈 */
const workbenchHierarchicalReadyRef = useRef(false);
+ /** 最後一筆 workbench 票號(階層清空或完成後仍可用於導向完成紀錄) */
+ const lastWorkbenchTicketNoRef = useRef(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 () => {
diff --git a/src/components/JoWorkbench/JoPickOrderList.tsx b/src/components/JoWorkbench/JoPickOrderList.tsx
index 01803ba..5f3a0be 100644
--- a/src/components/JoWorkbench/JoPickOrderList.tsx
+++ b/src/components/JoWorkbench/JoPickOrderList.tsx
@@ -45,7 +45,7 @@ const JoPickOrderList: React.FC = () => {
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 = () => {
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 = () => {
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" },
diff --git a/src/components/JoWorkbench/newJobPickExecution.tsx b/src/components/JoWorkbench/newJobPickExecution.tsx
index f3f8a31..5c09d51 100644
--- a/src/components/JoWorkbench/newJobPickExecution.tsx
+++ b/src/components/JoWorkbench/newJobPickExecution.tsx
@@ -3002,6 +3002,7 @@ const JobPickExecution: React.FC = ({ 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 = ({ filterArgs, onBackToList }) => {
if (canPostScanPick) {
const qtyToSend =
isUnavailableForJustComplete
+ ? 0
+ : isLotAvailabilityExpired(canonicalLotForSol)
? 0
: isNoLotForJustComplete
? 0
@@ -3060,7 +3063,7 @@ const JobPickExecution: React.FC = ({ 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 = ({ filterArgs, onBackToList }) => {
!Number.isNaN(Number(fromPickRow))
? Number(fromPickRow)
: lockedSubmitQtyDisplay;
-
+ const totalAvail = Number(lot.itemTotalAvailableQty ?? 0);
+ const isLastLotUnavailable = Number.isFinite(totalAvail) && totalAvail === 0;
return (
= ({ filterArgs, onBackToList }) => {
- {row.isGroupFirst ? (
- <>
- {lot.itemCode}
- {lot.itemName}
- {lot.uomDesc}
- >
- ) : ""}
-
+ {row.isGroupFirst ? (
+ <>
+ {lot.itemCode}
+ {lot.itemName}
+ {lot.uomDesc}
+ >
+ ) : ""}
+
@@ -4043,7 +4047,16 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => {
{t(
"is expired. Please check around have available QR code or not.",
)}
+
+ {isLastLotUnavailable && (
+
+ {t("This is last lot, so no available lot.")}
+
+ )}
>
) : isInventoryLotLineUnavailable(lot) &&
!(
@@ -4093,7 +4106,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => {
openWorkbenchLotLabelModalForLot(lot)
}
disabled={
- lot.lotAvailability === "expired" ||
+
(Number(lot.stockOutLineId) > 0 &&
actionBusyBySolId[
Number(lot.stockOutLineId)
@@ -4457,7 +4470,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => {
"partially_completed" ||
lot.stockOutLineStatus ===
"partially_complete" ||
- isUnavailableLot ||
+ // isUnavailableLot ||
(Number(lot.stockOutLineId) > 0 &&
issuePickedQtyBySolId[
Number(lot.stockOutLineId)
diff --git a/src/components/Jodetail/FinishedGoodSearchWrapper.tsx b/src/components/Jodetail/FinishedGoodSearchWrapper.tsx
index 8784982..c4c0896 100644
--- a/src/components/Jodetail/FinishedGoodSearchWrapper.tsx
+++ b/src/components/Jodetail/FinishedGoodSearchWrapper.tsx
@@ -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 ;
};
diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx
index 5e28a49..53ae52d 100644
--- a/src/components/ProductionProcess/ProductionProcessDetail.tsx
+++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx
@@ -770,7 +770,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
) : isPaused ? (
) : isPass ? (
-
+
) : (
)
diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json
index 6384f4e..8276d71 100644
--- a/src/i18n/zh/common.json
+++ b/src/i18n/zh/common.json
@@ -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": "全部",
diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json
index e59eae9..e900278 100644
--- a/src/i18n/zh/jo.json
+++ b/src/i18n/zh/jo.json
@@ -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": "批號需求數",
diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json
index a1c2d5a..963a3c4 100644
--- a/src/i18n/zh/pickOrder.json
+++ b/src/i18n/zh/pickOrder.json
@@ -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.": "換批失敗;揀貨行未標為已核對。",