CANCERYS\kw093 пре 3 недеља
родитељ
комит
c5c50d5fa6
4 измењених фајлова са 87 додато и 111 уклоњено
  1. +53
    -97
      src/components/DoDetail/DoLineTable.tsx
  2. +31
    -11
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  3. +2
    -2
      src/components/ProductionProcess/ProductionProcessList.tsx
  4. +1
    -1
      src/components/ProductionProcess/ProductionProcessPage.tsx

+ 53
- 97
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<string, never>;

const getStockAvailable = (line: DoDetailLine): number => {
if (line.stockQty != null && !Number.isNaN(Number(line.stockQty))) {
return Number(line.stockQty);
}
return 0;
};

const DoLineTable: React.FC<Props> = ({

}) => {
const { t } = useTranslation("do")
const {
watch
} = useFormContext<DoDetail>()
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<InventoryResult[]>([]);
const DoLineTable: React.FC<Props> = () => {
const { t } = useTranslation("do");
const { watch } = useFormContext<DoDetail>();
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(
() => <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />,
[],
);

const isStockSufficient = (line: any) => {
const stockAvailable = getStockAvailable(line);
return stockAvailable >= line.qty;
};
const insufficientStockIcon = useMemo(
() => <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />,
[],
);

const sufficientStockIcon = useMemo(() => {
return <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />
}, []);

const insufficientStockIcon = useMemo(() => {
return <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />
}, []);

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<GridColDef[]>(() => [
{
@@ -103,7 +63,7 @@ const DoLineTable: React.FC<Props> = ({
field: "itemName",
headerName: t("Item Name"),
flex: 1,
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => {
renderCell: (params: GridRenderCellParams<DoLineRow>) => {
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<Props> = ({
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => {
renderCell: (params: GridRenderCellParams<DoLineRow>) => {
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`;
},
},
@@ -126,7 +86,7 @@ const DoLineTable: React.FC<Props> = ({
align: "right",
headerAlign: "right",
type: "number",
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => {
renderCell: (params: GridRenderCellParams<DoLineRow>) => {
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`;
},
},
@@ -137,35 +97,31 @@ const DoLineTable: React.FC<Props> = ({
align: "right",
headerAlign: "right",
type: "boolean",
renderCell: (params: GridRenderCellParams<DoLineWithCalculations>) => {
renderCell: (params: GridRenderCellParams<DoLineRow>) => {
return params.row.isStockSufficient ? sufficientStockIcon : insufficientStockIcon;
},
},

], [t, inventoryData])
], [t, sufficientStockIcon, insufficientStockIcon]);

return (
<>
<StyledDataGrid
sx={{
"--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",
},
}}
disableColumnMenu
rows={rowsWithCalculatedFields}
columns={columns}
getRowHeight={() => 'auto'}
/>
</>
)
}
<StyledDataGrid
sx={{
"--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",
},
}}
disableColumnMenu
rows={rowsWithCalculatedFields}
columns={columns}
getRowHeight={() => 'auto'}
/>
);
};

export default DoLineTable;
export default DoLineTable;

+ 31
- 11
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx Прегледај датотеку

@@ -171,7 +171,17 @@ const [showBaseQty, setShowBaseQty] = useState<boolean>(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<JobOrderLineInfo>) => {
if (isExcludedFromPickStock(params.row.type)) {
return (
<Box sx={{ textAlign: "right" }}>
<Typography variant="body2" color="text.secondary">
{t("N/A")}
</Typography>
</Box>
);
}

const stockAvailable = getStockAvailable(params.row);
const qty = showBaseQty ? params.row.baseStockQty : (stockAvailable || 0);
const uom = showBaseQty ? params.row.stockBaseUom : params.row.stockUom;
return (
<Box sx={{ textAlign: "right" }}>
{decimalFormatter.format(qty || 0)} ({uom || ""})
@@ -667,7 +680,14 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
type: "boolean",
sortable: false, // ✅ 禁用排序
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
return isStockSufficient(params.row)
if (isExcludedFromPickStock(params.row.type)) {
return (
<Typography variant="body2" color="text.secondary">
{t("N/A")}
</Typography>
);
}
return isStockSufficient(params.row)
? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />
: <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />;
},


+ 2
- 2
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<ProductProcessListProps> = ({
return base;
}, [appliedSearch, disableDateFilter, filter, t]);

/** SearchBox 內部 state 只在掛載時讀 preFilled;套用搜後需 remount 才會與 appliedSearch 一致 */
/** SearchBox 內部 state 只在掛載時讀 preFilled;套用搜後需 remount 才會與 appliedSearch 一致 */
const searchBoxKey = useMemo(
() =>
[


+ 1
- 1
src/components/ProductionProcess/ProductionProcessPage.tsx Прегледај датотеку

@@ -32,7 +32,7 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo
pickOrderId: number;
} | null>(null);
const [tabIndex, setTabIndex] = useState(0);
/** 列表搜/分頁:保留在切換工單詳情時,返回後仍為同一條件 */
/** 列表搜/分頁:保留在切換工單詳情時,返回後仍為同一條件 */
const [productionListState, setProductionListState] = useState(() => ({
...createDefaultProductionProcessListPersistedState(),
// date: "",


Loading…
Откажи
Сачувај