@@ -0,0 +1,110 @@ | |||||
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory"; | |||||
import { Dispatch, SetStateAction, useMemo } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { Column } from "../SearchResults"; | |||||
import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults"; | |||||
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | |||||
import { arrayToDateString } from "@/app/utils/formatUtil"; | |||||
import { Typography } from "@mui/material"; | |||||
import { isFinite } from "lodash"; | |||||
interface Props { | |||||
inventoryLotLines: InventoryLotLineResult[] | null; | |||||
setPagingController: defaultSetPagingController; | |||||
pagingController: typeof defaultPagingController; | |||||
totalCount: number; | |||||
item: InventoryResult | null; | |||||
} | |||||
const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingController, setPagingController, totalCount, item }) => { | |||||
const { t } = useTranslation(["inventory"]); | |||||
const columns = useMemo<Column<InventoryLotLineResult>[]>( | |||||
() => [ | |||||
// { | |||||
// name: "item", | |||||
// label: t("Code"), | |||||
// renderCell: (params) => { | |||||
// return params.item.code; | |||||
// }, | |||||
// }, | |||||
// { | |||||
// name: "item", | |||||
// label: t("Name"), | |||||
// renderCell: (params) => { | |||||
// return params.item.name; | |||||
// }, | |||||
// }, | |||||
{ | |||||
name: "lotNo", | |||||
label: t("Lot No"), | |||||
}, | |||||
// { | |||||
// name: "item", | |||||
// label: t("Type"), | |||||
// renderCell: (params) => { | |||||
// return t(params.item.type); | |||||
// }, | |||||
// }, | |||||
{ | |||||
name: "availableQty", | |||||
label: t("Available Qty"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer", | |||||
}, | |||||
{ | |||||
name: "uom", | |||||
label: t("Sales UoM"), | |||||
align: "left", | |||||
headerAlign: "left", | |||||
}, | |||||
{ | |||||
name: "qtyPerSmallestUnit", | |||||
label: t("Available Qty Per Smallest Unit"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer", | |||||
}, | |||||
{ | |||||
name: "baseUom", | |||||
label: t("Base UoM"), | |||||
align: "left", | |||||
headerAlign: "left", | |||||
}, | |||||
{ | |||||
name: "expiryDate", | |||||
label: t("Expiry Date"), | |||||
renderCell: (params) => { | |||||
return arrayToDateString(params.expiryDate) | |||||
}, | |||||
}, | |||||
{ | |||||
name: "status", | |||||
label: t("Status"), | |||||
type: "icon", | |||||
icons: { | |||||
available: <CheckCircleOutline fontSize="small"/>, | |||||
unavailable: <DoDisturb fontSize="small"/>, | |||||
}, | |||||
colors: { | |||||
available: "success", | |||||
unavailable: "error", | |||||
} | |||||
}, | |||||
], | |||||
[t], | |||||
); | |||||
return <> | |||||
<Typography variant="h6">{item ? `${t("Item selected")}: ${item.itemCode} | ${item.itemName} (${t(item.itemType)})` : t("No items are selected yet.")}</Typography> | |||||
<SearchResults<InventoryLotLineResult> | |||||
items={inventoryLotLines ?? []} | |||||
columns={columns} | |||||
pagingController={pagingController} | |||||
setPagingController={setPagingController} | |||||
totalCount={totalCount} | |||||
/> | |||||
</> | |||||
} | |||||
export default InventoryLotLineTable; |
@@ -1,11 +1,15 @@ | |||||
"use client"; | "use client"; | ||||
import { InventoryResult } from "@/app/api/inventory"; | |||||
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
import { useCallback, useMemo, useState } from "react"; | |||||
import { uniq } from "lodash"; | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { orderBy, uniq, uniqBy } from "lodash"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | ||||
import InventoryTable from "./InventoryTable"; | |||||
import { defaultPagingController } from "../SearchResults/SearchResults"; | |||||
import InventoryLotLineTable from "./InventoryLotLineTable"; | |||||
import { SearchInventory, SearchInventoryLotLine, fetchInventories, fetchInventoryLotLines } from "@/app/api/inventory/actions"; | |||||
interface Props { | interface Props { | ||||
inventories: InventoryResult[]; | inventories: InventoryResult[]; | ||||
@@ -31,7 +35,30 @@ type SearchParamNames = keyof SearchQuery; | |||||
const InventorySearch: React.FC<Props> = ({ inventories }) => { | const InventorySearch: React.FC<Props> = ({ inventories }) => { | ||||
const { t } = useTranslation(["inventory", "common"]); | const { t } = useTranslation(["inventory", "common"]); | ||||
const [filteredInventories, setFilteredInventories] = useState(inventories); | |||||
// Inventory | |||||
const [filteredInventories, setFilteredInventories] = useState<InventoryResult[]>([]); | |||||
const [inventoriesPagingController, setInventoriesPagingController] = useState(defaultPagingController) | |||||
const [inventoriesTotalCount, setInventoriesTotalCount] = useState(0) | |||||
const [item, setItem] = useState<InventoryResult | null>(null) | |||||
// Inventory Lot Line | |||||
const [filteredInventoryLotLines, setFilteredInventoryLotLines] = useState<InventoryLotLineResult[]>([]); | |||||
const [inventoryLotLinesPagingController, setInventoryLotLinesPagingController] = useState(defaultPagingController) | |||||
const [inventoryLotLinesTotalCount, setInventoryLotLinesTotalCount] = useState(0) | |||||
const [inputs, setInputs] = useState<Record<SearchParamNames, string>>({ | |||||
itemId: "", | |||||
itemCode: "", | |||||
itemName: "", | |||||
itemType: "", | |||||
onHandQty: "", | |||||
onHoldQty: "", | |||||
unavailableQty: "", | |||||
availableQty: "", | |||||
currencyName: "", | |||||
status: "", | |||||
baseUom: "", | |||||
}); | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
() => [ | () => [ | ||||
@@ -43,88 +70,102 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => { | |||||
type: "select", | type: "select", | ||||
options: uniq(inventories.map((i) => i.itemType)), | options: uniq(inventories.map((i) => i.itemType)), | ||||
}, | }, | ||||
{ | |||||
label: t("Status"), | |||||
paramName: "status", | |||||
type: "select", | |||||
options: uniq(inventories.map((i) => i.status)), | |||||
}, | |||||
// { | |||||
// label: t("Status"), | |||||
// paramName: "status", | |||||
// type: "select", | |||||
// options: uniq(inventories.map((i) => i.status)), | |||||
// }, | |||||
], | ], | ||||
[t], | [t], | ||||
); | ); | ||||
const refetchInventoryData = useCallback(async ( | |||||
query: Record<SearchParamNames, string>, | |||||
actionType: "reset" | "search" | "paging" | |||||
) => { | |||||
const params: SearchInventory = { | |||||
code: query?.itemCode ?? '', | |||||
name: query?.itemName ?? '', | |||||
type: query?.itemType.toLowerCase() === "all" ? '' : query?.itemType ?? '', | |||||
pageNum: inventoriesPagingController.pageNum - 1, | |||||
pageSize: inventoriesPagingController.pageSize | |||||
} | |||||
const response = await fetchInventories(params) | |||||
if (response) { | |||||
console.log(response) | |||||
setInventoriesTotalCount(response.total); | |||||
switch (actionType) { | |||||
case "reset": | |||||
case "search": | |||||
setFilteredInventories(() => response.records); | |||||
break; | |||||
case "paging": | |||||
setFilteredInventories((fi) => | |||||
orderBy( | |||||
uniqBy([...fi, ...response.records], "id"), | |||||
["id"], ["desc"] | |||||
)); | |||||
} | |||||
} | |||||
}, [inventoriesPagingController, setInventoriesPagingController]) | |||||
useEffect(() => { | |||||
refetchInventoryData(inputs, "paging") | |||||
}, [inventoriesPagingController]) | |||||
const refetchInventoryLotLineData = useCallback(async ( | |||||
itemId: number | null, | |||||
actionType: "reset" | "search" | "paging" | |||||
) => { | |||||
if (!itemId) { | |||||
setItem(null) | |||||
setInventoryLotLinesTotalCount(0); | |||||
setFilteredInventoryLotLines([]) | |||||
return | |||||
} | |||||
const params: SearchInventoryLotLine = { | |||||
itemId: itemId, | |||||
pageNum: inventoriesPagingController.pageNum - 1, | |||||
pageSize: inventoriesPagingController.pageSize | |||||
} | |||||
const response = await fetchInventoryLotLines(params) | |||||
if (response) { | |||||
setInventoryLotLinesTotalCount(response.total); | |||||
switch (actionType) { | |||||
case "reset": | |||||
case "search": | |||||
setFilteredInventoryLotLines(() => response.records); | |||||
break; | |||||
case "paging": | |||||
setFilteredInventoryLotLines((fi) => | |||||
orderBy( | |||||
uniqBy([...fi, ...response.records], "id"), | |||||
["id"], ["desc"] | |||||
)); | |||||
} | |||||
} | |||||
}, [inventoriesPagingController, setInventoriesPagingController]) | |||||
useEffect(() => { | |||||
refetchInventoryData(inputs, "paging") | |||||
}, [inventoriesPagingController]) | |||||
const onReset = useCallback(() => { | const onReset = useCallback(() => { | ||||
setFilteredInventories(inventories); | |||||
}, [inventories]); | |||||
refetchInventoryData(inputs, "reset"); | |||||
refetchInventoryLotLineData(null, "reset"); | |||||
// setFilteredInventories(inventories); | |||||
}, []); | |||||
const columns = useMemo<Column<InventoryResult>[]>( | |||||
() => [ | |||||
{ | |||||
name: "itemCode", | |||||
label: t("Code"), | |||||
}, | |||||
{ | |||||
name: "itemName", | |||||
label: t("Name"), | |||||
}, | |||||
{ | |||||
name: "itemType", | |||||
label: t("Type"), | |||||
renderCell: (params) => { | |||||
return t(params.itemType); | |||||
}, | |||||
}, | |||||
{ | |||||
name: "availableQty", | |||||
label: t("Qty"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer", | |||||
}, | |||||
{ | |||||
name: "uomUdfudesc", | |||||
label: t("UoM"), | |||||
}, | |||||
// { | |||||
// name: "qtyPerSmallestUnit", | |||||
// label: t("Qty Per Smallest Unit"), | |||||
// align: "right", | |||||
// headerAlign: "right", | |||||
// type: "decimal" | |||||
// }, | |||||
// { | |||||
// name: "smallestUnit", | |||||
// label: t("Smallest Unit"), | |||||
// }, | |||||
// { | |||||
// name: "price", | |||||
// label: t("Price"), | |||||
// align: "right", | |||||
// sx: { | |||||
// alignItems: "right", | |||||
// justifyContent: "end", | |||||
// } | |||||
// }, | |||||
// { | |||||
// name: "currencyName", | |||||
// label: t("Currency"), | |||||
// }, | |||||
// { | |||||
// name: "status", | |||||
// label: t("Status"), | |||||
// type: "icon", | |||||
// icons: { | |||||
// available: <CheckCircleOutline fontSize="small"/>, | |||||
// unavailable: <DoDisturb fontSize="small"/>, | |||||
// }, | |||||
// colors: { | |||||
// available: "success", | |||||
// unavailable: "error", | |||||
// } | |||||
// }, | |||||
], | |||||
[t], | |||||
); | |||||
const onInventoryRowClick = useCallback((item: InventoryResult) => { | |||||
setItem(item) | |||||
refetchInventoryLotLineData(item.itemId, "search") | |||||
}, []) | |||||
return ( | return ( | ||||
<> | <> | ||||
@@ -133,28 +174,35 @@ const InventorySearch: React.FC<Props> = ({ inventories }) => { | |||||
onSearch={(query) => { | onSearch={(query) => { | ||||
// console.log(query) | // console.log(query) | ||||
// console.log(inventories) | // console.log(inventories) | ||||
setFilteredInventories( | |||||
inventories.filter( | |||||
(i) => | |||||
i.itemCode.toLowerCase().includes(query.itemCode.toLowerCase()) && | |||||
i.itemName.toLowerCase().includes(query.itemName.toLowerCase()) && | |||||
(query.itemType == "All" || | |||||
i.itemType.toLowerCase().includes(query.itemType.toLowerCase())) && | |||||
(query.status == "All" || | |||||
i.status.toLowerCase().includes(query.status.toLowerCase())), | |||||
), | |||||
); | |||||
setInputs(() => query) | |||||
refetchInventoryData(query, "search") | |||||
// setFilteredInventories( | |||||
// inventories.filter( | |||||
// (i) => | |||||
// i.itemCode.toLowerCase().includes(query.itemCode.toLowerCase()) && | |||||
// i.itemName.toLowerCase().includes(query.itemName.toLowerCase()) && | |||||
// (query.itemType == "All" || | |||||
// i.itemType.toLowerCase().includes(query.itemType.toLowerCase())) && | |||||
// (query.status == "All" || | |||||
// i.status.toLowerCase().includes(query.status.toLowerCase())), | |||||
// ), | |||||
// ); | |||||
}} | }} | ||||
onReset={onReset} | onReset={onReset} | ||||
/> | /> | ||||
<SearchResults<InventoryResult> | |||||
items={filteredInventories} | |||||
columns={columns} | |||||
pagingController={{ | |||||
pageNum: 0, | |||||
pageSize: 0, | |||||
// totalCount: 0, | |||||
}} | |||||
<InventoryTable | |||||
inventories={filteredInventories} | |||||
pagingController={inventoriesPagingController} | |||||
setPagingController={setInventoriesPagingController} | |||||
totalCount={inventoriesTotalCount} | |||||
onRowClick={onInventoryRowClick} | |||||
/> | |||||
<InventoryLotLineTable | |||||
inventoryLotLines={filteredInventoryLotLines} | |||||
pagingController={inventoryLotLinesPagingController} | |||||
setPagingController={setInventoryLotLinesPagingController} | |||||
totalCount={inventoryLotLinesTotalCount} | |||||
item={item} | |||||
/> | /> | ||||
</> | </> | ||||
); | ); | ||||
@@ -0,0 +1,111 @@ | |||||
import { InventoryResult } from "@/app/api/inventory"; | |||||
import { Dispatch, SetStateAction, useMemo } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { Column } from "../SearchResults"; | |||||
import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults"; | |||||
interface Props { | |||||
inventories: InventoryResult[]; | |||||
setPagingController: defaultSetPagingController; | |||||
pagingController: typeof defaultPagingController; | |||||
totalCount: number; | |||||
onRowClick: (item: InventoryResult) => void; | |||||
} | |||||
const InventoryTable: React.FC<Props> = ({ inventories, pagingController, setPagingController, totalCount, onRowClick }) => { | |||||
const { t } = useTranslation(["inventory"]); | |||||
const columns = useMemo<Column<InventoryResult>[]>( | |||||
() => [ | |||||
{ | |||||
name: "itemCode", | |||||
label: t("Code"), | |||||
}, | |||||
{ | |||||
name: "itemName", | |||||
label: t("Name"), | |||||
}, | |||||
{ | |||||
name: "itemType", | |||||
label: t("Type"), | |||||
renderCell: (params) => { | |||||
return t(params.itemType); | |||||
}, | |||||
}, | |||||
{ | |||||
name: "availableQty", | |||||
label: t("Available Qty"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer", | |||||
}, | |||||
{ | |||||
name: "uomUdfudesc", | |||||
label: t("Sales UoM"), | |||||
align: "left", | |||||
headerAlign: "left", | |||||
}, | |||||
{ | |||||
name: "qtyPerSmallestUnit", | |||||
label: t("Available Qty Per Smallest Unit"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer", | |||||
}, | |||||
{ | |||||
name: "baseUom", | |||||
label: t("Base UoM"), | |||||
align: "left", | |||||
headerAlign: "left", | |||||
}, | |||||
// { | |||||
// name: "qtyPerSmallestUnit", | |||||
// label: t("Qty Per Smallest Unit"), | |||||
// align: "right", | |||||
// headerAlign: "right", | |||||
// type: "decimal" | |||||
// }, | |||||
// { | |||||
// name: "smallestUnit", | |||||
// label: t("Smallest Unit"), | |||||
// }, | |||||
// { | |||||
// name: "price", | |||||
// label: t("Price"), | |||||
// align: "right", | |||||
// sx: { | |||||
// alignItems: "right", | |||||
// justifyContent: "end", | |||||
// } | |||||
// }, | |||||
// { | |||||
// name: "currencyName", | |||||
// label: t("Currency"), | |||||
// }, | |||||
// { | |||||
// name: "status", | |||||
// label: t("Status"), | |||||
// type: "icon", | |||||
// icons: { | |||||
// available: <CheckCircleOutline fontSize="small"/>, | |||||
// unavailable: <DoDisturb fontSize="small"/>, | |||||
// }, | |||||
// colors: { | |||||
// available: "success", | |||||
// unavailable: "error", | |||||
// } | |||||
// }, | |||||
], | |||||
[t], | |||||
); | |||||
return <SearchResults<InventoryResult> | |||||
items={inventories} | |||||
columns={columns} | |||||
pagingController={pagingController} | |||||
setPagingController={setPagingController} | |||||
totalCount={totalCount} | |||||
onRowClick={onRowClick} | |||||
/> | |||||
} | |||||
export default InventoryTable; |
@@ -248,7 +248,7 @@ function SearchBox<T extends string>({ | |||||
<MenuItem value={"All"}>{t("All")}</MenuItem> | <MenuItem value={"All"}>{t("All")}</MenuItem> | ||||
{c.options.map((option) => ( | {c.options.map((option) => ( | ||||
<MenuItem key={option} value={option}> | <MenuItem key={option} value={option}> | ||||
{option} | |||||
{t(option)} | |||||
</MenuItem> | </MenuItem> | ||||
))} | ))} | ||||
</Select> | </Select> | ||||
@@ -100,6 +100,7 @@ interface Props<T extends ResultWithId> { | |||||
isAutoPaging?: boolean; | isAutoPaging?: boolean; | ||||
checkboxIds?: (string | number)[]; | checkboxIds?: (string | number)[]; | ||||
setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>; | setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>; | ||||
onRowClick?: (item: T) => void; | |||||
} | } | ||||
function isActionColumn<T extends ResultWithId>( | function isActionColumn<T extends ResultWithId>( | ||||
@@ -138,8 +139,8 @@ function convertObjectKeysToLowercase<T extends object>( | |||||
): object | undefined { | ): object | undefined { | ||||
return obj | return obj | ||||
? Object.fromEntries( | ? Object.fromEntries( | ||||
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]), | |||||
) | |||||
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]), | |||||
) | |||||
: undefined; | : undefined; | ||||
} | } | ||||
@@ -174,6 +175,14 @@ export const defaultPagingController: { pageNum: number; pageSize: number } = { | |||||
pageNum: 1, | pageNum: 1, | ||||
pageSize: 10, | pageSize: 10, | ||||
}; | }; | ||||
export type defaultSetPagingController = Dispatch< | |||||
SetStateAction<{ | |||||
pageNum: number; | |||||
pageSize: number; | |||||
}> | |||||
> | |||||
function SearchResults<T extends ResultWithId>({ | function SearchResults<T extends ResultWithId>({ | ||||
items, | items, | ||||
columns, | columns, | ||||
@@ -184,6 +193,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
totalCount, | totalCount, | ||||
checkboxIds = [], | checkboxIds = [], | ||||
setCheckboxIds = undefined, | setCheckboxIds = undefined, | ||||
onRowClick = undefined, | |||||
}: Props<T>) { | }: Props<T>) { | ||||
const [page, setPage] = React.useState(0); | const [page, setPage] = React.useState(0); | ||||
const [rowsPerPage, setRowsPerPage] = React.useState(10); | const [rowsPerPage, setRowsPerPage] = React.useState(10); | ||||
@@ -279,40 +289,25 @@ function SearchResults<T extends ResultWithId>({ | |||||
<TableBody> | <TableBody> | ||||
{isAutoPaging | {isAutoPaging | ||||
? items | ? items | ||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | |||||
.map((item) => { | |||||
return ( | |||||
<TableRow | |||||
hover | |||||
tabIndex={-1} | |||||
key={item.id} | |||||
onClick={ | |||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | |||||
.map((item) => { | |||||
return ( | |||||
<TableRow | |||||
hover | |||||
tabIndex={-1} | |||||
key={item.id} | |||||
onClick={(event) => { | |||||
setCheckboxIds | setCheckboxIds | ||||
? (event) => handleRowClick(event, item, columns) | |||||
? handleRowClick(event, item, columns) | |||||
: undefined | : undefined | ||||
if (onRowClick) { | |||||
onRowClick(item) | |||||
} | |||||
} | } | ||||
role={setCheckboxIds ? "checkbox" : undefined} | |||||
> | |||||
{columns.map((column, idx) => { | |||||
const columnName = column.name; | |||||
return ( | |||||
<TabelCells | |||||
key={`${columnName.toString()}-${idx}`} | |||||
column={column} | |||||
columnName={columnName} | |||||
idx={idx} | |||||
item={item} | |||||
checkboxIds={checkboxIds} | |||||
/> | |||||
); | |||||
})} | |||||
</TableRow> | |||||
); | |||||
}) | |||||
: items.map((item) => { | |||||
return ( | |||||
<TableRow hover tabIndex={-1} key={item.id}> | |||||
} | |||||
role={setCheckboxIds ? "checkbox" : undefined} | |||||
> | |||||
{columns.map((column, idx) => { | {columns.map((column, idx) => { | ||||
const columnName = column.name; | const columnName = column.name; | ||||
@@ -329,7 +324,27 @@ function SearchResults<T extends ResultWithId>({ | |||||
})} | })} | ||||
</TableRow> | </TableRow> | ||||
); | ); | ||||
})} | |||||
}) | |||||
: items.map((item) => { | |||||
return ( | |||||
<TableRow hover tabIndex={-1} key={item.id}> | |||||
{columns.map((column, idx) => { | |||||
const columnName = column.name; | |||||
return ( | |||||
<TabelCells | |||||
key={`${columnName.toString()}-${idx}`} | |||||
column={column} | |||||
columnName={columnName} | |||||
idx={idx} | |||||
item={item} | |||||
checkboxIds={checkboxIds} | |||||
/> | |||||
); | |||||
})} | |||||
</TableRow> | |||||
); | |||||
})} | |||||
</TableBody> | </TableBody> | ||||
</Table> | </Table> | ||||
</TableContainer> | </TableContainer> | ||||
@@ -35,6 +35,7 @@ | |||||
"Project":"專案", | "Project":"專案", | ||||
"Product":"產品", | "Product":"產品", | ||||
"Material":"材料", | "Material":"材料", | ||||
"mat":"原料", | |||||
"FG":"成品", | "FG":"成品", | ||||
"FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | ||||
"View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | ||||
@@ -7,5 +7,13 @@ | |||||
"Qty": "數量", | "Qty": "數量", | ||||
"UoM": "單位", | "UoM": "單位", | ||||
"mat": "物料", | "mat": "物料", | ||||
"fg": "成品" | |||||
} | |||||
"fg": "成品", | |||||
"Available Qty": "可用數量 (銷售單位)", | |||||
"Sales UoM": "銷售單位", | |||||
"Available Qty Per Smallest Unit": "可用數量 (基本單位)", | |||||
"Base UoM": "基本單位", | |||||
"Lot No": "批號", | |||||
"Expiry Date": "到期日", | |||||
"No items are selected yet.": "未選擇項目", | |||||
"Item selected": "已選擇項目" | |||||
} |