FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

636 satır
20 KiB

  1. "use client";
  2. import { PickOrderResult } from "@/app/api/pickOrder";
  3. import { useCallback, useEffect, useMemo, useState } from "react";
  4. import { useTranslation } from "react-i18next";
  5. import SearchBox, { Criterion } from "../SearchBox";
  6. import {
  7. flatten,
  8. intersectionWith,
  9. isEmpty,
  10. sortBy,
  11. uniqBy,
  12. upperCase,
  13. upperFirst,
  14. } from "lodash";
  15. import {
  16. arrayToDayjs,
  17. } from "@/app/utils/formatUtil";
  18. import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box } from "@mui/material";
  19. import PickOrders from "./FinishedGood";
  20. import ConsolidatedPickOrders from "./ConsolidatedPickOrders";
  21. import PickExecution from "./GoodPickExecution";
  22. import CreatePickOrderModal from "./CreatePickOrderModal";
  23. import NewCreateItem from "./newcreatitem";
  24. import AssignAndRelease from "./AssignAndRelease";
  25. import AssignTo from "./assignTo";
  26. import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions";
  27. import { fetchPickOrderClient, autoAssignAndReleasePickOrder, autoAssignAndReleasePickOrderByStore, fetchReleasedDoPickOrders } from "@/app/api/pickOrder/actions";
  28. import Jobcreatitem from "./Jobcreatitem";
  29. import { useSession } from "next-auth/react";
  30. import { SessionWithTokens } from "@/config/authConfig";
  31. import PickExecutionDetail from "./GoodPickExecutiondetail";
  32. import GoodPickExecutionRecord from "./GoodPickExecutionRecord";
  33. import Swal from "sweetalert2";
  34. import { printDN, printDNLabels } from "@/app/api/do/actions";
  35. import { FGPickOrderResponse, fetchStoreLaneSummary, assignByLane,type StoreLaneSummary } from "@/app/api/pickOrder/actions";
  36. import FGPickOrderCard from "./FGPickOrderCard";
  37. import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
  38. import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
  39. import { DatePicker } from '@mui/x-date-pickers/DatePicker';
  40. import dayjs, { Dayjs } from 'dayjs';
  41. import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable";
  42. interface Props {
  43. pickOrders: PickOrderResult[];
  44. }
  45. type SearchQuery = Partial<
  46. Omit<PickOrderResult, "id" | "consoCode" | "completeDate">
  47. >;
  48. type SearchParamNames = keyof SearchQuery;
  49. const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
  50. const { t } = useTranslation("pickOrder");
  51. const { data: session } = useSession() as { data: SessionWithTokens | null };
  52. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  53. const [isOpenCreateModal, setIsOpenCreateModal] = useState(false)
  54. const [items, setItems] = useState<ItemCombo[]>([])
  55. const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false);
  56. const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
  57. const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
  58. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  59. const [tabIndex, setTabIndex] = useState(0);
  60. const [totalCount, setTotalCount] = useState<number>();
  61. const [isAssigning, setIsAssigning] = useState(false);
  62. // const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null);
  63. // const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null);
  64. const [isLoadingSummary, setIsLoadingSummary] = useState(false);
  65. const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
  66. typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
  67. );
  68. const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]);
  69. const [releasedOrderCount, setReleasedOrderCount] = useState<number>(0);
  70. const fetchReleasedOrderCount = useCallback(async () => {
  71. try {
  72. const releasedOrders = await fetchReleasedDoPickOrders();
  73. const validCount = releasedOrders.length;
  74. setReleasedOrderCount(validCount);
  75. } catch (error) {
  76. console.error("Error fetching released order count:", error);
  77. setReleasedOrderCount(0);
  78. }
  79. }, []);
  80. /*
  81. const loadSummaries = useCallback(async () => {
  82. setIsLoadingSummary(true);
  83. try {
  84. const [s2, s4] = await Promise.all([
  85. fetchStoreLaneSummary("2/F"),
  86. fetchStoreLaneSummary("4/F")
  87. ]);
  88. setSummary2F(s2);
  89. setSummary4F(s4);
  90. } catch (error) {
  91. console.error("Error loading summaries:", error);
  92. } finally {
  93. setIsLoadingSummary(false);
  94. }
  95. }, []);
  96. useEffect(() => {
  97. loadSummaries();
  98. // 每30秒刷新一次
  99. }, [loadSummaries]);
  100. */
  101. const handleDraft = useCallback(async () =>{
  102. try{
  103. if (fgPickOrdersData.length === 0) {
  104. console.error("No FG Pick order data available");
  105. Swal.fire({
  106. title: "",
  107. text: t("Please take one pick order before printing the draft."),
  108. icon: "info"
  109. })
  110. return;
  111. }
  112. const currentFgOrder = fgPickOrdersData[0];
  113. const printRequest = {
  114. printerId: 1,
  115. printQty: 1,
  116. isDraft: true,
  117. numOfCarton: 0,
  118. doPickOrderId: currentFgOrder.doPickOrderId
  119. };
  120. console.log("Printing draft with request: ", printRequest);
  121. const response = await printDN(printRequest);
  122. console.log("Print Draft response: ", response);
  123. if(response.success){
  124. Swal.fire({
  125. position: "bottom-end",
  126. icon: "success",
  127. text: t("Printed Successfully."),
  128. showConfirmButton: false,
  129. timer: 1500
  130. });
  131. } else {
  132. console.error("Print failed: ", response.message);
  133. }
  134. } catch(error){
  135. console.error("error: ", error)
  136. }
  137. },[t, fgPickOrdersData]);
  138. const handleAllDraft = useCallback(async () =>{
  139. try {
  140. const releasedOrders = await fetchReleasedDoPickOrders();
  141. console.log('fgPickOrdersData length:' + releasedOrders.length)
  142. if(releasedOrders.length === 0) {
  143. console.log("No released do_pick_order records found");
  144. Swal.fire({
  145. title: "",
  146. text: t("No released pick order records found."),
  147. icon: "info"
  148. })
  149. return;
  150. }
  151. console.log("Found released orders:", releasedOrders);
  152. const confirmResult = await Swal.fire({
  153. title: t("Batch Print"),
  154. text: t("Confirm print: (") + releasedOrders.length.toString() + t("piece(s))"),
  155. icon: "question",
  156. showCancelButton: true,
  157. confirmButtonText: t("Confirm"),
  158. cancelButtonText: t("Cancel"),
  159. confirmButtonColor: "#8dba00",
  160. cancelButtonColor: "#F04438"
  161. });
  162. if (!confirmResult.isConfirmed) {
  163. return;
  164. }
  165. Swal.fire({
  166. title: t("Printing..."),
  167. text: t("Please wait..."),
  168. allowOutsideClick: false,
  169. allowEscapeKey: false,
  170. didOpen: () => {
  171. Swal.showLoading();
  172. }
  173. });
  174. for (const order of releasedOrders) {
  175. const doPickOrderId = order.id
  176. console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`);
  177. const printRequest = {
  178. printerId: 1,
  179. printQty: 1,
  180. isDraft: true,
  181. numOfCarton: 0,
  182. doPickOrderId: doPickOrderId
  183. };
  184. console.log("Printing draft with request:", printRequest)
  185. const response = await printDN(printRequest);
  186. if(!response.success) {
  187. console.error(`Print failed for order ${order.ticketNo}:`, response.message);
  188. }
  189. }
  190. Swal.fire({
  191. position: "bottom-end",
  192. icon: "success",
  193. text: t("Printed Successfully."),
  194. showConfirmButton: false,
  195. timer: 1500
  196. });
  197. } catch(error){
  198. console.error("Error in handleAllDraft:",error);
  199. }
  200. },[t, fgPickOrdersData]);
  201. useEffect(() => {
  202. fetchReleasedOrderCount();
  203. }, [fetchReleasedOrderCount]);
  204. useEffect(() => {
  205. const onAssigned = () => {
  206. localStorage.removeItem('hideCompletedUntilNext');
  207. setHideCompletedUntilNext(false);
  208. // loadSummaries();
  209. };
  210. window.addEventListener('pickOrderAssigned', onAssigned);
  211. return () => window.removeEventListener('pickOrderAssigned', onAssigned);
  212. }, []);
  213. // ... existing code ...
  214. useEffect(() => {
  215. const handleCompletionStatusChange = (event: CustomEvent) => {
  216. const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
  217. // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态
  218. if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
  219. setPrintButtonsEnabled(allLotsCompleted);
  220. console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
  221. }
  222. };
  223. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  224. return () => {
  225. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  226. };
  227. }, [tabIndex]); // ✅ 添加 tabIndex 依赖
  228. // ✅ 新增:处理标签页切换时的打印按钮状态重置
  229. useEffect(() => {
  230. // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
  231. if (tabIndex === 2) {
  232. setPrintButtonsEnabled(false);
  233. console.log("Reset print buttons for Pick Execution Record tab");
  234. }
  235. }, [tabIndex]);
  236. /*
  237. // ... existing code ...
  238. const handleAssignByLane = useCallback(async (
  239. storeId: string,
  240. truckDepartureTime: string,
  241. truckLanceCode: string
  242. ) => {
  243. if (!currentUserId) {
  244. console.error("Missing user id in session");
  245. return;
  246. }
  247. setIsAssigning(true);
  248. try {
  249. const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
  250. if (res.code === "SUCCESS") {
  251. console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
  252. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  253. loadSummaries(); // 刷新按钮状态
  254. } else if (res.code === "USER_BUSY") {
  255. Swal.fire({
  256. icon: "warning",
  257. title: t("Warning"),
  258. text: t("You already have a pick order in progess. Please complete it first before taking next pick order."),
  259. confirmButtonText: t("Confirm"),
  260. confirmButtonColor: "#8dba00"
  261. });
  262. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  263. } else if (res.code === "NO_ORDERS") {
  264. Swal.fire({
  265. icon: "info",
  266. title: t("Info"),
  267. text: t("No available pick order(s) for this lane."),
  268. confirmButtonText: t("Confirm"),
  269. confirmButtonColor: "#8dba00"
  270. });
  271. } else {
  272. console.log("ℹ️ Assignment result:", res.message);
  273. }
  274. } catch (error) {
  275. console.error("❌ Error assigning by lane:", error);
  276. Swal.fire({
  277. icon: "error",
  278. title: t("Error"),
  279. text: t("Error occurred during assignment."),
  280. confirmButtonText: t("Confirm"),
  281. confirmButtonColor: "#8dba00"
  282. });
  283. } finally {
  284. setIsAssigning(false);
  285. }
  286. }, [currentUserId, t, loadSummaries]);
  287. // ✅ Manual assignment handler - uses the action function
  288. */
  289. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  290. (_e, newValue) => {
  291. setTabIndex(newValue);
  292. },
  293. [],
  294. );
  295. const openCreateModal = useCallback(async () => {
  296. console.log("testing")
  297. const res = await fetchAllItemsInClient()
  298. console.log(res)
  299. setItems(res)
  300. setIsOpenCreateModal(true)
  301. }, [])
  302. const closeCreateModal = useCallback(() => {
  303. setIsOpenCreateModal(false)
  304. }, [])
  305. useEffect(() => {
  306. if (tabIndex === 3) {
  307. const loadItems = async () => {
  308. try {
  309. const itemsData = await fetchAllItemsInClient();
  310. console.log("PickOrderSearch loaded items:", itemsData.length);
  311. setItems(itemsData);
  312. } catch (error) {
  313. console.error("Error loading items in PickOrderSearch:", error);
  314. }
  315. };
  316. // 如果还没有数据,则加载
  317. if (items.length === 0) {
  318. loadItems();
  319. }
  320. }
  321. }, [tabIndex, items.length]);
  322. useEffect(() => {
  323. const handleCompletionStatusChange = (event: CustomEvent) => {
  324. const { allLotsCompleted } = event.detail;
  325. setPrintButtonsEnabled(allLotsCompleted);
  326. console.log("Print buttons enabled:", allLotsCompleted);
  327. };
  328. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  329. return () => {
  330. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  331. };
  332. }, []);
  333. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  334. () => {
  335. const baseCriteria: Criterion<SearchParamNames>[] = [
  336. {
  337. label: tabIndex === 3 ? t("Item Code") : t("Code"),
  338. paramName: "code",
  339. type: "text"
  340. },
  341. {
  342. label: t("Type"),
  343. paramName: "type",
  344. type: "autocomplete",
  345. options: tabIndex === 3
  346. ?
  347. [
  348. { value: "Consumable", label: t("Consumable") },
  349. { value: "Material", label: t("Material") },
  350. { value: "Product", label: t("Product") }
  351. ]
  352. :
  353. sortBy(
  354. uniqBy(
  355. pickOrders.map((po) => ({
  356. value: po.type,
  357. label: t(upperCase(po.type)),
  358. })),
  359. "value",
  360. ),
  361. "label",
  362. ),
  363. },
  364. ];
  365. // Add Job Order search for Create Item tab (tabIndex === 3)
  366. if (tabIndex === 3) {
  367. baseCriteria.splice(1, 0, {
  368. label: t("Job Order"),
  369. paramName: "jobOrderCode" as any, // Type assertion for now
  370. type: "text",
  371. });
  372. baseCriteria.splice(2, 0, {
  373. label: t("Target Date"),
  374. paramName: "targetDate",
  375. type: "date",
  376. });
  377. } else {
  378. baseCriteria.splice(1, 0, {
  379. label: t("Target Date From"),
  380. label2: t("Target Date To"),
  381. paramName: "targetDate",
  382. type: "dateRange",
  383. });
  384. }
  385. // Add Items/Item Name criteria
  386. baseCriteria.push({
  387. label: tabIndex === 3 ? t("Item Name") : t("Items"),
  388. paramName: "items",
  389. type: tabIndex === 3 ? "text" : "autocomplete",
  390. options: tabIndex === 3
  391. ? []
  392. :
  393. uniqBy(
  394. flatten(
  395. sortBy(
  396. pickOrders.map((po) =>
  397. po.items
  398. ? po.items.map((item) => ({
  399. value: item.name,
  400. label: item.name,
  401. }))
  402. : [],
  403. ),
  404. "label",
  405. ),
  406. ),
  407. "value",
  408. ),
  409. });
  410. // Add Status criteria for non-Create Item tabs
  411. if (tabIndex !== 3) {
  412. baseCriteria.push({
  413. label: t("Status"),
  414. paramName: "status",
  415. type: "autocomplete",
  416. options: sortBy(
  417. uniqBy(
  418. pickOrders.map((po) => ({
  419. value: po.status,
  420. label: t(upperFirst(po.status)),
  421. })),
  422. "value",
  423. ),
  424. "label",
  425. ),
  426. });
  427. }
  428. return baseCriteria;
  429. },
  430. [pickOrders, t, tabIndex, items],
  431. );
  432. const fetchNewPagePickOrder = useCallback(
  433. async (
  434. pagingController: Record<string, number>,
  435. filterArgs: Record<string, number>,
  436. ) => {
  437. const params = {
  438. ...pagingController,
  439. ...filterArgs,
  440. };
  441. const res = await fetchPickOrderClient(params);
  442. if (res) {
  443. console.log(res);
  444. setFilteredPickOrders(res.records);
  445. setTotalCount(res.total);
  446. }
  447. },
  448. [],
  449. );
  450. const onReset = useCallback(() => {
  451. setFilteredPickOrders(pickOrders);
  452. }, [pickOrders]);
  453. useEffect(() => {
  454. if (!isOpenCreateModal) {
  455. setTabIndex(1)
  456. setTimeout(async () => {
  457. setTabIndex(0)
  458. }, 200)
  459. }
  460. }, [isOpenCreateModal])
  461. // 添加处理提料单创建成功的函数
  462. const handlePickOrderCreated = useCallback(() => {
  463. // 切换到 Assign & Release 标签页 (tabIndex = 1)
  464. setTabIndex(2);
  465. }, []);
  466. return (
  467. <Box sx={{
  468. // Full viewport height
  469. overflow: 'auto' // Single scrollbar for the whole page
  470. }}>
  471. {/* Header section */}
  472. <Box sx={{
  473. p: 1,
  474. borderBottom: '1px solid #e0e0e0',
  475. minHeight: 'auto' // 确保最小高度自适应
  476. }}>
  477. <Grid container alignItems="center" spacing={1}>
  478. <Grid item xs={8}>
  479. <Typography
  480. variant="h5"
  481. sx={{
  482. lineHeight: 1.4, // 调整行高
  483. m: 0,
  484. fontWeight: 500
  485. }}
  486. >
  487. {t("Finished Good Order")}
  488. </Typography>
  489. </Grid>
  490. <Grid item xs={4}>
  491. <Box sx={{
  492. display: 'flex',
  493. justifyContent: 'flex-end',
  494. alignItems: 'center',
  495. height: '100%'
  496. }}>
  497. <Stack
  498. direction="row"
  499. spacing={0.5}
  500. sx={{
  501. alignItems: 'center',
  502. height: '100%'
  503. }}
  504. >
  505. <Button
  506. variant="contained"
  507. sx={{
  508. py: 0.5, // 增加垂直padding
  509. px: 1.25, // 增加水平padding
  510. height: '40px', // 增加按钮高度
  511. fontSize: '0.75rem',
  512. lineHeight: 1.2, // 添加行高控制
  513. display: 'flex',
  514. alignItems: 'center',
  515. justifyContent: 'center',
  516. '&.Mui-disabled': {
  517. height: '40px'
  518. }
  519. }}
  520. onClick={handleAllDraft}
  521. >
  522. {t("Print All Draft")} ({releasedOrderCount})
  523. </Button>
  524. <Button
  525. variant="contained"
  526. sx={{
  527. py: 0.5,
  528. px: 1.25,
  529. height: '40px',
  530. fontSize: '0.75rem',
  531. lineHeight: 1.2,
  532. display: 'flex',
  533. alignItems: 'center',
  534. justifyContent: 'center',
  535. '&.Mui-disabled': {
  536. height: '40px'
  537. }
  538. }}
  539. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  540. onClick={handleDraft}
  541. >
  542. {t("Print Draft")}
  543. </Button>
  544. </Stack>
  545. </Box>
  546. </Grid>
  547. </Grid>
  548. </Box>
  549. {/* Tabs section - ✅ Move the click handler here */}
  550. <Box sx={{
  551. borderBottom: '1px solid #e0e0e0'
  552. }}>
  553. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  554. <Tab label={t("Pick Order Detail")} iconPosition="end" />
  555. <Tab label={t("Finished Good Detail")} iconPosition="end" />
  556. <Tab label={t("Finished Good Record")} iconPosition="end" />
  557. <Tab label={t("Ticket Release Table")} iconPosition="end" />
  558. </Tabs>
  559. </Box>
  560. {/* Content section - NO overflow: 'auto' here */}
  561. <Box sx={{
  562. p: 2
  563. }}>
  564. {tabIndex === 0 && <PickExecution filterArgs={filterArgs} onFgPickOrdersChange={setFgPickOrdersData}/>}
  565. {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />}
  566. {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />}
  567. {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>}
  568. </Box>
  569. </Box>
  570. );
  571. };
  572. export default PickOrderSearch;