FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

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