FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
28 KiB

  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Stack,
  6. TextField,
  7. Typography,
  8. Alert,
  9. CircularProgress,
  10. Table,
  11. TableBody,
  12. TableCell,
  13. TableContainer,
  14. TableHead,
  15. TableRow,
  16. Paper,
  17. TablePagination,
  18. Modal,
  19. Card,
  20. CardContent,
  21. CardActions,
  22. Chip,
  23. Accordion,
  24. AccordionSummary,
  25. AccordionDetails,
  26. } from "@mui/material";
  27. import { PrinterCombo } from "@/app/api/settings/printer";
  28. import { Autocomplete } from "@mui/material";
  29. import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
  30. import { useCallback, useEffect, useState, useRef, useMemo } from "react";
  31. import { useTranslation } from "react-i18next";
  32. import { useRouter } from "next/navigation";
  33. import {
  34. fetchALLPickOrderLineLotDetails,
  35. updateStockOutLineStatus,
  36. createStockOutLine,
  37. recordPickExecutionIssue,
  38. fetchFGPickOrders,
  39. FGPickOrderResponse,
  40. autoAssignAndReleasePickOrder,
  41. AutoAssignReleaseResponse,
  42. checkPickOrderCompletion,
  43. PickOrderCompletionResponse,
  44. checkAndCompletePickOrderByConsoCode,
  45. fetchCompletedDoPickOrders,
  46. CompletedDoPickOrderResponse,
  47. CompletedDoPickOrderSearchParams,
  48. fetchLotDetailsByDoPickOrderRecordId
  49. } from "@/app/api/pickOrder/actions";
  50. import { fetchNameList, NameList } from "@/app/api/user/actions";
  51. import {
  52. FormProvider,
  53. useForm,
  54. } from "react-hook-form";
  55. import SearchBox, { Criterion } from "../SearchBox";
  56. import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
  57. import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
  58. import QrCodeIcon from '@mui/icons-material/QrCode';
  59. import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
  60. import { useSession } from "next-auth/react";
  61. import { SessionWithTokens } from "@/config/authConfig";
  62. import { fetchStockInLineInfo } from "@/app/api/po/actions";
  63. import GoodPickExecutionForm from "./GoodPickExecutionForm";
  64. import FGPickOrderCard from "./FGPickOrderCard";
  65. import dayjs from "dayjs";
  66. import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  67. import { printDN, printDNLabels } from "@/app/api/do/actions";
  68. import Swal from "sweetalert2";
  69. interface Props {
  70. filterArgs: Record<string, any>;
  71. printerCombo: PrinterCombo[];
  72. a4Printer: PrinterCombo | null; // A4 打印机(DN 用)
  73. labelPrinter: PrinterCombo | null;
  74. }
  75. // 新增:Pick Order 数据接口
  76. interface PickOrderData {
  77. pickOrderId: number;
  78. pickOrderCode: string;
  79. pickOrderConsoCode: string;
  80. pickOrderStatus: string;
  81. completedDate: string;
  82. lots: any[];
  83. }
  84. const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs, printerCombo, a4Printer, labelPrinter }) => {
  85. const { t } = useTranslation("pickOrder");
  86. const router = useRouter();
  87. const { data: session } = useSession() as { data: SessionWithTokens | null };
  88. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  89. // 新增:已完成 DO Pick Orders 状态
  90. const [completedDoPickOrders, setCompletedDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]);
  91. const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false);
  92. // 新增:详情视图状态
  93. const [selectedDoPickOrder, setSelectedDoPickOrder] = useState<CompletedDoPickOrderResponse | null>(null);
  94. const [showDetailView, setShowDetailView] = useState(false);
  95. const [detailLotData, setDetailLotData] = useState<any[]>([]);
  96. // 新增:搜索状态
  97. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  98. const [filteredDoPickOrders, setFilteredDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]);
  99. // 新增:分页状态
  100. const [paginationController, setPaginationController] = useState({
  101. pageNum: 0,
  102. pageSize: 10,
  103. });
  104. const formProps = useForm();
  105. const errors = formProps.formState.errors;
  106. const handleDN = useCallback(async (recordId: number) => {
  107. if (!a4Printer) {
  108. Swal.fire({
  109. position: "bottom-end",
  110. icon: "warning",
  111. text: t("Please select a printer first"),
  112. showConfirmButton: false,
  113. timer: 1500
  114. });
  115. return;
  116. }
  117. const askNumofCarton = await Swal.fire({
  118. title: t("Enter the number of cartons: "),
  119. icon: "info",
  120. input: "number",
  121. inputPlaceholder: t("Number of cartons"),
  122. inputAttributes:{
  123. min: "1",
  124. step: "1"
  125. },
  126. inputValidator: (value) => {
  127. if(!value){
  128. return t("You need to enter a number")
  129. }
  130. if(parseInt(value) < 1){
  131. return t("Number must be at least 1");
  132. }
  133. return null
  134. },
  135. showCancelButton: true,
  136. confirmButtonText: t("Confirm"),
  137. cancelButtonText: t("Cancel"),
  138. confirmButtonColor: "#8dba00",
  139. cancelButtonColor: "#F04438",
  140. showLoaderOnConfirm: true,
  141. allowOutsideClick: () => !Swal.isLoading()
  142. });
  143. if (askNumofCarton.isConfirmed) {
  144. const numOfCartons = askNumofCarton.value;
  145. try{
  146. const printRequest = {
  147. printerId: a4Printer.id,
  148. printQty: 1,
  149. isDraft: false,
  150. numOfCarton: numOfCartons,
  151. doPickOrderId: recordId
  152. };
  153. console.log("Printing Delivery Note with request: ", printRequest);
  154. const response = await printDN(printRequest);
  155. console.log("Print Delivery Note response: ", response);
  156. if(response.success){
  157. Swal.fire({
  158. position: "bottom-end",
  159. icon: "success",
  160. text: t("Printed Successfully."),
  161. showConfirmButton: false,
  162. timer: 1500
  163. });
  164. } else {
  165. console.error("Print failed: ", response.message);
  166. }
  167. } catch(error){
  168. console.error("error: ", error)
  169. }
  170. }
  171. }, [t, a4Printer]);
  172. const handleDNandLabel = useCallback(async (recordId: number) => {
  173. if (!a4Printer || !labelPrinter) {
  174. Swal.fire({
  175. position: "bottom-end",
  176. icon: "warning",
  177. text: t("Please select a printer first"),
  178. showConfirmButton: false,
  179. timer: 1500
  180. });
  181. return;
  182. }
  183. if (!labelPrinter) {
  184. Swal.fire({
  185. position: "bottom-end",
  186. icon: "warning",
  187. text: t("Please select a label printer first"),
  188. showConfirmButton: false,
  189. timer: 1500
  190. });
  191. return;
  192. }
  193. const askNumofCarton = await Swal.fire({
  194. title: t("Enter the number of cartons: "),
  195. icon: "info",
  196. input: "number",
  197. inputPlaceholder: t("Number of cartons"),
  198. inputAttributes:{
  199. min: "1",
  200. step: "1"
  201. },
  202. inputValidator: (value) => {
  203. if(!value){
  204. return t("You need to enter a number")
  205. }
  206. if(parseInt(value) < 1){
  207. return t("Number must be at least 1");
  208. }
  209. return null
  210. },
  211. showCancelButton: true,
  212. confirmButtonText: t("Confirm"),
  213. cancelButtonText: t("Cancel"),
  214. confirmButtonColor: "#8dba00",
  215. cancelButtonColor: "#F04438",
  216. showLoaderOnConfirm: true,
  217. allowOutsideClick: () => !Swal.isLoading()
  218. });
  219. if (askNumofCarton.isConfirmed) {
  220. const numOfCartons = askNumofCarton.value;
  221. try{
  222. const printDNRequest = {
  223. printerId: a4Printer.id,
  224. printQty: 1,
  225. isDraft: false,
  226. numOfCarton: numOfCartons,
  227. doPickOrderId: recordId,
  228. };
  229. const printDNLabelsRequest = {
  230. printerId: labelPrinter.id,
  231. printQty: 1,
  232. numOfCarton: numOfCartons,
  233. doPickOrderId: recordId
  234. };
  235. console.log("Printing Labels with request: ", printDNLabelsRequest);
  236. console.log("Printing DN with request: ", printDNRequest);
  237. const LabelsResponse = await printDNLabels(printDNLabelsRequest);
  238. const DNResponse = await printDN(printDNRequest);
  239. console.log("Print Labels response: ", LabelsResponse);
  240. console.log("Print DN response: ", DNResponse);
  241. if(LabelsResponse.success && DNResponse.success){
  242. Swal.fire({
  243. position: "bottom-end",
  244. icon: "success",
  245. text: t("Printed Successfully."),
  246. showConfirmButton: false,
  247. timer: 1500
  248. });
  249. } else {
  250. if(!LabelsResponse.success){
  251. console.error("Print failed: ", LabelsResponse.message);
  252. }
  253. else{
  254. console.error("Print failed: ", DNResponse.message);
  255. }
  256. }
  257. } catch(error){
  258. console.error("error: ", error)
  259. }
  260. }
  261. }, [t, a4Printer, labelPrinter]);
  262. const handleLabel = useCallback(async (recordId: number) => {
  263. const askNumofCarton = await Swal.fire({
  264. title: t("Enter the number of cartons: "),
  265. icon: "info",
  266. input: "number",
  267. inputPlaceholder: t("Number of cartons"),
  268. inputAttributes:{
  269. min: "1",
  270. step: "1"
  271. },
  272. inputValidator: (value) => {
  273. if(!value){
  274. return t("You need to enter a number")
  275. }
  276. if(parseInt(value) < 1){
  277. return t("Number must be at least 1");
  278. }
  279. return null
  280. },
  281. showCancelButton: true,
  282. confirmButtonText: t("Confirm"),
  283. cancelButtonText: t("Cancel"),
  284. confirmButtonColor: "#8dba00",
  285. cancelButtonColor: "#F04438",
  286. showLoaderOnConfirm: true,
  287. allowOutsideClick: () => !Swal.isLoading()
  288. });
  289. if (askNumofCarton.isConfirmed) {
  290. const numOfCartons = askNumofCarton.value;
  291. try{
  292. const printRequest = {
  293. printerId: labelPrinter?.id ?? 0,
  294. printQty: 1,
  295. numOfCarton: numOfCartons,
  296. doPickOrderId: recordId
  297. };
  298. console.log("Printing Labels with request: ", printRequest);
  299. const response = await printDNLabels(printRequest);
  300. console.log("Print Labels response: ", response);
  301. if(response.success){
  302. Swal.fire({
  303. position: "bottom-end",
  304. icon: "success",
  305. text: t("Printed Successfully."),
  306. showConfirmButton: false,
  307. timer: 1500
  308. });
  309. } else {
  310. console.error("Print failed: ", response.message);
  311. }
  312. } catch(error){
  313. console.error("error: ", error)
  314. }
  315. }
  316. }, [t, labelPrinter]);
  317. // 修改:使用新的 API 获取已完成的 DO Pick Orders
  318. const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => {
  319. if (!currentUserId) return;
  320. setCompletedDoPickOrdersLoading(true);
  321. try {
  322. console.log("🔍 Fetching completed DO pick orders with params:", searchParams);
  323. const completedDoPickOrders = await fetchCompletedDoPickOrders(currentUserId, searchParams);
  324. setCompletedDoPickOrders(completedDoPickOrders);
  325. setFilteredDoPickOrders(completedDoPickOrders);
  326. console.log(" Fetched completed DO pick orders:", completedDoPickOrders);
  327. } catch (error) {
  328. console.error("❌ Error fetching completed DO pick orders:", error);
  329. setCompletedDoPickOrders([]);
  330. setFilteredDoPickOrders([]);
  331. } finally {
  332. setCompletedDoPickOrdersLoading(false);
  333. }
  334. }, [currentUserId]);
  335. // 初始化时获取数据
  336. useEffect(() => {
  337. if (currentUserId) {
  338. fetchCompletedDoPickOrdersData();
  339. }
  340. }, [currentUserId, fetchCompletedDoPickOrdersData]);
  341. // 修改:搜索功能使用新的 API
  342. const handleSearch = useCallback((query: Record<string, any>) => {
  343. setSearchQuery({ ...query });
  344. console.log("Search query:", query);
  345. const searchParams: CompletedDoPickOrderSearchParams = {
  346. targetDate: query.targetDate || undefined,
  347. shopName: query.shopName || undefined,
  348. deliveryNoteCode: query.deliveryNoteCode || undefined,
  349. //ticketNo: query.ticketNo || undefined,
  350. };
  351. // 使用新的 API 进行搜索
  352. fetchCompletedDoPickOrdersData(searchParams);
  353. }, [fetchCompletedDoPickOrdersData]);
  354. // 修复:重命名函数避免重复声明
  355. const handleSearchReset = useCallback(() => {
  356. setSearchQuery({});
  357. fetchCompletedDoPickOrdersData(); // 重新获取所有数据
  358. }, [fetchCompletedDoPickOrdersData]);
  359. // 分页功能
  360. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  361. setPaginationController(prev => ({
  362. ...prev,
  363. pageNum: newPage,
  364. }));
  365. }, []);
  366. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  367. const newPageSize = parseInt(event.target.value, 10);
  368. setPaginationController({
  369. pageNum: 0,
  370. pageSize: newPageSize,
  371. });
  372. }, []);
  373. // 分页数据
  374. const paginatedData = useMemo(() => {
  375. const startIndex = paginationController.pageNum * paginationController.pageSize;
  376. const endIndex = startIndex + paginationController.pageSize;
  377. return filteredDoPickOrders.slice(startIndex, endIndex);
  378. }, [filteredDoPickOrders, paginationController]);
  379. // 搜索条件
  380. const searchCriteria: Criterion<any>[] = [
  381. {
  382. label: t("Delivery Note Code"),
  383. paramName: "deliveryNoteCode",
  384. type: "text",
  385. },
  386. {
  387. label: t("Shop Name"),
  388. paramName: "shopName",
  389. type: "text",
  390. },
  391. {
  392. label: t("Target Date"),
  393. paramName: "targetDate",
  394. type: "date",
  395. }
  396. ];
  397. const handleDetailClick = useCallback(async (doPickOrder: CompletedDoPickOrderResponse) => {
  398. setSelectedDoPickOrder(doPickOrder);
  399. setShowDetailView(true);
  400. try {
  401. // 使用 doPickOrderRecordId 而不是 pickOrderId
  402. const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId);
  403. console.log(" Loaded hierarchical lot data:", hierarchicalData);
  404. // 转换为平铺格式
  405. const flatLotData: any[] = [];
  406. if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) {
  407. const toProc = (s?: string) => {
  408. if (!s) return 'pending';
  409. const v = s.toLowerCase();
  410. if (v === 'completed' || v === 'complete') return 'completed';
  411. if (v === 'rejected') return 'rejected';
  412. if (v === 'partially_completed') return 'pending';
  413. return 'pending';
  414. };
  415. hierarchicalData.pickOrders.forEach((po: any) => {
  416. po.pickOrderLines?.forEach((line: any) => {
  417. const lineStockouts = line.stockouts || []; // ← 用“行级” stockouts
  418. const lots = line.lots || [];
  419. if (lots.length > 0) {
  420. lots.forEach((lot: any) => {
  421. // 先按该 lot 过滤出匹配的 stockouts(同 lotId)
  422. const sos = lineStockouts.filter((so: any) => (so.lotId ?? null) === (lot.id ?? null));
  423. if (sos.length > 0) {
  424. sos.forEach((so: any) => {
  425. flatLotData.push({
  426. pickOrderCode: po.pickOrderCode,
  427. itemCode: line.item?.code,
  428. itemName: line.item?.name,
  429. lotNo: so.lotNo || lot.lotNo,
  430. location: so.location || lot.location,
  431. deliveryOrderCode: po.deliveryOrderCode,
  432. requiredQty: lot.requiredQty,
  433. actualPickQty: (so.qty ?? lot.actualPickQty ?? 0),
  434. processingStatus: toProc(so.status), // ← 用 stockouts.status
  435. stockOutLineStatus: so.status, // 可选保留
  436. noLot: so.noLot === true
  437. });
  438. });
  439. } else {
  440. // 没有匹配的 stockouts,也要显示 lot
  441. flatLotData.push({
  442. pickOrderCode: po.pickOrderCode,
  443. itemCode: line.item?.code,
  444. itemName: line.item?.name,
  445. lotNo: lot.lotNo,
  446. location: lot.location,
  447. deliveryOrderCode: po.deliveryOrderCode,
  448. requiredQty: lot.requiredQty,
  449. actualPickQty: lot.actualPickQty ?? 0,
  450. processingStatus: lot.processingStatus || 'pending',
  451. stockOutLineStatus: lot.stockOutLineStatus || 'pending',
  452. noLot: false
  453. });
  454. }
  455. });
  456. } else if (lineStockouts.length > 0) {
  457. // lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示
  458. lineStockouts.forEach((so: any) => {
  459. flatLotData.push({
  460. pickOrderCode: po.pickOrderCode,
  461. itemCode: line.item?.code,
  462. itemName: line.item?.name,
  463. lotNo: so.lotNo || '',
  464. location: so.location || '',
  465. deliveryOrderCode: po.deliveryOrderCode,
  466. requiredQty: line.requiredQty ?? 0, // 行级没有 lot 时,用行的 requiredQty
  467. actualPickQty: (so.qty ?? 0),
  468. processingStatus: toProc(so.status),
  469. stockOutLineStatus: so.status,
  470. noLot: so.noLot === true
  471. });
  472. });
  473. }
  474. });
  475. });
  476. }
  477. setDetailLotData(flatLotData);
  478. // 计算完成状态
  479. const allCompleted = flatLotData.length > 0 && flatLotData.every(lot =>
  480. lot.processingStatus === 'completed'
  481. );
  482. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  483. detail: {
  484. allLotsCompleted: allCompleted,
  485. tabIndex: 2
  486. }
  487. }));
  488. } catch (error) { // 添加 catch 块
  489. console.error("❌ Error loading detail lot data:", error);
  490. setDetailLotData([]);
  491. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  492. detail: {
  493. allLotsCompleted: false,
  494. tabIndex: 2
  495. }
  496. }));
  497. }
  498. }, []);
  499. // 返回列表视图
  500. const handleBackToList = useCallback(() => {
  501. setShowDetailView(false);
  502. setSelectedDoPickOrder(null);
  503. setDetailLotData([]);
  504. // 返回列表时禁用打印按钮
  505. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  506. detail: {
  507. allLotsCompleted: false,
  508. tabIndex: 2
  509. }
  510. }));
  511. }, []);
  512. // 如果显示详情视图,渲染类似 GoodPickExecution 的表格
  513. // 如果显示详情视图,渲染层级结构
  514. if (showDetailView && selectedDoPickOrder) {
  515. return (
  516. <FormProvider {...formProps}>
  517. <Box>
  518. {/* 返回按钮和标题 */}
  519. <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
  520. <Button variant="outlined" onClick={handleBackToList}>
  521. {t("Back to List")}
  522. </Button>
  523. <Typography variant="h6">
  524. {t("Pick Order Details")}: {selectedDoPickOrder.ticketNo}
  525. </Typography>
  526. </Box>
  527. {/* FG 订单基本信息 */}
  528. <Paper sx={{ mb: 2, p: 2 }}>
  529. <Stack spacing={1}>
  530. <Typography variant="subtitle1">
  531. <strong>{t("Shop Name")}:</strong> {selectedDoPickOrder.shopName}
  532. </Typography>
  533. <Typography variant="subtitle1">
  534. <strong>{t("Store ID")}:</strong> {selectedDoPickOrder.storeId}
  535. </Typography>
  536. <Typography variant="subtitle1">
  537. <strong>{t("Ticket No.")}:</strong> {selectedDoPickOrder.ticketNo}
  538. </Typography>
  539. <Typography variant="subtitle1">
  540. <strong>{t("Truck Lance Code")}:</strong> {selectedDoPickOrder.truckLanceCode}
  541. </Typography>
  542. <Typography variant="subtitle1">
  543. <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)}
  544. </Typography>
  545. {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && (
  546. <Typography variant="subtitle1">
  547. <strong>{t("Pick Order Code(s)")}:</strong>{" "}
  548. {(typeof selectedDoPickOrder.pickOrderCodes === 'string'
  549. ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim())
  550. : Array.isArray(selectedDoPickOrder.pickOrderCodes)
  551. ? selectedDoPickOrder.pickOrderCodes
  552. : []
  553. ).filter(Boolean).join(', ')}
  554. </Typography>
  555. )}
  556. </Stack>
  557. </Paper>
  558. {/* 数据检查 */}
  559. {detailLotData.length === 0 ? (
  560. <Box sx={{ p: 3, textAlign: 'center' }}>
  561. <Typography variant="body2" color="text.secondary">
  562. {t("No lot details found for this order")}
  563. </Typography>
  564. </Box>
  565. ) : (
  566. /* 按 Pick Order 分组显示 */
  567. <Stack spacing={2}>
  568. {/* 按 pickOrderCode 分组 */}
  569. {Object.entries(
  570. detailLotData.reduce((acc: any, lot: any) => {
  571. const key = lot.pickOrderCode || 'Unknown';
  572. if (!acc[key]) {
  573. acc[key] = {
  574. lots: [],
  575. deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // 保存对应的 deliveryOrderCode
  576. };
  577. }
  578. acc[key].lots.push(lot);
  579. return acc;
  580. }, {})
  581. ).map(([pickOrderCode, data]: [string, any]) => (
  582. <Accordion key={pickOrderCode} defaultExpanded={true}>
  583. <AccordionSummary expandIcon={<ExpandMoreIcon />}>
  584. <Typography variant="subtitle1" fontWeight="bold">
  585. {t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")})
  586. {" | "}
  587. {t("Delivery Order")}: {data.deliveryOrderCode} {/* 使用保存的 deliveryOrderCode */}
  588. </Typography>
  589. </AccordionSummary>
  590. <AccordionDetails>
  591. <TableContainer component={Paper}>
  592. <Table size="small">
  593. <TableHead>
  594. <TableRow>
  595. <TableCell>{t("Index")}</TableCell>
  596. <TableCell>{t("Item Code")}</TableCell>
  597. <TableCell>{t("Item Name")}</TableCell>
  598. <TableCell>{t("Lot No")}</TableCell>
  599. <TableCell>{t("Location")}</TableCell>
  600. <TableCell align="right">{t("Required Qty")}</TableCell>
  601. <TableCell align="right">{t("Actual Pick Qty")}</TableCell>
  602. <TableCell align="center">{t("Status")}</TableCell>
  603. </TableRow>
  604. </TableHead>
  605. <TableBody>
  606. {data.lots.map((lot: any, index: number) => (
  607. <TableRow key={index}>
  608. <TableCell>{index + 1}</TableCell>
  609. <TableCell>{lot.itemCode || 'N/A'}</TableCell>
  610. <TableCell>{lot.itemName || 'N/A'}</TableCell>
  611. <TableCell>{lot.lotNo || 'N/A'}</TableCell>
  612. <TableCell>{lot.location || 'N/A'}</TableCell>
  613. <TableCell align="right">{lot.requiredQty || 0}</TableCell>
  614. <TableCell align="right">{lot.actualPickQty || 0}</TableCell>
  615. <TableCell align="center">
  616. <Chip
  617. label={t(lot.processingStatus || 'unknown')}
  618. color={lot.processingStatus === 'completed' ? 'success' : 'default'}
  619. size="small"
  620. />
  621. </TableCell>
  622. </TableRow>
  623. ))}
  624. </TableBody>
  625. </Table>
  626. </TableContainer>
  627. </AccordionDetails>
  628. </Accordion>
  629. ))}
  630. </Stack>
  631. )}
  632. </Box>
  633. </FormProvider>
  634. );
  635. }
  636. // 默认列表视图
  637. return (
  638. <FormProvider {...formProps}>
  639. <Box>
  640. {/* 搜索框 */}
  641. <Box sx={{ mb: 2 }}>
  642. <SearchBox
  643. criteria={searchCriteria}
  644. onSearch={handleSearch}
  645. onReset={handleSearchReset}
  646. />
  647. </Box>
  648. {/* 加载状态 */}
  649. {completedDoPickOrdersLoading ? (
  650. <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
  651. <CircularProgress />
  652. </Box>
  653. ) : (
  654. <Box>
  655. {/* 结果统计 */}
  656. <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
  657. {t("Completed DO pick orders: ")} {filteredDoPickOrders.length}
  658. </Typography>
  659. {/* 列表 */}
  660. {filteredDoPickOrders.length === 0 ? (
  661. <Box sx={{ p: 3, textAlign: 'center' }}>
  662. <Typography variant="body2" color="text.secondary">
  663. {t("No completed DO pick orders found")}
  664. </Typography>
  665. </Box>
  666. ) : (
  667. <Stack spacing={2}>
  668. {paginatedData.map((doPickOrder) => (
  669. <Card key={doPickOrder.id}>
  670. <CardContent>
  671. <Stack direction="row" justifyContent="space-between" alignItems="center">
  672. <Box>
  673. <Typography variant="h6">
  674. {doPickOrder.deliveryNoteCode}
  675. </Typography>
  676. <Typography variant="body2" color="text.secondary">
  677. {doPickOrder.shopName}
  678. </Typography>
  679. <Typography variant="body2" color="text.secondary">
  680. {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)}
  681. </Typography>
  682. </Box>
  683. <Box>
  684. <Chip
  685. label={t(doPickOrder.pickOrderStatus)}
  686. color={doPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'}
  687. size="small"
  688. sx={{ mb: 1 }}
  689. />
  690. <Typography variant="body2" color="text.secondary">
  691. {doPickOrder.fgPickOrders.length} {t("FG orders")}
  692. </Typography>
  693. </Box>
  694. </Stack>
  695. </CardContent>
  696. <CardActions>
  697. <Button
  698. variant="outlined"
  699. onClick={() => handleDetailClick(doPickOrder)}
  700. >
  701. {t("View Details")}
  702. </Button>
  703. <>
  704. <Button
  705. variant="contained"
  706. onClick={() => handleDN(
  707. doPickOrder.doPickOrderRecordId
  708. )}
  709. >
  710. {t("Print Pick Order")}
  711. </Button>
  712. <Button
  713. variant="contained"
  714. onClick={() => handleDNandLabel(
  715. doPickOrder.doPickOrderRecordId
  716. )}
  717. >
  718. {t("Print DN & Label")}
  719. </Button>
  720. <Button
  721. variant="contained"
  722. onClick={() => handleLabel(
  723. doPickOrder.doPickOrderRecordId
  724. )}
  725. >
  726. {t("Print Label")}
  727. </Button>
  728. </>
  729. </CardActions>
  730. </Card>
  731. ))}
  732. </Stack>
  733. )}
  734. {/* 分页 */}
  735. {filteredDoPickOrders.length > 0 && (
  736. <TablePagination
  737. component="div"
  738. count={filteredDoPickOrders.length}
  739. page={paginationController.pageNum}
  740. rowsPerPage={paginationController.pageSize}
  741. onPageChange={handlePageChange}
  742. onRowsPerPageChange={handlePageSizeChange}
  743. rowsPerPageOptions={[5, 10, 25, 50]}
  744. />
  745. )}
  746. </Box>
  747. )}
  748. </Box>
  749. </FormProvider>
  750. );
  751. };
  752. export default GoodPickExecutionRecord;