FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

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