diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 87d6fcfb..8153f8cd 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -906,7 +906,7 @@ export const fetchAllJoborderProductProcessInfo = cache(async (type?: string | n ); }); -/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */ export const fetchJoborderProductProcessesPage = cache(async (params: { /** Job order / process date(YYYY-MM-DD) */ date?: string | null; diff --git a/src/components/ProductionProcess/ProductionOutputForm.tsx b/src/components/ProductionProcess/ProductionOutputForm.tsx index 1f1d536d..b45d58e7 100644 --- a/src/components/ProductionProcess/ProductionOutputForm.tsx +++ b/src/components/ProductionProcess/ProductionOutputForm.tsx @@ -17,25 +17,42 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import { useTranslation } from "react-i18next"; import { UpdateProductProcessLineQtyRequest } from "@/app/api/jo/actions"; +type QtyInput = number | ""; + +export type ProductionOutputFormData = Omit< + UpdateProductProcessLineQtyRequest, + | "outputFromProcessQty" + | "byproductQty" + | "defectQty" + | "defect2Qty" + | "defect3Qty" + | "scrapQty" +> & { + outputFromProcessQty: QtyInput; + byproductQty: QtyInput; + defectQty: QtyInput; + defect2Qty: QtyInput; + defect3Qty: QtyInput; + scrapQty: QtyInput; +}; + +const parseQtyInput = (raw: string): QtyInput => { + if (raw === "") return ""; + const n = parseInt(raw, 10); + return Number.isNaN(n) ? "" : n; +}; + +export const toSubmitQty = (v: QtyInput | null | undefined): number => + v === "" || v == null || Number.isNaN(Number(v)) ? 0 : Number(v); + interface ProductionOutputFormProps { - outputData: (UpdateProductProcessLineQtyRequest & { - byproductName: string; - byproductQty: number; - byproductUom: string; - }); - setOutputData: React.Dispatch< - React.SetStateAction< - UpdateProductProcessLineQtyRequest & { - byproductName: string; - byproductQty: number; - byproductUom: string; - } - > - >; + outputData: ProductionOutputFormData; + setOutputData: React.Dispatch>; onSubmit: () => Promise | void; onCancel: () => void; } +/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */ const ProductionOutputForm: React.FC = ({ outputData, setOutputData, @@ -68,7 +85,7 @@ const ProductionOutputForm: React.FC = ({ onChange={(e) => setOutputData({ ...outputData, - outputFromProcessQty: parseInt(e.target.value) || 0, + outputFromProcessQty: parseQtyInput(e.target.value), }) } /> @@ -103,7 +120,7 @@ const ProductionOutputForm: React.FC = ({ onChange={(e) => setOutputData({ ...outputData, - byproductQty: parseInt(e.target.value) || 0, + byproductQty: parseQtyInput(e.target.value), }) } /> @@ -138,7 +155,7 @@ const ProductionOutputForm: React.FC = ({ onChange={(e) => setOutputData({ ...outputData, - defectQty: parseInt(e.target.value) || 0, + defectQty: parseQtyInput(e.target.value), }) } /> @@ -173,7 +190,7 @@ const ProductionOutputForm: React.FC = ({ onChange={(e) => setOutputData({ ...outputData, - scrapQty: parseInt(e.target.value) || 0, + scrapQty: parseQtyInput(e.target.value), }) } /> @@ -207,4 +224,4 @@ const ProductionOutputForm: React.FC = ({ ); }; -export default ProductionOutputForm; \ No newline at end of file +export default ProductionOutputForm; diff --git a/src/components/ProductionProcess/ProductionOutputFormPage.tsx b/src/components/ProductionProcess/ProductionOutputFormPage.tsx index bdbbffba..002b4cee 100644 --- a/src/components/ProductionProcess/ProductionOutputFormPage.tsx +++ b/src/components/ProductionProcess/ProductionOutputFormPage.tsx @@ -5,47 +5,47 @@ import { Button, Typography, } from "@mui/material"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useTranslation } from "react-i18next"; import { fetchProductProcessLineDetail, updateProductProcessLineQty, - UpdateProductProcessLineQtyRequest, JobOrderProcessLineDetailResponse, } from "@/app/api/jo/actions"; -import ProductionOutputForm from "./ProductionOutputForm"; +import ProductionOutputForm, { + ProductionOutputFormData, + toSubmitQty, +} from "./ProductionOutputForm"; interface ProductionOutputFormPageProps { lineId: number | null; onBack: () => void; } +/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */ const ProductionOutputFormPage: React.FC = ({ lineId, onBack, }) => { - const { t } = useTranslation(); + const { t } = useTranslation(["common", "jo", "productionProcess"]); const [lineDetail, setLineDetail] = useState(null); - const [outputData, setOutputData] = useState({ + const [outputData, setOutputData] = useState({ productProcessLineId: lineId ?? 0, - outputFromProcessQty: 0, + outputFromProcessQty: "", outputFromProcessUom: "", - defectQty: 0, + defectQty: "", defectUom: "", - defect2Qty: 0, + defect2Qty: "", defect2Uom: "", - defect3Qty: 0, + defect3Qty: "", defect3Uom: "", defectDescription: "", defectDescription2: "", defectDescription3: "", - scrapQty: 0, + scrapQty: "", scrapUom: "", byproductName: "", - byproductQty: 0, + byproductQty: "", byproductUom: "", }); @@ -60,14 +60,14 @@ const ProductionOutputFormPage: React.FC = ({ setOutputData((prev) => ({ ...prev, productProcessLineId: detail.id, - outputFromProcessQty: (detail as any).outputFromProcessQty || 0, + outputFromProcessQty: (detail as any).outputFromProcessQty || "", outputFromProcessUom: (detail as any).outputFromProcessUom || "", - defectQty: detail.defectQty || 0, + defectQty: detail.defectQty || "", defectUom: detail.defectUom || "", - scrapQty: detail.scrapQty || 0, + scrapQty: detail.scrapQty || "", scrapUom: detail.scrapUom || "", byproductName: detail.byproductName || "", - byproductQty: detail.byproductQty || 0, + byproductQty: detail.byproductQty || "", byproductUom: detail.byproductUom || "", })); }) @@ -84,6 +84,12 @@ const ProductionOutputFormPage: React.FC = ({ await updateProductProcessLineQty({ ...outputData, productProcessLineId: lineDetail.id || 0, + outputFromProcessQty: toSubmitQty(outputData.outputFromProcessQty), + byproductQty: toSubmitQty(outputData.byproductQty), + defectQty: toSubmitQty(outputData.defectQty), + defect2Qty: toSubmitQty(outputData.defect2Qty), + defect3Qty: toSubmitQty(outputData.defect3Qty), + scrapQty: toSubmitQty(outputData.scrapQty), }); console.log("Output data submitted successfully"); @@ -94,14 +100,14 @@ const ProductionOutputFormPage: React.FC = ({ setOutputData((prev) => ({ ...prev, productProcessLineId: detail.id, - outputFromProcessQty: (detail as any).outputFromProcessQty || 0, + outputFromProcessQty: (detail as any).outputFromProcessQty || "", outputFromProcessUom: (detail as any).outputFromProcessUom || "", - defectQty: detail.defectQty || 0, + defectQty: detail.defectQty || "", defectUom: detail.defectUom || "", - scrapQty: detail.scrapQty || 0, + scrapQty: detail.scrapQty || "", scrapUom: detail.scrapUom || "", byproductName: detail.byproductName || "", - byproductQty: detail.byproductQty || 0, + byproductQty: detail.byproductQty || "", byproductUom: detail.byproductUom || "", })); @@ -116,8 +122,13 @@ const ProductionOutputFormPage: React.FC = ({ return ( - diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 7700330f..2c30fa27 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -64,13 +64,16 @@ interface ProductProcessDetailProps { jobOrderId: number; onBack: () => void; fromJosave?: boolean; + /** 進入步驟執行頁時通知父層(用來隱藏「返回列表」) */ + onStepViewChange?: (inStepView: boolean) => void; } -/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.1 | 2026-08-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.2 | 2026-10-29 */ const ProductionProcessDetail: React.FC = ({ jobOrderId, onBack, fromJosave, + onStepViewChange, }) => { // console.log(" ProductionProcessDetail RENDER", { jobOrderId, fromJosave }); @@ -143,6 +146,12 @@ const fetchProcessDetailRef = useRef<() => Promise>(); useEffect(() => { onBackRef.current = onBack; }, [onBack]); + + useEffect(() => { + const inStepView = isExecutingLine && selectedLineId != null; + onStepViewChange?.(inStepView); + return () => onStepViewChange?.(false); + }, [isExecutingLine, selectedLineId, onStepViewChange]); // 获取 process 和 lines 数据 const fetchProcessDetail = useCallback(async () => { // console.log(" fetchProcessDetail CALLED", { jobOrderId, timestamp: new Date().toISOString() }); diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 78818a53..8fb026bc 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -54,7 +54,7 @@ interface ProductProcessJobOrderDetailProps { initialTabIndex?: number; } -/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */ const ProductionProcessJobOrderDetail: React.FC = ({ jobOrderId, onBack, @@ -68,6 +68,7 @@ const ProductionProcessJobOrderDetail: React.FC([]); const [tabIndex, setTabIndex] = useState(initialTabIndex); const [selectedProcessId, setSelectedProcessId] = useState(null); + const [inStepView, setInStepView] = useState(false); const [operationPriority, setOperationPriority] = useState(50); const [openOperationPriorityDialog, setOpenOperationPriorityDialog] = useState(false); const [openPlanStartDialog, setOpenPlanStartDialog] = useState(false); @@ -320,6 +321,9 @@ const handleRelease = useCallback(async (jobOrderId: number) => { const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); + if (newValue !== 2) { + setInStepView(false); + } }, [], ); @@ -796,12 +800,14 @@ const handleRelease = useCallback(async (jobOrderId: number) => { return ( - {/* 返回按钮 */} - - - + {/* 步驟執行頁不顯示「返回列表」,避免誤離開工單 */} + {!inStepView && ( + + + + )} {/* 标签页 */} @@ -833,6 +839,7 @@ const handleRelease = useCallback(async (jobOrderId: number) => { }} fromJosave={Boolean(fromJosave && !isPutAwayed)} + onStepViewChange={setInStepView} /> )} {tabIndex === 3 && } diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index 38667525..48d6d8d1 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -179,7 +179,7 @@ function isWaitingQcPutAway( return s !== "completed" && s !== "rejected"; } -/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.10 | 2026-10-15 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */ const ProductProcessList: React.FC = ({ onSelectProcess, printerCombo, diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx index 096acbb5..3fa51bff 100644 --- a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx +++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx @@ -28,6 +28,7 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import StopIcon from "@mui/icons-material/Stop"; import PauseIcon from "@mui/icons-material/Pause"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useTranslation } from "react-i18next"; import { JobOrderProcessLineDetailResponse, @@ -47,6 +48,35 @@ import { fetchNameList, NameList } from "@/app/api/user/actions"; import BagConsumptionForm from "./BagConsumptionForm"; // ✅ 添加导入 import OverallTimeRemainingCard from "./OverallTimeRemainingCard"; // ✅ 添加导入 import dayjs from "dayjs"; + +type QtyInput = number | ""; + +type OutputFormState = Omit< + UpdateProductProcessLineQtyRequest, + | "outputFromProcessQty" + | "byproductQty" + | "defectQty" + | "defect2Qty" + | "defect3Qty" + | "scrapQty" +> & { + outputFromProcessQty: QtyInput; + byproductQty: QtyInput; + defectQty: QtyInput; + defect2Qty: QtyInput; + defect3Qty: QtyInput; + scrapQty: QtyInput; +}; + +const parseQtyInput = (raw: string): QtyInput => { + if (raw === "") return ""; + const n = parseInt(raw, 10); + return Number.isNaN(n) ? "" : n; +}; + +const toSubmitQty = (v: QtyInput | null | undefined): number => + v === "" || v == null || Number.isNaN(Number(v)) ? 0 : Number(v); + interface ProductionProcessStepExecutionProps { lineId: number | null; onBack: () => void; @@ -55,7 +85,7 @@ interface ProductionProcessStepExecutionProps { jobOrderId?: number; // ✅ 添加 } -/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.0 | 2026-08-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */ const ProductionProcessStepExecution: React.FC = ({ lineId, onBack, @@ -75,24 +105,20 @@ const ProductionProcessStepExecution: React.FC({ + const [outputData, setOutputData] = useState({ productProcessLineId: lineId ?? 0, - outputFromProcessQty: 0, + outputFromProcessQty: "", outputFromProcessUom: "", - defectQty: 0, + defectQty: "", defectUom: "", - scrapQty: 0, + scrapQty: "", scrapUom: "", byproductName: "", - byproductQty: 0, + byproductQty: "", byproductUom: "", - defect2Qty: 0, + defect2Qty: "", defect2Uom: "", - defect3Qty: 0, + defect3Qty: "", defect3Uom: "", defectDescription: "", defectDescription2: "", @@ -153,14 +179,14 @@ const ProductionProcessStepExecution: React.FC ({ ...prev, productProcessLineId: detail.id, - outputFromProcessQty: (detail as any).outputFromProcessQty || 0, + outputFromProcessQty: (detail as any).outputFromProcessQty || "", outputFromProcessUom: (detail as any).outputFromProcessUom || "", - defectQty: detail.defectQty || 0, + defectQty: detail.defectQty || "", defectUom: detail.defectUom || "", - scrapQty: detail.scrapQty || 0, + scrapQty: detail.scrapQty || "", scrapUom: detail.scrapUom || "", byproductName: detail.byproductName || "", - byproductQty: detail.byproductQty || 0, + byproductQty: detail.byproductQty || "", byproductUom: detail.byproductUom || "" })); }) @@ -351,20 +377,20 @@ const ProductionProcessStepExecution: React.FC ({ ...prev, productProcessLineId: detail.id, - outputFromProcessQty: (detail as any).outputFromProcessQty || 0, + outputFromProcessQty: (detail as any).outputFromProcessQty || "", outputFromProcessUom: (detail as any).outputFromProcessUom || "", - defectQty: detail.defectQty || 0, + defectQty: detail.defectQty || "", defectUom: detail.defectUom || "", defectDescription: detail.defectDescription || "", defectDescription2: detail.defectDescription2 || "", defectDescription3: detail.defectDescription3 || "", - defectQty2: detail.defectQty2 || 0, - defectUom2: detail.defectUom2 || "", - defectQty3: detail.defectQty3 || 0, - defectUom3: detail.defectUom3 || "", - scrapQty: detail.scrapQty || 0, + defect2Qty: (detail as any).defect2Qty || "", + defect2Uom: (detail as any).defect2Uom || "", + defect3Qty: (detail as any).defect3Qty || "", + defect3Uom: (detail as any).defect3Uom || "", + scrapQty: detail.scrapQty || "", scrapUom: detail.scrapUom || "", byproductName: detail.byproductName || "", - byproductQty: detail.byproductQty || 0, + byproductQty: detail.byproductQty || "", byproductUom: detail.byproductUom || "" })); }) @@ -561,8 +587,13 @@ const ProductionProcessStepExecution: React.FC - {processData && ( @@ -812,7 +843,7 @@ const ProductionProcessStepExecution: React.FC setOutputData({ ...outputData, - outputFromProcessQty: parseInt(e.target.value) || 0 + outputFromProcessQty: parseQtyInput(e.target.value) })} /> @@ -854,7 +885,7 @@ const ProductionProcessStepExecution: React.FC setOutputData({ ...outputData, - defectQty: parseInt(e.target.value) || 0 + defectQty: parseQtyInput(e.target.value) })} /> @@ -902,7 +933,7 @@ const ProductionProcessStepExecution: React.FC setOutputData({ ...outputData, - defect2Qty: parseInt(e.target.value) || 0 + defect2Qty: parseQtyInput(e.target.value) })} /> @@ -950,7 +981,7 @@ const ProductionProcessStepExecution: React.FC setOutputData({ ...outputData, - defect3Qty: parseInt(e.target.value) || 0 + defect3Qty: parseQtyInput(e.target.value) })} /> @@ -998,7 +1029,7 @@ const ProductionProcessStepExecution: React.FC setOutputData({ ...outputData, - scrapQty: parseInt(e.target.value) || 0 + scrapQty: parseQtyInput(e.target.value) })} /> diff --git a/src/i18n/en/jo.json b/src/i18n/en/jo.json index 06a212c3..dcfa37ea 100644 --- a/src/i18n/en/jo.json +++ b/src/i18n/en/jo.json @@ -32,6 +32,8 @@ "BOM Type": "BOM Type", "Back": "Back", "Back to List": "Back to List", + "Back to Process": "Back to Process Steps", + "Back to Process Steps": "Back to Process Steps", "Bad Item Qty": "Bad Item Qty", "Bad Package Qty": "Bad Package Qty", "Bag": "Bag", diff --git a/src/i18n/en/productionProcess.json b/src/i18n/en/productionProcess.json index efdff3e8..17f8b3c1 100644 --- a/src/i18n/en/productionProcess.json +++ b/src/i18n/en/productionProcess.json @@ -10,6 +10,8 @@ "Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes", "Back": "Back", "Back to List": "Back to List", + "Back to Process": "Back to Process Steps", + "Back to Process Steps": "Back to Process Steps", "Bag": "Bag", "Bag Consumption": "Bag Consumption", "Balance": "Balance", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 84135db3..ffe12d1a 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -34,6 +34,8 @@ "BOM Type": "BOM 類型", "Back": "返回", "Back to List": "返回列表", + "Back to Process": "返回列表", + "Back to Process Steps": "返回列表", "Bad Item Qty": "不良物品數量", "Bad Package Qty": "不良包裝數量", "Bag": "包裝袋", diff --git a/src/i18n/zh/productionProcess.json b/src/i18n/zh/productionProcess.json index 814ff3fc..d0130616 100644 --- a/src/i18n/zh/productionProcess.json +++ b/src/i18n/zh/productionProcess.json @@ -10,6 +10,8 @@ "Auto-refresh every 10 minutes": "每10分鐘自動刷新", "Back": "返回", "Back to List": "返回列表", + "Back to Process": "返回列表", + "Back to Process Steps": "返回列表", "Bag": "包裝袋", "Bag Consumption": "包裝袋消耗", "Balance": "可用數量",