"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 (doPickOrderId: 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: doPickOrderId }; 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 (doPickOrderId: 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: doPickOrderId, }; const printDNLabelsRequest = { printerId: 1, printQty: 1, numOfCarton: numOfCartons, doPickOrderId: doPickOrderId }; 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 (doPickOrderId: 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: doPickOrderId }; 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 = { pickOrderCode: query.pickOrderCode || undefined, shopName: query.shopName || undefined, deliveryNo: query.deliveryNo || 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("Pick Order Code"), paramName: "pickOrderCode", type: "text", }, { label: t("Shop Name"), paramName: "shopName", type: "text", }, { label: t("Delivery No"), paramName: "deliveryNo", type: "text", } ]; 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) { hierarchicalData.pickOrders.forEach((po: any) => { po.pickOrderLines?.forEach((line: any) => { if (line.lots && line.lots.length > 0) { line.lots.forEach((lot: any) => { 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, processingStatus: lot.processingStatus, stockOutLineStatus: lot.stockOutLineStatus }); }); } }); }); } 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)} {/* ✅ 添加:多个 Pick Orders 信息(如果有) */} {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( {t("This ticket contains")} {selectedDoPickOrder.pickOrderIds.length} {t("pick orders")}: {selectedDoPickOrder.pickOrderCodes?.split(', ').map((code, idx) => ( ))} )} {/* ✅ 数据检查 */} {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.pickOrderCode} {doPickOrder.shopName} - {doPickOrder.deliveryNo} {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} {doPickOrder.fgPickOrders.length} {t("FG orders")} <> ) ))} )} {/* 分页 */} {filteredDoPickOrders.length > 0 && ( )} )} ); }; export default GoodPickExecutionRecord;