|
- "use client";
- import {
- FooterPropsOverrides,
- GridCellParams,
- GridRowId,
- GridRowIdGetter,
- GridRowModel,
- GridRowModes,
- GridRowModesModel,
- GridToolbarContainer,
- GridValidRowModel,
- useGridApiRef,
- } from "@mui/x-data-grid";
- import {
- Dispatch,
- MutableRefObject,
- SetStateAction,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
- } from "react";
- import StyledDataGrid from "../StyledDataGrid";
- import { GridColDef } from "@mui/x-data-grid";
- import { Box, Button, Grid, Typography, useMediaQuery, useTheme } from "@mui/material";
- import { useTranslation } from "react-i18next";
- import { Add } from "@mui/icons-material";
- import SaveIcon from "@mui/icons-material/Save";
- import DeleteIcon from "@mui/icons-material/Delete";
- import CancelIcon from "@mui/icons-material/Cancel";
- import FactCheckIcon from "@mui/icons-material/FactCheck";
- import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
- // import { QcItemWithChecks } from "src/app/api/qc";
- import PlayArrowIcon from "@mui/icons-material/PlayArrow";
- import { PurchaseOrderLine } from "@/app/api/po";
- import { StockInLine } from "@/app/api/stockIn";
- import { createStockInLine, deleteStockInLine, updateStockInLine, QcResult } from "@/app/api/stockIn/actions";
- import { usePathname, useSearchParams } from "next/navigation";
- import {
- returnWeightUnit,
- calculateWeight,
- stockInLineStatusMap,
- arrayToDateString,
- } from "@/app/utils/formatUtil";
- // import PoQcStockInModal from "./PoQcStockInModal";
- import NotificationImportantIcon from "@mui/icons-material/NotificationImportant";
- import { WarehouseResult } from "@/app/api/warehouse";
- import LooksOneIcon from "@mui/icons-material/LooksOne";
- import LooksTwoIcon from "@mui/icons-material/LooksTwo";
- import Looks3Icon from "@mui/icons-material/Looks3";
- import axiosInstance from "@/app/(main)/axios/axiosInstance";
- // import axios, { AxiosRequestConfig } from "axios";
- import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api";
- import qs from "qs";
- import QrCodeIcon from "@mui/icons-material/QrCode";
- import { downloadFile } from "@/app/utils/commonUtil";
- import { fetchPoQrcode } from "@/app/api/pdf/actions";
- import { fetchQcResult } from "@/app/api/qc/actions";
- import DoDisturbIcon from "@mui/icons-material/DoDisturb";
- import { useSession } from "next-auth/react";
- // import { SessionWithTokens } from "src/config/authConfig";
- import QcStockInModal from "../Qc/QcStockInModal";
- import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
- import { PrinterCombo } from "@/app/api/settings/printer";
- import { EscalationResult } from "@/app/api/escalation";
- import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions";
- import { SessionWithTokens } from "@/config/authConfig";
- import { EscalationCombo } from "@/app/api/user";
- import { deleteDialog } from "../Swal/CustomAlerts";
- import StockInLineRowActions from "./StockInLineRowActions";
- import { StockQtyRoundMode, needsPoQcStockQtyRound } from "./stockQtyRound";
-
- // 3 buttons after QC (view + print QR + delete): 176*3 + gap + cell padding
- const ACTIONS_COLUMN_WIDTH = 580;
- const PURCHASE_QTY_COLUMN_WIDTH = 72;
- const UOM_COLUMN_WIDTH = 124;
- const STOCK_QTY_COLUMN_WIDTH = 110;
- const STOCK_IN_ROW_HEIGHT = 58;
-
- /** Extra table width is shared by text columns; qty / status / actions stay tight. */
- const COLUMN_GROW: Record<string, number> = {
- dnNo: 1,
- productLotNo: 1,
- uom: 1.5,
- stockQty: 1,
- stockUom: 1.5,
- };
-
- /** Tighter horizontal padding for narrow data columns (headers unchanged). */
- const COMPACT_STOCK_IN_CELL_FIELDS = [
- "dnNo",
- "productLotNo",
- "purchaseAcceptedQty",
- "uom",
- "stockUom",
- "status",
- ] as const;
-
- function canDeleteStockInLine(sil: StockInLineRow): boolean {
- if (sil._isNew || sil.status === "draft") {
- return true;
- }
- const hasPutAway = (sil.putAwayLines ?? []).some(
- (p) => Number(p.stockQty ?? p.qty ?? 0) > 0,
- );
- if (hasPutAway) return false;
- const status = (sil.status ?? "").toLowerCase();
- return status !== "completed" && status !== "partially_completed";
- }
-
- interface ResultWithId {
- id: number;
- }
-
- interface Props {
- // qc: QcItemWithChecks[];
- setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
- setStockInLine: Dispatch<SetStateAction<StockInLine[]>>;
- setProcessedQty: Dispatch<SetStateAction<number>>;
- itemDetail: PurchaseOrderLine;
- stockInLine: StockInLine[];
- warehouse: WarehouseResult[];
- fetchPoDetail: (poId: string, preserveDnNo?: boolean, preferredPolId?: number) => void;
- handleMailTemplateForStockInLine: (stockInLineId: number) => void;
- printerCombo: PrinterCombo[];
- }
-
- export type StockInLineEntryError = {
- [field in keyof StockInLine]?: string;
- };
-
- export type StockInLineRow = Partial<
- StockInLine & {
- isActive: boolean | undefined;
- _isNew: boolean;
- _error: StockInLineEntryError;
- } & ResultWithId
- >;
-
- class ProcessRowUpdateError extends Error {
- public readonly row: StockInLineRow;
- public readonly errors: StockInLineEntryError | undefined;
- constructor(
- row: StockInLineRow,
- message?: string,
- errors?: StockInLineEntryError,
- ) {
- super(message);
- this.row = row;
- this.errors = errors;
-
- Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
- }
- }
-
- /** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */
- function PoInputGrid({
- // qc,
- setRows,
- setStockInLine,
- setProcessedQty,
- itemDetail,
- stockInLine,
- warehouse,
- fetchPoDetail,
- handleMailTemplateForStockInLine,
- printerCombo,
- }: Props) {
-
- const { t } = useTranslation("purchaseOrder");
- const theme = useTheme();
- /** Narrow phones: hide low-priority columns. */
- const isCompact = useMediaQuery(theme.breakpoints.down("md"), { noSsr: true });
- const apiRef = useGridApiRef();
- const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
- const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>(
- (row) => row.id as number,
- [],
- );
-
- const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
- useEffect(() => {
- setEntries(stockInLine);
- }, [stockInLine])
- const [modalInfo, setModalInfo] = useState<
- StockInLine & { qcResult?: QcResult[] } & { escalationResult?: EscalationResult[] }
- >();
- const pathname = usePathname()
- const searchParams = useSearchParams();
-
- const [qcOpen, setQcOpen] = useState(false);
- const [escalOpen, setEscalOpen] = useState(false);
- const [stockInOpen, setStockInOpen] = useState(false);
- const [putAwayOpen, setPutAwayOpen] = useState(false);
- const [rejectOpen, setRejectOpen] = useState(false);
- const [btnIsLoading, setBtnIsLoading] = useState(false);
- const [isDeleting, setIsDeleting] = useState(false);
- const deleteInFlightRef = useRef(false);
- const roundInFlightRef = useRef(false);
- const [roundingSilId, setRoundingSilId] = useState<number | null>(null);
- const [currQty, setCurrQty] = useState(() => {
- const total = entries.reduce(
- // remaining qty (M18 unit)
- (acc, curr) => acc + (curr.purchaseAcceptedQty || 0),
- 0,
- );
- return total;
- });
-
- const { data: session } = useSession();
- const sessionToken = session as SessionWithTokens | null;
-
- useEffect(() => {
- const completedList = entries.filter(
- (e) => stockInLineStatusMap[e.status!] >= 8,
- );
- const processedQty = completedList.reduce(
- (acc, curr) => acc + (curr.acceptedQty || 0),
- 0,
- );
- setProcessedQty(processedQty);
- }, [entries, setProcessedQty]);
-
- const handleDelete = useCallback(
- (id: GridRowId) => () => {
- setEntries((es) => es.filter((e) => getRowId(e) !== id));
- },
- [getRowId],
- );
-
- const handleSoftDelete = useCallback(
- (row: StockInLineRow) => {
- if (deleteInFlightRef.current || isDeleting) return;
- if (
- needsPoQcStockQtyRound(
- row.purchaseOrderLineId,
- row.status,
- row.acceptedQty,
- )
- ) {
- alert("請先在換算庫存數量選擇向上或向下取整");
- return;
- }
-
- const rowId = row.id as number;
- const isDraft = row._isNew || row.status === "draft";
-
- const doDelete = async () => {
- if (deleteInFlightRef.current) return;
- deleteInFlightRef.current = true;
- setIsDeleting(true);
- try {
- if (isDraft) {
- handleDelete(rowId)();
- return;
- }
- await deleteStockInLine(rowId);
- await fetchPoDetail(
- String(itemDetail.purchaseOrderId),
- true,
- itemDetail.id,
- );
- } catch (error) {
- console.error("Failed to delete stock in line:", error);
- alert(t("Cannot delete put away record"));
- } finally {
- setIsDeleting(false);
- deleteInFlightRef.current = false;
- }
- };
-
- void deleteDialog(doDelete, t);
- },
- [fetchPoDetail, handleDelete, isDeleting, itemDetail.id, itemDetail.purchaseOrderId, t],
- );
-
- const handleRoundStockQty = useCallback(
- (row: StockInLineRow, mode: StockQtyRoundMode) => {
- if (roundInFlightRef.current) return;
- const silId = row.id;
- const itemId = row.itemId;
- const purchaseQty = Number(row.purchaseAcceptedQty ?? 0);
- if (!silId || !itemId || purchaseQty <= 0) return;
-
- const doRound = async () => {
- if (roundInFlightRef.current) return;
- roundInFlightRef.current = true;
- setRoundingSilId(silId);
- try {
- const res = await updateStockInLine({
- id: silId,
- itemId,
- purchaseOrderLineId: row.purchaseOrderLineId,
- acceptedQty: purchaseQty,
- dnNo: row.dnNo,
- productLotNo: row.productLotNo,
- stockQtyRoundMode: mode,
- stockQtyRoundSource: "CREATE",
- });
- if (res) {
- await fetchPoDetail(
- String(itemDetail.purchaseOrderId),
- true,
- itemDetail.id,
- );
- }
- } catch (error) {
- console.error("Failed to round stock qty:", error);
- alert(t("Please choose a rounding method"));
- } finally {
- setRoundingSilId(null);
- roundInFlightRef.current = false;
- }
- };
-
- void doRound();
- },
- [fetchPoDetail, itemDetail.id, itemDetail.purchaseOrderId, t],
- );
-
- const closeQcModal = useCallback(() => {
- setQcOpen(false);
- }, []);
- const openQcModal = useCallback(() => {
- setQcOpen(true);
- }, []);
-
- const closeStockInModal = useCallback(() => {
- setStockInOpen(false);
- }, []);
- const openStockInModal = useCallback(() => {
- setStockInOpen(true);
- }, []);
-
- const closePutAwayModal = useCallback(() => {
- setPutAwayOpen(false);
- }, []);
- const openPutAwayModal = useCallback(() => {
- setPutAwayOpen(true);
- }, []);
-
- const closeEscalationModal = useCallback(() => {
- setEscalOpen(false);
- }, []);
- const openEscalationModal = useCallback(() => {
- setEscalOpen(true);
- }, []);
-
- const closeRejectModal = useCallback(() => {
- setRejectOpen(false);
- }, []);
- const openRejectModal = useCallback(() => {
- setRejectOpen(true);
- }, []);
-
- const handleStart = useCallback( // NOTE: Seems unused!!!!!!!!
- (id: GridRowId, params: any) => () => {
- setBtnIsLoading(true);
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
- setTimeout(async () => {
- // post stock in line
- const oldId = params.row.id;
- const postData = {
- itemId: params.row.itemId,
- itemNo: params.row.itemNo,
- itemName: params.row.itemName,
- // purchaseOrderId: params.row.purchaseOrderId,
- purchaseOrderLineId: params.row.purchaseOrderLineId,
- // For PO-origin, backend expects M18 qty and converts it to stock qty.
- acceptedQty: params.row.purchaseAcceptedQty ?? params.row.acceptedQty,
- };
- const res = await createStockInLine(postData);
- console.log(res);
- setEntries((prev) =>
- prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)),
- );
- setStockInLine(
- (prev) =>
- prev.map((p) =>
- p.id === oldId ? (res.entity as StockInLine) : p,
- ) as StockInLine[],
- );
- setBtnIsLoading(false);
- // do post directly to test
- // openStartModal();
- }, 200);
- },
- [setStockInLine],
- );
- const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => {
- return await fetchQcResult(stockInLineId as number);
- }, []);
-
- // const handleQC = useCallback( // UNUSED NOW!
- // (id: GridRowId, params: any) => async () => {
- // setBtnIsLoading(true);
- // setRowModesModel((prev) => ({
- // ...prev,
- // [id]: { mode: GridRowModes.View },
- // }));
- // const qcResult = await fetchQcDefaultValue(id);
- // // console.log(params.row);
- // console.log("Fetched QC Result:", qcResult);
-
- // setModalInfo({
- // ...params.row,
- // qcResult: qcResult,
- // });
- // // set default values
- // setTimeout(() => {
- // // open qc modal
- // console.log("delayed");
- // openQcModal();
- // setBtnIsLoading(false);
- // }, 200);
- // },
- // [fetchQcDefaultValue, openQcModal],
- // );
-
- const [newOpen, setNewOpen] = useState(false);
- const stockInLineIdFromNext = searchParams.get("stockInLineId");
- const poLineId = searchParams.get("poLineId");
-
- const patchQuery = useCallback(
- (mutate: (params: URLSearchParams) => void) => {
- if (typeof window === "undefined") return;
- const params = new URLSearchParams(window.location.search);
- mutate(params);
- const qs = params.toString();
- window.history.replaceState(
- window.history.state,
- "",
- qs ? `${pathname}?${qs}` : pathname,
- );
- },
- [pathname],
- );
-
- const getLiveStockInLineId = useCallback((): string | null => {
- if (typeof window !== "undefined") {
- return new URLSearchParams(window.location.search).get("stockInLineId");
- }
- return stockInLineIdFromNext;
- }, [stockInLineIdFromNext]);
-
- const closeNewModal = useCallback((updatedStockInLine?: StockInLine) => {
- patchQuery((params) => {
- params.delete("stockInLineId");
- });
- setNewOpen(false);
-
- if (updatedStockInLine?.id != null) {
- setEntries((prev) =>
- prev.map((e) => (e.id === updatedStockInLine.id ? { ...e, ...updatedStockInLine } : e))
- );
- setStockInLine((prev) =>
- (prev || []).map((p) => (p.id === updatedStockInLine.id ? { ...p, ...updatedStockInLine } : p))
- );
- }
- }, [patchQuery, setStockInLine]);
-
- // Open modal
- const openNewModal = useCallback(() => {
- setNewOpen(() => true);
- }, []);
-
- // Button handler to update the URL and open the modal
- const handleNewQC = useCallback(
- (id: GridRowId, params: any) => async() => {
- if (!params?.row) return;
- if (
- needsPoQcStockQtyRound(
- params.row.purchaseOrderLineId,
- params.row.status,
- params.row.acceptedQty,
- )
- ) {
- alert("請先在換算庫存數量選擇向上或向下取整");
- return;
- }
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
-
- setModalInfo(() => ({
- ...params.row,
- receivedQty: itemDetail.receivedQty,
- }));
-
- // Avoid router.replace — it scrolls the page to top
- patchQuery((params) => {
- params.set("stockInLineId", id.toString());
- });
- openNewModal();
- },
- [openNewModal, patchQuery, itemDetail.receivedQty],
- );
-
- // Open modal if `stockInLineId` exists in the live URL (and belongs to current grid)
- const [firstCheckForSil, setFirstCheckForSil] = useState(false);
- useEffect(() => {
- setFirstCheckForSil(false);
- }, [itemDetail.id]);
- useEffect(() => {
- if (!itemDetail || firstCheckForSil) return;
-
- const liveStockInLineId = getLiveStockInLineId();
- if (!liveStockInLineId) {
- setFirstCheckForSil(true);
- return;
- }
-
- const row = apiRef.current.getRow(Number(liveStockInLineId));
- if (!row) {
- // Stale query from another POL: drop it once current entries are known
- if (
- entries.length > 0 &&
- !entries.some((e) => String(e.id) === String(liveStockInLineId))
- ) {
- patchQuery((params) => {
- params.delete("stockInLineId");
- });
- setFirstCheckForSil(true);
- }
- return;
- }
-
- setFirstCheckForSil(true);
- void handleNewQC(liveStockInLineId, { row })();
- }, [
- stockInLineIdFromNext,
- poLineId,
- itemDetail,
- firstCheckForSil,
- entries,
- handleNewQC,
- getLiveStockInLineId,
- patchQuery,
- ]);
- const handleEscalation = useCallback(
- (id: GridRowId, params: any) => () => {
- // setBtnIsLoading(true);
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
- setModalInfo(params.row);
- setTimeout(() => {
- // open qc modal
- console.log("delayed");
- openEscalationModal();
- // setBtnIsLoading(false);
- }, 200);
- },
- [openEscalationModal],
- );
-
- const handleReject = useCallback(
- (id: GridRowId, params: any) => () => {
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
- setModalInfo(params.row);
- setTimeout(() => {
- // open stock in modal
- // openPutAwayModal();
- // return the record with its status as pending
- // update layout
- console.log("delayed");
- openRejectModal();
- // printQrcode(params.row);
- }, 200);
- },
- [openRejectModal],
- );
-
- const handleStockIn = useCallback(
- (id: GridRowId, params: any) => () => {
- // setBtnIsLoading(true);
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
- setModalInfo(params.row);
- setTimeout(() => {
- // open stock in modal
- openStockInModal();
- // return the record with its status as pending
- // update layout
- console.log("delayed");
- // setBtnIsLoading(false);
- }, 200);
- },
- [openStockInModal],
- );
-
- const handlePutAway = useCallback(
- (id: GridRowId, params: any) => () => {
- // setBtnIsLoading(true);
- setRowModesModel((prev) => ({
- ...prev,
- [id]: { mode: GridRowModes.View },
- }));
- setModalInfo(params.row);
- setTimeout(() => {
- // open stock in modal
- openPutAwayModal();
- // return the record with its status as pending
- // update layout
- console.log("delayed");
- // setBtnIsLoading(false);
- }, 200);
- },
- [openPutAwayModal],
- );
-
- const printQrcode = useCallback(
- async (row: any) => {
- setBtnIsLoading(true);
- console.log(row.id);
- const postData = { stockInLineIds: [row.id] };
- // const postData = { stockInLineIds: [42,43,44] };
- const response = await fetchPoQrcode(postData);
- if (response) {
- console.log(response);
- downloadFile(new Uint8Array(response.blobValue), response.filename!);
- }
- setBtnIsLoading(false);
- },
- [],
- );
-
- const getButtonSx = (sil: StockInLineRow) => {
- const status = sil?.status?.toLowerCase();
- let btnSx = { label: "", color: "" };
- switch (status) {
- case "received":
- btnSx = { label: t("view putaway"), color: "secondary.main" };
- break;
- case "escalated":
- if (sessionToken?.id == sil?.handlerId) {
- btnSx = { label: t("escalation processing"), color: "warning.main" };
- break;
- }
- btnSx = { label: t("qc processing"), color: "success.main" };
- break;
- case "rejected":
- case "partially_completed":
- case "completed":
- btnSx = { label: t("view stockin"), color: "info.main" };
- break;
- default:
- btnSx = { label: t("qc processing"), color: "success.main" };
- }
- return btnSx;
- };
-
- const columnVisibilityModel = useMemo(
- () => ({
- uom: !isCompact,
- stockUom: !isCompact,
- }),
- [isCompact],
- );
-
- // const handleQrCode = useCallback(
- // (id: GridRowId, params: any) => () => {
- // setRowModesModel((prev) => ({
- // ...prev,
- // [id]: { mode: GridRowModes.View },
- // }));
- // setModalInfo(params.row);
- // setTimeout(() => {
- // // open stock in modal
- // // openPutAwayModal();
- // // return the record with its status as pending
- // // update layout
- // console.log("delayed");
- // printQrcode(params.row);
- // }, 200);
- // },
- // [printQrcode],
- // );
-
- const columns = useMemo<GridColDef[]>(() => {
- const baseColumns: GridColDef[] = [
- {
- field: "dnNo",
- headerName: t("dnNo"),
- width: 100,
- minWidth: 100,
- },
- {
- field: "receiptDate",
- headerName: t("receiptDate"),
- width: 125,
- renderCell: (params) => arrayToDateString(params.value),
- },
- {
- field: "productLotNo",
- headerName: t("productLotNo"),
- width: 110,
- minWidth: 110,
- },
- {
- field: "purchaseAcceptedQty",
- headerName: t("acceptedQty"),
- width: PURCHASE_QTY_COLUMN_WIDTH,
- minWidth: PURCHASE_QTY_COLUMN_WIDTH,
- flex: 0,
- align: "right",
- headerAlign: "right",
- type: "number",
- renderCell: (params) => {
- const qty = params.row.purchaseAcceptedQty ?? 0;
- return integerFormatter.format(qty);
- },
- },
- {
- field: "uom",
- headerName: t("uom"),
- width: UOM_COLUMN_WIDTH,
- minWidth: UOM_COLUMN_WIDTH,
- flex: 0,
- renderCell: () => {
- const text = itemDetail.uom?.udfudesc ?? "-";
- return (
- <Box
- sx={{
- overflow: "hidden",
- textOverflow: "ellipsis",
- whiteSpace: "nowrap",
- width: "100%",
- }}
- title={text}
- >
- {text}
- </Box>
- );
- },
- },
- {
- field: "stockQty",
- headerName: t("Stock In Qty"),
- width: STOCK_QTY_COLUMN_WIDTH,
- minWidth: STOCK_QTY_COLUMN_WIDTH,
- flex: 0,
- type: "number",
- align: "left",
- headerAlign: "left",
- renderCell: (params) => {
- const stockQty = Number(params.row.acceptedQty ?? 0);
- return decimalFormatter.format(stockQty);
- },
- },
- {
- field: "stockUom",
- headerName: t("Stock UoM"),
- width: UOM_COLUMN_WIDTH,
- minWidth: UOM_COLUMN_WIDTH,
- flex: 0,
- renderCell: () => {
- const text = itemDetail.stockUom.stockUomDesc ?? "-";
- return (
- <Box
- sx={{
- overflow: "hidden",
- textOverflow: "ellipsis",
- whiteSpace: "nowrap",
- width: "100%",
- }}
- title={text}
- >
- {text}
- </Box>
- );
- },
- },
- {
- field: "status",
- headerName: t("Status"),
- width: 88,
- renderCell: (params) => {
- const status = params.row.status;
- return (
- <span
- style={{
- color:
- status == "escalated"
- ? "red"
- : status == "rejected" || status == "partially_completed"
- ? "orange"
- : "inherit",
- }}
- >
- {t(`${params.row.status}`)}
- </span>
- );
- },
- },
- {
- field: "actions",
- headerName: "操作",
- width: ACTIONS_COLUMN_WIDTH,
- minWidth: ACTIONS_COLUMN_WIDTH,
- flex: 0,
- sortable: false,
- filterable: false,
- disableColumnMenu: true,
- cellClassName: "actions",
- renderCell: (params) => {
- const data = params.row as StockInLineRow;
- const btnSx = getButtonSx(data);
- const status = (data.status ?? "").toLowerCase();
- const canEmail =
- status === "rejected" || status === "partially_completed";
- const canPrint = status === "received";
- const canDelete = canDeleteStockInLine(data);
- const needsStockQtyRound = needsPoQcStockQtyRound(
- data.purchaseOrderLineId,
- data.status,
- data.acceptedQty,
- );
-
- return (
- <StockInLineRowActions
- btnSx={btnSx}
- onPrimaryClick={() => {
- void handleNewQC(params.row.id, params)();
- }}
- canEmail={canEmail}
- canPrint={canPrint}
- canDelete={canDelete}
- onEmail={() =>
- handleMailTemplateForStockInLine(params.row.id as number)
- }
- onPrint={() => printQrcode(params.row)}
- onDelete={() => handleSoftDelete(data)}
- btnIsLoading={btnIsLoading}
- isDeleting={isDeleting}
- needsStockQtyRound={needsStockQtyRound}
- stockQty={Number(data.acceptedQty ?? 0)}
- isRounding={roundingSilId === data.id}
- onRound={(mode) => handleRoundStockQty(data, mode)}
- />
- );
- },
- },
- ];
-
- return baseColumns.map((col) => {
- const grow = COLUMN_GROW[col.field] ?? 0;
- if (grow > 0) {
- return {
- ...col,
- flex: grow,
- minWidth: col.minWidth ?? col.width,
- width: undefined,
- };
- }
- return {
- ...col,
- flex: 0,
- width: col.width,
- minWidth: col.minWidth ?? col.width,
- };
- });
- }, [
- t,
- itemDetail,
- handleNewQC,
- handleMailTemplateForStockInLine,
- printQrcode,
- handleSoftDelete,
- handleRoundStockQty,
- roundingSilId,
- btnIsLoading,
- isDeleting,
- sessionToken?.id,
- ]);
-
- const unsortableColumns = useMemo(() =>
- columns.map(column => ({ ...column, sortable: false }))
- , [columns]);
-
- const addRow = useCallback(() => {
- console.log(itemDetail);
- const newEntry = {
- id: Date.now(),
- _isNew: true,
- itemId: itemDetail.itemId,
- purchaseOrderId: itemDetail.purchaseOrderId,
- purchaseOrderLineId: itemDetail.id,
- itemNo: itemDetail.itemNo,
- itemName: itemDetail.itemName,
- // User inputs qty in M18 unit; backend will convert to stock unit on create.
- purchaseAcceptedQty: itemDetail.qty - currQty,
- uom: itemDetail.uom,
- status: "draft",
- };
- setEntries((e) => [...e, newEntry]);
- setRowModesModel((model) => ({
- ...model,
- [getRowId(newEntry)]: {
- mode: GridRowModes.Edit,
- // fieldToFocus: "projectId",
- },
- }));
- }, [currQty, getRowId, itemDetail]);
-
- const validation = useCallback(
- (
- newRow: GridRowModel<StockInLineRow>,
- // rowModel: GridRowSelectionModel
- ): StockInLineEntryError | undefined => {
- const error: StockInLineEntryError = {};
- console.log(newRow);
- console.log(currQty);
- if (
- newRow.purchaseAcceptedQty &&
- newRow.purchaseAcceptedQty > itemDetail.qty
- ) {
- error["purchaseAcceptedQty"] = t(
- "qty cannot be greater than remaining qty",
- );
- }
- return Object.keys(error).length > 0 ? error : undefined;
- },
- [currQty, itemDetail.qty, t],
- );
- const processRowUpdate = useCallback(
- (
- newRow: GridRowModel<StockInLineRow>,
- originalRow: GridRowModel<StockInLineRow>,
- ) => {
- const errors = validation(newRow); // change to validation
- if (errors) {
- throw new ProcessRowUpdateError(
- originalRow,
- "validation error",
- errors,
- );
- }
- const { _isNew, _error, ...updatedRow } = newRow;
- const rowToSave = {
- ...updatedRow,
- } satisfies StockInLineRow;
- const newEntries = entries.map((e) =>
- getRowId(e) === getRowId(originalRow) ? rowToSave : e,
- );
- setStockInLine(newEntries as StockInLine[]);
- console.log("triggered");
- setEntries(newEntries);
- //update remaining qty
- const total = newEntries.reduce(
- (acc, curr) => acc + (curr.purchaseAcceptedQty || 0),
- 0,
- );
- setCurrQty(total);
- return rowToSave;
- },
- [validation, entries, setStockInLine, getRowId],
- );
-
- const onProcessRowUpdateError = useCallback(
- (updateError: ProcessRowUpdateError) => {
- const errors = updateError.errors;
- const oldRow = updateError.row;
-
- apiRef.current.updateRows([{ ...oldRow, _error: errors }]);
- },
- [apiRef],
- );
-
- const footer = (
- <>
- {/* <Box display="flex" gap={2} alignItems="center">
- <Button
- disableRipple
- variant="outlined"
- startIcon={<Add />}
- disabled={itemDetail.qty - currQty <= 0}
- onClick={addRow}
- size="small"
- >
- {t("Record pol")}
- </Button>
- </Box> */}
- </>
- );
-
- const getRowHeight = useCallback(() => STOCK_IN_ROW_HEIGHT, []);
-
- return (
- <>
- <Box
- sx={{
- width: "100%",
- maxWidth: "100%",
- minWidth: 0,
- overflowX: "auto",
- WebkitOverflowScrolling: "touch",
- pb: 1,
- }}
- >
- <StyledDataGrid
- getRowId={getRowId}
- apiRef={apiRef}
- autoHeight
- getRowHeight={getRowHeight}
- columnVisibilityModel={columnVisibilityModel}
- sx={{
- width: "100%",
- minWidth: 0,
- "--DataGrid-overlayHeight": "100px",
- ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
- border: "1px solid",
- borderColor: "error.main",
- },
- ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
- border: "1px solid",
- borderColor: "warning.main",
- },
- "& .MuiDataGrid-cell.actions": {
- overflow: "visible",
- alignItems: "center",
- py: 0.5,
- lineHeight: "normal",
- },
- "& .MuiDataGrid-cell[data-field='stockQty']": {
- overflow: "visible",
- alignItems: "center",
- justifyContent: "flex-start",
- py: 0.5,
- px: 0.75,
- },
- "& .MuiDataGrid-cell[data-field='actions']": {
- py: 0.5,
- px: 1,
- },
- "& .MuiDataGrid-columnHeader[data-field='purchaseAcceptedQty']": {
- whiteSpace: "normal",
- lineHeight: 1.2,
- px: 0.5,
- },
- "& .MuiDataGrid-cell[data-field='purchaseAcceptedQty']": {
- px: 0.5,
- },
- "& .MuiDataGrid-columnHeader[data-field='uom']": {
- whiteSpace: "nowrap",
- px: 0.75,
- },
- "& .MuiDataGrid-cell[data-field='uom']": {
- px: 0.75,
- },
- "& .MuiDataGrid-columnHeader[data-field='stockQty']": {
- px: 0.75,
- },
- ...Object.fromEntries(
- COMPACT_STOCK_IN_CELL_FIELDS.flatMap((field) => [
- [
- `& .MuiDataGrid-cell[data-field="${field}"]`,
- { px: 1 },
- ],
- [
- `& .MuiDataGrid-columnHeader[data-field="${field}"]`,
- { px: 1 },
- ],
- ]),
- ),
- }}
- disableColumnMenu
- editMode="row"
- rows={entries}
- rowModesModel={rowModesModel}
- onRowModesModelChange={setRowModesModel}
- processRowUpdate={processRowUpdate}
- onProcessRowUpdateError={onProcessRowUpdateError}
- columns={unsortableColumns}
- isCellEditable={(params) => {
- const status = params.row.status.toLowerCase();
- return (
- stockInLineStatusMap[status] >= 0 ||
- stockInLineStatusMap[status] <= 1
- );
- }}
- getCellClassName={(params: GridCellParams<StockInLineRow>) => {
- let classname = "";
- if (params.row._error) {
- classname = "hasError";
- }
- return classname;
- }}
- slots={{
- footer: FooterToolbar,
- noRowsOverlay: NoRowsOverlay,
- }}
- slotProps={{
- footer: { child: footer },
- }}
- />
- </Box>
- {/* {modalInfo !== undefined && ( */}
- <>
- <QcStockInModal
- session={sessionToken}
- open={newOpen}
- onClose={closeNewModal}
- // itemDetail={modalInfo}
- inputDetail={modalInfo}
- warehouse={warehouse}
- printerCombo={printerCombo}
- printSource="stockIn"
- />
- </>
- {/* )
- } */}
- </>
- );
- }
- const NoRowsOverlay: React.FC = () => {
- const { t } = useTranslation("purchaseOrder");
- return (
- <Box
- display="flex"
- justifyContent="center"
- alignItems="center"
- height="100%"
- >
- <Typography variant="caption">{t("Add some entries!")}</Typography>
- </Box>
- );
- };
-
- const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
- return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
- };
- export default PoInputGrid;
|