|
|
@@ -1,11 +1,27 @@ |
|
|
import { DoDetail } from "@/app/api/do/actions"; |
|
|
import { DoDetail } from "@/app/api/do/actions"; |
|
|
import { decimalFormatter } from "@/app/utils/formatUtil"; |
|
|
import { decimalFormatter } from "@/app/utils/formatUtil"; |
|
|
import { GridColDef } from "@mui/x-data-grid"; |
|
|
|
|
|
|
|
|
import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; |
|
|
import { isEmpty, upperFirst } from "lodash"; |
|
|
import { isEmpty, upperFirst } from "lodash"; |
|
|
import { useMemo } from "react"; |
|
|
|
|
|
|
|
|
import { useMemo, useEffect, useState } from "react"; |
|
|
import { useFormContext } from "react-hook-form"; |
|
|
import { useFormContext } from "react-hook-form"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import StyledDataGrid from "../StyledDataGrid/StyledDataGrid"; |
|
|
import StyledDataGrid from "../StyledDataGrid/StyledDataGrid"; |
|
|
|
|
|
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; |
|
|
|
|
|
import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded'; |
|
|
|
|
|
import { fetchInventories } from "@/app/api/inventory/actions"; |
|
|
|
|
|
import { InventoryResult } from "@/app/api/inventory"; |
|
|
|
|
|
|
|
|
|
|
|
type DoLineWithCalculations = { |
|
|
|
|
|
id?: number; |
|
|
|
|
|
itemNo: string; |
|
|
|
|
|
itemName: string; |
|
|
|
|
|
qty: number; |
|
|
|
|
|
uomCode: string; |
|
|
|
|
|
shortUom?: string; |
|
|
|
|
|
status: string; |
|
|
|
|
|
stockAvailable: number; |
|
|
|
|
|
isStockSufficient: boolean; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
type Props = { |
|
|
type Props = { |
|
|
|
|
|
|
|
|
@@ -19,59 +35,114 @@ const DoLineTable: React.FC<Props> = ({ |
|
|
watch |
|
|
watch |
|
|
} = useFormContext<DoDetail>() |
|
|
} = useFormContext<DoDetail>() |
|
|
|
|
|
|
|
|
|
|
|
const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]); |
|
|
|
|
|
const deliveryOrderLines = watch("deliveryOrderLines"); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
const fetchInventoryData = async () => { |
|
|
|
|
|
try { |
|
|
|
|
|
const inventoryResponse = await fetchInventories({ |
|
|
|
|
|
code: "", |
|
|
|
|
|
name: "", |
|
|
|
|
|
type: "", |
|
|
|
|
|
pageNum: 0, |
|
|
|
|
|
pageSize: 1000 |
|
|
|
|
|
}); |
|
|
|
|
|
setInventoryData(inventoryResponse.records); |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error("Error fetching inventory data:", error); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
fetchInventoryData(); |
|
|
|
|
|
}, [deliveryOrderLines]); |
|
|
|
|
|
|
|
|
|
|
|
const getStockAvailable = (line: any) => { |
|
|
|
|
|
const inventory = inventoryData.find(inventory => |
|
|
|
|
|
inventory.itemCode === line.itemNo || inventory.itemName === line.itemName |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (inventory) { |
|
|
|
|
|
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const isStockSufficient = (line: any) => { |
|
|
|
|
|
const stockAvailable = getStockAvailable(line); |
|
|
|
|
|
return stockAvailable >= line.qty; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const sufficientStockIcon = useMemo(() => { |
|
|
|
|
|
return <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" /> |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const insufficientStockIcon = useMemo(() => { |
|
|
|
|
|
return <DoDisturbAltRoundedIcon fontSize={"large"} color="error" /> |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const rowsWithCalculatedFields = useMemo(() => { |
|
|
|
|
|
return deliveryOrderLines.map((line, index) => ({ |
|
|
|
|
|
...line, |
|
|
|
|
|
id: line.id || index, |
|
|
|
|
|
shortUom: line.shortUom, // 假设 shortUom 就是 uomCode,如果有其他字段请修改 |
|
|
|
|
|
stockAvailable: getStockAvailable(line), |
|
|
|
|
|
isStockSufficient: isStockSufficient(line), |
|
|
|
|
|
})); |
|
|
|
|
|
}, [deliveryOrderLines, inventoryData]); |
|
|
|
|
|
|
|
|
const columns = useMemo<GridColDef[]>(() => [ |
|
|
const columns = useMemo<GridColDef[]>(() => [ |
|
|
{ |
|
|
{ |
|
|
field: "itemNo", |
|
|
field: "itemNo", |
|
|
headerName: t("Item No."), |
|
|
headerName: t("Item No."), |
|
|
flex: 1, |
|
|
|
|
|
|
|
|
flex: 0.6, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
field: "itemName", |
|
|
field: "itemName", |
|
|
headerName: t("Item Name"), |
|
|
headerName: t("Item Name"), |
|
|
flex: 1, |
|
|
flex: 1, |
|
|
renderCell: (row) => { |
|
|
|
|
|
return isEmpty(row.value) ? "N/A" : row.value |
|
|
|
|
|
|
|
|
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => { |
|
|
|
|
|
const name = isEmpty(params.value) ? "N/A" : params.value; |
|
|
|
|
|
const uom = params.row.uomCode || ""; |
|
|
|
|
|
return `${name} (${uom})`; |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
field: "qty", |
|
|
field: "qty", |
|
|
headerName: t("Quantity"), |
|
|
headerName: t("Quantity"), |
|
|
flex: 1, |
|
|
|
|
|
|
|
|
flex: 0.7, |
|
|
align: "right", |
|
|
align: "right", |
|
|
headerAlign: "right", |
|
|
headerAlign: "right", |
|
|
renderCell: (row) => { |
|
|
|
|
|
return decimalFormatter.format(row.value) |
|
|
|
|
|
|
|
|
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => { |
|
|
|
|
|
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
field: "price", |
|
|
|
|
|
headerName: t("Price"), |
|
|
|
|
|
flex: 1, |
|
|
|
|
|
|
|
|
field: "stockAvailable", |
|
|
|
|
|
headerName: t("Stock Available"), |
|
|
|
|
|
flex: 0.7, |
|
|
align: "right", |
|
|
align: "right", |
|
|
headerAlign: "right", |
|
|
headerAlign: "right", |
|
|
renderCell: (row) => { |
|
|
|
|
|
return decimalFormatter.format(row.value) |
|
|
|
|
|
}, |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
field: "uomCode", |
|
|
|
|
|
headerName: t("UoM"), |
|
|
|
|
|
flex: 1, |
|
|
|
|
|
align: "left", |
|
|
|
|
|
headerAlign: "left", |
|
|
|
|
|
renderCell: (row) => { |
|
|
|
|
|
return isEmpty(row.value) ? "N/A" : row.value |
|
|
|
|
|
|
|
|
type: "number", |
|
|
|
|
|
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => { |
|
|
|
|
|
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
field: "status", |
|
|
|
|
|
headerName: t("Status"), |
|
|
|
|
|
flex: 1, |
|
|
|
|
|
renderCell: (row) => { |
|
|
|
|
|
return t(upperFirst(row.value)) |
|
|
|
|
|
|
|
|
field: "stockStatus", |
|
|
|
|
|
headerName: t("Stock Status"), |
|
|
|
|
|
flex: 0.5, |
|
|
|
|
|
align: "right", |
|
|
|
|
|
headerAlign: "right", |
|
|
|
|
|
type: "boolean", |
|
|
|
|
|
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => { |
|
|
|
|
|
return params.row.isStockSufficient ? sufficientStockIcon : insufficientStockIcon; |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
], [t]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
], [t, inventoryData]) |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<> |
|
|
<> |
|
|
@@ -88,8 +159,9 @@ const DoLineTable: React.FC<Props> = ({ |
|
|
}, |
|
|
}, |
|
|
}} |
|
|
}} |
|
|
disableColumnMenu |
|
|
disableColumnMenu |
|
|
rows={watch("deliveryOrderLines")} |
|
|
|
|
|
|
|
|
rows={rowsWithCalculatedFields} |
|
|
columns={columns} |
|
|
columns={columns} |
|
|
|
|
|
getRowHeight={() => 'auto'} |
|
|
/> |
|
|
/> |
|
|
</> |
|
|
</> |
|
|
) |
|
|
) |
|
|
|