diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 07b56e0..4f1ab3e 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -101,6 +101,7 @@ export interface AssignJobOrderResponse { message: string | null; errorPosition: string | null; } + export const recordSecondScanIssue = cache(async ( pickOrderId: number, itemId: number, @@ -179,11 +180,19 @@ export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => { }, ); }); - -// 获取已完成的 Job Order pick orders export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) => { return serverFetchJson( - `${BASE_API_URL}/jo/completed-job-order-pick-orders/${userId}`, + `${BASE_API_URL}/jo/completed-job-order-pick-orders${userId}`, + { + method: "GET", + next: { tags: ["jo-completed"] }, + }, + ); +}); +// 获取已完成的 Job Order pick orders +export const fetchCompletedJobOrderPickOrdersrecords = cache(async (userId: number) => { + return serverFetchJson( + `${BASE_API_URL}/jo/completed-job-order-pick-orders-only/${userId}`, { method: "GET", next: { tags: ["jo-completed"] }, @@ -300,4 +309,10 @@ export const fetchCompletedJobOrderPickOrderLotDetails = cache(async (pickOrderI method: "GET", headers: { "Content-Type": "application/json" } }) -}) \ No newline at end of file +}) +export const fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick = cache(async (pickOrderId: number) => { + return serverFetchJson(`${BASE_API_URL}/jo/completed-job-order-pick-order-lot-details-completed-pick/${pickOrderId}`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }) +}) diff --git a/src/components/Jodetail/JobPickExecutionsecondscan.tsx b/src/components/Jodetail/JobPickExecutionsecondscan.tsx index 349235e..47a74bc 100644 --- a/src/components/Jodetail/JobPickExecutionsecondscan.tsx +++ b/src/components/Jodetail/JobPickExecutionsecondscan.tsx @@ -1152,33 +1152,33 @@ const paginatedData = useMemo(() => { - {lot.secondQrScanStatus?.toLowerCase() !== 'pending' ? ( - - - - ) : null} - + {lot.secondQrScanStatus?.toLowerCase() !== 'pending' ? ( + + + + ) : null} + diff --git a/src/components/Jodetail/JodetailSearch.tsx b/src/components/Jodetail/JodetailSearch.tsx index 8e27963..6ea1bb7 100644 --- a/src/components/Jodetail/JodetailSearch.tsx +++ b/src/components/Jodetail/JodetailSearch.tsx @@ -25,6 +25,7 @@ import { SessionWithTokens } from "@/config/authConfig"; import JobPickExecutionsecondscan from "./JobPickExecutionsecondscan"; import FInishedJobOrderRecord from "./FInishedJobOrderRecord"; import JobPickExecution from "./JobPickExecution"; +import CompleteJobOrderRecord from "./completeJobOrderRecord"; import { fetchUnassignedJobOrderPickOrders, assignJobOrderPickOrder, @@ -172,6 +173,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; setUnassignedOrders(orders); } catch (error) { console.error("Error loading unassigned orders:", error); + setUnassignedOrders([]); } finally { setIsLoadingUnassigned(false); } @@ -419,7 +421,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; {/* Last 2 buttons aligned right */} {/* Unassigned Job Orders */} -{!hasAnyAssignedData && unassignedOrders.length > 0 && ( + {!hasAnyAssignedData && unassignedOrders && unassignedOrders.length > 0 && ( {t("Unassigned Job Orders")} ({unassignedOrders.length}) @@ -452,8 +454,9 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; }}> + - + {/* */} @@ -463,8 +466,9 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; p: 2 }}> {tabIndex === 0 && } - {tabIndex === 1 && } - {tabIndex === 2 && } + {tabIndex === 1 && } + {tabIndex === 2 && } + {/* {tabIndex === 3 && } */} ); diff --git a/src/components/Jodetail/completeJobOrderRecord.tsx b/src/components/Jodetail/completeJobOrderRecord.tsx new file mode 100644 index 0000000..7f88370 --- /dev/null +++ b/src/components/Jodetail/completeJobOrderRecord.tsx @@ -0,0 +1,571 @@ +"use client"; + +import { + Box, + Button, + Stack, + TextField, + Typography, + Alert, + CircularProgress, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + TablePagination, + Modal, + Card, + CardContent, + CardActions, + Chip, + Accordion, + AccordionSummary, + AccordionDetails, + Checkbox, +} from "@mui/material"; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { useCallback, useEffect, useState, useRef, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useRouter } from "next/navigation"; +import { + fetchCompletedJobOrderPickOrdersrecords, + fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick +} from "@/app/api/jo/actions"; +import { fetchNameList, NameList } from "@/app/api/user/actions"; +import { + FormProvider, + useForm, +} from "react-hook-form"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useSession } from "next-auth/react"; +import { SessionWithTokens } from "@/config/authConfig"; + +interface Props { + filterArgs: Record; +} + +// ✅ 修改:已完成的 Job Order Pick Order 接口 +interface CompletedJobOrderPickOrder { + id: number; + pickOrderId: number; + pickOrderCode: string; + pickOrderConsoCode: string; + pickOrderTargetDate: string; + pickOrderStatus: string; + completedDate: string; + jobOrderId: number; + jobOrderCode: string; + jobOrderName: string; + reqQty: number; + uom: string; + planStart: string; + planEnd: string; + secondScanCompleted: boolean; + totalItems: number; + completedItems: number; +} + +// ✅ 新增:Lot 详情接口 +interface LotDetail { + lotId: number; + lotNo: string; + expiryDate: string; + location: string; + availableQty: number; + requiredQty: number; + actualPickQty: number; + processingStatus: string; + lotAvailability: string; + pickOrderId: number; + pickOrderCode: string; + pickOrderConsoCode: string; + pickOrderLineId: number; + stockOutLineId: number; + stockOutLineStatus: string; + routerIndex: number; + routerArea: string; + routerRoute: string; + uomShortDesc: string; + secondQrScanStatus: string; + itemId: number; + itemCode: string; + itemName: string; + uomCode: string; + uomDesc: string; +} + +const CompleteJobOrderRecord: React.FC = ({ filterArgs }) => { + const { t } = useTranslation("jo"); + const router = useRouter(); + const { data: session } = useSession() as { data: SessionWithTokens | null }; + + const currentUserId = session?.id ? parseInt(session.id) : undefined; + + // ✅ 修改:已完成 Job Order Pick Orders 状态 + const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState([]); + const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false); + + // ✅ 修改:详情视图状态 + const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState(null); + const [showDetailView, setShowDetailView] = useState(false); + const [detailLotData, setDetailLotData] = useState([]); + const [detailLotDataLoading, setDetailLotDataLoading] = useState(false); + + // ✅ 修改:搜索状态 + const [searchQuery, setSearchQuery] = useState>({}); + const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState([]); + + // ✅ 修改:分页状态 + const [paginationController, setPaginationController] = useState({ + pageNum: 0, + pageSize: 10, + }); + + const formProps = useForm(); + const errors = formProps.formState.errors; + + // ✅ 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders(仅完成pick的) + const fetchCompletedJobOrderPickOrdersData = useCallback(async () => { + if (!currentUserId) return; + + setCompletedJobOrderPickOrdersLoading(true); + try { + console.log("🔍 Fetching completed Job Order pick orders (pick completed only)..."); + + const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords(currentUserId); + + // ✅ Fix: Ensure the data is always an array + const safeData = Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []; + + setCompletedJobOrderPickOrders(safeData); + setFilteredJobOrderPickOrders(safeData); + console.log("✅ Fetched completed Job Order pick orders:", safeData); + } catch (error) { + console.error("❌ Error fetching completed Job Order pick orders:", error); + setCompletedJobOrderPickOrders([]); + setFilteredJobOrderPickOrders([]); + } finally { + setCompletedJobOrderPickOrdersLoading(false); + } + }, [currentUserId]); + + // ✅ 新增:获取 lot 详情数据(使用新的API) + const fetchLotDetailsData = useCallback(async (pickOrderId: number) => { + setDetailLotDataLoading(true); + try { + console.log("🔍 Fetching lot details for completed pick order:", pickOrderId); + + const lotDetails = await fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId); + + setDetailLotData(lotDetails); + console.log("✅ Fetched lot details:", lotDetails); + } catch (error) { + console.error("❌ Error fetching lot details:", error); + setDetailLotData([]); + } finally { + setDetailLotDataLoading(false); + } + }, []); + + // ✅ 修改:初始化时获取数据 + useEffect(() => { + if (currentUserId) { + fetchCompletedJobOrderPickOrdersData(); + } + }, [currentUserId, fetchCompletedJobOrderPickOrdersData]); + + // ✅ 修改:搜索功能 + const handleSearch = useCallback((query: Record) => { + setSearchQuery({ ...query }); + console.log("Search query:", query); + + // ✅ Fix: Ensure completedJobOrderPickOrders is an array before filtering + if (!Array.isArray(completedJobOrderPickOrders)) { + setFilteredJobOrderPickOrders([]); + return; + } + + const filtered = completedJobOrderPickOrders.filter((pickOrder) => { + const pickOrderCodeMatch = !query.pickOrderCode || + pickOrder.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase()); + + const jobOrderCodeMatch = !query.jobOrderCode || + pickOrder.jobOrderCode?.toLowerCase().includes((query.jobOrderCode || "").toLowerCase()); + + const jobOrderNameMatch = !query.jobOrderName || + pickOrder.jobOrderName?.toLowerCase().includes((query.jobOrderName || "").toLowerCase()); + + return pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch; + }); + + setFilteredJobOrderPickOrders(filtered); + console.log("Filtered Job Order pick orders count:", filtered.length); + }, [completedJobOrderPickOrders]); + + // ✅ 修改:重置搜索 + const handleSearchReset = useCallback(() => { + setSearchQuery({}); + // ✅ Fix: Ensure completedJobOrderPickOrders is an array before setting + setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []); + }, [completedJobOrderPickOrders]); + + // ✅ 修改:分页功能 + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setPaginationController(prev => ({ + ...prev, + pageNum: newPage, + })); + }, []); + + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10); + setPaginationController({ + pageNum: 0, + pageSize: newPageSize, + }); + }, []); + + // ✅ 修改:分页数据 + const paginatedData = useMemo(() => { + // ✅ Fix: Ensure filteredJobOrderPickOrders is an array before calling slice + if (!Array.isArray(filteredJobOrderPickOrders)) { + return []; + } + + const startIndex = paginationController.pageNum * paginationController.pageSize; + const endIndex = startIndex + paginationController.pageSize; + return filteredJobOrderPickOrders.slice(startIndex, endIndex); + }, [filteredJobOrderPickOrders, paginationController]); + + // ✅ 修改:搜索条件 + const searchCriteria: Criterion[] = [ + { + label: t("Pick Order Code"), + paramName: "pickOrderCode", + type: "text", + }, + { + label: t("Job Order Code"), + paramName: "jobOrderCode", + type: "text", + }, + { + label: t("Job Order Item Name"), + paramName: "jobOrderName", + type: "text", + } + ]; + + // ✅ 修改:详情点击处理 + const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => { + setSelectedJobOrderPickOrder(jobOrderPickOrder); + setShowDetailView(true); + + // ✅ 获取 lot 详情数据(使用新的API) + await fetchLotDetailsData(jobOrderPickOrder.pickOrderId); + + // ✅ 触发打印按钮状态更新 - 基于详情数据 + const allCompleted = jobOrderPickOrder.secondScanCompleted; + + // ✅ 发送事件,包含标签页信息 + window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { + detail: { + allLotsCompleted: allCompleted, + tabIndex: 3 // ✅ 明确指定这是来自标签页 3 的事件 + } + })); + + }, [fetchLotDetailsData]); + + // ✅ 修改:返回列表视图 + const handleBackToList = useCallback(() => { + setShowDetailView(false); + setSelectedJobOrderPickOrder(null); + setDetailLotData([]); + + // ✅ 返回列表时禁用打印按钮 + window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { + detail: { + allLotsCompleted: false, + tabIndex: 3 + } + })); + }, []); + + // ✅ 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息 + if (showDetailView && selectedJobOrderPickOrder) { + return ( + + + {/* 返回按钮和标题 */} + + + + {t("Job Order Pick Order Details")}: {selectedJobOrderPickOrder.pickOrderCode} + + + + {/* Job Order 信息卡片 */} + + + + + {t("Pick Order Code")}: {selectedJobOrderPickOrder.pickOrderCode} + + + {t("Job Order Code")}: {selectedJobOrderPickOrder.jobOrderCode} + + + {t("Job Order Item Name")}: {selectedJobOrderPickOrder.jobOrderName} + + + {t("Target Date")}: {selectedJobOrderPickOrder.pickOrderTargetDate} + + + + + + {t("Required Qty")}: {selectedJobOrderPickOrder.reqQty} {selectedJobOrderPickOrder.uom} + + + + + + + {/* ✅ 修改:Lot 详情表格 - 添加复选框列 */} + + + + {t("Lot Details")} + + + {detailLotDataLoading ? ( + + + + ) : ( + + + + + {t("Index")} + {t("Route")} + {t("Item Code")} + {t("Item Name")} + {t("Lot No")} + {t("Location")} + {t("Required Qty")} + {t("Actual Pick Qty")} + {t("Processing Status")} + {t("Second Scan Status")} + + + + {detailLotData.length === 0 ? ( + + + + {t("No lot details available")} + + + + ) : ( + detailLotData.map((lot, index) => ( + + + + {index + 1} + + + + + {lot.routerRoute || '-'} + + + {lot.itemCode} + {lot.itemName} + {lot.lotNo} + {lot.location} + + {lot.requiredQty?.toLocaleString() || 0} ({lot.uomShortDesc}) + + + {lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc}) + + {/* ✅ 修改:Processing Status 使用复选框 */} + + + + + + {/* ✅ 修改:Second Scan Status 使用复选框 */} + + + + + + + )) + )} + +
+
+ )} +
+
+
+
+ ); + } + + // ✅ 修改:默认列表视图 + return ( + + + {/* 搜索框 */} + + + + + {/* 加载状态 */} + {completedJobOrderPickOrdersLoading ? ( + + + + ) : ( + + {/* 结果统计 */} + + {t("Total")}: {filteredJobOrderPickOrders.length} {t("completed Job Order pick orders with matching")} + + + {/* 列表 */} + {filteredJobOrderPickOrders.length === 0 ? ( + + + {t("No completed Job Order pick orders with matching found")} + + +) : ( + + {paginatedData.map((jobOrderPickOrder) => ( + + + + + + {jobOrderPickOrder.pickOrderCode} + + + {jobOrderPickOrder.jobOrderName} - {jobOrderPickOrder.jobOrderCode} + + + {t("Completed")}: {new Date(jobOrderPickOrder.completedDate).toLocaleString()} + + + {t("Target Date")}: {jobOrderPickOrder.pickOrderTargetDate} + + + + + + {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")} + + + + + + + + + + ))} + + )} + + {/* 分页 */} + {filteredJobOrderPickOrders.length > 0 && ( + + )} + + )} + + + ); +}; + +export default CompleteJobOrderRecord; \ No newline at end of file diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index dd318dc..ae309e7 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -103,6 +103,7 @@ "Create": "創建", "Confirm Lot Substitution": "確認批號替換", "Processing...": "處理中", -"Processing...": "處理中" +"Processing...": "處理中", +"Complete Job Order Record": "已完成工單記錄" }