"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, } 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 { fetchALLPickOrderLineLotDetails, updateStockOutLineStatus, createStockOutLine, recordPickExecutionIssue, fetchFGPickOrders, FGPickOrderResponse, autoAssignAndReleasePickOrder, AutoAssignReleaseResponse, checkPickOrderCompletion, PickOrderCompletionResponse, checkAndCompletePickOrderByConsoCode, fetchCompletedDoPickOrders, CompletedDoPickOrderResponse, CompletedDoPickOrderSearchParams, fetchLotDetailsByDoPickOrderRecordId } from "@/app/api/pickOrder/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { FormProvider, useForm, } from "react-hook-form"; import SearchBox, { Criterion } from "../SearchBox"; import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; import QrCodeIcon from '@mui/icons-material/QrCode'; import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider'; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import { fetchStockInLineInfo } from "@/app/api/po/actions"; import GoodPickExecutionForm from "./GoodPickExecutionForm"; import FGPickOrderCard from "./FGPickOrderCard"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { printDN, printDNLabels } from "@/app/api/do/actions"; import Swal from "sweetalert2"; interface Props { filterArgs: Record; } // 新增:Pick Order 数据接口 interface PickOrderData { pickOrderId: number; pickOrderCode: string; pickOrderConsoCode: string; pickOrderStatus: string; completedDate: string; lots: any[]; } const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; // 新增:已完成 DO Pick Orders 状态 const [completedDoPickOrders, setCompletedDoPickOrders] = useState([]); const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false); // 新增:详情视图状态 const [selectedDoPickOrder, setSelectedDoPickOrder] = useState(null); const [showDetailView, setShowDetailView] = useState(false); const [detailLotData, setDetailLotData] = useState([]); // 新增:搜索状态 const [searchQuery, setSearchQuery] = useState>({}); const [filteredDoPickOrders, setFilteredDoPickOrders] = useState([]); // 新增:分页状态 const [paginationController, setPaginationController] = useState({ pageNum: 0, pageSize: 10, }); const formProps = useForm(); const errors = formProps.formState.errors; const handleDN = useCallback(async (recordId: number) => { const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ min: "1", step: "1" }, inputValidator: (value) => { if(!value){ return t("You need to enter a number") } if(parseInt(value) < 1){ return t("Number must be at least 1"); } return null }, showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); if (askNumofCarton.isConfirmed) { const numOfCartons = askNumofCarton.value; try{ const printRequest = { printerId: 1, printQty: 1, isDraft: false, numOfCarton: numOfCartons, doPickOrderId: recordId }; console.log("Printing Delivery Note with request: ", printRequest); const response = await printDN(printRequest); console.log("Print Delivery Note response: ", response); if(response.success){ Swal.fire({ position: "bottom-end", icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 }); } else { console.error("Print failed: ", response.message); } } catch(error){ console.error("error: ", error) } } }, [t]); const handleDNandLabel = useCallback(async (recordId: number) => { const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ min: "1", step: "1" }, inputValidator: (value) => { if(!value){ return t("You need to enter a number") } if(parseInt(value) < 1){ return t("Number must be at least 1"); } return null }, showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); if (askNumofCarton.isConfirmed) { const numOfCartons = askNumofCarton.value; try{ const printDNRequest = { printerId: 1, printQty: 1, isDraft: false, numOfCarton: numOfCartons, doPickOrderId: recordId, }; const printDNLabelsRequest = { printerId: 1, printQty: 1, numOfCarton: numOfCartons, doPickOrderId: recordId }; console.log("Printing Labels with request: ", printDNLabelsRequest); console.log("Printing DN with request: ", printDNRequest); const LabelsResponse = await printDNLabels(printDNLabelsRequest); const DNResponse = await printDN(printDNRequest); console.log("Print Labels response: ", LabelsResponse); console.log("Print DN response: ", DNResponse); if(LabelsResponse.success && DNResponse.success){ Swal.fire({ position: "bottom-end", icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 }); } else { if(!LabelsResponse.success){ console.error("Print failed: ", LabelsResponse.message); } else{ console.error("Print failed: ", DNResponse.message); } } } catch(error){ console.error("error: ", error) } } }, [t]); const handleLabel = useCallback(async (recordId: number) => { const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ min: "1", step: "1" }, inputValidator: (value) => { if(!value){ return t("You need to enter a number") } if(parseInt(value) < 1){ return t("Number must be at least 1"); } return null }, showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); if (askNumofCarton.isConfirmed) { const numOfCartons = askNumofCarton.value; try{ const printRequest = { printerId: 1, printQty: 1, numOfCarton: numOfCartons, doPickOrderId: recordId }; console.log("Printing Labels with request: ", printRequest); const response = await printDNLabels(printRequest); console.log("Print Labels response: ", response); if(response.success){ Swal.fire({ position: "bottom-end", icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 }); } else { console.error("Print failed: ", response.message); } } catch(error){ console.error("error: ", error) } } }, [t]); // 修改:使用新的 API 获取已完成的 DO Pick Orders const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { if (!currentUserId) return; setCompletedDoPickOrdersLoading(true); try { console.log("🔍 Fetching completed DO pick orders with params:", searchParams); const completedDoPickOrders = await fetchCompletedDoPickOrders(currentUserId, searchParams); setCompletedDoPickOrders(completedDoPickOrders); setFilteredDoPickOrders(completedDoPickOrders); console.log(" Fetched completed DO pick orders:", completedDoPickOrders); } catch (error) { console.error("❌ Error fetching completed DO pick orders:", error); setCompletedDoPickOrders([]); setFilteredDoPickOrders([]); } finally { setCompletedDoPickOrdersLoading(false); } }, [currentUserId]); // 初始化时获取数据 useEffect(() => { if (currentUserId) { fetchCompletedDoPickOrdersData(); } }, [currentUserId, fetchCompletedDoPickOrdersData]); // 修改:搜索功能使用新的 API const handleSearch = useCallback((query: Record) => { setSearchQuery({ ...query }); console.log("Search query:", query); const searchParams: CompletedDoPickOrderSearchParams = { targetDate: query.targetDate || undefined, shopName: query.shopName || undefined, deliveryNoteCode: query.deliveryNoteCode || undefined, //ticketNo: query.ticketNo || undefined, }; // 使用新的 API 进行搜索 fetchCompletedDoPickOrdersData(searchParams); }, [fetchCompletedDoPickOrdersData]); // 修复:重命名函数避免重复声明 const handleSearchReset = useCallback(() => { setSearchQuery({}); fetchCompletedDoPickOrdersData(); // 重新获取所有数据 }, [fetchCompletedDoPickOrdersData]); // 分页功能 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(() => { const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; 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 handleDetailClick = useCallback(async (doPickOrder: CompletedDoPickOrderResponse) => { setSelectedDoPickOrder(doPickOrder); setShowDetailView(true); try { // 使用 doPickOrderRecordId 而不是 pickOrderId const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId); console.log(" Loaded hierarchical lot data:", hierarchicalData); // 转换为平铺格式 const flatLotData: any[] = []; if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { const toProc = (s?: string) => { if (!s) return 'pending'; const v = s.toLowerCase(); if (v === 'completed' || v === 'complete') return 'completed'; if (v === 'rejected') return 'rejected'; if (v === 'partially_completed') return 'pending'; return 'pending'; }; hierarchicalData.pickOrders.forEach((po: any) => { po.pickOrderLines?.forEach((line: any) => { const lineStockouts = line.stockouts || []; // ← 用“行级” stockouts const lots = line.lots || []; if (lots.length > 0) { lots.forEach((lot: any) => { // 先按该 lot 过滤出匹配的 stockouts(同 lotId) const sos = lineStockouts.filter((so: any) => (so.lotId ?? null) === (lot.id ?? null)); if (sos.length > 0) { sos.forEach((so: any) => { flatLotData.push({ pickOrderCode: po.pickOrderCode, itemCode: line.item?.code, itemName: line.item?.name, lotNo: so.lotNo || lot.lotNo, location: so.location || lot.location, deliveryOrderCode: po.deliveryOrderCode, requiredQty: lot.requiredQty, actualPickQty: (so.qty ?? lot.actualPickQty ?? 0), processingStatus: toProc(so.status), // ← 用 stockouts.status stockOutLineStatus: so.status, // 可选保留 noLot: so.noLot === true }); }); } else { // 没有匹配的 stockouts,也要显示 lot flatLotData.push({ pickOrderCode: po.pickOrderCode, itemCode: line.item?.code, itemName: line.item?.name, lotNo: lot.lotNo, location: lot.location, deliveryOrderCode: po.deliveryOrderCode, requiredQty: lot.requiredQty, actualPickQty: lot.actualPickQty ?? 0, processingStatus: lot.processingStatus || 'pending', stockOutLineStatus: lot.stockOutLineStatus || 'pending', noLot: false }); } }); } else if (lineStockouts.length > 0) { // lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示 lineStockouts.forEach((so: any) => { flatLotData.push({ pickOrderCode: po.pickOrderCode, itemCode: line.item?.code, itemName: line.item?.name, lotNo: so.lotNo || '', location: so.location || '', deliveryOrderCode: po.deliveryOrderCode, requiredQty: line.requiredQty ?? 0, // 行级没有 lot 时,用行的 requiredQty actualPickQty: (so.qty ?? 0), processingStatus: toProc(so.status), stockOutLineStatus: so.status, noLot: so.noLot === true }); }); } }); }); } setDetailLotData(flatLotData); // 计算完成状态 const allCompleted = flatLotData.length > 0 && flatLotData.every(lot => lot.processingStatus === 'completed' ); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, tabIndex: 2 } })); } catch (error) { // 添加 catch 块 console.error("❌ Error loading detail lot data:", error); setDetailLotData([]); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, tabIndex: 2 } })); } }, []); // 返回列表视图 const handleBackToList = useCallback(() => { setShowDetailView(false); setSelectedDoPickOrder(null); setDetailLotData([]); // 返回列表时禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, tabIndex: 2 } })); }, []); // 如果显示详情视图,渲染类似 GoodPickExecution 的表格 // 如果显示详情视图,渲染层级结构 if (showDetailView && selectedDoPickOrder) { return ( {/* 返回按钮和标题 */} {t("Pick Order Details")}: {selectedDoPickOrder.ticketNo} {/* FG 订单基本信息 */} {t("Shop Name")}: {selectedDoPickOrder.shopName} {t("Store ID")}: {selectedDoPickOrder.storeId} {t("Ticket No.")}: {selectedDoPickOrder.ticketNo} {t("Truck Lance Code")}: {selectedDoPickOrder.truckLanceCode} {t("Completed Date")}: {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( {t("Pick Order Code(s)")}:{" "} {(typeof selectedDoPickOrder.pickOrderCodes === 'string' ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim()) : Array.isArray(selectedDoPickOrder.pickOrderCodes) ? selectedDoPickOrder.pickOrderCodes : [] ).filter(Boolean).join(', ')} )} {/* 数据检查 */} {detailLotData.length === 0 ? ( {t("No lot details found for this order")} ) : ( /* 按 Pick Order 分组显示 */ {/* 按 pickOrderCode 分组 */} {Object.entries( detailLotData.reduce((acc: any, lot: any) => { const key = lot.pickOrderCode || 'Unknown'; if (!acc[key]) { acc[key] = { lots: [], deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // 保存对应的 deliveryOrderCode }; } acc[key].lots.push(lot); return acc; }, {}) ).map(([pickOrderCode, data]: [string, any]) => ( }> {t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")}) {" | "} {t("Delivery Order")}: {data.deliveryOrderCode} {/* 使用保存的 deliveryOrderCode */} {t("Index")} {t("Item Code")} {t("Item Name")} {t("Lot No")} {t("Location")} {t("Required Qty")} {t("Actual Pick Qty")} {t("Status")} {data.lots.map((lot: any, index: number) => ( {index + 1} {lot.itemCode || 'N/A'} {lot.itemName || 'N/A'} {lot.lotNo || 'N/A'} {lot.location || 'N/A'} {lot.requiredQty || 0} {lot.actualPickQty || 0} ))}
))}
)}
); } // 默认列表视图 return ( {/* 搜索框 */} {/* 加载状态 */} {completedDoPickOrdersLoading ? ( ) : ( {/* 结果统计 */} {t("Completed DO pick orders: ")} {filteredDoPickOrders.length} {/* 列表 */} {filteredDoPickOrders.length === 0 ? ( {t("No completed DO pick orders found")} ) : ( {paginatedData.map((doPickOrder) => ( {doPickOrder.deliveryNoteCode} {doPickOrder.shopName} {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} {doPickOrder.fgPickOrders.length} {t("FG orders")} <> ))} )} {/* 分页 */} {filteredDoPickOrders.length > 0 && ( )} )} ); }; export default GoodPickExecutionRecord;