From 9bcfe9c38014025bb20cd0ba2f1c07dd016142f0 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Sun, 22 Mar 2026 21:09:39 +0800 Subject: [PATCH] revert/ just complete do pick order --- src/app/api/do/actions.tsx | 2 + src/app/api/pickOrder/actions.ts | 57 +- .../FGPickOrderTicketReleaseTable.tsx | 680 ++++++++++-------- .../FinishedGoodSearch/FinishedGoodSearch.tsx | 28 +- .../GoodPickExecutionRecord.tsx | 120 +++- src/components/SearchBox/SearchBox.tsx | 13 +- src/i18n/zh/pickOrder.json | 3 + src/i18n/zh/ticketReleaseTable.json | 15 +- 8 files changed, 572 insertions(+), 346 deletions(-) diff --git a/src/app/api/do/actions.tsx b/src/app/api/do/actions.tsx index f7fc806..dd9c16b 100644 --- a/src/app/api/do/actions.tsx +++ b/src/app/api/do/actions.tsx @@ -134,6 +134,8 @@ export interface getTicketReleaseTable { requiredDeliveryDate: string | null; handlerName: string | null; numberOfFGItems: number; + /** 進行中 do_pick_order 為 true,才可呼叫 force-complete / revert-assignment(id 為 do_pick_order 主鍵) */ + isActiveDoPickOrder?: boolean; } export interface TruckScheduleDashboardItem { diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 44287e6..5811294 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -377,6 +377,8 @@ export interface CompletedDoPickOrderSearchParams { targetDate?: string; shopName?: string; deliveryNoteCode?: string; + /** 卡車/車道(後端 truckLanceCode 模糊匹配) */ + truckLanceCode?: string; } export interface PickExecutionIssue { id: number; @@ -670,7 +672,10 @@ export const fetchCompletedDoPickOrders = async ( if (searchParams?.targetDate) { params.append('targetDate', searchParams.targetDate); } - + if (searchParams?.truckLanceCode) { + params.append("truckLanceCode", searchParams.truckLanceCode); + } + const queryString = params.toString(); const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders/${userId}${queryString ? `?${queryString}` : ''}`; @@ -680,6 +685,56 @@ export const fetchCompletedDoPickOrders = async ( return response; }; + +/** 全部已完成 DO 提貨記錄(不限經手人),需後端 `/completed-do-pick-orders-all` */ +export const fetchCompletedDoPickOrdersAll = async ( + searchParams?: CompletedDoPickOrderSearchParams +): Promise => { + const params = new URLSearchParams(); + + if (searchParams?.deliveryNoteCode) { + params.append("deliveryNoteCode", searchParams.deliveryNoteCode); + } + if (searchParams?.shopName) { + params.append("shopName", searchParams.shopName); + } + if (searchParams?.targetDate) { + params.append("targetDate", searchParams.targetDate); + } + if (searchParams?.truckLanceCode) { + params.append("truckLanceCode", searchParams.truckLanceCode); + } + + const queryString = params.toString(); + const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-all${queryString ? `?${queryString}` : ""}`; + + const response = await serverFetchJson(url, { + method: "GET", + }); + + return response; +}; + +/** 強制完成進行中的 do_pick_order(僅改狀態並歸檔,不調整揀貨數量) */ +export const forceCompleteDoPickOrder = async ( + doPickOrderId: number, +): Promise => { + return serverFetchJson( + `${BASE_API_URL}/doPickOrder/force-complete/${doPickOrderId}`, + { method: "POST", headers: { "Content-Type": "application/json" } }, + ); +}; + +/** 撤銷使用者領取,可再次分配 */ +export const revertDoPickOrderAssignment = async ( + doPickOrderId: number, +): Promise => { + return serverFetchJson( + `${BASE_API_URL}/doPickOrder/revert-assignment/${doPickOrderId}`, + { method: "POST", headers: { "Content-Type": "application/json" } }, + ); +}; + export const updatePickOrderHideStatus = async (pickOrderId: number, hide: boolean) => { const response = await serverFetchJson( `${BASE_API_URL}/pickOrder/update-hide-status/${pickOrderId}?hide=${hide}`, diff --git a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx index 1fad172..041851b 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx @@ -1,6 +1,14 @@ "use client"; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +/** + * 權限說明(與全站一致): + * - 登入後 JWT / session 帶有 `abilities: string[]`(見 config/authConfig、authorities.ts)。 + * - 導航「Finished Good Order」等使用 `requiredAbility: [AUTH.STOCK_FG, AUTH.ADMIN]`。 + * - 本表「撤銷領取 / 強制完成」僅允許具 **ADMIN** 能力者操作(專案內以 ADMIN 作為管理員層級權限)。 + * - 一般使用者可進入本頁與檢視列表;按鈕會 disabled 並以 Tooltip 提示。 + */ + +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Box, Typography, @@ -20,16 +28,46 @@ import { Paper, CircularProgress, TablePagination, - Chip -} from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import dayjs from 'dayjs'; -import { arrayToDayjs } from '@/app/utils/formatUtil'; -import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions'; + Chip, + Button, + Tooltip, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useSession } from "next-auth/react"; +import dayjs, { Dayjs } from "dayjs"; +import { arrayToDayjs } from "@/app/utils/formatUtil"; +import { fetchTicketReleaseTable, getTicketReleaseTable } from "@/app/api/do/actions"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import { + forceCompleteDoPickOrder, + revertDoPickOrderAssignment, +} from "@/app/api/pickOrder/actions"; +import Swal from "sweetalert2"; +import { AUTH } from "@/authorities"; +import { SessionWithTokens } from "@/config/authConfig"; + +function isCompletedStatus(status: string | null | undefined): boolean { + return (status ?? "").toLowerCase() === "completed"; +} + +/** 已領取(有負責人)的進行中單據才可撤銷或強制完成;未領取不可強制完成 */ +function showDoPickOpsButtons(row: getTicketReleaseTable): boolean { + return ( + row.isActiveDoPickOrder === true && + !isCompletedStatus(row.ticketStatus) && + row.handledBy != null + ); +} const FGPickOrderTicketReleaseTable: React.FC = () => { const { t } = useTranslation("ticketReleaseTable"); - const [selectedDate, setSelectedDate] = useState("today"); + const { data: session } = useSession() as { data: SessionWithTokens | null }; + const abilities = session?.abilities ?? session?.user?.abilities ?? []; + const canManageDoPickOps = abilities.includes(AUTH.ADMIN); + + const [queryDate, setQueryDate] = useState(() => dayjs()); const [selectedFloor, setSelectedFloor] = useState(""); const [selectedStatus, setSelectedStatus] = useState("released"); @@ -41,89 +79,77 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { }); const [now, setNow] = useState(dayjs()); -const [lastDataRefreshTime, setLastDataRefreshTime] = useState(null); - const formatTime = (timeData: any): string => { - if (!timeData) return ''; - + const [lastDataRefreshTime, setLastDataRefreshTime] = useState(null); + + const formatTime = (timeData: unknown): string => { + if (!timeData) return ""; + let hour: number; let minute: number; - - if (typeof timeData === 'string') { - const parts = timeData.split(':'); + if (typeof timeData === "string") { + const parts = timeData.split(":"); hour = parseInt(parts[0], 10); - minute = parseInt(parts[1] || '0', 10); + minute = parseInt(parts[1] || "0", 10); } else if (Array.isArray(timeData)) { - hour = timeData[0] || 0; minute = timeData[1] || 0; - } - else { - return ''; + } else { + return ""; } - - const formattedHour = hour.toString().padStart(2, '0'); - const formattedMinute = minute.toString().padStart(2, '0'); - return `${formattedHour}:${formattedMinute}`; - }; - const getDateLabel = (offset: number) => { - return dayjs().add(offset, 'day').format('YYYY-MM-DD'); - }; - const getDateRange = () => { - const today = dayjs().format('YYYY-MM-DD'); - const dayAfterTomorrow = dayjs().add(2, 'day').format('YYYY-MM-DD'); - return { startDate: today, endDate: dayAfterTomorrow }; + const formattedHour = hour.toString().padStart(2, "0"); + const formattedMinute = minute.toString().padStart(2, "0"); + return `${formattedHour}:${formattedMinute}`; }; -const loadData = useCallback(async () => { - setLoading(true); - try { - const { startDate, endDate } = getDateRange(); - const result = await fetchTicketReleaseTable(startDate, endDate); - setData(result); - setLastDataRefreshTime(dayjs()); - } catch (error) { - console.error('Error fetching ticket release table:', error); - } finally { - setLoading(false); - } -}, []); - -useEffect(() => { - loadData(); - const id = setInterval(loadData, 5 * 60 * 1000); - return () => clearInterval(id); -}, [loadData]); - - const filteredData = data.filter((item) => { - // Filter by floor if selected - if (selectedFloor && item.storeId !== selectedFloor) { - return false; + const loadData = useCallback(async () => { + setLoading(true); + try { + const dayStr = queryDate.format("YYYY-MM-DD"); + const result = await fetchTicketReleaseTable(dayStr, dayStr); + setData(result); + setLastDataRefreshTime(dayjs()); + } catch (error) { + console.error("Error fetching ticket release table:", error); + } finally { + setLoading(false); } + }, [queryDate]); - // Filter by date if selected - if (selectedDate && item.requiredDeliveryDate) { - const itemDate = dayjs(item.requiredDeliveryDate).format('YYYY-MM-DD'); - const targetDate = getDateLabel( - selectedDate === "today" ? 0 : selectedDate === "tomorrow" ? 1 : 2 - ); - if (itemDate !== targetDate) { - return false; - } - } + useEffect(() => { + loadData(); + const id = setInterval(loadData, 5 * 60 * 1000); + return () => clearInterval(id); + }, [loadData]); - // Filter by status if selected - if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) { - return false; - } + useEffect(() => { + const tick = setInterval(() => setNow(dayjs()), 30 * 1000); + return () => clearInterval(tick); + }, []); + const dayStr = queryDate.format("YYYY-MM-DD"); - return true; - },[data, selectedDate, selectedFloor, selectedStatus]); + const filteredData = useMemo(() => { + return data.filter((item) => { + if (selectedFloor && item.storeId !== selectedFloor) { + return false; + } + if (item.requiredDeliveryDate) { + const itemDate = dayjs(item.requiredDeliveryDate).format("YYYY-MM-DD"); + if (itemDate !== dayStr) { + return false; + } + } + if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) { + return false; + } + return true; + }); + }, [data, dayStr, selectedFloor, selectedStatus]); - const handlePageChange = useCallback((event: unknown, newPage: number) => { - setPaginationController(prev => ({ + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setPaginationController((prev) => ({ ...prev, pageNum: newPage, })); @@ -144,265 +170,319 @@ useEffect(() => { }, [filteredData, paginationController]); useEffect(() => { - setPaginationController(prev => ({ ...prev, pageNum: 0 })); - }, [selectedDate, selectedFloor, selectedStatus]); + setPaginationController((prev) => ({ ...prev, pageNum: 0 })); + }, [queryDate, selectedFloor, selectedStatus]); + + const handleRevert = async (row: getTicketReleaseTable) => { + if (!canManageDoPickOps) return; + const r = await Swal.fire({ + title: t("Confirm revert assignment"), + text: t("Revert assignment hint"), + icon: "warning", + showCancelButton: true, + confirmButtonText: t("Confirm"), + cancelButtonText: t("Cancel"), + }); + if (!r.isConfirmed) return; + try { + const res = await revertDoPickOrderAssignment(row.id); + if (res.code === "SUCCESS") { + await Swal.fire({ icon: "success", text: t("Operation succeeded"), timer: 1500, showConfirmButton: false }); + await loadData(); + } else { + await Swal.fire({ icon: "error", title: res.code ?? "", text: res.message ?? "" }); + } + } catch (e) { + console.error(e); + await Swal.fire({ icon: "error", text: String(e) }); + } + }; + + const handleForceComplete = async (row: getTicketReleaseTable) => { + if (!canManageDoPickOps) return; + const r = await Swal.fire({ + title: t("Confirm force complete"), + text: t("Force complete hint"), + icon: "warning", + showCancelButton: true, + confirmButtonText: t("Confirm"), + cancelButtonText: t("Cancel"), + }); + if (!r.isConfirmed) return; + try { + const res = await forceCompleteDoPickOrder(row.id); + if (res.code === "SUCCESS") { + await Swal.fire({ icon: "success", text: t("Operation succeeded"), timer: 1500, showConfirmButton: false }); + await loadData(); + } else { + await Swal.fire({ icon: "error", title: res.code ?? "", text: res.message ?? "" }); + } + } catch (e) { + console.error(e); + await Swal.fire({ icon: "error", text: String(e) }); + } + }; + + const opsTooltip = !canManageDoPickOps ? t("Manager only hint") : ""; return ( - - - {/* Title */} - - {t("Ticket Release Table")} - - - {/* Dropdown Menus */} - - - {t("Select Date")} - - - - - - {t("Floor")} - - - - - - - {t("Status")} - - - - - - - - {t("Now")}: {now.format('HH:mm')} - - - {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} - + + + + + {t("Ticket Release Table")} + + + + v && setQueryDate(v)} + slotProps={{ textField: { size: "small", sx: { minWidth: 180 } } }} + /> + + + + + {t("Floor")} + + + + + + + {t("Status")} + + + + + + + + {t("Now")}: {now.format("HH:mm")} + + + {t("Auto-refresh every 5 minutes")} | {t("Last updated")}:{" "} + {lastDataRefreshTime ? lastDataRefreshTime.format("HH:mm:ss") : "--:--:--"} + + - - - - {loading ? ( - - - - ) : ( - <> - - - - - {t("Store ID")} - {t("Required Delivery Date")} - - - - {t("Truck Information")} - - - {t("Truck Lane Code")} - {t("Departure Time")} - - - - {/*{t("Truck Departure Time")} - {t("Truck Lane Code")}*/} - {t("Shop Name")} - {t("Loading Sequence")} - {/*{t("Delivery Order Code(s)")} - {t("Pick Order Code(s)")} - {t("Ticket Number")} - {t("Ticket Release Time")} - {t("Ticket Complete Date Time")} - {t("Ticket Status")}*/} - - - - {t("Ticket Information")} - - - {t("Ticket No.")} ({t("Status")}) - - - {t("Released Time")} - {t("Completed Time")} - - - - {t("Handler Name")} - {t("Number of FG Items (Order Item(s) Count)")} - - - - {paginatedData.length === 0 ? ( - - - {t("No data available")} + + + {loading ? ( + + + + ) : ( + <> + +
+ + + {t("Store ID")} + {t("Required Delivery Date")} + + + + {t("Truck Information")} + + + {t("Truck Lane Code")} - {t("Departure Time")} + + + + {t("Shop Name")} + {t("Loading Sequence")} + + + + {t("Ticket Information")} + + + {t("Ticket No.")} ({t("Status")}) + + + {t("Released Time")} - {t("Completed Time")} + + + + {t("Handler Name")} + + {t("Number of FG Items (Order Item(s) Count)")} + + + {t("Actions")} - ) : ( - paginatedData.map((row) => { - return ( - - {row.storeId || '-'} + + + {paginatedData.length === 0 ? ( + + + {t("No data available")} + + + ) : ( + paginatedData.map((row) => ( + + {row.storeId || "-"} {row.requiredDeliveryDate - ? dayjs(row.requiredDeliveryDate).format('YYYY-MM-DD') - : '-'} + ? dayjs(row.requiredDeliveryDate).format("YYYY-MM-DD") + : "-"} - - - {row.truckLanceCode && ( - - )} - {row.truckDepartureTime && ( - - )} - {!row.truckLanceCode && !row.truckDepartureTime && ( - - - - - )} - - + + + {row.truckLanceCode && ( + + )} + {row.truckDepartureTime && ( + + )} + {!row.truckLanceCode && !row.truckDepartureTime && ( + + - + + )} + + - {row.shopName || '-'} - {row.loadingSequence || '-'} - {/*{row.deliveryOrderCode || '-'} - {row.pickOrderCode || '-'} - {row.ticketNo || '-'} - - {row.ticketReleaseTime - ? dayjs(row.ticketReleaseTime).format('YYYY-MM-DD HH:mm:ss') - : '-'} - - - {row.ticketCompleteDateTime - ? dayjs(row.ticketCompleteDateTime).format('YYYY-MM-DD HH:mm:ss') - : '-'} - - {row.ticketStatus || '-'}*/} + {row.shopName || "-"} + {row.loadingSequence ?? "-"} - + - {row.ticketNo || '-'} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : '-'}) + {row.ticketNo || "-"} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : "-"}) - {t("Released Time")}: {row.ticketReleaseTime + {t("Released Time")}:{" "} + {row.ticketReleaseTime ? (() => { if (Array.isArray(row.ticketReleaseTime)) { - return arrayToDayjs(row.ticketReleaseTime, true).format('HH:mm'); + return arrayToDayjs(row.ticketReleaseTime, true).format("HH:mm"); } - const parsedDate = dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss'); + const parsedDate = dayjs(row.ticketReleaseTime, "YYYYMMDDHHmmss"); if (!parsedDate.isValid()) { - return dayjs(row.ticketReleaseTime).format('HH:mm'); + return dayjs(row.ticketReleaseTime).format("HH:mm"); } - return parsedDate.format('HH:mm'); + return parsedDate.format("HH:mm"); })() - : '-'} + : "-"} - {t("Completed Time")}: {row.ticketCompleteDateTime + {t("Completed Time")}:{" "} + {row.ticketCompleteDateTime ? (() => { if (Array.isArray(row.ticketCompleteDateTime)) { - return arrayToDayjs(row.ticketCompleteDateTime, true).format('HH:mm'); + return arrayToDayjs(row.ticketCompleteDateTime, true).format("HH:mm"); } - const parsedDate = dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss'); + const parsedDate = dayjs(row.ticketCompleteDateTime, "YYYYMMDDHHmmss"); if (!parsedDate.isValid()) { - return dayjs(row.ticketCompleteDateTime).format('HH:mm'); + return dayjs(row.ticketCompleteDateTime).format("HH:mm"); } - return parsedDate.format('HH:mm'); + return parsedDate.format("HH:mm"); })() - : '-'} + : "-"} - {row.handlerName ?? 0} - {row.numberOfFGItems ?? 0} + {row.handlerName ?? "-"} + + {row.numberOfFGItems ?? 0} + + + {showDoPickOpsButtons(row) ? ( + + + + + + + + + + + + + ) : ( + + — + + )} + - ); - }) - )} - -
-
- {filteredData.length > 0 && ( - - )} - - )} -
-
-
+ )) + )} + + + + {filteredData.length > 0 && ( + + )} + + )} + + + + ); }; -export default FGPickOrderTicketReleaseTable; \ No newline at end of file +export default FGPickOrderTicketReleaseTable; diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 2465887..8ae9faa 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -315,8 +315,8 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState { - // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 - if (tabIndex === 2) { + // 当切换到成品提貨記錄標籤時,重置打印按钮状态 + if (tabIndex === 2 || tabIndex === 4) { setPrintButtonsEnabled(false); console.log("Reset print buttons for Pick Execution Record tab"); } @@ -709,6 +709,7 @@ const handleAssignByLane = useCallback(async ( + @@ -742,8 +743,27 @@ const handleAssignByLane = useCallback(async ( onRefreshReleasedOrderCount={fetchReleasedOrderCount} /> ) } - {tabIndex === 2 && } - {tabIndex === 3 && } + {tabIndex === 2 && ( + + )} + {tabIndex === 3 && } + {tabIndex === 4 && ( + + )} ); diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index be57310..225bab7 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -44,6 +44,7 @@ import { PickOrderCompletionResponse, checkAndCompletePickOrderByConsoCode, fetchCompletedDoPickOrders, + fetchCompletedDoPickOrdersAll, CompletedDoPickOrderResponse, CompletedDoPickOrderSearchParams, fetchLotDetailsByDoPickOrderRecordId @@ -74,6 +75,10 @@ interface Props { printerCombo: PrinterCombo[]; a4Printer: PrinterCombo | null; // A4 打印机(DN 用) labelPrinter: PrinterCombo | null; + /** 與 FinishedGoodSearch 標籤索引一致,用於 pickOrderCompletionStatus 事件 */ + recordTabIndex?: number; + /** mine:僅本人經手的完成記錄;all:全部人員的完成記錄(新分頁) */ + listScope?: "mine" | "all"; } @@ -87,7 +92,14 @@ interface PickOrderData { lots: any[]; } -const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4Printer, labelPrinter }) => { +const GoodPickExecutionRecord: React.FC = ({ + filterArgs, + printerCombo, + a4Printer, + labelPrinter, + recordTabIndex = 2, + listScope = "mine", +}) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; @@ -103,8 +115,10 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 const [showDetailView, setShowDetailView] = useState(false); const [detailLotData, setDetailLotData] = useState([]); - // 新增:搜索状态 - const [searchQuery, setSearchQuery] = useState>({}); + // 新增:搜索状态(預設為今日) + const [searchQuery, setSearchQuery] = useState>(() => ({ + targetDate: dayjs().format("YYYY-MM-DD"), + })); const [filteredDoPickOrders, setFilteredDoPickOrders] = useState([]); // 新增:分页状态 @@ -353,14 +367,17 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 // 修改:使用新的 API 获取已完成的 DO Pick Orders const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { - if (!currentUserId) return; - + if (listScope === "mine" && !currentUserId) return; + setCompletedDoPickOrdersLoading(true); try { console.log("🔍 Fetching completed DO pick orders with params:", searchParams); - - const completedDoPickOrders = await fetchCompletedDoPickOrders(currentUserId, searchParams); - + + const completedDoPickOrders = + listScope === "all" + ? await fetchCompletedDoPickOrdersAll(searchParams) + : await fetchCompletedDoPickOrders(currentUserId!, searchParams); + setCompletedDoPickOrders(completedDoPickOrders); setFilteredDoPickOrders(completedDoPickOrders); console.log(" Fetched completed DO pick orders:", completedDoPickOrders); @@ -371,14 +388,19 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 } finally { setCompletedDoPickOrdersLoading(false); } - }, [currentUserId]); + }, [currentUserId, listScope]); - // 初始化时获取数据 + // 初始化时获取数据(預設依「今日」篩選) useEffect(() => { - if (currentUserId) { - fetchCompletedDoPickOrdersData(); + const todayParams: CompletedDoPickOrderSearchParams = { + targetDate: dayjs().format("YYYY-MM-DD"), + }; + if (listScope === "all") { + fetchCompletedDoPickOrdersData(todayParams); + } else if (currentUserId) { + fetchCompletedDoPickOrdersData(todayParams); } - }, [currentUserId, fetchCompletedDoPickOrdersData]); + }, [currentUserId, listScope, fetchCompletedDoPickOrdersData]); // 修改:搜索功能使用新的 API const handleSearch = useCallback((query: Record) => { @@ -389,7 +411,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 targetDate: query.targetDate || undefined, shopName: query.shopName || undefined, deliveryNoteCode: query.deliveryNoteCode || undefined, - //ticketNo: query.ticketNo || undefined, + truckLanceCode: query.truckLanceCode || undefined, }; // 使用新的 API 进行搜索 @@ -398,8 +420,9 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 // 修复:重命名函数避免重复声明 const handleSearchReset = useCallback(() => { - setSearchQuery({}); - fetchCompletedDoPickOrdersData(); // 重新获取所有数据 + const today = dayjs().format("YYYY-MM-DD"); + setSearchQuery({ targetDate: today }); + fetchCompletedDoPickOrdersData({ targetDate: today }); }, [fetchCompletedDoPickOrdersData]); // 分页功能 @@ -425,24 +448,42 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4 return filteredDoPickOrders.slice(startIndex, endIndex); }, [filteredDoPickOrders, paginationController]); - // 搜索条件 - const searchCriteria: Criterion[] = [ - { - label: t("Delivery Note Code"), - paramName: "deliveryNoteCode", - type: "text", - }, - { - label: t("Shop Name"), - paramName: "shopName", - type: "text", - }, - { - label: t("Target Date"), - paramName: "targetDate", - type: "date", + // 搜索条件(目標日期預設為今日) + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("Delivery Note Code"), + paramName: "deliveryNoteCode", + type: "text", + }, + { + label: t("Shop Name"), + paramName: "shopName", + type: "text", + }, + { + label: t("Truck Lance Code"), + paramName: "truckLanceCode", + type: "text", + }, + { + label: t("Target Date"), + paramName: "targetDate", + type: "date", + defaultValue: dayjs().format("YYYY-MM-DD"), + }, + ], + [t], + ); + + const searchDateDisplay = useMemo(() => { + const raw = searchQuery.targetDate; + if (raw && String(raw).trim() !== "") { + const d = dayjs(raw); + return d.isValid() ? d.format(OUTPUT_DATE_FORMAT) : t("All dates"); } - ]; + return t("All dates"); + }, [searchQuery.targetDate, t]); const handleDetailClick = useCallback(async (doPickOrder: CompletedDoPickOrderResponse) => { setSelectedDoPickOrder(doPickOrder); @@ -540,7 +581,7 @@ setDetailLotData(flatLotData); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, - tabIndex: 2 + tabIndex: recordTabIndex } })); @@ -551,11 +592,11 @@ setDetailLotData(flatLotData); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, - tabIndex: 2 + tabIndex: recordTabIndex } })); } - }, []); + }, [recordTabIndex]); // 返回列表视图 @@ -568,10 +609,10 @@ setDetailLotData(flatLotData); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, - tabIndex: 2 + tabIndex: recordTabIndex } })); - }, []); + }, [recordTabIndex]); // 如果显示详情视图,渲染类似 GoodPickExecution 的表格 @@ -723,7 +764,8 @@ if (showDetailView && selectedDoPickOrder) { {/* 结果统计 */} - {t("Completed DO pick orders: ")} {filteredDoPickOrders.length} + {t("Search date")}: {searchDateDisplay} | {t("Completed DO pick orders: ")}{" "} + {filteredDoPickOrders.length} {/* 列表 */} diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 848fd14..959ec2e 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -164,6 +164,12 @@ function SearchBox({ [`${c.paramName}To`]: c.defaultValueTo ?? "", }; } + if (c.type === "date") { + tempCriteria = { + ...tempCriteria, + [c.paramName]: c.defaultValue ?? "", + }; + } return tempCriteria; }, {} as Record, @@ -274,7 +280,7 @@ function SearchBox({ }, []); const handleReset = () => { - setInputs(defaultInputs); + setInputs(preFilledInputs); onReset?.(); setIsReset(!isReset); }; @@ -555,6 +561,11 @@ function SearchBox({ label={t(c.label)} onChange={makeDateChangeHandler(c.paramName)} disabled={disabled} + value={ + inputs[c.paramName] && dayjs(inputs[c.paramName]).isValid() + ? dayjs(inputs[c.paramName]) + : null + } /> diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index cb6d98c..f6c307e 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -350,6 +350,9 @@ "Enter the number of cartons:": "請輸入總箱數", "Finished Good Detail": "成品提貨詳情", "Finished Good Record": "成品提貨記錄", + "Finished Good Record (All)": "成品提貨記錄(全部)", + "All dates": "全部日期", + "Search date": "搜索日期", "Hide Completed: OFF": "完成: OFF", "Hide Completed: ON": "完成: ON", "Number must be at least 1": "數量至少為1", diff --git a/src/i18n/zh/ticketReleaseTable.json b/src/i18n/zh/ticketReleaseTable.json index 9a981ae..fd7c8af 100644 --- a/src/i18n/zh/ticketReleaseTable.json +++ b/src/i18n/zh/ticketReleaseTable.json @@ -31,5 +31,18 @@ "pending": "待撳單", "released": "提貨中", "completed": "已完成", - "All Statuses": "所有提貨狀態" + "All Statuses": "所有提貨狀態", + "Target Date": "目標日期", + "Reload data": "重新載入", + "Manager only hint": "僅管理員(ADMIN 權限)可使用", + "Actions": "操作", + "Revert assignment": "撤銷領取", + "Force complete DO": "強制完成提貨單", + "Confirm revert assignment": "確認撤銷領取?", + "Revert assignment hint": "將清空負責人,單據回到待分配,其他人可再領取。", + "Confirm force complete": "確認強制完成?", + "Force complete hint": "僅將狀態標為完成並歸檔,不修改已揀數量;適用於已全部提交但系統未完成的情況。", + "Operation succeeded": "操作成功", + "Confirm": "確認", + "Cancel": "取消" } \ No newline at end of file