From 333e67e03b3c231c760435be031da38c9e29bae1 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Thu, 13 Nov 2025 17:36:06 +0800 Subject: [PATCH] update --- src/app/api/jo/actions.ts | 39 +- .../GoodPickExecutionRecord.tsx | 23 +- .../ProductionProcessDetail.tsx | 646 +++++++++++------- .../ProductionProcessStepExecution.tsx | 580 ++++++++++------ 4 files changed, 795 insertions(+), 493 deletions(-) diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 4535c8f..55ddffa 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -26,7 +26,15 @@ export interface SearchJoResultRequest extends Pageable { planStartTo?: string; } - +export interface productProcessLineQtyRequest { + productProcessLineId: number; + outputFromProcessQty: number; + outputFromProcessUom: string; + defectQty: number; + defectUom: string; + scrapQty: number; + scrapUom: string; +} export interface SearchJoResultResponse { records: JobOrder[]; total: number; @@ -194,6 +202,9 @@ export interface UpdateProductProcessLineQtyRequest { productProcessLineId: number; outputFromProcessQty: number; outputFromProcessUom: string; + byproductName: string; + byproductQty: number; + byproductUom: string; defectQty: number; defectUom: string; scrapQty: number; @@ -246,7 +257,7 @@ export interface ProductProcessInfoResponse { status: string; } export interface ProductProcessLineQrscanUpadteRequest { - lineId: number; + productProcessLineId: number; operatorId?: number; equipmentId?: number; } @@ -261,7 +272,7 @@ export interface ProductProcessLineDetailResponse { seqNo: number, name: string, description: string, - equipment: string, + equipmentId: number, startTime: string, endTime: string, defectQty: number, @@ -282,6 +293,14 @@ export interface ProductProcessLineDetailResponse { operatorName: string, handlerId: number, seqNo: number, + isDark: string, + isDense: number, + isFloat: string, + outputQtyUom: string, + outputQty: number, + pickOrderId: number, + jobOrderCode: string, + jobOrderId: number, name: string, description: string, equipment: string, @@ -314,6 +333,17 @@ export const fetchProductProcessLineDetail = cache(async (lineId: number) => { } ); }); +export const updateProductProcessLineQty = cache(async (request: UpdateProductProcessLineQtyRequest) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/ProcessLine/update/qty/${request.productProcessLineId}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + } + ); +}); + export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/update`, @@ -333,6 +363,7 @@ export const fetchAllJoborderProductProcessInfo = cache(async () => { } ); }); +/* export const updateProductProcessLineQty = async (request: UpdateProductProcessLineQtyRequest) => { return serverFetchJson( `${BASE_API_URL}/product-process/lines/${request.productProcessLineId}/update/qty`, @@ -343,7 +374,7 @@ export const updateProductProcessLineQty = async (request: UpdateProductProcessL } ); }; - +*/ export const startProductProcessLine = async (lineId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/start/${lineId}`, diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index 89b6351..c5981f9 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -568,20 +568,25 @@ if (showDetailView && selectedDoPickOrder) { {/* 添加:多个 Pick Orders 信息(如果有) */} - {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( + {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 0 && ( {t("This ticket contains")} {selectedDoPickOrder.pickOrderIds.length} {t("pick orders")}: - {selectedDoPickOrder.pickOrderCodes?.split(', ').map((code, idx) => ( - - ))} + {(typeof selectedDoPickOrder.pickOrderCodes === 'string' + ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim()) + : Array.isArray(selectedDoPickOrder.pickOrderCodes) + ? selectedDoPickOrder.pickOrderCodes + : [] + ).filter(Boolean).map((code, idx) => ( + + ))} )} diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 4da7216..531f8f0 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -17,6 +17,10 @@ import { Card, CardContent, CircularProgress, + Dialog, + DialogTitle, + DialogContent, + DialogActions, } from "@mui/material"; import QrCodeIcon from '@mui/icons-material/QrCode'; import { useTranslation } from "react-i18next"; @@ -75,13 +79,17 @@ const ProductionProcessDetail: React.FC = ({ // 选中的 line 和执行状态 const [selectedLineId, setSelectedLineId] = useState(null); const [isExecutingLine, setIsExecutingLine] = useState(false); - + const [isAutoSubmitting, setIsAutoSubmitting] = useState(false); // 扫描器状态 const [isManualScanning, setIsManualScanning] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); - const [scannedOperators, setScannedOperators] = useState([]); - const [scannedMachines, setScannedMachines] = useState([]); - + const [scannedOperatorId, setScannedOperatorId] = useState(null); + const [scannedEquipmentId, setScannedEquipmentId] = useState(null); + const [scanningLineId, setScanningLineId] = useState(null); + const [lineDetailForScan, setLineDetailForScan] = useState(null); + const [showScanDialog, setShowScanDialog] = useState(false); + const autoSubmitTimerRef = useRef(null); + // 产出表单 const [outputData, setOutputData] = useState({ byproductName: "", @@ -97,133 +105,7 @@ const ProductionProcessDetail: React.FC = ({ // 处理 QR 码扫描 // 处理 QR 码扫描 - const processQrCode = useCallback((qrValue: string) => { - // 操作员格式:{2fitestu1} - 键盘模拟输入(测试用) - if (qrValue.match(/\{2fitestu(\d+)\}/)) { - const match = qrValue.match(/\{2fitestu(\d+)\}/); - const userId = parseInt(match![1]); - - // 调用 API 获取用户信息 - fetchNameList().then((users: NameList[]) => { - const user = users.find((u: NameList) => u.id === userId); - if (user) { - setScannedOperators([{ - id: user.id, - name: user.name, - username: user.name - }]); - - updateProductProcessLineQrscan({ - lineId: selectedLineId || 0 as number, - operatorId: user.id, - }); - } - }); - return; - } - - // 设备格式:{2fiteste1} - 键盘模拟输入(测试用) - if (qrValue.match(/\{2fiteste(\d+)\}/)) { - const match = qrValue.match(/\{2fiteste(\d+)\}/); - const equipmentId = parseInt(match![1]); - - // 使用本地设备数据库 - const machine = machineDatabase.find((m: Machine) => m.id === equipmentId); - if (machine) { - setScannedMachines([machine]); - } - - updateProductProcessLineQrscan({ - lineId: selectedLineId || 0 as number, - equipmentId: equipmentId, - }).then((res) => { - console.log(res); - }); - return; - } - - // 正常 QR 扫描器扫描:格式为 "operatorId: 1" 或 "equipmentId: 1" - const trimmedValue = qrValue.trim(); - - // 检查 operatorId 格式 - const operatorMatch = trimmedValue.match(/^operatorId:\s*(\d+)$/i); - if (operatorMatch) { - const operatorId = parseInt(operatorMatch[1]); - fetchNameList().then((users: NameList[]) => { - const user = users.find((u: NameList) => u.id === operatorId); - if (user) { - setScannedOperators([{ - id: user.id, - name: user.name, - username: user.name - }]); - - updateProductProcessLineQrscan({ - lineId: selectedLineId || 0 as number, - operatorId: user.id, - }); - } else { - console.warn(`User with ID ${operatorId} not found`); - } - }); - return; - } - - // 检查 equipmentId 格式 - const equipmentMatch = trimmedValue.match(/^equipmentId:\s*(\d+)$/i); - if (equipmentMatch) { - const equipmentId = parseInt(equipmentMatch[1]); - const machine = machineDatabase.find((m: Machine) => m.id === equipmentId); - if (machine) { - setScannedMachines([machine]); - } - - updateProductProcessLineQrscan({ - lineId: selectedLineId || 0 as number, - equipmentId: equipmentId, - }).then((res) => { - console.log(res); - }); - return; - } - - // 其他格式处理(JSON、普通文本等) - try { - const qrData = JSON.parse(qrValue); - // TODO: 处理 JSON 格式的 QR 码 - } catch { - // 普通文本格式 - // TODO: 处理普通文本格式 - } - }, [selectedLineId]); - - // 处理 QR 码扫描效果 - useEffect(() => { - if (isManualScanning && qrValues.length > 0 && isExecutingLine) { - const latestQr = qrValues[qrValues.length - 1]; - - if (processedQrCodes.has(latestQr)) { - return; - } - - setProcessedQrCodes(prev => new Set(prev).add(latestQr)); - processQrCode(latestQr); - } - }, [qrValues, isManualScanning, isExecutingLine, processedQrCodes, processQrCode]); - - // 开始扫描 - const handleStartScan = useCallback(() => { - setIsManualScanning(true); - setProcessedQrCodes(new Set()); - startScan(); - }, [startScan]); - - // 停止扫描 - const handleStopScan = useCallback(() => { - setIsManualScanning(false); - stopScan(); - resetScan(); - }, [stopScan, resetScan]); + // 获取 process 和 lines 数据 const fetchProcessDetail = useCallback(async () => { @@ -263,53 +145,239 @@ const ProductionProcessDetail: React.FC = ({ }, [fetchProcessDetail]); // 开始执行某个 line - const handleStartLine = async (lineId: number) => { + - try { - // 使用 Server Action 而不是直接 fetch - await startProductProcessLine(lineId); + // 提交产出数据 + const processQrCode = useCallback((qrValue: string, lineId: number) => { + // 操作员格式:{2fitestu1} - 键盘模拟输入(测试用) + if (qrValue.match(/\{2fitestu(\d+)\}/)) { + const match = qrValue.match(/\{2fitestu(\d+)\}/); + const userId = parseInt(match![1]); - // 刷新数据 - //await fetchProcessDetail(); - } catch (error) { - console.error("Error starting line:", error); - alert("Failed to start line. Please try again."); + fetchNameList().then((users: NameList[]) => { + const user = users.find((u: NameList) => u.id === userId); + if (user) { + setScannedOperatorId(user.id); + } + }); + return; + } + + // 设备格式:{2fiteste1} - 键盘模拟输入(测试用) + if (qrValue.match(/\{2fiteste(\d+)\}/)) { + const match = qrValue.match(/\{2fiteste(\d+)\}/); + const equipmentId = parseInt(match![1]); + setScannedEquipmentId(equipmentId); + return; } - }; - - // 提交产出数据 - const handleSubmitOutput = async () => { - if (!selectedLineId) return; - if (scannedOperators.length === 0 || scannedMachines.length === 0) { - alert("Please scan operator and machine first!"); + + // 正常 QR 扫描器扫描:格式为 "operatorId: 1" 或 "equipmentId: 1" + const trimmedValue = qrValue.trim(); + + // 检查 operatorId 格式 + const operatorMatch = trimmedValue.match(/^operatorId:\s*(\d+)$/i); + if (operatorMatch) { + const operatorId = parseInt(operatorMatch[1]); + fetchNameList().then((users: NameList[]) => { + const user = users.find((u: NameList) => u.id === operatorId); + if (user) { + setScannedOperatorId(user.id); + } else { + console.warn(`User with ID ${operatorId} not found`); + } + }); + return; + } + + // 检查 equipmentId 格式 + const equipmentMatch = trimmedValue.match(/^equipmentId:\s*(\d+)$/i); + if (equipmentMatch) { + const equipmentId = parseInt(equipmentMatch[1]); + setScannedEquipmentId(equipmentId); return; } + + // 其他格式处理(JSON、普通文本等) + try { + const qrData = JSON.parse(qrValue); + // TODO: 处理 JSON 格式的 QR 码 + } catch { + // 普通文本格式 + // TODO: 处理普通文本格式 + } + }, []); + + // 处理 QR 码扫描效果 + useEffect(() => { + if (isManualScanning && qrValues.length > 0 && scanningLineId) { + const latestQr = qrValues[qrValues.length - 1]; + + if (processedQrCodes.has(latestQr)) { + return; + } + + setProcessedQrCodes(prev => new Set(prev).add(latestQr)); + processQrCode(latestQr, scanningLineId); + } + }, [qrValues, isManualScanning, scanningLineId, processedQrCodes, processQrCode]); + const submitScanAndStart = useCallback(async (lineId: number) => { + console.log("submitScanAndStart called with:", { + lineId, + scannedOperatorId, + scannedEquipmentId, + }); + + if (!scannedOperatorId) { + console.log("No operatorId, cannot submit"); + return false; // 没有 operatorId,不能提交 + } try { - // 直接使用 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, + // 获取 line detail 以检查 bomProcessEquipmentId + const lineDetail = lineDetailForScan || await fetchProductProcessLineDetail(lineId); + + // 提交 operatorId 和 equipmentId + console.log("Submitting scan data:", { + productProcessLineId: lineId, + operatorId: scannedOperatorId, + equipmentId: scannedEquipmentId || undefined, + }); + + const response = await updateProductProcessLineQrscan({ + productProcessLineId: lineId, + operatorId: scannedOperatorId, + equipmentId: scannedEquipmentId || undefined, }); - console.log(" Output data submitted successfully"); - setIsExecutingLine(false); - setSelectedLineId(null); - handleStopScan(); - await fetchProcessDetail(); + console.log("Scan submit response:", response); + + // 检查响应中的 message 字段来判断是否成功 + // 如果后端返回 message 不为 null,说明验证失败 + if (response && response.message) { + //alert(response.message || t("Validation failed. Please check operator and equipment.")); + return false; + } + // 验证通过,继续执行后续步骤 + console.log("Validation passed, starting line..."); + handleStopScan(); + setShowScanDialog(false); + setIsAutoSubmitting(false); + + await handleStartLine(lineId); + setSelectedLineId(lineId); + setIsExecutingLine(true); + await fetchProcessDetail(); + + return true; + } catch (error) { + console.error("Error submitting scan:", error); + //alert(t("Failed to submit scan data. Please try again.")); + setIsAutoSubmitting(false); + return false; + } + }, [scannedOperatorId, scannedEquipmentId, lineDetailForScan, t, fetchProcessDetail]); + const handleSubmitScanAndStart = useCallback(async (lineId: number) => { + console.log("handleSubmitScanAndStart called with lineId:", lineId); + + if (!scannedOperatorId) { + //alert(t("Please scan operator code first")); + return; + } + + // 如果正在自动提交,等待一下 + if (isAutoSubmitting) { + console.log("Already auto-submitting, skipping manual submit"); + return; + } + + await submitScanAndStart(lineId); + }, [scannedOperatorId, isAutoSubmitting, submitScanAndStart, t]); + + + // 开始扫描 + const handleStartScan = useCallback((lineId: number) => { + setScanningLineId(lineId); + setIsManualScanning(true); + setProcessedQrCodes(new Set()); + setScannedOperatorId(null); + setScannedEquipmentId(null); + // 获取 line detail 以获取 bomProcessEquipmentId + fetchProductProcessLineDetail(lineId) + .then(setLineDetailForScan) + .catch(err => console.error("Failed to load line detail", err)); + startScan(); + }, [startScan]); + + // 停止扫描 + const handleStopScan = useCallback(() => { + setIsManualScanning(false); + stopScan(); + resetScan(); + }, [stopScan, resetScan]); + + // 开始执行某个 line(原有逻辑,现在在验证通过后调用) + const handleStartLine = async (lineId: number) => { + try { + await startProductProcessLine(lineId); } catch (error) { - console.error("Error submitting output:", error); - alert("Failed to submit output data. Please try again."); + console.error("Error starting line:", error); + //alert("Failed to start line. Please try again."); } }; + // 提交扫描结果并验证 + + useEffect(() => { + console.log("Auto-submit check:", { + scanningLineId, + scannedOperatorId, + scannedEquipmentId, + isAutoSubmitting, + isManualScanning, + }); + + if ( + scanningLineId && + scannedOperatorId !== null && + scannedEquipmentId !== null && + !isAutoSubmitting && + isManualScanning + ) { + console.log("Auto-submitting triggered!"); + setIsAutoSubmitting(true); + + // 清除之前的定时器(如果有) + if (autoSubmitTimerRef.current) { + clearTimeout(autoSubmitTimerRef.current); + } + + // 延迟一点时间,让用户看到两个都扫描完成了 + autoSubmitTimerRef.current = setTimeout(() => { + console.log("Executing auto-submit..."); + submitScanAndStart(scanningLineId); + autoSubmitTimerRef.current = null; + }, 500); + } + + // 清理函数:只在组件卸载或条件不再满足时清除定时器 + return () => { + // 注意:这里不立即清除定时器,因为我们需要它执行 + // 只在组件卸载时清除 + }; + }, [scanningLineId, scannedOperatorId, scannedEquipmentId, isAutoSubmitting, isManualScanning, submitScanAndStart]); + useEffect(() => { + return () => { + if (autoSubmitTimerRef.current) { + clearTimeout(autoSubmitTimerRef.current); + } + }; + }, []); + + const handleStartLineWithScan = async (lineId: number) => { + setScanningLineId(lineId); + setShowScanDialog(true); + handleStartScan(lineId); + }; const selectedLine = lines.find(l => l.id === selectedLineId); @@ -337,16 +405,30 @@ const ProductionProcessDetail: React.FC = ({ - {t("Process Code")}: {processData?.productProcessCode} + {t("Job Order Code")}: {processData?.jobOrderCode} + + + {t("Is Dark")}: {processData?.isDark} + + + {t("Is Dense")}: {processData?.isDense} + + + {t("Is Float")}: {processData?.isFloat} + {t("Output Qty")}: {processData?.outputQty+" "+"("+processData?.outputQtyUom +")"} + + {t("Status")}:{" "} - + {t("Date")}: {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)} @@ -376,103 +458,151 @@ const ProductionProcessDetail: React.FC = ({ {t("Action")} - - {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' ? ( - - ): - statusLower === 'in_progress' || statusLower === 'in progress' ? ( - - ):( - - )} - - - );})} + + {lines.map((line) => { + const status = (line as any).status || ''; + const statusLower = status.toLowerCase(); + const equipmentName = (line as any).equipment_name || line.equipmentType || "-"; + + const isCompleted = statusLower === 'completed'; + const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress'; + const isPending = statusLower === 'pending' || status === ''; + + return ( + + {line.seqNo} + + {line.name} + + {line.description || "-"} + {equipmentName} + + {isCompleted ? ( + + ) : isInProgress ? ( + + ) : isPending ? ( + + ) : ( + + )} + + + {statusLower === 'pending' ? ( + + ) : statusLower === 'in_progress' || statusLower === 'in progress' ? ( + + ) : ( + + )} + + + ); + })} ) : ( /* ========== 步骤执行视图 ========== */ { - setIsExecutingLine(false); - setSelectedLineId(null); - handleStopScan(); - fetchProcessDetail(); - }} - onSubmitOutput={handleSubmitOutput} - onOutputDataChange={(data) => setOutputData({...outputData, ...data})} - /> + lineId={selectedLineId} + //onClose={() => { + // setIsExecutingLine(false) + // setSelectedLineId(null) + //}} + //onOutputSubmitted={async () => { + // await fetchProcessDetail() + //}} + /> )} + + {/* QR 扫描对话框 */} + { + handleStopScan(); + setShowScanDialog(false); + }} + maxWidth="sm" + fullWidth + > + {t("Scan Operator & Equipment")} + + + + + {scannedOperatorId + ? `${t("Operator")}: ${scannedOperatorId}` + : t("Please scan operator code") + } + + + + + + {scannedEquipmentId + ? `${t("Equipment")}: ${scannedEquipmentId}` + : t("Please scan equipment code (optional if not required)") + } + + + + + + + + + + + ); }; diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx index 455498b..46dbc5c 100644 --- a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx +++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx @@ -1,5 +1,4 @@ "use client"; -import React from "react"; import { Box, Button, @@ -14,255 +13,392 @@ import { TableRow, Card, CardContent, + Grid, } from "@mui/material"; import QrCodeIcon from '@mui/icons-material/QrCode'; 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 { useTranslation } from "react-i18next"; -import { ProductProcessLineResponse } from "@/app/api/jo/actions"; +import { ProductProcessLineDetailResponse, updateProductProcessLineQty,updateProductProcessLineQrscan,fetchProductProcessLineDetail ,UpdateProductProcessLineQtyRequest} from "@/app/api/jo/actions"; import { Operator, Machine } from "@/app/api/jo"; - +import React, { useCallback, useEffect, useState } from "react"; +import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; +import { fetchNameList, NameList } from "@/app/api/user/actions"; 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; + lineId: number | null + //onClose: () => void + // onOutputSubmitted: () => Promise } - const ProductionProcessStepExecution: React.FC = ({ - selectedLine, - scannedOperators, - scannedMachines, - isManualScanning, - outputData, - onStartScan, - onStopScan, - onCancel, - onSubmitOutput, - onOutputDataChange, + lineId, }) => { const { t } = useTranslation(); - const equipmentName = (selectedLine as any)?.equipment_name || selectedLine?.equipmentType || "-"; + const [lineDetail, setLineDetail] = useState(null); + const [outputData, setOutputData] = useState({ + productProcessLineId: lineId ?? 0, + outputFromProcessQty: 0, + outputFromProcessUom: "", + defectQty: 0, + defectUom: "", + scrapQty: 0, + scrapUom: "", + byproductName: "", + byproductQty: 0, + byproductUom: "" + }); + const [isManualScanning, setIsManualScanning] = useState(false); + const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); + const [scannedOperators, setScannedOperators] = useState([]); + const [scannedMachines, setScannedMachines] = useState([]); + const [isPaused, setIsPaused] = useState(false); + const [showOutputTable, setShowOutputTable] = useState(false); + const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); + const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-"; + + // 检查是否两个都已扫描 + const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId; + + + + const handleSubmitOutput = async () => { + if (!lineDetail?.id) return; + + + try { + // 直接使用 actions.ts 中定义的函数 + await updateProductProcessLineQty({ + productProcessLineId: lineDetail?.id || 0 as number, + byproductName: outputData.byproductName, + byproductQty: outputData.byproductQty, + byproductUom: outputData.byproductUom, + outputFromProcessQty: outputData.outputFromProcessQty, + outputFromProcessUom: outputData.outputFromProcessUom, + // outputFromProcessUom: outputData.outputFromProcessUom, + defectQty: outputData.defectQty, + defectUom: outputData.defectUom, + scrapQty: outputData.scrapQty, + scrapUom: outputData.scrapUom, + }); + + console.log(" Output data submitted successfully"); + + + } catch (error) { + console.error("Error submitting output:", error); + alert("Failed to submit output data. Please try again."); + } + }; + + useEffect(() => { + if (!lineId) { + setLineDetail(null); + return; + } + fetchProductProcessLineDetail(lineId) + .then((detail) => { + setLineDetail(detail); + // 初始化 outputData 从 lineDetail + setOutputData(prev => ({ + ...prev, + productProcessLineId: detail.id, + //outputFromProcessQty: detail.outputFromProcessQty || 0, + // outputFromProcessUom: detail.outputFromProcessUom || "", + defectQty: detail.defectQty || 0, + defectUom: detail.defectUom || "", + scrapQty: detail.scrapQty || 0, + scrapUom: detail.scrapUom || "", + byproductName: detail.byproductName || "", + byproductQty: detail.byproductQty || 0, + byproductUom: detail.byproductUom || "" + })); + }) + .catch(err => { + console.error("Failed to load line detail", err); + setLineDetail(null); + }); + }, [lineId]); + + // 处理 QR 码扫描效果 + useEffect(() => { + if (isManualScanning && qrValues.length > 0 && lineDetail?.id) { + const latestQr = qrValues[qrValues.length - 1]; + + if (processedQrCodes.has(latestQr)) { + return; + } + + setProcessedQrCodes(prev => new Set(prev).add(latestQr)); + //processQrCode(latestQr); + } + }, [qrValues, isManualScanning, lineDetail?.id, processedQrCodes]); + + // 开始扫描 + + + const handlePause = () => { + setIsPaused(true); + + }; + + const handleContinue = () => { + setIsPaused(false); + + }; + + const handleStop = () => { + setIsPaused(false); + + // TODO: 调用停止流程的 API + }; return ( {/* 当前步骤信息 */} - - - - {t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo}) - - - {selectedLine?.description} - - - {t("Equipment")}: {equipmentName} - - - - - - {/* 合并的扫描器 */} - - - {t("Scan Operator & Equipment")} - - - - {/* 操作员扫描 */} - + + + + + + {t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo}) + - {scannedOperators.length > 0 - ? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}` - : t("Please scan operator code") - } + {lineDetail?.description} - - - {/* 设备扫描 */} - - {scannedMachines.length > 0 - ? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}` - : t("Please scan equipment code") - } + {t("Operator")}: {lineDetail?.operatorName || "-"} - - - {/* 单个扫描按钮 */} - - - + + {t("Equipment")}: {equipmentName} + + + + {!isPaused ? ( + + ) : ( + + )} + + + + + + + + + + - {/* ========== 产出输入表单 ========== */} - {scannedOperators.length > 0 && scannedMachines.length > 0 && ( - - + {/* ========== 产出输入表单 ========== */} + {bothScanned && ( + + + {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")} + + + + {showOutputTable && ( + +
+ + + {t("Type")} + {t("Quantity")} + {t("Unit")} + + + + {/* start line output */} + + + {t("Output from Process")} + + + setOutputData({ + ...outputData, + outputFromProcessQty: parseInt(e.target.value) || 0 + })} + /> + + + setOutputData({ + ...outputData, + outputFromProcessUom: e.target.value + })} + //placeholder="KG, L, PCS..." + /> + + + {/* byproduct */} + + + + {t("By-product")} + setOutputData({ + ...outputData, + byproductName: e.target.value + })} + placeholder={t("By-product name")} + sx={{ mt: 1 }} + /> + + + + setOutputData({ + ...outputData, + byproductQty: parseInt(e.target.value) || 0 + })} + /> + + onOutputDataChange({ byproductName: e.target.value })} - placeholder={t("By-product name")} - sx={{ mt: 1 }} + value={outputData.byproductUom} + onChange={(e) => setOutputData({ + ...outputData, + byproductUom: e.target.value + })} + //placeholder="KG, L, PCS..." /> - - - - 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..." - /> - - + {/* defect */} + + + {t("Defect")} + + + setOutputData({ + ...outputData, + defectQty: parseInt(e.target.value) || 0 + })} + /> + + + setOutputData({ + ...outputData, + defectUom: e.target.value + })} + //placeholder="KG, L, PCS..." + /> + + - {/* 废品 */} - - - {t("Scrap")} - - - onOutputDataChange({ scrapQty: e.target.value })} - /> - - - onOutputDataChange({ scrapUom: e.target.value })} - placeholder="KG, L, PCS..." - /> - - - -
+ {/* scrap */} + + + {t("Scrap")} + + + setOutputData({ + ...outputData, + scrapQty: parseInt(e.target.value) || 0 + })} + /> + + + setOutputData({ + ...outputData, + scrapUom: e.target.value + })} + //placeholder="KG, L, PCS..." + /> + + +
+ - {/* 提交按钮 */} - - - - - - )} -
+ {/* submit button */} + + + + + + )} + + )} ); };