From fb471d78046a1a6515c7a554bf5df50befe2c9df Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 11 Nov 2025 18:25:00 +0800 Subject: [PATCH] update --- src/app/api/jo/actions.ts | 23 +- src/components/DoSearch/DoSearch.tsx | 32 +- .../GoodPickExecutionForm.tsx | 2 +- .../ProductionProcessDetail.tsx | 446 ++++++------------ .../ProductionProcessList.tsx | 4 +- .../ProductionProcessPage.tsx | 11 +- .../ProductionProcessStepExecution.tsx | 270 +++++++++++ 7 files changed, 453 insertions(+), 335 deletions(-) create mode 100644 src/components/ProductionProcess/ProductionProcessStepExecution.tsx diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 23e0e01..4535c8f 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -186,7 +186,9 @@ export interface ProductProcessWithLinesResponse { startTime?: string; endTime?: string; date: string; - lines: ProductProcessLineResponse[]; + bomId?: number; + jobOrderId?: number; + productProcessLines: ProductProcessLineResponse[]; } export interface UpdateProductProcessLineQtyRequest { productProcessLineId: number; @@ -341,15 +343,26 @@ export const updateProductProcessLineQty = async (request: UpdateProductProcessL } ); }; -export const startProductProcessLine = async (lineId: number, userId: number) => { - return serverFetchJson( - `${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`, + +export const startProductProcessLine = async (lineId: number) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/ProcessLine/start/${lineId}`, { method: "POST", headers: { "Content-Type": "application/json" }, } ); }; +export const completeProductProcessLine = async (lineId: number) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/demo/ProcessLine/complete/${lineId}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + } + ); +}; + // 查询所有 production processes export const fetchProductProcesses = cache(async () => { return serverFetchJson<{ content: ProductProcessResponse[] }>( @@ -375,7 +388,7 @@ export const fetchProductProcessById = cache(async (id: number) => { // 根据 Job Order ID 查询 export const fetchProductProcessesByJobOrderId = cache(async (jobOrderId: number) => { return serverFetchJson( - `${BASE_API_URL}/product-process/by-job-order/${jobOrderId}`, + `${BASE_API_URL}/product-process/demo/joid/${jobOrderId}`, { method: "GET", next: { tags: ["productProcess"] }, diff --git a/src/components/DoSearch/DoSearch.tsx b/src/components/DoSearch/DoSearch.tsx index f8b6e4c..ef3274a 100644 --- a/src/components/DoSearch/DoSearch.tsx +++ b/src/components/DoSearch/DoSearch.tsx @@ -147,20 +147,21 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Code"), paramName: "code", type: "text" }, - +/* { label: t("Order Date From"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange", }, + */ { label: t("Shop Name"), paramName: "shopName", type: "text" }, { - label: t("Estimated Arrival From"), - label2: t("Estimated Arrival To"), + label: t("Estimated Arrival"), + //label2: t("Estimated Arrival To"), paramName: "estimatedArrivalDate", - type: "dateRange", + type: "date", }, { @@ -254,6 +255,7 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear headerName: t("Supplier Name"), flex: 1, }, + { field: "orderDate", headerName: t("Order Date"), @@ -263,7 +265,9 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear ? arrayToDateString(params.row.orderDate) : "N/A"; }, + }, + { field: "estimatedArrivalDate", headerName: t("Estimated Arrival"), @@ -302,23 +306,23 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear try { setCurrentSearchParams(query); - let orderStartDate = query.orderDate; - let orderEndDate = query.orderDateTo; + let orderStartDate = ""; + let orderEndDate = ""; let estArrStartDate = query.estimatedArrivalDate; - let estArrEndDate = query.estimatedArrivalDateTo; + let estArrEndDate = query.estimatedArrivalDate; const time = "T00:00:00"; - if(orderStartDate != ""){ - orderStartDate = query.orderDate + time; - } - if(orderEndDate != ""){ - orderEndDate = query.orderDateTo + time; - } + //if(orderStartDate != ""){ + // orderStartDate = query.orderDate + time; + //} + //if(orderEndDate != ""){ + // orderEndDate = query.orderDateTo + time; + //} if(estArrStartDate != ""){ estArrStartDate = query.estimatedArrivalDate + time; } if(estArrEndDate != ""){ - estArrEndDate = query.estimatedArrivalDateTo + time; + estArrEndDate = query.estimatedArrivalDate + time; } let status = ""; diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 1ee2fa8..1e7046a 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -190,7 +190,7 @@ const validateForm = (): boolean => { } // 2. 检查 actualPickQty 不能超过可用数量或需求数量 - if (ap > Math.min(remainingAvailableQty, req)) { + if (ap > Math.min( req)) { newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); } diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 9940775..4da7216 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -33,12 +33,16 @@ import { fetchProductProcessLines, updateProductProcessLineQrscan, fetchProductProcessLineDetail, + ProductProcessLineDetailResponse, + updateLineOutput, ProductProcessResponse, ProductProcessLineResponse, - startProductProcessLine + completeProductProcessLine, + startProductProcessLine, + fetchProductProcessesByJobOrderId } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; - +import ProductionProcessStepExecution from "./ProductionProcessStepExecution"; // 添加设备数据库(从 MachineScanner.tsx) const machineDatabase: Machine[] = [ { id: 1, name: "CNC Mill #1", code: "CNC001", qrCode: "QR-CNC001" }, @@ -48,30 +52,14 @@ const machineDatabase: Machine[] = [ { id: 5, name: "Drill Press #5", code: "DRL005", qrCode: "QR-DRL005" }, ]; -interface ProcessLine { - id: number; - seqNo: number; - name: string; - description?: string; - equipmentType?: string; - startTime?: string; - endTime?: string; - outputFromProcessQty?: number; - outputFromProcessUom?: string; - defectQty?: number; - scrapQty?: number; - byproductName?: string; - byproductQty?: number; - handlerId?: number; // 添加 handlerId -} interface ProductProcessDetailProps { - processId: number; + jobOrderId: number; onBack: () => void; } const ProductionProcessDetail: React.FC = ({ - processId, + jobOrderId, onBack, }) => { const { t } = useTranslation(); @@ -81,7 +69,7 @@ const ProductionProcessDetail: React.FC = ({ // 基本信息 const [processData, setProcessData] = useState(null); - const [lines, setLines] = useState([]); + const [lines, setLines] = useState([]); const [loading, setLoading] = useState(false); // 选中的 line 和执行状态 @@ -241,24 +229,34 @@ const ProductionProcessDetail: React.FC = ({ const fetchProcessDetail = useCallback(async () => { setLoading(true); try { - console.log(`🔍 Loading process detail for ID: ${processId}`); + console.log(`🔍 Loading process detail for JobOrderId: ${jobOrderId}`); + + // 使用 fetchProductProcessesByJobOrderId 获取基础数据 + const processesWithLines = await fetchProductProcessesByJobOrderId(jobOrderId); + + if (!processesWithLines || processesWithLines.length === 0) { + throw new Error("No processes found for this job order"); + } + + // 如果有多个 process,取第一个(或者可以根据需要选择) + const currentProcess = processesWithLines[0]; - const data = await fetchProductProcessLineDetail(processId); - setProcessData(data); + setProcessData(currentProcess); - const linesData = await fetchProductProcessLineDetail(processId); - setLines([linesData]); + // 使用 productProcessLines 字段(API 返回的字段名) + const lines = (currentProcess as any).productProcessLines || []; + setLines(lines); - console.log(" Process data loaded:", data); - console.log(" Lines loaded:", linesData); + console.log(" Process data loaded:", currentProcess); + console.log(" Lines loaded:", lines); } catch (error) { console.error("❌ Error loading process detail:", error); - alert(`无法加载生产流程 ID ${processId}。该记录可能不存在。`); + //alert(`无法加载 Job Order ID ${jobOrderId} 的生产流程。该记录可能不存在。`); onBack(); } finally { setLoading(false); } - }, [processId, onBack]); + }, [jobOrderId, onBack]); useEffect(() => { fetchProcessDetail(); @@ -266,34 +264,13 @@ const ProductionProcessDetail: React.FC = ({ // 开始执行某个 line const handleStartLine = async (lineId: number) => { - if (!currentUserId) { - alert("Please login first!"); - return; - } - + try { // 使用 Server Action 而不是直接 fetch - await startProductProcessLine(lineId, currentUserId); - - console.log(` Starting line ${lineId} with handlerId: ${currentUserId}`); - setSelectedLineId(lineId); - setIsExecutingLine(true); - setScannedOperators([]); - setScannedMachines([]); - setOutputData({ - byproductName: "", - byproductQty: "", - byproductUom: "", - scrapQty: "", - scrapUom: "", - defectQty: "", - defectUom: "", - outputFromProcessQty: "", - outputFromProcessUom: "", - }); + await startProductProcessLine(lineId); // 刷新数据 - await fetchProcessDetail(); + //await fetchProcessDetail(); } catch (error) { console.error("Error starting line:", error); alert("Failed to start line. Please try again."); @@ -310,31 +287,27 @@ const ProductionProcessDetail: React.FC = ({ } try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8090'}/product-process/lines/${selectedLineId}/output`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - outputQty: parseFloat(outputData.outputFromProcessQty) || 0, - outputUom: outputData.outputFromProcessUom, - defectQty: parseFloat(outputData.defectQty) || 0, - defectUom: outputData.defectUom, - scrapQty: parseFloat(outputData.scrapQty) || 0, - scrapUom: outputData.scrapUom, - byproductName: outputData.byproductName, - byproductQty: parseFloat(outputData.byproductQty) || 0, - byproductUom: outputData.byproductUom, - }), + // 直接使用 actions.ts 中定义的函数 + await updateLineOutput(selectedLineId, { + outputQty: parseFloat(outputData.outputFromProcessQty) || 0, + outputUom: outputData.outputFromProcessUom, + defectQty: parseFloat(outputData.defectQty) || 0, + defectUom: outputData.defectUom, + scrapQty: parseFloat(outputData.scrapQty) || 0, + scrapUom: outputData.scrapUom, + byproductName: outputData.byproductName, + byproductQty: parseFloat(outputData.byproductQty) || 0, + byproductUom: outputData.byproductUom, }); - if (response.ok) { - console.log(" Output data submitted successfully"); - setIsExecutingLine(false); - setSelectedLineId(null); - handleStopScan(); // 停止扫描 - fetchProcessDetail(); // 刷新数据 - } + console.log(" Output data submitted successfully"); + setIsExecutingLine(false); + setSelectedLineId(null); + handleStopScan(); + await fetchProcessDetail(); } catch (error) { console.error("Error submitting output:", error); + alert("Failed to submit output data. Please try again."); } }; @@ -403,250 +376,101 @@ const ProductionProcessDetail: React.FC = ({ {t("Action")} - - {lines.map((line) => ( - - {line.seqNo} - - {line.name} - - {line.description} - {line.equipmentType} - - {line.endTime ? ( - - ) : line.startTime ? ( - - ) : ( - - )} - - - - - - ))} - - - - ) : ( - /* ========== 步骤执行视图 ========== */ - - {/* 当前步骤信息 */} - - - - {t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo}) - - - {selectedLine?.description} - - - {t("Equipment")}: {selectedLine?.equipmentType} - - - - - - {/* 合并的扫描器 */} - - - {t("Scan Operator & Equipment")} - - - - {/* 操作员扫描 */} - - - {scannedOperators.length > 0 - ? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}` - : t("Please scan operator code") - } - - - - {/* 设备扫描 */} - - - {scannedMachines.length > 0 - ? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}` - : t("Please scan equipment code") - } - - - - {/* 单个扫描按钮 */} + + {lines.map((line) => { + const status = (line as any).status || ''; + const statusLower = status.toLowerCase(); + const equipmentName = (line as any).equipment_name || line.equipmentType || "-"; + + // 使用 status 字段判断状态 + const isCompleted = statusLower === 'completed'; + const isInProgress = statusLower === 'in_progress' || statusLower === 'in progress'; + const isPending = statusLower === 'pending' || status === ''; + + return ( + + {line.seqNo} + + {line.name} + + {line.description || "-"} + {equipmentName} + + {isCompleted ? ( + + ) : isInProgress ? ( + + ) : ( + + )} + + + {statusLower === 'pending' ? ( - - - - {/* ========== 产出输入表单 ========== */} - {scannedOperators.length > 0 && scannedMachines.length > 0 && ( - - - {t("Production Output Data Entry")} - - - - - - {t("Type")} - {t("Quantity")} - {t("Unit of Measure")} - - - - {/* 步骤收成 */} - - - {t("Output from Process")} - - - setOutputData(prev => ({ ...prev, outputFromProcessQty: e.target.value }))} - /> - - - setOutputData(prev => ({ ...prev, outputFromProcessUom: e.target.value }))} - placeholder="KG, L, PCS..." - /> - - - - {/* 副产品 */} - - - - {t("By-product")} - setOutputData(prev => ({ ...prev, byproductName: e.target.value }))} - placeholder={t("By-product name")} - sx={{ mt: 1 }} - /> - - - - setOutputData(prev => ({ ...prev, byproductQty: e.target.value }))} - /> - - - setOutputData(prev => ({ ...prev, byproductUom: e.target.value }))} - placeholder="KG, L, PCS..." - /> - - - - {/* 次品 */} - - - {t("Defect")} - - - setOutputData(prev => ({ ...prev, defectQty: e.target.value }))} - /> - - - setOutputData(prev => ({ ...prev, defectUom: e.target.value }))} - placeholder="KG, L, PCS..." - /> - - - - {/* 废品 */} - - - {t("Scrap")} - - - setOutputData(prev => ({ ...prev, scrapQty: e.target.value }))} - /> - - - setOutputData(prev => ({ ...prev, scrapUom: e.target.value }))} - placeholder="KG, L, PCS..." - /> - - - -
- - {/* 提交按钮 */} - + ): + statusLower === 'in_progress' || statusLower === 'in progress' ? ( + + ):( - - -
- )} -
-
+ )} + + + );})} + + + + ) : ( + /* ========== 步骤执行视图 ========== */ + { + setIsExecutingLine(false); + setSelectedLineId(null); + handleStopScan(); + fetchProcessDetail(); + }} + onSubmitOutput={handleSubmitOutput} + onOutputDataChange={(data) => setOutputData({...outputData, ...data})} + /> )} diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index 8180892..dd5f134 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -24,7 +24,7 @@ import { } from "@/app/api/jo/actions"; interface ProductProcessListProps { - onSelectProcess: (processId: number) => void; + onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void; } const PER_PAGE = 6; @@ -152,7 +152,7 @@ const ProductProcessList: React.FC = ({ onSelectProcess - diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx index 4343ac4..907b1ba 100644 --- a/src/components/ProductionProcess/ProductionProcessPage.tsx +++ b/src/components/ProductionProcess/ProductionProcessPage.tsx @@ -51,7 +51,7 @@ const ProductionProcessPage = () => { if (selectedProcessId !== null) { return ( setSelectedProcessId(null)} /> ); @@ -59,7 +59,14 @@ const ProductionProcessPage = () => { return ( setSelectedProcessId(id)} + onSelectProcess={(jobOrderId, productProcessId) => { + const id = jobOrderId ?? null; + if (id !== null) { + setSelectedProcessId(id); + } else { + + } + }} /> ); }; diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx new file mode 100644 index 0000000..455498b --- /dev/null +++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx @@ -0,0 +1,270 @@ +"use client"; +import React from "react"; +import { + Box, + Button, + Paper, + Stack, + Typography, + TextField, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Card, + CardContent, +} from "@mui/material"; +import QrCodeIcon from '@mui/icons-material/QrCode'; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import { useTranslation } from "react-i18next"; +import { ProductProcessLineResponse } from "@/app/api/jo/actions"; +import { Operator, Machine } from "@/app/api/jo"; + +interface ProductionProcessStepExecutionProps { + selectedLine: ProductProcessLineResponse | undefined; + scannedOperators: Operator[]; + scannedMachines: Machine[]; + isManualScanning: boolean; + outputData: { + byproductName: string; + byproductQty: string; + byproductUom: string; + scrapQty: string; + scrapUom: string; + defectQty: string; + defectUom: string; + outputFromProcessQty: string; + outputFromProcessUom: string; + }; + onStartScan: () => void; + onStopScan: () => void; + onCancel: () => void; + onSubmitOutput: () => void; + onOutputDataChange: (data: Partial) => void; +} + +const ProductionProcessStepExecution: React.FC = ({ + selectedLine, + scannedOperators, + scannedMachines, + isManualScanning, + outputData, + onStartScan, + onStopScan, + onCancel, + onSubmitOutput, + onOutputDataChange, +}) => { + const { t } = useTranslation(); + const equipmentName = (selectedLine as any)?.equipment_name || selectedLine?.equipmentType || "-"; + + return ( + + {/* 当前步骤信息 */} + + + + {t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo}) + + + {selectedLine?.description} + + + {t("Equipment")}: {equipmentName} + + + + + + {/* 合并的扫描器 */} + + + {t("Scan Operator & Equipment")} + + + + {/* 操作员扫描 */} + + + {scannedOperators.length > 0 + ? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}` + : t("Please scan operator code") + } + + + + {/* 设备扫描 */} + + + {scannedMachines.length > 0 + ? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}` + : t("Please scan equipment code") + } + + + + {/* 单个扫描按钮 */} + + + + + {/* ========== 产出输入表单 ========== */} + {scannedOperators.length > 0 && scannedMachines.length > 0 && ( + + + {t("Production Output Data Entry")} + + + + + + {t("Type")} + {t("Quantity")} + {t("Unit of Measure")} + + + + {/* 步骤收成 */} + + + {t("Output from Process")} + + + onOutputDataChange({ outputFromProcessQty: e.target.value })} + /> + + + onOutputDataChange({ outputFromProcessUom: e.target.value })} + placeholder="KG, L, PCS..." + /> + + + + {/* 副产品 */} + + + + {t("By-product")} + onOutputDataChange({ byproductName: e.target.value })} + placeholder={t("By-product name")} + sx={{ mt: 1 }} + /> + + + + onOutputDataChange({ byproductQty: e.target.value })} + /> + + + onOutputDataChange({ byproductUom: e.target.value })} + placeholder="KG, L, PCS..." + /> + + + + {/* 次品 */} + + + {t("Defect")} + + + onOutputDataChange({ defectQty: e.target.value })} + /> + + + onOutputDataChange({ defectUom: e.target.value })} + placeholder="KG, L, PCS..." + /> + + + + {/* 废品 */} + + + {t("Scrap")} + + + onOutputDataChange({ scrapQty: e.target.value })} + /> + + + onOutputDataChange({ scrapUom: e.target.value })} + placeholder="KG, L, PCS..." + /> + + + +
+ + {/* 提交按钮 */} + + + + +
+ )} +
+
+ ); +}; + +export default ProductionProcessStepExecution; \ No newline at end of file