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.
 
 

530 lines
17 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, FGPickOrderResponse, fetchFGPickOrders } 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 { PrintDeliveryNoteRequest, printDN } from "@/app/api/do/actions";
  35. interface Props {
  36. pickOrders: PickOrderResult[];
  37. }
  38. type SearchQuery = Partial<
  39. Omit<PickOrderResult, "id" | "consoCode" | "completeDate">
  40. >;
  41. type SearchParamNames = keyof SearchQuery;
  42. const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
  43. const { t } = useTranslation("pickOrder");
  44. const { data: session } = useSession() as { data: SessionWithTokens | null };
  45. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  46. const [isOpenCreateModal, setIsOpenCreateModal] = useState(false)
  47. const [items, setItems] = useState<ItemCombo[]>([])
  48. const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false);
  49. const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
  50. const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
  51. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  52. const [tabIndex, setTabIndex] = useState(0);
  53. const [totalCount, setTotalCount] = useState<number>();
  54. const [isAssigning, setIsAssigning] = useState(false);
  55. const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
  56. typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
  57. );
  58. useEffect(() => {
  59. const onAssigned = () => {
  60. localStorage.removeItem('hideCompletedUntilNext');
  61. setHideCompletedUntilNext(false);
  62. };
  63. window.addEventListener('pickOrderAssigned', onAssigned);
  64. return () => window.removeEventListener('pickOrderAssigned', onAssigned);
  65. }, []);
  66. const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]);
  67. const handleAssignByStore = async (storeId: "2/F" | "4/F") => {
  68. if (!currentUserId) {
  69. console.error("Missing user id in session");
  70. return;
  71. }
  72. setIsAssigning(true);
  73. try {
  74. const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId);
  75. console.log("Assign by store result:", res);
  76. // ✅ Handle different response codes
  77. if (res.code === "SUCCESS") {
  78. console.log("✅ Successfully assigned pick order to store", storeId);
  79. // ✅ Trigger refresh to show newly assigned data
  80. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  81. } else if (res.code === "USER_BUSY") {
  82. console.warn("⚠️ User already has pick orders in progress:", res.message);
  83. // ✅ Show warning but still refresh to show existing orders
  84. alert(`Warning: ${res.message}`);
  85. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  86. } else if (res.code === "NO_ORDERS") {
  87. console.log("ℹ️ No available pick orders for store", storeId);
  88. alert(`Info: ${res.message}`);
  89. } else {
  90. console.log("ℹ️ Assignment result:", res.message);
  91. alert(`Info: ${res.message}`);
  92. }
  93. } catch (error) {
  94. console.error("❌ Error assigning by store:", error);
  95. alert("Error occurred during assignment");
  96. } finally {
  97. setIsAssigning(false);
  98. }
  99. };
  100. // ✅ Manual assignment handler - uses the action function
  101. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  102. (_e, newValue) => {
  103. setTabIndex(newValue);
  104. },
  105. [],
  106. );
  107. const openCreateModal = useCallback(async () => {
  108. console.log("testing")
  109. const res = await fetchAllItemsInClient()
  110. console.log(res)
  111. setItems(res)
  112. setIsOpenCreateModal(true)
  113. }, [])
  114. const closeCreateModal = useCallback(() => {
  115. setIsOpenCreateModal(false)
  116. }, [])
  117. const handleDN = useCallback(async () =>{
  118. const askNumofCarton = await Swal.fire({
  119. title: t("Enter the number of cartons: "),
  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. showLoaderOnConfirm: true,
  139. allowOutsideClick: () => !Swal.isLoading()
  140. });
  141. if (askNumofCarton.isConfirmed) {
  142. const numOfCartons = askNumofCarton.value;
  143. console.log(numOfCartons)
  144. }
  145. },[t]);
  146. const handleDNandLabel = useCallback(async () =>{
  147. const askNumofCarton = await Swal.fire({
  148. title: t("Enter the number of cartons: "),
  149. input: "number",
  150. inputPlaceholder: t("Number of cartons"),
  151. inputAttributes:{
  152. min: "1",
  153. step: "1"
  154. },
  155. inputValidator: (value) => {
  156. if(!value){
  157. return t("You need to enter a number")
  158. }
  159. if(parseInt(value) < 1){
  160. return t("Number must be at least 1");
  161. }
  162. return null
  163. },
  164. showCancelButton: true,
  165. confirmButtonText: t("Confirm"),
  166. cancelButtonText: t("Cancel"),
  167. showLoaderOnConfirm: true,
  168. allowOutsideClick: () => !Swal.isLoading()
  169. });
  170. if (askNumofCarton.isConfirmed) {
  171. const numOfCartons = askNumofCarton.value;
  172. }
  173. },[t]);
  174. const handleLabel = useCallback(async () =>{
  175. const askNumofCarton = await Swal.fire({
  176. title: t("Enter the number of cartons: "),
  177. input: "number",
  178. inputPlaceholder: t("Number of cartons"),
  179. inputAttributes:{
  180. min: "1",
  181. step: "1"
  182. },
  183. inputValidator: (value) => {
  184. if(!value){
  185. return t("You need to enter a number")
  186. }
  187. if(parseInt(value) < 1){
  188. return t("Number must be at least 1");
  189. }
  190. return null
  191. },
  192. showCancelButton: true,
  193. confirmButtonText: t("Confirm"),
  194. cancelButtonText: t("Cancel"),
  195. showLoaderOnConfirm: true,
  196. allowOutsideClick: () => !Swal.isLoading()
  197. });
  198. if (askNumofCarton.isConfirmed) {
  199. const numOfCartons = askNumofCarton.value;
  200. }
  201. },[t]);
  202. const handleDraft = useCallback(async () =>{
  203. },[t]);
  204. useEffect(() => {
  205. if (tabIndex === 3) {
  206. const loadItems = async () => {
  207. try {
  208. const itemsData = await fetchAllItemsInClient();
  209. console.log("PickOrderSearch loaded items:", itemsData.length);
  210. setItems(itemsData);
  211. } catch (error) {
  212. console.error("Error loading items in PickOrderSearch:", error);
  213. }
  214. };
  215. // 如果还没有数据,则加载
  216. if (items.length === 0) {
  217. loadItems();
  218. }
  219. }
  220. }, [tabIndex, items.length]);
  221. useEffect(() => {
  222. const handleCompletionStatusChange = (event: CustomEvent) => {
  223. const { allLotsCompleted } = event.detail;
  224. setPrintButtonsEnabled(allLotsCompleted);
  225. console.log("Print buttons enabled:", allLotsCompleted);
  226. };
  227. window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  228. return () => {
  229. window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
  230. };
  231. }, []);
  232. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  233. () => {
  234. const baseCriteria: Criterion<SearchParamNames>[] = [
  235. {
  236. label: tabIndex === 3 ? t("Item Code") : t("Code"),
  237. paramName: "code",
  238. type: "text"
  239. },
  240. {
  241. label: t("Type"),
  242. paramName: "type",
  243. type: "autocomplete",
  244. options: tabIndex === 3
  245. ?
  246. [
  247. { value: "Consumable", label: t("Consumable") },
  248. { value: "Material", label: t("Material") },
  249. { value: "Product", label: t("Product") }
  250. ]
  251. :
  252. sortBy(
  253. uniqBy(
  254. pickOrders.map((po) => ({
  255. value: po.type,
  256. label: t(upperCase(po.type)),
  257. })),
  258. "value",
  259. ),
  260. "label",
  261. ),
  262. },
  263. ];
  264. // Add Job Order search for Create Item tab (tabIndex === 3)
  265. if (tabIndex === 3) {
  266. baseCriteria.splice(1, 0, {
  267. label: t("Job Order"),
  268. paramName: "jobOrderCode" as any, // Type assertion for now
  269. type: "text",
  270. });
  271. baseCriteria.splice(2, 0, {
  272. label: t("Target Date"),
  273. paramName: "targetDate",
  274. type: "date",
  275. });
  276. } else {
  277. baseCriteria.splice(1, 0, {
  278. label: t("Target Date From"),
  279. label2: t("Target Date To"),
  280. paramName: "targetDate",
  281. type: "dateRange",
  282. });
  283. }
  284. // Add Items/Item Name criteria
  285. baseCriteria.push({
  286. label: tabIndex === 3 ? t("Item Name") : t("Items"),
  287. paramName: "items",
  288. type: tabIndex === 3 ? "text" : "autocomplete",
  289. options: tabIndex === 3
  290. ? []
  291. :
  292. uniqBy(
  293. flatten(
  294. sortBy(
  295. pickOrders.map((po) =>
  296. po.items
  297. ? po.items.map((item) => ({
  298. value: item.name,
  299. label: item.name,
  300. }))
  301. : [],
  302. ),
  303. "label",
  304. ),
  305. ),
  306. "value",
  307. ),
  308. });
  309. // Add Status criteria for non-Create Item tabs
  310. if (tabIndex !== 3) {
  311. baseCriteria.push({
  312. label: t("Status"),
  313. paramName: "status",
  314. type: "autocomplete",
  315. options: sortBy(
  316. uniqBy(
  317. pickOrders.map((po) => ({
  318. value: po.status,
  319. label: t(upperFirst(po.status)),
  320. })),
  321. "value",
  322. ),
  323. "label",
  324. ),
  325. });
  326. }
  327. return baseCriteria;
  328. },
  329. [pickOrders, t, tabIndex, items],
  330. );
  331. const fetchNewPagePickOrder = useCallback(
  332. async (
  333. pagingController: Record<string, number>,
  334. filterArgs: Record<string, number>,
  335. ) => {
  336. const params = {
  337. ...pagingController,
  338. ...filterArgs,
  339. };
  340. const res = await fetchPickOrderClient(params);
  341. if (res) {
  342. console.log(res);
  343. setFilteredPickOrders(res.records);
  344. setTotalCount(res.total);
  345. }
  346. },
  347. [],
  348. );
  349. const onReset = useCallback(() => {
  350. setFilteredPickOrders(pickOrders);
  351. }, [pickOrders]);
  352. useEffect(() => {
  353. if (!isOpenCreateModal) {
  354. setTabIndex(1)
  355. setTimeout(async () => {
  356. setTabIndex(0)
  357. }, 200)
  358. }
  359. }, [isOpenCreateModal])
  360. // 添加处理提料单创建成功的函数
  361. const handlePickOrderCreated = useCallback(() => {
  362. // 切换到 Assign & Release 标签页 (tabIndex = 1)
  363. setTabIndex(2);
  364. }, []);
  365. return (
  366. <Box sx={{
  367. height: '100vh', // Full viewport height
  368. overflow: 'auto' // Single scrollbar for the whole page
  369. }}>
  370. {/* Header section */}
  371. <Box sx={{ p: 2, borderBottom: '1px solid #e0e0e0' }}>
  372. <Stack rowGap={2}>
  373. <Grid container alignItems="center">
  374. <Grid item xs={8}>
  375. <Box mb={2}>
  376. <Typography variant="h4" marginInlineEnd={2}>
  377. {t("Finished Good Order")}
  378. </Typography>
  379. </Box>
  380. </Grid>
  381. </Grid>
  382. {/* First 4 buttons aligned left */}
  383. <Grid item xs={6}>
  384. <Stack direction="row" spacing={1}>
  385. <Button variant="contained" onClick={handleDraft}>{t("Print Draft")}</Button>
  386. <Button variant="contained" onClick={handleDNandLabel}>{t("Print Pick Order and DN Label")}</Button>
  387. <Button variant="contained" onClick={handleDN}>{t("Print Pick Order")}</Button>
  388. <Button variant="contained" onClick={handleLabel}>{t("Print DN Label")}</Button>
  389. </Stack>
  390. </Grid>
  391. {/* Last 2 buttons aligned right */}
  392. <Grid item xs={6} >
  393. <Stack direction="row" spacing={1}>
  394. <Button
  395. variant="contained"
  396. onClick={() => handleAssignByStore("2/F")}
  397. disabled={isAssigning}
  398. >
  399. {isAssigning ? t("Assigning pick order...") : t("Pick Execution 2/F")}
  400. </Button>
  401. <Button
  402. variant="contained"
  403. onClick={() => handleAssignByStore("4/F")}
  404. disabled={isAssigning}
  405. >
  406. {isAssigning ? t("Assigning pick order...") : t("Pick Execution 4/F")}
  407. </Button>
  408. </Stack>
  409. </Grid>
  410. {/* ✅ Updated print buttons with completion status */}
  411. <Grid item xs={6} display="flex" justifyContent="flex-end">
  412. <Stack direction="row" spacing={1}>
  413. {/*
  414. <Button
  415. variant={hideCompletedUntilNext ? "contained" : "outlined"}
  416. color={hideCompletedUntilNext ? "warning" : "inherit"}
  417. onClick={() => {
  418. const next = !hideCompletedUntilNext;
  419. setHideCompletedUntilNext(next);
  420. if (next) localStorage.setItem('hideCompletedUntilNext', 'true');
  421. else localStorage.removeItem('hideCompletedUntilNext');
  422. window.dispatchEvent(new Event('pickOrderAssigned')); // ask detail to re-fetch
  423. }}
  424. >
  425. {hideCompletedUntilNext ? t("Hide Completed: ON") : t("Hide Completed: OFF")}
  426. </Button>
  427. */}
  428. <Button
  429. variant="contained"
  430. disabled={!printButtonsEnabled}
  431. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  432. >
  433. {t("Print Draft")}
  434. </Button>
  435. <Button
  436. variant="contained"
  437. disabled={!printButtonsEnabled}
  438. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  439. >
  440. {t("Print Pick Order and DN Label")}
  441. </Button>
  442. <Button
  443. variant="contained"
  444. disabled={!printButtonsEnabled}
  445. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  446. >
  447. {t("Print Pick Order")}
  448. </Button>
  449. <Button
  450. variant="contained"
  451. disabled={!printButtonsEnabled}
  452. title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
  453. >
  454. {t("Print DN Label")}
  455. </Button>
  456. </Stack>
  457. </Grid>
  458. </Grid>
  459. </Stack>
  460. </Box>
  461. {/* Tabs section - ✅ Move the click handler here */}
  462. <Box sx={{
  463. borderBottom: '1px solid #e0e0e0'
  464. }}>
  465. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  466. <Tab label={t("Pick Order Detail")} iconPosition="end" />
  467. <Tab label={t("Pick Execution Detail")} iconPosition="end" />
  468. <Tab label={t("Pick Execution Record")} iconPosition="end" />
  469. </Tabs>
  470. </Box>
  471. {/* Content section - NO overflow: 'auto' here */}
  472. <Box sx={{
  473. p: 2
  474. }}>
  475. {tabIndex === 0 && <PickExecution filterArgs={filterArgs} />}
  476. {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />}
  477. {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />}
  478. </Box>
  479. </Box>
  480. );
  481. };
  482. export default PickOrderSearch;