diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 248ea07..565393e 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1210,12 +1210,15 @@ export const fetchMaterialPickStatus = cache(async (): Promise { - return serverFetchJson( - `${BASE_API_URL}/product-process/Demo/JobProcessStatus`, - { - method: "GET", - next: { tags: ["jobProcessStatus"] }, - } - ); +export const fetchJobProcessStatus = cache(async (date?: string) => { + const params = new URLSearchParams(); + if (date) params.set("date", date); // yyyy-MM-dd + + const qs = params.toString(); + const url = `${BASE_API_URL}/product-process/Demo/JobProcessStatus${qs ? `?${qs}` : ""}`; + + return serverFetchJson(url, { + method: "GET", + next: { tags: ["jobProcessStatus"] }, + }); }); export const deleteProductProcessLine = async (lineId: number) => { return serverFetchJson( diff --git a/src/app/api/stockIn/actions.ts b/src/app/api/stockIn/actions.ts index db43102..f6e2dcd 100644 --- a/src/app/api/stockIn/actions.ts +++ b/src/app/api/stockIn/actions.ts @@ -12,7 +12,7 @@ import { RecordsRes } from "../utils"; import { Uom } from "../settings/uom"; import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; // import { BASE_API_URL } from "@/config/api"; - +import { Result } from "../settings/item"; export interface PostStockInLineResponse { id: number | null; name: string; @@ -242,3 +242,9 @@ export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => }, ) }) +// 添加服务器端 action 用于从客户端组件获取 item 信息 +export const fetchItemForPutAway = cache(async (id: number): Promise => { + return serverFetchJson(`${BASE_API_URL}/items/details/${id}`, { + next: { tags: ["items"] }, + }); +}); diff --git a/src/components/PickOrderSearch/LotTable.tsx b/src/components/PickOrderSearch/LotTable.tsx index 61aa7c6..1db2f3c 100644 --- a/src/components/PickOrderSearch/LotTable.tsx +++ b/src/components/PickOrderSearch/LotTable.tsx @@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import import PickExecutionForm from "./PickExecutionForm"; interface LotPickData { id: number; - lotId: number; - lotNo: string; + lotId: number ; + lotNo: string ; expiryDate: string; - location: string; + location: string| null; stockUnit: string; inQty: number; availableQty: number; @@ -45,6 +45,7 @@ interface LotPickData { stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; + noLot?: boolean; } interface PickQtyData { diff --git a/src/components/PickOrderSearch/PickExecution.tsx b/src/components/PickOrderSearch/PickExecution.tsx index 50f6d92..98f70e1 100644 --- a/src/components/PickOrderSearch/PickExecution.tsx +++ b/src/components/PickOrderSearch/PickExecution.tsx @@ -334,7 +334,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { const handlePickQtyChange = useCallback((lineId: number, lotId: number, value: number | string) => { console.log("Changing pick qty:", { lineId, lotId, value }); - const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseInt(value, 10)) : value; + const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseFloat(value) || 0) : value; setPickQtyData(prev => { const newData = { diff --git a/src/components/PickOrderSearch/SearchResultsTable.tsx b/src/components/PickOrderSearch/SearchResultsTable.tsx index ff82ac8..bee2140 100644 --- a/src/components/PickOrderSearch/SearchResultsTable.tsx +++ b/src/components/PickOrderSearch/SearchResultsTable.tsx @@ -74,7 +74,7 @@ const SearchResultsTable: React.FC = ({ const handleQtyChange = useCallback((itemId: number, value: string) => { // Only allow numbers - if (value === "" || /^\d+$/.test(value)) { + if (value === "" || /^\d*\.?\d+$/.test(value)) { const numValue = value === "" ? null : Number(value); onQtyChange(itemId, numValue); } diff --git a/src/components/ProductionProcess/JobProcessStatus.tsx b/src/components/ProductionProcess/JobProcessStatus.tsx index 7f2e219..7dda26e 100644 --- a/src/components/ProductionProcess/JobProcessStatus.tsx +++ b/src/components/ProductionProcess/JobProcessStatus.tsx @@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import { fetchJobProcessStatus, JobProcessStatusResponse } from '@/app/api/jo/actions'; import { arrayToDayjs } from '@/app/utils/formatUtil'; - +import { FormControl, Select, MenuItem } from "@mui/material"; const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const JobProcessStatus: React.FC = () => { @@ -29,6 +29,7 @@ const JobProcessStatus: React.FC = () => { const [loading, setLoading] = useState(true); const refreshCountRef = useRef(0); const [currentTime, setCurrentTime] = useState(dayjs()); + const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); // Update current time every second for countdown useEffect(() => { @@ -41,21 +42,8 @@ const JobProcessStatus: React.FC = () => { const loadData = useCallback(async () => { setLoading(true); try { - const result = await fetchJobProcessStatus(); - - // On second refresh, filter out completed jobs - if (refreshCountRef.current >= 1) { - const filtered = result.filter(item => { - // Check if all required processes are completed - const allCompleted = item.processes - .filter(p => p.isRequired) - .every(p => p.endTime != null); - return !allCompleted; - }); - setData(filtered); - } else { - setData(result); - } + const result = await fetchJobProcessStatus(selectedDate); + setData(result); refreshCountRef.current += 1; } catch (error) { console.error('Error fetching job process status:', error); @@ -63,7 +51,7 @@ const JobProcessStatus: React.FC = () => { } finally { setLoading(false); } - }, []); + }, [selectedDate]); useEffect(() => { loadData(); @@ -183,12 +171,22 @@ const JobProcessStatus: React.FC = () => { return ( - - - {t("Job Process Status", )} - - - + + + {t("Job Process Status")} + + + + + + {loading ? ( @@ -285,12 +283,16 @@ const JobProcessStatus: React.FC = () => { ); } - + const label = [ + process.processName, + process.equipmentDescription, + process.equipmentDetailName ? `-${process.equipmentDetailName}` : "", + ].filter(Boolean).join(" "); // 如果工序是必需的,显示三行(Start、Finish、Wait Time) return ( - {process.equipmentCode || '-'} + {label || "-"} {formatTime(process.startTime)} diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 808c1d2..4e8c15f 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -467,17 +467,19 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "left", headerAlign: "left", type: "number", - + sortable: false, // ✅ 禁用排序 }, { field: "itemCode", - headerName: t("Item Code"), + headerName: t("Material Code"), flex: 0.6, + sortable: false, // ✅ 禁用排序 }, { field: "itemName", headerName: t("Item Name"), flex: 1, + sortable: false, // ✅ 禁用排序 renderCell: (params: GridRenderCellParams) => { return `${params.value} (${params.row.reqUom})`; }, @@ -488,21 +490,35 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { flex: 0.7, align: "right", headerAlign: "right", - renderCell: (params: GridRenderCellParams) => { - const qty = showBaseQty ? params.row.baseReqQty : params.value; - const uom = showBaseQty ? params.row.reqBaseUom : params.row.reqUom; - + sortable: false, // ✅ 禁用排序 + // ✅ 将切换功能移到 header + renderHeader: () => { + const qty = showBaseQty ? t("Base") : t("Req"); + const uom = showBaseQty ? t("Base UOM") : t(" "); return ( + {t("Bom Req. Qty")} ({uom}) + + ); + }, + // ✅ 移除 cell 中的 onClick,只显示值 + renderCell: (params: GridRenderCellParams) => { + const qty = showBaseQty ? params.row.baseReqQty : params.value; + const uom = showBaseQty ? params.row.reqBaseUom : params.row.reqUom; + + return ( + {decimalFormatter.format(qty || 0)} ({uom || ""}) ); @@ -514,27 +530,39 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { flex: 0.7, align: "right", headerAlign: "right", - renderCell: (params: GridRenderCellParams) => { - const qty = showBaseQty ? params.row.baseReqQty : params.value; - const uom = showBaseQty ? params.row.reqBaseUom : params.row.stockUom; - + sortable: false, // ✅ 禁用排序 + // ✅ 将切换功能移到 header + renderHeader: () => { + const uom = showBaseQty ? t("Base UOM") : t("Stock UOM"); return ( + {t("Stock Req. Qty")} ({uom}) + + ); + }, + // ✅ 移除 cell 中的 onClick + renderCell: (params: GridRenderCellParams) => { + const qty = showBaseQty ? params.row.baseReqQty : params.value; + const uom = showBaseQty ? params.row.reqBaseUom : params.row.stockUom; + + return ( + {decimalFormatter.format(qty || 0)} ({uom || ""}) ); }, }, - { field: "stockAvailable", headerName: t("Stock Available"), @@ -542,22 +570,35 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "right", headerAlign: "right", type: "number", - renderCell: (params: GridRenderCellParams) => { - const stockAvailable = getStockAvailable(params.row); - const qty = showBaseQty ? params.row.baseStockQty : (stockAvailable || 0); - const uom = showBaseQty ? params.row.stockBaseUom : params.row.stockUom; - + sortable: false, // ✅ 禁用排序 + // ✅ 将切换功能移到 header + renderHeader: () => { + const uom = showBaseQty ? t("Base UOM") : t("Stock UOM"); return ( + {t("Stock Available")} ({uom}) + + ); + }, + // ✅ 移除 cell 中的 onClick + renderCell: (params: GridRenderCellParams) => { + 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 || ""}) ); @@ -570,17 +611,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "right", headerAlign: "right", type: "number", + sortable: false, // ✅ 禁用排序 }, -/* - { - field: "seqNoRemark", - headerName: t("Seq No Remark"), - flex: 1, - align: "left", - headerAlign: "left", - type: "string", - }, - */ { field: "stockStatus", headerName: t("Stock Status"), @@ -588,8 +620,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "center", headerAlign: "center", type: "boolean", + sortable: false, // ✅ 禁用排序 renderCell: (params: GridRenderCellParams) => { - return isStockSufficient(params.row) ? : ; diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx index dd293c1..3a7bd7a 100644 --- a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx +++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx @@ -18,6 +18,8 @@ import { Card, CardContent, Grid, + Select, + MenuItem, } from "@mui/material"; import { Alert } from "@mui/material"; @@ -106,7 +108,9 @@ const ProductionProcessStepExecution: React.FC { if (lineId) { @@ -179,7 +183,7 @@ const ProductionProcessStepExecution: React.FC { - console.error("❌ Failed to load line detail after resume", err); + console.error(" Failed to load line detail after resume", err); }); } } catch (error) { - console.error("❌ Error resuming:", error); + console.error(" Error resuming:", error); alert(t("Failed to resume. Please try again.")); } }; @@ -791,7 +795,7 @@ const ProductionProcessStepExecution: React.FC - + displayEmpty + > + + {t("Select Unit")} + + {uomList.map((uom) => ( + + {uom} + + ))} + {t("Description")} @@ -823,7 +837,7 @@ const ProductionProcessStepExecution: React.FC - + displayEmpty + > + + {t("Select Unit")} + + {uomList.map((uom) => ( + + {uom} + + ))} + - + displayEmpty + > + + {t("Select Unit")} + + {uomList.map((uom) => ( + + {uom} + + ))} + - + displayEmpty + > + + {t("Select Unit")} + + {uomList.map((uom) => ( + + {uom} + + ))} + - + displayEmpty + > + + {t("Select Unit")} + + {uomList.map((uom) => ( + + {uom} + + ))} + diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index b4b4a22..8016c4a 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -207,7 +207,13 @@ "Job Order Match": "工單對料", "All Pick Order Lots": "所有提料單批號", "Row per page": "每頁行數", + "Select Unit": "選擇單位", "No data available": "沒有資料", + "Bom Req. Qty": "需求數(BOM單位)", + "Material Name": "材料清單", + "Material Code": "材料清單", + "Base UOM": "基本單位", + "Stock UOM": "庫存單位", "jodetail": "工單細節", "Sign out": "登出", "By-product": "副產品", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 2870fec..9517120 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -25,6 +25,8 @@ "Overall Time Remaining": "總剩餘時間", "User not found with staffNo:": "用戶不存在", "Time Remaining": "剩餘時間", + "Base UOM": "基本單位", + "Stock UOM": "庫存單位", "Over Time": "超時", "Staff No:": "員工編號:", "Timer Paused": "計時器已暫停", @@ -107,6 +109,8 @@ "Lines with sufficient stock: ": "可提料項目數量: ", "Lines with insufficient stock: ": "未能提料項目數量: ", "Item Name": "成品/半成品", + "Material Code": "材料編號", + "Select Unit": "選擇單位", "Job Order Pickexcution": "工單提料", "Pick Order Detail": "提料單細節", "Finished Job Order Record": "已完成工單記錄",