"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, Autocomplete, } 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 { fetchJobOrderPickOrdersrecords, fetchJobOrderPickOrderLotDetailsForPick, PrintPickRecord } 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"; import Swal from "sweetalert2"; import { PrinterCombo } from "@/app/api/settings/printer"; import { buildPrintPickRecordRequest, promptAllFloorsPlasticBoxCartonQty, promptPlasticBoxCartonQty, type PickRecordFloor, } from "./pickRecordHelpers"; import dayjs from "dayjs"; interface Props { filterArgs: Record; printerCombo: PrinterCombo[]; selectedPrinter?: PrinterCombo | null; printQty?: number; initialPickOrderCode?: string; initialTargetDate?: string; /** When true (Item Tracing `openDetail=1`), auto-open matching pick-order detail. */ openDetail?: boolean; } // 修改:已完成的 Job Order Pick Order 接口 interface CompletedJobOrderPickOrder { id: number; pickOrderId: number; pickOrderCode: string; pickOrderConsoCode: string; pickOrderTargetDate: string; pickOrderStatus: string; // Backend可能返回 [yyyy, MM, dd, HH, mm, ss] 或字符串,前端统一在 helper 里转成 YYYY-MM-DD completedDate: any; jobOrderId: number; jobOrderCode: string; jobOrderName: string; reqQty: number; uom: string; planStart: any; planEnd: any; secondScanCompleted: boolean; totalItems: number; completedItems: number; } // 新增:Lot 详情接口 interface LotDetail { lotId: number | null; lotNo: string | null; expiryDate: string | null; location: string | null; availableQty: number | null; requiredQty: number | null; actualPickQty: number | null; processingStatus: string; lotAvailability: string; pickOrderId: number; pickOrderCode: string; pickOrderConsoCode: string; pickOrderLineId: number; stockOutLineId: number | null; stockOutLineStatus: string; routerIndex: number | null; routerArea: string | null; routerRoute: string | null; uomShortDesc: string | null; secondQrScanStatus: string; itemId: number; itemCode: string; itemName: string; uomCode: string; uomDesc: string; match_status: string | null; } const CompleteJobOrderRecord: React.FC = ({ filterArgs, printerCombo, selectedPrinter: selectedPrinterProp, printQty: printQtyProp, initialPickOrderCode, initialTargetDate, openDetail = false, }) => { 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 raw = initialTargetDate?.trim() || ""; const match = raw.match(/(\d{4}-\d{2}-\d{2})/); return { completedDate: match?.[1] || dayjs().format("YYYY-MM-DD"), ...(initialPickOrderCode?.trim() ? { pickOrderCode: initialPickOrderCode.trim() } : {}), }; }); const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState([]); // Use props with fallback const selectedPrinter = selectedPrinterProp ?? (printerCombo && printerCombo.length > 0 ? printerCombo[0] : null); const printQty = printQtyProp ?? 1; const pickRecordPrintInFlightRef = useRef(false); const initialDetailOpenedRef = useRef(false); // 修改:分页状态 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 fetchJobOrderPickOrdersData = useCallback( async (forDate?: string, forStatus?: string) => { if (!currentUserId) return; setCompletedJobOrderPickOrdersLoading(true); try { const dateParam = forDate !== undefined ? forDate : searchQuery.completedDate ? String(searchQuery.completedDate) : dayjs().format("YYYY-MM-DD"); const statusParam = forStatus !== undefined ? forStatus : searchQuery.pickOrderStatus ? String(searchQuery.pickOrderStatus) : null; const data = await fetchJobOrderPickOrdersrecords( dateParam?.trim() ? dateParam.trim() : null, statusParam?.trim() ? statusParam.trim() : null, ); const safeData = Array.isArray(data) ? data : []; setCompletedJobOrderPickOrders(safeData); setFilteredJobOrderPickOrders(safeData); } finally { setCompletedJobOrderPickOrdersLoading(false); } }, [currentUserId, searchQuery.completedDate, searchQuery.pickOrderStatus], ); // 新增:获取 lot 详情数据(使用新的API) const fetchLotDetailsData = useCallback(async (pickOrderId: number) => { setDetailLotDataLoading(true); try { console.log("🔍 Fetching lot details for completed pick order:", pickOrderId); const lotDetails = await fetchJobOrderPickOrderLotDetailsForPick(pickOrderId); setDetailLotData(Array.isArray(lotDetails) ? lotDetails : []); console.log(" Fetched lot details:", lotDetails); } catch (error) { console.error("❌ Error fetching lot details:", error); setDetailLotData([]); } finally { setDetailLotDataLoading(false); } }, []); // 修改:初始化时获取数据 useEffect(() => { if (!currentUserId) return; const d = searchQuery?.completedDate; const s = searchQuery?.pickOrderStatus; const dateStr = d != null && String(d).trim() !== "" ? String(d).trim() : ""; const statusStr = s != null && String(s).trim() !== "" ? String(s).trim() : ""; void fetchJobOrderPickOrdersData(dateStr || undefined, statusStr || undefined); }, [currentUserId, searchQuery?.completedDate, searchQuery?.pickOrderStatus, fetchJobOrderPickOrdersData]); // 修改:搜索功能(只更新 query;实际过滤交给 useEffect + date filter 统一处理) const handleSearch = useCallback((query: Record) => { setSearchQuery({ ...query }); }, []); const formatDateTime = (value: any) => { if (!value) return "-"; // 后端发来的是 [yyyy, MM, dd, HH, mm, ss] if (Array.isArray(value)) { const [year, month, day, hour = 0, minute = 0, second = 0] = value; return new Date(year, month - 1, day, hour, minute, second).toLocaleString(); } // 如果以后改成字符串/ISO,也兼容 const d = new Date(value); return isNaN(d.getTime()) ? "-" : d.toLocaleString(); }; const toDateYMD = (value: any): string | null => { if (!value) return null; // Backend 常见格式:[yyyy, MM, dd, HH, mm, ss] if (Array.isArray(value)) { const [year, month, day] = value; if (!year || !month || !day) return null; return dayjs(new Date(Number(year), Number(month) - 1, Number(day))).format("YYYY-MM-DD"); } // 兼容 ISO 字符串 / Date if (typeof value === "string") { const d = dayjs(value); return d.isValid() ? d.format("YYYY-MM-DD") : null; } if (value instanceof Date) { return dayjs(value).format("YYYY-MM-DD"); } return null; }; useEffect(() => { const query = searchQuery || {}; const dateFilter = query.completedDate ? String(query.completedDate) : ""; const filtered = (Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []).filter((pickOrder) => { const pickOrderDateYMD = toDateYMD((pickOrder as any).completedDate ?? (pickOrder as any).planEnd); const dateMatch = !dateFilter || pickOrderDateYMD === dateFilter; const pickOrderCodeMatch = !query.pickOrderCode || pickOrder.pickOrderCode?.toLowerCase().includes(String(query.pickOrderCode).toLowerCase()); const jobOrderCodeMatch = !query.jobOrderCode || pickOrder.jobOrderCode?.toLowerCase().includes(String(query.jobOrderCode).toLowerCase()); const jobOrderNameMatch = !query.jobOrderName || pickOrder.jobOrderName?.toLowerCase().includes(String(query.jobOrderName).toLowerCase()); return dateMatch && pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch; }); setFilteredJobOrderPickOrders(filtered); }, [completedJobOrderPickOrders, searchQuery]); // 修改:重置搜索 const handleSearchReset = useCallback(() => { setSearchQuery({}); }, []); // 修改:分页功能 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("Target Date"), paramName: "completedDate", type: "date", }, { label: t("Pick Order Code"), paramName: "pickOrderCode", type: "text", }, { label: t("Job Order Code"), paramName: "jobOrderCode", type: "text", }, { label: t("Pick Order Status"), paramName: "pickOrderStatus", type: "select-labelled", options: [ { label: t("Released"), value: "RELEASED" }, { label: t("Completed"), value: "COMPLETED" }, ], // 依你后端实际枚举 }, { 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]); useEffect(() => { if (!openDetail) return; if (initialDetailOpenedRef.current || completedJobOrderPickOrdersLoading) return; const code = initialPickOrderCode?.trim(); if (!code) return; const match = completedJobOrderPickOrders.find( (row) => row.pickOrderCode?.trim() === code || row.pickOrderConsoCode?.trim() === code, ); if (!match) return; initialDetailOpenedRef.current = true; void handleDetailClick(match); }, [ openDetail, completedJobOrderPickOrders, completedJobOrderPickOrdersLoading, initialPickOrderCode, handleDetailClick, ]); // 修改:返回列表视图 const handleBackToList = useCallback(() => { setShowDetailView(false); setSelectedJobOrderPickOrder(null); setDetailLotData([]); // 返回列表时禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, tabIndex: 3 } })); }, []); const handlePickRecord = useCallback(async ( jobOrderPickOrder: CompletedJobOrderPickOrder, floor: PickRecordFloor, ) => { if (pickRecordPrintInFlightRef.current) return; try { if (!jobOrderPickOrder) { console.error("No selected job order pick order available"); return; } if (!selectedPrinter) { Swal.fire({ position: "bottom-end", icon: "warning", text: t("Please select a printer first"), showConfirmButton: false, timer: 1500 }); return; } if (!printQty || printQty < 1) { Swal.fire({ position: "bottom-end", icon: "warning", text: t("Please enter a valid print quantity (at least 1)"), showConfirmButton: false, timer: 1500 }); return; } const pickOrderId = jobOrderPickOrder.pickOrderId; let printRequest; if (floor === "ALL") { const allFloorsQty = await promptAllFloorsPlasticBoxCartonQty(t, pickOrderId); if (allFloorsQty === null) { return; } printRequest = buildPrintPickRecordRequest({ pickOrderId, printerId: selectedPrinter.id, printQty, floor, allFloorsQty, }); } else { const plasticBoxCartonQty = await promptPlasticBoxCartonQty(t); if (plasticBoxCartonQty === null) { return; } printRequest = buildPrintPickRecordRequest({ pickOrderId, printerId: selectedPrinter.id, printQty, floor, plasticBoxCartonQty, }); } pickRecordPrintInFlightRef.current = true; const response = await PrintPickRecord(printRequest); if (response.success) { Swal.fire({ position: "bottom-end", icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 }); } else { Swal.fire({ position: "bottom-end", icon: "error", text: response.message || t("Print failed"), showConfirmButton: false, timer: 1500 }); } } catch (error) { console.error("error: ", error); Swal.fire({ position: "bottom-end", icon: "error", text: t("An error occurred while printing"), showConfirmButton: false, timer: 1500 }); } finally { pickRecordPrintInFlightRef.current = false; } }, [t, selectedPrinter, printQty]); // 修改:如果显示详情视图,渲染 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("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.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) => { const normalizedStatus = String(jobOrderPickOrder.pickOrderStatus ?? "").toLowerCase(); return ( {jobOrderPickOrder.jobOrderCode} {jobOrderPickOrder.jobOrderName} - {jobOrderPickOrder.pickOrderCode} {t("Completed")}: {formatDateTime(jobOrderPickOrder.planEnd)} {t("Target Date")}: {jobOrderPickOrder.pickOrderTargetDate} {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")} ); })} )} {/* 分页 */} {filteredJobOrderPickOrders.length > 0 && ( )} )} ); }; export default CompleteJobOrderRecord;