From 15a9d175e1a277f1be21ff166420b71cae5f8769 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 15 Oct 2025 11:09:28 +0800 Subject: [PATCH] update --- src/app/api/pickOrder/actions.ts | 49 +++ .../FinishedGoodSearch/FinishedGoodSearch.tsx | 387 ++++++++++++------ .../GoodPickExecutionForm.tsx | 4 +- .../Jodetail/JobPickExecutionForm.tsx | 13 +- src/i18n/zh/inventory.json | 4 +- src/i18n/zh/jo.json | 1 + 6 files changed, 333 insertions(+), 125 deletions(-) diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 5aca878..ead62f8 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -370,6 +370,21 @@ export interface UpdatePickExecutionIssueRequest { handleRemark?: string; } +export interface StoreLaneSummary { + storeId: string; + rows: LaneRow[]; +} + +export interface LaneRow { + truckDepartureTime: string; + lanes: LaneBtn[]; +} + +export interface LaneBtn { + truckLanceCode: string; + unassigned: number; + total: number; +} export const updatePickExecutionIssueStatus = async ( data: UpdatePickExecutionIssueRequest ): Promise => { @@ -384,6 +399,40 @@ export const updatePickExecutionIssueStatus = async ( revalidateTag("pickExecutionIssues"); return result; }; +export async function fetchStoreLaneSummary(storeId: string): Promise { + const response = await serverFetchJson( + `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}`, + { + method: "GET", + } + ); + return response; +} + +// 按车道分配订单 +export async function assignByLane( + userId: number, + storeId: string, + truckLanceCode: string, + truckDepartureTime?: string +): Promise { + const response = await serverFetchJson( + `${BASE_API_URL}/doPickOrder/assign-by-lane`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userId, + storeId, + truckLanceCode, + truckDepartureTime, + }), + } + ); + return response; +} // ✅ 新增:获取已完成的 DO Pick Orders API export const fetchCompletedDoPickOrders = async ( userId: number, diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 4034a8d..9f9c4e2 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -32,7 +32,7 @@ import PickExecutionDetail from "./GoodPickExecutiondetail"; import GoodPickExecutionRecord from "./GoodPickExecutionRecord"; import Swal from "sweetalert2"; import { printDN, printDNLabels } from "@/app/api/do/actions"; -import { FGPickOrderResponse } from "@/app/api/pickOrder/actions"; +import { FGPickOrderResponse, fetchStoreLaneSummary, assignByLane,type StoreLaneSummary } from "@/app/api/pickOrder/actions"; import FGPickOrderCard from "./FGPickOrderCard"; interface Props { @@ -59,6 +59,9 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const [tabIndex, setTabIndex] = useState(0); const [totalCount, setTotalCount] = useState(); const [isAssigning, setIsAssigning] = useState(false); + const [summary2F, setSummary2F] = useState(null); + const [summary4F, setSummary4F] = useState(null); + const [isLoadingSummary, setIsLoadingSummary] = useState(false); const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState( typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' ); @@ -76,7 +79,27 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { setReleasedOrderCount(0); } }, []); + const loadSummaries = useCallback(async () => { + setIsLoadingSummary(true); + try { + const [s2, s4] = await Promise.all([ + fetchStoreLaneSummary("2/F"), + fetchStoreLaneSummary("4/F") + ]); + setSummary2F(s2); + setSummary4F(s4); + } catch (error) { + console.error("Error loading summaries:", error); + } finally { + setIsLoadingSummary(false); + } + }, []); + + useEffect(() => { + loadSummaries(); + // 每30秒刷新一次 + }, [loadSummaries]); const handleDraft = useCallback(async () =>{ try{ if (fgPickOrdersData.length === 0) { @@ -419,10 +442,11 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const onAssigned = () => { localStorage.removeItem('hideCompletedUntilNext'); setHideCompletedUntilNext(false); + loadSummaries(); }; window.addEventListener('pickOrderAssigned', onAssigned); return () => window.removeEventListener('pickOrderAssigned', onAssigned); - }, []); + }, [loadSummaries]); // ... existing code ... useEffect(() => { @@ -453,62 +477,57 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { }, [tabIndex]); // ... existing code ... - const handleAssignByStore = async (storeId: "2/F" | "4/F") => { - if (!currentUserId) { - console.error("Missing user id in session"); - return; - } +const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string +) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); - setIsAssigning(true); - try { - const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId); - console.log("Assign by store result:", res); - - // ✅ Handle different response codes - if (res.code === "SUCCESS") { - console.log("✅ Successfully assigned pick order to store", storeId); - // ✅ Trigger refresh to show newly assigned data - window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - } else if (res.code === "USER_BUSY") { - console.warn("⚠️ User already has pick orders in progress:", res.message); - Swal.fire({ - icon: "warning", - title: t("Warning"), - text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - // ✅ Show warning but still refresh to show existing orders - //alert(`Warning: ${res.message}`); - window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - } else if (res.code === "NO_ORDERS") { - console.log("ℹ️ No available pick orders for store", storeId); - Swal.fire({ - icon: "info", - title: t("Info"), - text: t("No available pick order(s) for this floor."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - //alert(`Info: ${res.message}`); - } else { - console.log("ℹ️ Assignment result:", res.message); - alert(`Info: ${res.message}`); - } - } catch (error) { - console.error("❌ Error assigning by store:", error); + if (res.code === "SUCCESS") { + console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + loadSummaries(); // 刷新按钮状态 + } else if (res.code === "USER_BUSY") { Swal.fire({ - icon: "error", - title: t("Error"), - text: t("Error occurred during assignment."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - //alert("Error occurred during assignment"); - } finally { - setIsAssigning(false); + icon: "warning", + title: t("Warning"), + text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + } else if (res.code === "NO_ORDERS") { + Swal.fire({ + icon: "info", + title: t("Info"), + text: t("No available pick order(s) for this lane."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } else { + console.log("ℹ️ Assignment result:", res.message); } - }; + } catch (error) { + console.error("❌ Error assigning by lane:", error); + Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Error occurred during assignment."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } finally { + setIsAssigning(false); + } +}, [currentUserId, t, loadSummaries]); // ✅ Manual assignment handler - uses the action function const handleTabChange = useCallback>( @@ -711,6 +730,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { height: '100vh', // Full viewport height overflow: 'auto' // Single scrollbar for the whole page }}> + {/* Header section */} @@ -724,74 +744,205 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { {/* Last 2 buttons aligned right */} - - - - - - + + + {/* 2/F 楼层面板 */} + + + + 2/F + + {isLoadingSummary ? ( + Loading... + ) : ( + + {summary2F?.rows.map((row, rowIdx) => ( + + + {row.truckDepartureTime} + + + {row.lanes.map((lane, laneIdx) => ( + + ))} + + + ))} + + )} + + + + {/* 4/F 楼层面板 */} + + + + 4/F + + {isLoadingSummary ? ( + Loading... + ) : ( + + {summary4F?.rows.map((row, rowIdx) => ( + + + {row.truckDepartureTime} + + + {row.lanes.map((lane, laneIdx) => ( + + ))} + + + ))} + + )} + + {/* ✅ Updated print buttons with completion status */} - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + {/* Tabs section - ✅ Move the click handler here */} = ({ const [handlers, setHandlers] = useState>([]); // 计算剩余可用数量 const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { - const remainingQty = lot.inQty - lot.outQty; - return Math.max(0, remainingQty); + // ✅ 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty + return lot.availableQty || 0; }, []); const calculateRequiredQty = useCallback((lot: LotPickData) => { // ✅ Use the original required quantity, not subtracting actualPickQty diff --git a/src/components/Jodetail/JobPickExecutionForm.tsx b/src/components/Jodetail/JobPickExecutionForm.tsx index 7f408bf..a7ab185 100644 --- a/src/components/Jodetail/JobPickExecutionForm.tsx +++ b/src/components/Jodetail/JobPickExecutionForm.tsx @@ -88,8 +88,7 @@ const PickExecutionForm: React.FC = ({ const [verifiedQty, setVerifiedQty] = useState(0); const { data: session } = useSession() as { data: SessionWithTokens | null }; const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { - const remainingQty = lot.inQty - lot.outQty; - return Math.max(0, remainingQty); + return lot.availableQty || 0; }, []); const calculateRequiredQty = useCallback((lot: LotPickData) => { // ✅ Use the original required quantity, not subtracting actualPickQty @@ -275,7 +274,15 @@ useEffect(() => { // helperText={t('Still need to pick')} /> - + + + diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index 31d338c..266656b 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -55,9 +55,9 @@ "Material": "物料", "Miss Qty": "缺貨數量", "No issues found": "未找到問題", - "Pick Order": "揀貨單", + "Pick Order": "提貨單", "Pick Order, Issue No, Item, Lot...": "揀貨單, 問題編號, 貨品, 批號...", - "Picker": "揀貨員", + "Picker": "提貨員", "Refresh": "刷新", "Required Qty": "所需數量", "Resolved": "已解決", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 178d111..cd88460 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -200,6 +200,7 @@ "Verified successfully!": "驗證成功!", "At least one issue must be reported": "至少有一個問題必須報告", "Available in warehouse": "在倉庫中可用", + "Remaining Available Qty": "剩餘可用數量", "Describe the issue with bad items": "描述不良物料的問題", "Enter bad item quantity (required if no missing items)": "請輸入不良物料數量(如果沒有缺失物料)", "Enter missing quantity (required if no bad items)": "請輸入缺失物料數量(如果沒有不良物料)",