diff --git a/src/components/DoDetail/DoLineTable.tsx b/src/components/DoDetail/DoLineTable.tsx index 302d2a2..9873448 100644 --- a/src/components/DoDetail/DoLineTable.tsx +++ b/src/components/DoDetail/DoLineTable.tsx @@ -1,97 +1,57 @@ -import { DoDetail } from "@/app/api/do/actions"; +import { DoDetail, DoDetailLine } from "@/app/api/do/actions"; import { decimalFormatter } from "@/app/utils/formatUtil"; import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; -import { isEmpty, upperFirst } from "lodash"; -import { useMemo, useEffect, useState } from "react"; +import { isEmpty } from "lodash"; +import { useMemo } from "react"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; 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; - uom?: string; - shortUom?: string; - status: string; +type DoLineRow = DoDetailLine & { stockAvailable: number; isStockSufficient: boolean; }; -type Props = { +type Props = Record; +const getStockAvailable = (line: DoDetailLine): number => { + if (line.stockQty != null && !Number.isNaN(Number(line.stockQty))) { + return Number(line.stockQty); + } + return 0; }; -const DoLineTable: React.FC = ({ - -}) => { - const { t } = useTranslation("do") - const { - watch - } = useFormContext() +const isStockSufficient = (line: DoDetailLine): boolean => { + if (line.availableStatus === "available") return true; + if (line.availableStatus === "insufficient") return false; + return getStockAvailable(line) >= (line.qty ?? 0); +}; - const [inventoryData, setInventoryData] = useState([]); +const DoLineTable: React.FC = () => { + const { t } = useTranslation("do"); + const { watch } = useFormContext(); 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 sufficientStockIcon = useMemo( + () => , + [], + ); - const isStockSufficient = (line: any) => { - const stockAvailable = getStockAvailable(line); - return stockAvailable >= line.qty; - }; + const insufficientStockIcon = useMemo( + () => , + [], + ); - const sufficientStockIcon = useMemo(() => { - return - }, []); - - const insufficientStockIcon = useMemo(() => { - return - }, []); - - const rowsWithCalculatedFields = useMemo(() => { + const rowsWithCalculatedFields = useMemo((): DoLineRow[] => { return deliveryOrderLines.map((line, index) => ({ ...line, id: line.id || index, - shortUom: line.shortUom, // 假设 shortUom 就是 uomCode,如果有其他字段请修改 stockAvailable: getStockAvailable(line), isStockSufficient: isStockSufficient(line), })); - }, [deliveryOrderLines, inventoryData]); + }, [deliveryOrderLines]); const columns = useMemo(() => [ { @@ -103,7 +63,7 @@ const DoLineTable: React.FC = ({ field: "itemName", headerName: t("Item Name"), flex: 1, - renderCell: (params: GridRenderCellParams) => { + renderCell: (params: GridRenderCellParams) => { const name = isEmpty(params.value) ? "N/A" : params.value; const uom = params.row.uom || ""; return `${name} (${uom})`; @@ -115,7 +75,7 @@ const DoLineTable: React.FC = ({ flex: 0.7, align: "right", headerAlign: "right", - renderCell: (params: GridRenderCellParams) => { + renderCell: (params: GridRenderCellParams) => { return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; }, }, @@ -126,7 +86,7 @@ const DoLineTable: React.FC = ({ align: "right", headerAlign: "right", type: "number", - renderCell: (params: GridRenderCellParams) => { + renderCell: (params: GridRenderCellParams) => { return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; }, }, @@ -137,35 +97,31 @@ const DoLineTable: React.FC = ({ align: "right", headerAlign: "right", type: "boolean", - renderCell: (params: GridRenderCellParams) => { + renderCell: (params: GridRenderCellParams) => { return params.row.isStockSufficient ? sufficientStockIcon : insufficientStockIcon; }, }, - - - ], [t, inventoryData]) + ], [t, sufficientStockIcon, insufficientStockIcon]); return ( - <> - 'auto'} - /> - - ) -} + 'auto'} + /> + ); +}; -export default DoLineTable; \ No newline at end of file +export default DoLineTable; diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index ddd0852..694911a 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -171,7 +171,17 @@ const [showBaseQty, setShowBaseQty] = useState(false); useEffect(() => { fetchData(); }, [fetchData]); -// PickTable 组件内容 +// PickTable 组件内容 — 与 JoSearch / stockCounts 一致,不参与提料库存统计 +const isExcludedFromPickStock = (type?: string) => { + const normalized = type?.toLowerCase(); + return ( + normalized === "consumables" || + normalized === "consumable" || + normalized === "cmb" || + normalized === "nm" + ); +}; + const getStockAvailable = (line: JobOrderLineInfo) => { if (line.type?.toLowerCase() === "consumables" || line.type?.toLowerCase() === "nm") { return line.stockQty || 0; @@ -247,15 +257,8 @@ const isStockSufficient = (line: JobOrderLineInfo) => { return stockAvailable >= line.reqQty; }; const stockCounts = useMemo(() => { - // 过滤掉 consumables 类型的 lines const nonConsumablesLines = jobOrderLines.filter( - line => { - const type = line.type?.toLowerCase(); - return type !== "consumables" && - type !== "consumable" && // ✅ 添加单数形式 - type !== "cmb" && - type !== "nm" - } + (line) => !isExcludedFromPickStock(line.type), ); const total = nonConsumablesLines.length; const sufficient = nonConsumablesLines.filter(isStockSufficient).length; @@ -638,10 +641,20 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { }, // ✅ 移除 cell 中的 onClick renderCell: (params: GridRenderCellParams) => { + if (isExcludedFromPickStock(params.row.type)) { + return ( + + + {t("N/A")} + + + ); + } + const stockAvailable = getStockAvailable(params.row); const qty = showBaseQty ? params.row.baseStockQty : (stockAvailable || 0); const uom = showBaseQty ? params.row.stockBaseUom : params.row.stockUom; - + return ( {decimalFormatter.format(qty || 0)} ({uom || ""}) @@ -667,7 +680,14 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { type: "boolean", sortable: false, // ✅ 禁用排序 renderCell: (params: GridRenderCellParams) => { - return isStockSufficient(params.row) + if (isExcludedFromPickStock(params.row.type)) { + return ( + + {t("N/A")} + + ); + } + return isStockSufficient(params.row) ? : ; }, diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index 2c2a34c..ba9c482 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -73,7 +73,7 @@ export type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType"; const PAGE_SIZE = 50; -/** 預設依 JobOrder.planStart 搜尋:今天往前 3 天~往後 3 天(含當日) */ +/** 預設依 JobOrder.planStart 搜索:今天往前 3 天~往後 3 天(含當日) */ function defaultPlanStartRange() { return { from: dayjs().subtract(0, "day").format("YYYY-MM-DD"), @@ -423,7 +423,7 @@ const ProductProcessList: React.FC = ({ return base; }, [appliedSearch, disableDateFilter, filter, t]); - /** SearchBox 內部 state 只在掛載時讀 preFilled;套用搜尋後需 remount 才會與 appliedSearch 一致 */ + /** SearchBox 內部 state 只在掛載時讀 preFilled;套用搜索後需 remount 才會與 appliedSearch 一致 */ const searchBoxKey = useMemo( () => [ diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx index 082f0bb..f78c95e 100644 --- a/src/components/ProductionProcess/ProductionProcessPage.tsx +++ b/src/components/ProductionProcess/ProductionProcessPage.tsx @@ -32,7 +32,7 @@ const ProductionProcessPage: React.FC = ({ printerCo pickOrderId: number; } | null>(null); const [tabIndex, setTabIndex] = useState(0); - /** 列表搜尋/分頁:保留在切換工單詳情時,返回後仍為同一條件 */ + /** 列表搜索/分頁:保留在切換工單詳情時,返回後仍為同一條件 */ const [productionListState, setProductionListState] = useState(() => ({ ...createDefaultProductionProcessListPersistedState(), // date: "",