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.
 
 

983 line
30 KiB

  1. "use client";
  2. import {
  3. FooterPropsOverrides,
  4. GridActionsCellItem,
  5. GridCellParams,
  6. GridRowId,
  7. GridRowIdGetter,
  8. GridRowModel,
  9. GridRowModes,
  10. GridRowModesModel,
  11. GridToolbarContainer,
  12. GridValidRowModel,
  13. useGridApiRef,
  14. } from "@mui/x-data-grid";
  15. import {
  16. Dispatch,
  17. MutableRefObject,
  18. SetStateAction,
  19. useCallback,
  20. useEffect,
  21. useMemo,
  22. useState,
  23. } from "react";
  24. import StyledDataGrid from "../StyledDataGrid";
  25. import { GridColDef } from "@mui/x-data-grid";
  26. import { Box, Button, Grid, Typography } from "@mui/material";
  27. import { useTranslation } from "react-i18next";
  28. import { Add } from "@mui/icons-material";
  29. import SaveIcon from "@mui/icons-material/Save";
  30. import DeleteIcon from "@mui/icons-material/Delete";
  31. import CancelIcon from "@mui/icons-material/Cancel";
  32. import FactCheckIcon from "@mui/icons-material/FactCheck";
  33. import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
  34. // import { QcItemWithChecks } from "src/app/api/qc";
  35. import PlayArrowIcon from "@mui/icons-material/PlayArrow";
  36. import { PurchaseOrderLine } from "@/app/api/po";
  37. import { StockInLine } from "@/app/api/stockIn";
  38. import { createStockInLine, QcResult } from "@/app/api/stockIn/actions";
  39. import { usePathname, useRouter, useSearchParams } from "next/navigation";
  40. import {
  41. returnWeightUnit,
  42. calculateWeight,
  43. stockInLineStatusMap,
  44. arrayToDateString,
  45. } from "@/app/utils/formatUtil";
  46. // import PoQcStockInModal from "./PoQcStockInModal";
  47. import NotificationImportantIcon from "@mui/icons-material/NotificationImportant";
  48. import { WarehouseResult } from "@/app/api/warehouse";
  49. import LooksOneIcon from "@mui/icons-material/LooksOne";
  50. import LooksTwoIcon from "@mui/icons-material/LooksTwo";
  51. import Looks3Icon from "@mui/icons-material/Looks3";
  52. import axiosInstance from "@/app/(main)/axios/axiosInstance";
  53. // import axios, { AxiosRequestConfig } from "axios";
  54. import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api";
  55. import qs from "qs";
  56. import QrCodeIcon from "@mui/icons-material/QrCode";
  57. import { downloadFile } from "@/app/utils/commonUtil";
  58. import { fetchPoQrcode } from "@/app/api/pdf/actions";
  59. import { fetchQcResult } from "@/app/api/qc/actions";
  60. import DoDisturbIcon from "@mui/icons-material/DoDisturb";
  61. import { useSession } from "next-auth/react";
  62. // import { SessionWithTokens } from "src/config/authConfig";
  63. import QcStockInModal from "../Qc/QcStockInModal";
  64. import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
  65. import { PrinterCombo } from "@/app/api/settings/printer";
  66. import { EscalationResult } from "@/app/api/escalation";
  67. import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions";
  68. import { SessionWithTokens } from "@/config/authConfig";
  69. import { EscalationCombo } from "@/app/api/user";
  70. interface ResultWithId {
  71. id: number;
  72. }
  73. interface Props {
  74. // qc: QcItemWithChecks[];
  75. setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
  76. setStockInLine: Dispatch<SetStateAction<StockInLine[]>>;
  77. setProcessedQty: Dispatch<SetStateAction<number>>;
  78. itemDetail: PurchaseOrderLine;
  79. stockInLine: StockInLine[];
  80. warehouse: WarehouseResult[];
  81. fetchPoDetail: (poId: string) => void;
  82. handleMailTemplateForStockInLine: (stockInLineId: number) => void;
  83. printerCombo: PrinterCombo[];
  84. }
  85. export type StockInLineEntryError = {
  86. [field in keyof StockInLine]?: string;
  87. };
  88. export type StockInLineRow = Partial<
  89. StockInLine & {
  90. isActive: boolean | undefined;
  91. _isNew: boolean;
  92. _error: StockInLineEntryError;
  93. } & ResultWithId
  94. >;
  95. class ProcessRowUpdateError extends Error {
  96. public readonly row: StockInLineRow;
  97. public readonly errors: StockInLineEntryError | undefined;
  98. constructor(
  99. row: StockInLineRow,
  100. message?: string,
  101. errors?: StockInLineEntryError,
  102. ) {
  103. super(message);
  104. this.row = row;
  105. this.errors = errors;
  106. Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
  107. }
  108. }
  109. function PoInputGrid({
  110. // qc,
  111. setRows,
  112. setStockInLine,
  113. setProcessedQty,
  114. itemDetail,
  115. stockInLine,
  116. warehouse,
  117. fetchPoDetail,
  118. handleMailTemplateForStockInLine,
  119. printerCombo,
  120. }: Props) {
  121. const { t } = useTranslation("purchaseOrder");
  122. const apiRef = useGridApiRef();
  123. const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  124. const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>(
  125. (row) => row.id as number,
  126. [],
  127. );
  128. const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
  129. useEffect(() => {
  130. setEntries(stockInLine);
  131. }, [stockInLine])
  132. const [modalInfo, setModalInfo] = useState<
  133. StockInLine & { qcResult?: QcResult[] } & { escalationResult?: EscalationResult[] }
  134. >();
  135. const pathname = usePathname()
  136. const router = useRouter();
  137. const searchParams = useSearchParams();
  138. const [qcOpen, setQcOpen] = useState(false);
  139. const [escalOpen, setEscalOpen] = useState(false);
  140. const [stockInOpen, setStockInOpen] = useState(false);
  141. const [putAwayOpen, setPutAwayOpen] = useState(false);
  142. const [rejectOpen, setRejectOpen] = useState(false);
  143. const [btnIsLoading, setBtnIsLoading] = useState(false);
  144. const [currQty, setCurrQty] = useState(() => {
  145. const total = entries.reduce(
  146. (acc, curr) => acc + (curr.acceptedQty || 0),
  147. 0,
  148. );
  149. return total;
  150. });
  151. const { data: session } = useSession();
  152. const sessionToken = session as SessionWithTokens | null;
  153. useEffect(() => {
  154. const completedList = entries.filter(
  155. (e) => stockInLineStatusMap[e.status!] >= 8,
  156. );
  157. const processedQty = completedList.reduce(
  158. (acc, curr) => acc + (curr.acceptedQty || 0),
  159. 0,
  160. );
  161. setProcessedQty(processedQty);
  162. }, [entries, setProcessedQty]);
  163. const handleDelete = useCallback(
  164. (id: GridRowId) => () => {
  165. setEntries((es) => es.filter((e) => getRowId(e) !== id));
  166. },
  167. [getRowId],
  168. );
  169. const closeQcModal = useCallback(() => {
  170. setQcOpen(false);
  171. }, []);
  172. const openQcModal = useCallback(() => {
  173. setQcOpen(true);
  174. }, []);
  175. const closeStockInModal = useCallback(() => {
  176. setStockInOpen(false);
  177. }, []);
  178. const openStockInModal = useCallback(() => {
  179. setStockInOpen(true);
  180. }, []);
  181. const closePutAwayModal = useCallback(() => {
  182. setPutAwayOpen(false);
  183. }, []);
  184. const openPutAwayModal = useCallback(() => {
  185. setPutAwayOpen(true);
  186. }, []);
  187. const closeEscalationModal = useCallback(() => {
  188. setEscalOpen(false);
  189. }, []);
  190. const openEscalationModal = useCallback(() => {
  191. setEscalOpen(true);
  192. }, []);
  193. const closeRejectModal = useCallback(() => {
  194. setRejectOpen(false);
  195. }, []);
  196. const openRejectModal = useCallback(() => {
  197. setRejectOpen(true);
  198. }, []);
  199. const handleStart = useCallback( // NOTE: Seems unused!!!!!!!!
  200. (id: GridRowId, params: any) => () => {
  201. setBtnIsLoading(true);
  202. setRowModesModel((prev) => ({
  203. ...prev,
  204. [id]: { mode: GridRowModes.View },
  205. }));
  206. setTimeout(async () => {
  207. // post stock in line
  208. const oldId = params.row.id;
  209. const postData = {
  210. itemId: params.row.itemId,
  211. itemNo: params.row.itemNo,
  212. itemName: params.row.itemName,
  213. // purchaseOrderId: params.row.purchaseOrderId,
  214. purchaseOrderLineId: params.row.purchaseOrderLineId,
  215. acceptedQty: params.row.acceptedQty,
  216. };
  217. const res = await createStockInLine(postData);
  218. console.log(res);
  219. setEntries((prev) =>
  220. prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)),
  221. );
  222. setStockInLine(
  223. (prev) =>
  224. prev.map((p) =>
  225. p.id === oldId ? (res.entity as StockInLine) : p,
  226. ) as StockInLine[],
  227. );
  228. setBtnIsLoading(false);
  229. // do post directly to test
  230. // openStartModal();
  231. }, 200);
  232. },
  233. [setStockInLine],
  234. );
  235. const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => {
  236. return await fetchQcResult(stockInLineId as number);
  237. }, []);
  238. // const handleQC = useCallback( // UNUSED NOW!
  239. // (id: GridRowId, params: any) => async () => {
  240. // setBtnIsLoading(true);
  241. // setRowModesModel((prev) => ({
  242. // ...prev,
  243. // [id]: { mode: GridRowModes.View },
  244. // }));
  245. // const qcResult = await fetchQcDefaultValue(id);
  246. // // console.log(params.row);
  247. // console.log("Fetched QC Result:", qcResult);
  248. // setModalInfo({
  249. // ...params.row,
  250. // qcResult: qcResult,
  251. // });
  252. // // set default values
  253. // setTimeout(() => {
  254. // // open qc modal
  255. // console.log("delayed");
  256. // openQcModal();
  257. // setBtnIsLoading(false);
  258. // }, 200);
  259. // },
  260. // [fetchQcDefaultValue, openQcModal],
  261. // );
  262. const [newOpen, setNewOpen] = useState(false);
  263. const stockInLineId = searchParams.get("stockInLineId");
  264. const poLineId = searchParams.get("poLineId");
  265. const closeNewModal = useCallback(() => {
  266. const newParams = new URLSearchParams(searchParams.toString());
  267. newParams.delete("stockInLineId"); // Remove the parameter
  268. router.replace(`${pathname}?${newParams.toString()}`);
  269. fetchPoDetail(itemDetail.purchaseOrderId.toString());
  270. setNewOpen(false); // Close the modal first
  271. // setTimeout(() => {
  272. // }, 300); // Add a delay to avoid immediate re-trigger of useEffect
  273. }, [searchParams, pathname, router]);
  274. // Open modal
  275. const openNewModal = useCallback(() => {
  276. setNewOpen(() => true);
  277. }, []);
  278. // Button handler to update the URL and open the modal
  279. const handleNewQC = useCallback(
  280. (id: GridRowId, params: any) => async() => {
  281. // setBtnIsLoading(true);
  282. setRowModesModel((prev) => ({
  283. ...prev,
  284. [id]: { mode: GridRowModes.View },
  285. }));
  286. // const qcResult = await fetchQcDefaultValue(id);
  287. // const escResult = await fetchEscalationLogsByStockInLines([Number(id)]);
  288. setModalInfo(() => ({
  289. ...params.row,
  290. // qcResult: qcResult,
  291. // escResult: escResult,
  292. receivedQty: itemDetail.receivedQty,
  293. }));
  294. const newParams = new URLSearchParams(searchParams.toString());
  295. newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates
  296. router.replace(`${pathname}?${newParams.toString()}`);
  297. openNewModal()
  298. // setTimeout(() => {
  299. // }, 200);
  300. },
  301. [openNewModal, pathname, router, searchParams]
  302. );
  303. // Open modal if `stockInLineId` exists in the URL
  304. const [firstCheckForSil, setFirstCheckForSil] = useState(false)
  305. useEffect(() => {
  306. if (stockInLineId && itemDetail && !firstCheckForSil) {
  307. // console.log(stockInLineId)
  308. // console.log(apiRef.current.getRow(stockInLineId))
  309. setFirstCheckForSil(true)
  310. const fn = handleNewQC(stockInLineId, {row: apiRef.current.getRow(stockInLineId)});
  311. fn();
  312. }
  313. }, [stockInLineId, poLineId, itemDetail]);
  314. const handleEscalation = useCallback(
  315. (id: GridRowId, params: any) => () => {
  316. // setBtnIsLoading(true);
  317. setRowModesModel((prev) => ({
  318. ...prev,
  319. [id]: { mode: GridRowModes.View },
  320. }));
  321. setModalInfo(params.row);
  322. setTimeout(() => {
  323. // open qc modal
  324. console.log("delayed");
  325. openEscalationModal();
  326. // setBtnIsLoading(false);
  327. }, 200);
  328. },
  329. [openEscalationModal],
  330. );
  331. const handleReject = useCallback(
  332. (id: GridRowId, params: any) => () => {
  333. setRowModesModel((prev) => ({
  334. ...prev,
  335. [id]: { mode: GridRowModes.View },
  336. }));
  337. setModalInfo(params.row);
  338. setTimeout(() => {
  339. // open stock in modal
  340. // openPutAwayModal();
  341. // return the record with its status as pending
  342. // update layout
  343. console.log("delayed");
  344. openRejectModal();
  345. // printQrcode(params.row);
  346. }, 200);
  347. },
  348. [openRejectModal],
  349. );
  350. const handleStockIn = useCallback(
  351. (id: GridRowId, params: any) => () => {
  352. // setBtnIsLoading(true);
  353. setRowModesModel((prev) => ({
  354. ...prev,
  355. [id]: { mode: GridRowModes.View },
  356. }));
  357. setModalInfo(params.row);
  358. setTimeout(() => {
  359. // open stock in modal
  360. openStockInModal();
  361. // return the record with its status as pending
  362. // update layout
  363. console.log("delayed");
  364. // setBtnIsLoading(false);
  365. }, 200);
  366. },
  367. [openStockInModal],
  368. );
  369. const handlePutAway = useCallback(
  370. (id: GridRowId, params: any) => () => {
  371. // setBtnIsLoading(true);
  372. setRowModesModel((prev) => ({
  373. ...prev,
  374. [id]: { mode: GridRowModes.View },
  375. }));
  376. setModalInfo(params.row);
  377. setTimeout(() => {
  378. // open stock in modal
  379. openPutAwayModal();
  380. // return the record with its status as pending
  381. // update layout
  382. console.log("delayed");
  383. // setBtnIsLoading(false);
  384. }, 200);
  385. },
  386. [openPutAwayModal],
  387. );
  388. const printQrcode = useCallback(
  389. async (row: any) => {
  390. setBtnIsLoading(true);
  391. console.log(row.id);
  392. const postData = { stockInLineIds: [row.id] };
  393. // const postData = { stockInLineIds: [42,43,44] };
  394. const response = await fetchPoQrcode(postData);
  395. if (response) {
  396. console.log(response);
  397. downloadFile(new Uint8Array(response.blobValue), response.filename!);
  398. }
  399. setBtnIsLoading(false);
  400. },
  401. [],
  402. );
  403. const getButtonSx = (sil : StockInLine) => {
  404. const status = sil?.status?.toLowerCase();
  405. let btnSx = {label:"", color:""};
  406. switch (status) {
  407. case "received": btnSx = {label: t("view putaway"), color:"secondary.main"}; break;
  408. case "escalated": if (sessionToken?.id == sil?.handlerId) {
  409. btnSx = {label: t("escalation processing"), color:"warning.main"};
  410. break;}
  411. case "rejected":
  412. case "partially_completed":
  413. case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break;
  414. default: btnSx = {label: t("qc processing"), color:"success.main"};
  415. }
  416. return btnSx
  417. };
  418. // const handleQrCode = useCallback(
  419. // (id: GridRowId, params: any) => () => {
  420. // setRowModesModel((prev) => ({
  421. // ...prev,
  422. // [id]: { mode: GridRowModes.View },
  423. // }));
  424. // setModalInfo(params.row);
  425. // setTimeout(() => {
  426. // // open stock in modal
  427. // // openPutAwayModal();
  428. // // return the record with its status as pending
  429. // // update layout
  430. // console.log("delayed");
  431. // printQrcode(params.row);
  432. // }, 200);
  433. // },
  434. // [printQrcode],
  435. // );
  436. const columns = useMemo<GridColDef[]>(
  437. () => [
  438. // {
  439. // field: "itemNo",
  440. // headerName: t("itemNo"),
  441. // width: 100,
  442. // // flex: 0.4,
  443. // },
  444. {
  445. field: "dnNo",
  446. headerName: t("dnNo"),
  447. width: 125,
  448. // renderCell: () => {
  449. // return <>DN0000001</>
  450. // }
  451. // flex: 0.4,
  452. },
  453. {
  454. field: "receiptDate",
  455. headerName: t("receiptDate"),
  456. width: 125,
  457. renderCell: (params) => {
  458. // console.log(params.row)
  459. // return <>07/08/2025</>
  460. return arrayToDateString(params.value)
  461. }
  462. // flex: 0.4,
  463. },
  464. {
  465. field: "productLotNo",
  466. headerName: t("productLotNo"),
  467. width: 125,
  468. },
  469. // {
  470. // field: "itemName",
  471. // headerName: t("itemName"),
  472. // width: 100,
  473. // // flex: 0.6,
  474. // },
  475. {
  476. field: "acceptedQty",
  477. headerName: t("acceptedQty"),
  478. // flex: 0.5,
  479. width: 125,
  480. type: "number",
  481. // editable: true,
  482. // replace with tooltip + content
  483. renderCell: (params) => {
  484. return integerFormatter.format(params.value)
  485. }
  486. },
  487. {
  488. field: "uom",
  489. headerName: t("uom"),
  490. width: 150,
  491. // flex: 0.5,
  492. renderCell: (params) => {
  493. return params.row.uom?.udfudesc;
  494. },
  495. },
  496. {
  497. field: "stockQty",
  498. headerName: t("Stock In Qty"),
  499. // flex: 0.5,
  500. width: 125,
  501. type: "number",
  502. // editable: true,
  503. // replace with tooltip + content
  504. renderCell: (params) => {
  505. const baseQty = (params.row.acceptedQty ?? 0) * (itemDetail.stockUom.purchaseRatioN ?? 1) / (itemDetail.stockUom.purchaseRatioD ?? 1)
  506. const stockQty = baseQty * (itemDetail.stockUom.stockRatioD ?? 1) / (itemDetail.stockUom.stockRatioN ?? 1)
  507. return decimalFormatter.format(stockQty)
  508. }
  509. },
  510. {
  511. field: "stockUom",
  512. headerName: t("Stock UoM"),
  513. width: 150,
  514. // flex: 0.5,
  515. renderCell: (params) => {
  516. return itemDetail.stockUom.stockUomDesc;
  517. },
  518. },
  519. // {
  520. // field: "weight",
  521. // headerName: t("weight"),
  522. // width: 120,
  523. // // flex: 0.5,
  524. // renderCell: (params) => {
  525. // const weight = calculateWeight(
  526. // params.row.acceptedQty,
  527. // params.row.uom,
  528. // );
  529. // const weightUnit = returnWeightUnit(params.row.uom);
  530. // return `${decimalFormatter.format(weight)} ${weightUnit}`;
  531. // },
  532. // },
  533. {
  534. field: "status",
  535. headerName: t("Status"),
  536. width: 140,
  537. // flex: 0.5,
  538. renderCell: (params) => {
  539. const handlerId = params.row.handlerId
  540. const status = params.row.status
  541. return (<span style={{
  542. color:
  543. (status == "escalated")? "red":
  544. (status == "rejected" || status == "partially_completed") ? "orange" : "inherit"}}
  545. >
  546. {t(`${params.row.status}`)}
  547. </span>);
  548. },
  549. },
  550. {
  551. field: "actions",
  552. type: "actions",
  553. // headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t(
  554. // "stock in",
  555. // )} | ${t("putaway")} | ${t("delete")}`,
  556. headerName: "操作",
  557. // headerName: "start | qc | escalation | stock in | putaway | delete",
  558. width: 350, //200
  559. // flex: 2,
  560. cellClassName: "actions",
  561. getActions: (params) => {
  562. const data = params.row;
  563. // console.log(params.row.status);
  564. const btnSx = getButtonSx(data);
  565. // console.log(stockInLineStatusMap[status]);
  566. // console.log(session?.user?.abilities?.includes("APPROVAL"));
  567. return [
  568. <GridActionsCellItem
  569. icon={<Button variant="contained" sx={{ width: '150px', backgroundColor: btnSx.color }}>
  570. {btnSx.label}</Button>}
  571. label="start"
  572. sx={{
  573. // color: "primary.main",
  574. // marginRight: 1,
  575. }}
  576. onClick={handleNewQC(params.row.id, params)}
  577. color="inherit"
  578. key={`edit`}
  579. />,
  580. <GridActionsCellItem
  581. icon={
  582. data.status !== "received" ?
  583. (<Button
  584. id="emailSupplier"
  585. type="button"
  586. variant="contained"
  587. color="primary"
  588. sx={{ width: '150px' }}
  589. disabled={params.row.status != "rejected" && params.row.status != "partially_completed"}
  590. onClick={() => handleMailTemplateForStockInLine(params.row.id as number)}
  591. >
  592. {t("email supplier")}
  593. </Button>) : (
  594. <Button
  595. id="printQrCode"
  596. type="button"
  597. variant="contained"
  598. // color="red"
  599. sx={{ width: '150px', backgroundColor: '#7f434a' }}
  600. disabled={btnIsLoading || data.status != "received"}
  601. onClick={() => printQrcode(params.row)}
  602. >
  603. {t("printQrCode")}
  604. </Button>
  605. )
  606. }
  607. label="start"
  608. sx={{
  609. // color: "primary.main",
  610. // marginRight: 1,
  611. }}
  612. // onClick={handleNewQC(params.row.id, params)}
  613. color="inherit"
  614. key="edit"
  615. />,
  616. // <GridActionsCellItem
  617. // icon={<Button variant="contained">{t("putawayBtn")}</Button>}
  618. // label="start"
  619. // sx={{
  620. // color: "primary.main",
  621. // // marginRight: 1,
  622. // }}
  623. // // disabled={!(stockInLineStatusMap[status] === 0)}
  624. // // set _isNew to false after posting
  625. // // or check status
  626. // onClick={handleStart(params.row.id, params)}
  627. // color="inherit"
  628. // key="edit"
  629. // />,
  630. // <GridActionsCellItem
  631. // icon={<Button variant="contained">{t("qc processing")}</Button>}
  632. // label="start"
  633. // sx={{
  634. // color: "primary.main",
  635. // // marginRight: 1,
  636. // }}
  637. // disabled={!(stockInLineStatusMap[status] === 0)}
  638. // // set _isNew to false after posting
  639. // // or check status
  640. // onClick={handleStart(params.row.id, params)}
  641. // color="inherit"
  642. // key="edit"
  643. // />,
  644. // <GridActionsCellItem
  645. // icon={<FactCheckIcon />}
  646. // label="qc"
  647. // sx={{
  648. // color: "primary.main",
  649. // // marginRight: 1,
  650. // }}
  651. // disabled={
  652. // // stockInLineStatusMap[status] === 9 ||
  653. // stockInLineStatusMap[status] < 1
  654. // }
  655. // // set _isNew to false after posting
  656. // // or check status
  657. // onClick={handleQC(params.row.id, params)}
  658. // color="inherit"
  659. // key="edit"
  660. // />,
  661. // <GridActionsCellItem
  662. // icon={<NotificationImportantIcon />}
  663. // label="escalation"
  664. // sx={{
  665. // color: "primary.main",
  666. // // marginRight: 1,
  667. // }}
  668. // disabled={
  669. // stockInLineStatusMap[status] === 9 ||
  670. // stockInLineStatusMap[status] <= 0 ||
  671. // stockInLineStatusMap[status] >= 5
  672. // }
  673. // // set _isNew to false after posting
  674. // // or check status
  675. // onClick={handleEscalation(params.row.id, params)}
  676. // color="inherit"
  677. // key="edit"
  678. // />,
  679. // <GridActionsCellItem
  680. // icon={<ShoppingCartIcon />}
  681. // label="stockin"
  682. // sx={{
  683. // color: "primary.main",
  684. // // marginRight: 1,
  685. // }}
  686. // disabled={
  687. // stockInLineStatusMap[status] === 9 ||
  688. // stockInLineStatusMap[status] <= 2 ||
  689. // stockInLineStatusMap[status] >= 7 ||
  690. // (stockInLineStatusMap[status] >= 3 &&
  691. // stockInLineStatusMap[status] <= 5 &&
  692. // !session?.user?.abilities?.includes("APPROVAL"))
  693. // }
  694. // // set _isNew to false after posting
  695. // // or check status
  696. // onClick={handleStockIn(params.row.id, params)}
  697. // color="inherit"
  698. // key="edit"
  699. // />,
  700. // <GridActionsCellItem
  701. // icon={<ShoppingCartIcon />}
  702. // label="putaway"
  703. // sx={{
  704. // color: "primary.main",
  705. // // marginRight: 1,
  706. // }}
  707. // disabled={
  708. // stockInLineStatusMap[status] === 9 ||
  709. // stockInLineStatusMap[status] < 7
  710. // }
  711. // // set _isNew to false after posting
  712. // // or check status
  713. // onClick={handlePutAway(params.row.id, params)}
  714. // color="inherit"
  715. // key="edit"
  716. // />,
  717. // // <GridActionsCellItem
  718. // // icon={<QrCodeIcon />}
  719. // // label="putaway"
  720. // // sx={{
  721. // // color: "primary.main",
  722. // // // marginRight: 1,
  723. // // }}
  724. // // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8}
  725. // // // set _isNew to false after posting
  726. // // // or check status
  727. // // onClick={handleQrCode(params.row.id, params)}
  728. // // color="inherit"
  729. // // key="edit"
  730. // // />,
  731. // <GridActionsCellItem
  732. // icon={
  733. // stockInLineStatusMap[status] >= 1 ? (
  734. // <DoDisturbIcon />
  735. // ) : (
  736. // <DeleteIcon />
  737. // )
  738. // }
  739. // label="Delete"
  740. // sx={{
  741. // color: "error.main",
  742. // }}
  743. // disabled={
  744. // stockInLineStatusMap[status] >= 7 &&
  745. // stockInLineStatusMap[status] <= 9
  746. // }
  747. // onClick={
  748. // stockInLineStatusMap[status] === 0
  749. // ? handleDelete(params.row.id)
  750. // : handleReject(params.row.id, params)
  751. // }
  752. // color="inherit"
  753. // key="edit"
  754. // />,
  755. ];
  756. },
  757. },
  758. ],
  759. [t, handleStart, handleEscalation, handleStockIn, handlePutAway, handleDelete, handleReject, itemDetail],
  760. );
  761. const unsortableColumns = useMemo(() =>
  762. columns.map(column => ({ ...column, sortable: false }))
  763. , [columns]);
  764. const addRow = useCallback(() => {
  765. console.log(itemDetail);
  766. const newEntry = {
  767. id: Date.now(),
  768. _isNew: true,
  769. itemId: itemDetail.itemId,
  770. purchaseOrderId: itemDetail.purchaseOrderId,
  771. purchaseOrderLineId: itemDetail.id,
  772. itemNo: itemDetail.itemNo,
  773. itemName: itemDetail.itemName,
  774. acceptedQty: itemDetail.qty - currQty, // this bug
  775. uom: itemDetail.uom,
  776. status: "draft",
  777. };
  778. setEntries((e) => [...e, newEntry]);
  779. setRowModesModel((model) => ({
  780. ...model,
  781. [getRowId(newEntry)]: {
  782. mode: GridRowModes.Edit,
  783. // fieldToFocus: "projectId",
  784. },
  785. }));
  786. }, [currQty, getRowId, itemDetail]);
  787. const validation = useCallback(
  788. (
  789. newRow: GridRowModel<StockInLineRow>,
  790. // rowModel: GridRowSelectionModel
  791. ): StockInLineEntryError | undefined => {
  792. const error: StockInLineEntryError = {};
  793. console.log(newRow);
  794. console.log(currQty);
  795. if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) {
  796. error["acceptedQty"] = t("qty cannot be greater than remaining qty");
  797. }
  798. return Object.keys(error).length > 0 ? error : undefined;
  799. },
  800. [currQty, itemDetail.qty, t],
  801. );
  802. const processRowUpdate = useCallback(
  803. (
  804. newRow: GridRowModel<StockInLineRow>,
  805. originalRow: GridRowModel<StockInLineRow>,
  806. ) => {
  807. const errors = validation(newRow); // change to validation
  808. if (errors) {
  809. throw new ProcessRowUpdateError(
  810. originalRow,
  811. "validation error",
  812. errors,
  813. );
  814. }
  815. const { _isNew, _error, ...updatedRow } = newRow;
  816. const rowToSave = {
  817. ...updatedRow,
  818. } satisfies StockInLineRow;
  819. const newEntries = entries.map((e) =>
  820. getRowId(e) === getRowId(originalRow) ? rowToSave : e,
  821. );
  822. setStockInLine(newEntries as StockInLine[]);
  823. console.log("triggered");
  824. setEntries(newEntries);
  825. //update remaining qty
  826. const total = newEntries.reduce(
  827. (acc, curr) => acc + (curr.acceptedQty || 0),
  828. 0,
  829. );
  830. setCurrQty(total);
  831. return rowToSave;
  832. },
  833. [validation, entries, setStockInLine, getRowId],
  834. );
  835. const onProcessRowUpdateError = useCallback(
  836. (updateError: ProcessRowUpdateError) => {
  837. const errors = updateError.errors;
  838. const oldRow = updateError.row;
  839. apiRef.current.updateRows([{ ...oldRow, _error: errors }]);
  840. },
  841. [apiRef],
  842. );
  843. const footer = (
  844. <>
  845. {/* <Box display="flex" gap={2} alignItems="center">
  846. <Button
  847. disableRipple
  848. variant="outlined"
  849. startIcon={<Add />}
  850. disabled={itemDetail.qty - currQty <= 0}
  851. onClick={addRow}
  852. size="small"
  853. >
  854. {t("Record pol")}
  855. </Button>
  856. </Box> */}
  857. </>
  858. );
  859. return (
  860. <>
  861. <StyledDataGrid
  862. getRowId={getRowId}
  863. apiRef={apiRef}
  864. autoHeight
  865. sx={{
  866. "--DataGrid-overlayHeight": "100px",
  867. ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
  868. border: "1px solid",
  869. borderColor: "error.main",
  870. },
  871. ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
  872. border: "1px solid",
  873. borderColor: "warning.main",
  874. },
  875. }}
  876. disableColumnMenu
  877. editMode="row"
  878. rows={entries}
  879. rowModesModel={rowModesModel}
  880. onRowModesModelChange={setRowModesModel}
  881. processRowUpdate={processRowUpdate}
  882. onProcessRowUpdateError={onProcessRowUpdateError}
  883. columns={unsortableColumns}
  884. isCellEditable={(params) => {
  885. const status = params.row.status.toLowerCase();
  886. return (
  887. stockInLineStatusMap[status] >= 0 ||
  888. stockInLineStatusMap[status] <= 1
  889. );
  890. }}
  891. getCellClassName={(params: GridCellParams<StockInLineRow>) => {
  892. let classname = "";
  893. if (params.row._error) {
  894. classname = "hasError";
  895. }
  896. return classname;
  897. }}
  898. slots={{
  899. footer: FooterToolbar,
  900. noRowsOverlay: NoRowsOverlay,
  901. }}
  902. slotProps={{
  903. footer: { child: footer },
  904. }}
  905. />
  906. {/* {modalInfo !== undefined && ( */}
  907. <>
  908. <QcStockInModal
  909. session={sessionToken}
  910. open={newOpen}
  911. onClose={closeNewModal}
  912. // itemDetail={modalInfo}
  913. inputDetail={modalInfo}
  914. printerCombo={printerCombo}
  915. />
  916. </>
  917. {/* )
  918. } */}
  919. </>
  920. );
  921. }
  922. const NoRowsOverlay: React.FC = () => {
  923. const { t } = useTranslation("purchaseOrder");
  924. return (
  925. <Box
  926. display="flex"
  927. justifyContent="center"
  928. alignItems="center"
  929. height="100%"
  930. >
  931. <Typography variant="caption">{t("Add some entries!")}</Typography>
  932. </Box>
  933. );
  934. };
  935. const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  936. return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
  937. };
  938. export default PoInputGrid;