FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 

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