Browse Source

update

master
CANCERYS\kw093 1 month ago
parent
commit
fb471d7804
7 changed files with 453 additions and 335 deletions
  1. +18
    -5
      src/app/api/jo/actions.ts
  2. +18
    -14
      src/components/DoSearch/DoSearch.tsx
  3. +1
    -1
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  4. +135
    -311
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  5. +2
    -2
      src/components/ProductionProcess/ProductionProcessList.tsx
  6. +9
    -2
      src/components/ProductionProcess/ProductionProcessPage.tsx
  7. +270
    -0
      src/components/ProductionProcess/ProductionProcessStepExecution.tsx

+ 18
- 5
src/app/api/jo/actions.ts View File

@@ -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<ProductProcessLineResponse>(
`${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`,

export const startProductProcessLine = async (lineId: number) => {
return serverFetchJson<any>(
`${BASE_API_URL}/product-process/Demo/ProcessLine/start/${lineId}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
}
);
};
export const completeProductProcessLine = async (lineId: number) => {
return serverFetchJson<any>(
`${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<ProductProcessWithLinesResponse[]>(
`${BASE_API_URL}/product-process/by-job-order/${jobOrderId}`,
`${BASE_API_URL}/product-process/demo/joid/${jobOrderId}`,
{
method: "GET",
next: { tags: ["productProcess"] },


+ 18
- 14
src/components/DoSearch/DoSearch.tsx View File

@@ -147,20 +147,21 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
const searchCriteria: Criterion<SearchParamNames>[] = 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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
headerName: t("Supplier Name"),
flex: 1,
},
{
field: "orderDate",
headerName: t("Order Date"),
@@ -263,7 +265,9 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
? arrayToDateString(params.row.orderDate)
: "N/A";
},
},
{
field: "estimatedArrivalDate",
headerName: t("Estimated Arrival"),
@@ -302,23 +306,23 @@ const DoSearch: React.FC<Props> = ({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 = "";


+ 1
- 1
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -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');
}


+ 135
- 311
src/components/ProductionProcess/ProductionProcessDetail.tsx View File

@@ -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<ProductProcessDetailProps> = ({
processId,
jobOrderId,
onBack,
}) => {
const { t } = useTranslation();
@@ -81,7 +69,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
// 基本信息
const [processData, setProcessData] = useState<any>(null);
const [lines, setLines] = useState<ProcessLine[]>([]);
const [lines, setLines] = useState<ProductProcessLineDetailResponse[]>([]);
const [loading, setLoading] = useState(false);
// 选中的 line 和执行状态
@@ -241,24 +229,34 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
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<ProductProcessDetailProps> = ({

// 开始执行某个 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<ProductProcessDetailProps> = ({
}

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<ProductProcessDetailProps> = ({
<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{lines.map((line) => (
<TableRow key={line.id}>
<TableCell>{line.seqNo}</TableCell>
<TableCell>
<Typography fontWeight={500}>{line.name}</Typography>
</TableCell>
<TableCell>{line.description}</TableCell>
<TableCell>{line.equipmentType}</TableCell>
<TableCell align="center">
{line.endTime ? (
<Chip label={t("Completed")} color="success" size="small" />
) : line.startTime ? (
<Chip label={t("In Progress")} color="primary" size="small" />
) : (
<Chip label={t("Pending")} color="default" size="small" />
)}
</TableCell>
<TableCell align="center">
<Button
variant="contained"
size="small"
startIcon={<PlayArrowIcon />}
onClick={() => handleStartLine(line.id)}
disabled={!!line.endTime}
>
{line.endTime ? t("Completed") : t("Start")}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
) : (
/* ========== 步骤执行视图 ========== */
<Box>
{/* 当前步骤信息 */}
<Card sx={{ mb: 3, bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
{t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo})
</Typography>
<Typography variant="body2" color="text.secondary">
{selectedLine?.description}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {selectedLine?.equipmentType}
</Typography>
</CardContent>
</Card>

<Stack spacing={3}>
{/* 合并的扫描器 */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
{t("Scan Operator & Equipment")}
</Typography>
<Stack spacing={2}>
{/* 操作员扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedOperators.length > 0
? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}`
: t("Please scan operator code")
}
</Typography>
</Box>
{/* 设备扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedMachines.length > 0
? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}`
: t("Please scan equipment code")
}
</Typography>
</Box>
{/* 单个扫描按钮 */}
<TableBody>
{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 (
<TableRow key={line.id}>
<TableCell>{line.seqNo}</TableCell>
<TableCell>
<Typography fontWeight={500}>{line.name}</Typography>
</TableCell>
<TableCell>{line.description || "-"}</TableCell>
<TableCell>{equipmentName}</TableCell>
<TableCell align="center">
{isCompleted ? (
<Chip label={t("Completed")} color="success" size="small" />
) : isInProgress ? (
<Chip label={t("In Progress")} color="primary" size="small" />
) : (
<Chip label={t("Pending")} color="default" size="small" />
)}
</TableCell>
<TableCell align="center">
{statusLower === 'pending' ? (
<Button
variant={isManualScanning ? "outlined" : "contained"}
startIcon={<QrCodeIcon />}
onClick={isManualScanning ? handleStopScan : handleStartScan}
color={isManualScanning ? "secondary" : "primary"}
variant="contained"
size="small"
startIcon={<PlayArrowIcon />}
onClick={async () => {
await handleStartLine(line.id);
setSelectedLineId(line.id);
setIsExecutingLine(true);
await fetchProcessDetail();
}}
>
{isManualScanning ? t("Stop QR Scan") : t("Start QR Scan")}
{t("Start")}
</Button>
</Stack>
</Paper>

{/* ========== 产出输入表单 ========== */}
{scannedOperators.length > 0 && scannedMachines.length > 0 && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Typography variant="h6" gutterBottom fontWeight={600}>
{t("Production Output Data Entry")}
</Typography>

<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit of Measure")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* 步骤收成 */}
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.outputFromProcessQty}
onChange={(e) => setOutputData(prev => ({ ...prev, outputFromProcessQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) => setOutputData(prev => ({ ...prev, outputFromProcessUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 副产品 */}
<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
<TextField
fullWidth
size="small"
value={outputData.byproductName}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductName: e.target.value }))}
placeholder={t("By-product name")}
sx={{ mt: 1 }}
/>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 次品 */}
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.defectQty}
onChange={(e) => setOutputData(prev => ({ ...prev, defectQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) => setOutputData(prev => ({ ...prev, defectUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 废品 */}
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.scrapQty}
onChange={(e) => setOutputData(prev => ({ ...prev, scrapQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) => setOutputData(prev => ({ ...prev, scrapUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>
</TableBody>
</Table>

{/* 提交按钮 */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
):
statusLower === 'in_progress' || statusLower === 'in progress' ? (
<Button
variant="contained"
size="small"
startIcon={<CheckCircleIcon />}
onClick={async () => {
setSelectedLineId(line.id);
setIsExecutingLine(true);
await fetchProcessDetail();
}}
>
{t("View")}
</Button>
):(
<Button
variant="outlined"
onClick={() => {
setIsExecutingLine(false);
setSelectedLineId(null);
handleStopScan();
size="small"
onClick={async() => {
setSelectedLineId(line.id);
setIsExecutingLine(true);
await fetchProcessDetail();
}}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<CheckCircleIcon />}
onClick={handleSubmitOutput}
>
{t("Complete Step")}
{t("View")}
</Button>
</Box>
</Paper>
)}
</Stack>
</Box>
)}
</TableCell>
</TableRow>
);})}
</TableBody>
</Table>
</TableContainer>
) : (
/* ========== 步骤执行视图 ========== */
<ProductionProcessStepExecution
selectedLine={selectedLine}
scannedOperators={scannedOperators}
scannedMachines={scannedMachines}
isManualScanning={isManualScanning}
outputData={outputData}
onStartScan={handleStartScan}
onStopScan={handleStopScan}
onCancel={() => {
setIsExecutingLine(false);
setSelectedLineId(null);
handleStopScan();
fetchProcessDetail();
}}
onSubmitOutput={handleSubmitOutput}
onOutputDataChange={(data) => setOutputData({...outputData, ...data})}
/>
)}
</Paper>
</Box>


+ 2
- 2
src/components/ProductionProcess/ProductionProcessList.tsx View File

@@ -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<ProductProcessListProps> = ({ onSelectProcess
</CardContent>

<CardActions sx={{ pt: 0.5 }}>
<Button variant="contained" size="small" onClick={() => onSelectProcess(process.id)}>
<Button variant="contained" size="small" onClick={() => onSelectProcess(process.jobOrderId, process.id)}>
{t("View Details")}
</Button>
<Box sx={{ flex: 1 }} />


+ 9
- 2
src/components/ProductionProcess/ProductionProcessPage.tsx View File

@@ -51,7 +51,7 @@ const ProductionProcessPage = () => {
if (selectedProcessId !== null) {
return (
<ProductionProcessDetail
processId={selectedProcessId}
jobOrderId={selectedProcessId}
onBack={() => setSelectedProcessId(null)}
/>
);
@@ -59,7 +59,14 @@ const ProductionProcessPage = () => {

return (
<ProductionProcessList
onSelectProcess={(id) => setSelectedProcessId(id)}
onSelectProcess={(jobOrderId, productProcessId) => {
const id = jobOrderId ?? null;
if (id !== null) {
setSelectedProcessId(id);
} else {
}
}}
/>
);
};


+ 270
- 0
src/components/ProductionProcess/ProductionProcessStepExecution.tsx View File

@@ -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<ProductionProcessStepExecutionProps['outputData']>) => void;
}

const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
selectedLine,
scannedOperators,
scannedMachines,
isManualScanning,
outputData,
onStartScan,
onStopScan,
onCancel,
onSubmitOutput,
onOutputDataChange,
}) => {
const { t } = useTranslation();
const equipmentName = (selectedLine as any)?.equipment_name || selectedLine?.equipmentType || "-";

return (
<Box>
{/* 当前步骤信息 */}
<Card sx={{ mb: 3, bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
{t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo})
</Typography>
<Typography variant="body2" color="text.secondary">
{selectedLine?.description}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {equipmentName}
</Typography>
</CardContent>
</Card>

<Stack spacing={3}>
{/* 合并的扫描器 */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
{t("Scan Operator & Equipment")}
</Typography>
<Stack spacing={2}>
{/* 操作员扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedOperators.length > 0
? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}`
: t("Please scan operator code")
}
</Typography>
</Box>
{/* 设备扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedMachines.length > 0
? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}`
: t("Please scan equipment code")
}
</Typography>
</Box>
{/* 单个扫描按钮 */}
<Button
variant={isManualScanning ? "outlined" : "contained"}
startIcon={<QrCodeIcon />}
onClick={isManualScanning ? onStopScan : onStartScan}
color={isManualScanning ? "secondary" : "primary"}
>
{isManualScanning ? t("Stop QR Scan") : t("Start QR Scan")}
</Button>
</Stack>
</Paper>

{/* ========== 产出输入表单 ========== */}
{scannedOperators.length > 0 && scannedMachines.length > 0 && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Typography variant="h6" gutterBottom fontWeight={600}>
{t("Production Output Data Entry")}
</Typography>

<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit of Measure")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* 步骤收成 */}
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.outputFromProcessQty}
onChange={(e) => onOutputDataChange({ outputFromProcessQty: e.target.value })}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) => onOutputDataChange({ outputFromProcessUom: e.target.value })}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 副产品 */}
<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
<TextField
fullWidth
size="small"
value={outputData.byproductName}
onChange={(e) => onOutputDataChange({ byproductName: e.target.value })}
placeholder={t("By-product name")}
sx={{ mt: 1 }}
/>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) => onOutputDataChange({ byproductQty: e.target.value })}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) => onOutputDataChange({ byproductUom: e.target.value })}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 次品 */}
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.defectQty}
onChange={(e) => onOutputDataChange({ defectQty: e.target.value })}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) => onOutputDataChange({ defectUom: e.target.value })}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 废品 */}
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.scrapQty}
onChange={(e) => onOutputDataChange({ scrapQty: e.target.value })}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) => onOutputDataChange({ scrapUom: e.target.value })}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>
</TableBody>
</Table>

{/* 提交按钮 */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
<Button
variant="outlined"
onClick={onCancel}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<CheckCircleIcon />}
onClick={onSubmitOutput}
>
{t("Complete Step")}
</Button>
</Box>
</Paper>
)}
</Stack>
</Box>
);
};

export default ProductionProcessStepExecution;

Loading…
Cancel
Save