浏览代码

update

master
CANCERYS\kw093 3 周前
父节点
当前提交
0e6b857328
共有 9 个文件被更改,包括 628 次插入111 次删除
  1. +5
    -3
      src/app/(main)/production/page.tsx
  2. +6
    -1
      src/app/api/jo/actions.ts
  3. +210
    -0
      src/components/ProductionProcess/ProductionOutputForm.tsx
  4. +146
    -0
      src/components/ProductionProcess/ProductionOutputFormPage.tsx
  5. +28
    -12
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  6. +92
    -60
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  7. +13
    -9
      src/components/ProductionProcess/ProductionProcessList.tsx
  8. +39
    -23
      src/components/ProductionProcess/ProductionProcessStepExecution.tsx
  9. +89
    -3
      src/i18n/zh/common.json

+ 5
- 3
src/app/(main)/production/page.tsx 查看文件

@@ -1,5 +1,5 @@
import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage";
import { getServerI18n } from "../../../i18n";
import { I18nProvider, getServerI18n } from "../../../i18n";

import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
@@ -15,7 +15,7 @@ export const metadata: Metadata = {
};

const production: React.FC = async () => {
const { t } = await getServerI18n("claims");
const { t } = await getServerI18n("common");
const printerCombo = await fetchPrinterCombo();
return (
<>
@@ -38,7 +38,9 @@ const production: React.FC = async () => {
{t("Create Process")}
</Button> */}
</Stack>
<ProductionProcessPage printerCombo={printerCombo} /> {/* Use new component */}
<I18nProvider namespaces={["common", "production","purchaseOrder"]}>
<ProductionProcessPage printerCombo={printerCombo} /> {/* Use new component */}
</I18nProvider>
</>
);
};


+ 6
- 1
src/app/api/jo/actions.ts 查看文件

@@ -263,6 +263,7 @@ export interface AllJoborderProductProcessInfoResponse {
date: string;
bomId?: number;
itemName: string;
requiredQty: number;
jobOrderId: number;
stockInLineId: number;
jobOrderCode: string;
@@ -332,6 +333,7 @@ export interface JobOrderProcessLineDetailResponse {
operatorName: string;
handlerId: number;
seqNo: number;
durationInMinutes: number;
name: string;
description: string;
equipmentId: number;
@@ -360,7 +362,10 @@ export interface JobOrderLineInfo {
stockQty: number,
uom: string,
shortUom: string,
availableStatus: string
availableStatus: string,
bomProcessId: number,
bomProcessSeqNo: number,

}
export interface ProductProcessLineInfoResponse {
id: number,


+ 210
- 0
src/components/ProductionProcess/ProductionOutputForm.tsx 查看文件

@@ -0,0 +1,210 @@
"use client";
import React from "react";
import {
Box,
Button,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
Typography,
} from "@mui/material";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { useTranslation } from "react-i18next";
import { UpdateProductProcessLineQtyRequest } from "@/app/api/jo/actions";

interface ProductionOutputFormProps {
outputData: (UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
byproductUom: string;
});
setOutputData: React.Dispatch<
React.SetStateAction<
UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
byproductUom: string;
}
>
>;
onSubmit: () => Promise<void> | void;
onCancel: () => void;
}

const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
outputData,
setOutputData,
onSubmit,
onCancel,
}) => {
const { t } = useTranslation();

return (
<Paper sx={{ p: 3, bgcolor: "grey.50" }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit")}</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({
...outputData,
outputFromProcessQty: parseInt(e.target.value) || 0,
})
}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) =>
setOutputData({
...outputData,
outputFromProcessUom: e.target.value,
})
}
/>
</TableCell>
</TableRow>

<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) =>
setOutputData({
...outputData,
byproductQty: parseInt(e.target.value) || 0,
})
}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) =>
setOutputData({
...outputData,
byproductUom: e.target.value,
})
}
/>
</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({
...outputData,
defectQty: parseInt(e.target.value) || 0,
})
}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) =>
setOutputData({
...outputData,
defectUom: e.target.value,
})
}
/>
</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({
...outputData,
scrapQty: parseInt(e.target.value) || 0,
})
}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) =>
setOutputData({
...outputData,
scrapUom: e.target.value,
})
}
/>
</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={onSubmit}>
{t("Complete Step")}
</Button>
</Box>
</Paper>
);
};

export default ProductionOutputForm;

+ 146
- 0
src/components/ProductionProcess/ProductionOutputFormPage.tsx 查看文件

@@ -0,0 +1,146 @@
"use client";
import React, { useEffect, useState } from "react";
import {
Box,
Button,
Typography,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import {
fetchProductProcessLineDetail,
updateProductProcessLineQty,
UpdateProductProcessLineQtyRequest,
JobOrderProcessLineDetailResponse,
} from "@/app/api/jo/actions";
import ProductionOutputForm from "./ProductionOutputForm";

interface ProductionOutputFormPageProps {
lineId: number | null;
onBack: () => void;
}

const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
lineId,
onBack,
}) => {
const { t } = useTranslation();
const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
byproductUom: string;
}>({
productProcessLineId: lineId ?? 0,
outputFromProcessQty: 0,
outputFromProcessUom: "",
defectQty: 0,
defectUom: "",
scrapQty: 0,
scrapUom: "",
byproductName: "",
byproductQty: 0,
byproductUom: "",
});

useEffect(() => {
if (!lineId) {
setLineDetail(null);
return;
}
fetchProductProcessLineDetail(lineId)
.then((detail) => {
setLineDetail(detail as any);
setOutputData((prev) => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessUom: (detail as any).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]);

const handleSubmitOutput = async () => {
if (!lineDetail?.id) return;

try {
await updateProductProcessLineQty({
productProcessLineId: lineDetail.id || 0,
byproductName: outputData.byproductName,
byproductQty: outputData.byproductQty,
byproductUom: outputData.byproductUom,
outputFromProcessQty: outputData.outputFromProcessQty,
outputFromProcessUom: outputData.outputFromProcessUom,
defectQty: outputData.defectQty,
defectUom: outputData.defectUom,
scrapQty: outputData.scrapQty,
scrapUom: outputData.scrapUom,
});

console.log("Output data submitted successfully");

// 重新加载数据
const detail = await fetchProductProcessLineDetail(lineDetail.id);
setLineDetail(detail as any);
setOutputData((prev) => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessUom: (detail as any).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 || "",
}));

// 提交成功后返回
onBack();
} catch (error) {
console.error("Error submitting output:", error);
alert("Failed to submit output data. Please try again.");
}
};

return (
<Box>
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
</Button>
</Box>

<Box sx={{ mb: 3 }}>
<Typography variant="h5" gutterBottom fontWeight="bold">
{t("Production Output Data Entry")}
</Typography>
{lineDetail && (
<Typography variant="body2" color="text.secondary">
{t("Step")}: {lineDetail.name} (Seq: {lineDetail.seqNo})
</Typography>
)}
</Box>

<ProductionOutputForm
outputData={outputData}
setOutputData={setOutputData}
onSubmit={handleSubmitOutput}
onCancel={onBack}
/>
</Box>
);
};

export default ProductionOutputFormPage;

+ 28
- 12
src/components/ProductionProcess/ProductionProcessDetail.tsx 查看文件

@@ -48,7 +48,7 @@ import {
} from "@/app/api/jo/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import ProductionProcessStepExecution from "./ProductionProcessStepExecution";
import ProductionOutputFormPage from "./ProductionOutputFormPage";

interface ProductProcessDetailProps {
jobOrderId: number;
@@ -63,7 +63,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [showOutputPage, setShowOutputPage] = useState(false);
// 基本信息
const [processData, setProcessData] = useState<any>(null);
const [lines, setLines] = useState<ProductProcessLineInfoResponse[]>([]);
@@ -102,6 +102,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
await fetchProcessDetail(); // 重新拉取最新的 process/lines
setIsExecutingLine(false);
setSelectedLineId(null);
setShowOutputPage(false);
};

// 获取 process 和 lines 数据
@@ -486,11 +487,12 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
<TableCell>{t("Seq")}</TableCell>
<TableCell>{t("Step Name")}</TableCell>
<TableCell>{t("Description")}</TableCell>
<TableCell>{t("Equipment Type/Code")}</TableCell>
<TableCell>{t("Operator")}</TableCell>
<TableCell>{t("Equipment Type")}</TableCell>
<TableCell>{t("Duration")}</TableCell>
<TableCell>{t("Prep Time")}</TableCell>
<TableCell>{t("Post Prod Time")}</TableCell>
<TableCell>{t("Processing Time (mins)")}</TableCell>
<TableCell>{t("Setup Time (mins)")}</TableCell>
<TableCell>{t("Changeover Time (mins)")}</TableCell>
<TableCell align="center">{t("Status")}</TableCell>
<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
@@ -512,16 +514,30 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
<Typography fontWeight={500}>{line.name}</Typography>
</TableCell>
<TableCell><Typography fontWeight={500}>{line.description || "-"}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.operatorName}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{equipmentName}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.durationInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.prepTimeInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.postProdTimeInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.operatorName}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.durationInMinutes} </Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.prepTimeInMinutes} </Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.postProdTimeInMinutes} </Typography></TableCell>
<TableCell align="center">
{isCompleted ? (
<Chip label={t("Completed")} color="success" size="small" />
<Chip label={t("Completed")} color="success" size="small"
onClick={async () => {
setSelectedLineId(line.id);
setShowOutputPage(false); // 不显示输出页面
setIsExecutingLine(true);
await fetchProcessDetail();
}}
/>
) : isInProgress ? (
<Chip label={t("In Progress")} color="primary" size="small" />
<Chip label={t("In Progress")} color="primary" size="small"
onClick={async () => {
setSelectedLineId(line.id);
setShowOutputPage(false); // 不显示输出页面
setIsExecutingLine(true);
await fetchProcessDetail();
}} />
) : isPending ? (
<Chip label={t("Pending")} color="default" size="small" />
) : (


+ 92
- 60
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx 查看文件

@@ -1,5 +1,5 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useState, useMemo } from "react";
import {
Box,
Button,
@@ -99,7 +99,36 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
useEffect(() => {
fetchData();
}, [fetchData]);
// PickTable 组件内容
const getStockAvailable = (line: JobOrderLine) => {
const inventory = inventoryData.find(inv =>
inv.itemCode === line.itemCode || inv.itemName === line.itemName
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return line.stockQty || 0;
};

const isStockSufficient = (line: JobOrderLine) => {
const stockAvailable = getStockAvailable(line);
return stockAvailable >= line.reqQty;
};
const stockCounts = useMemo(() => {
const total = jobOrderLines.length;
const sufficient = jobOrderLines.filter(isStockSufficient).length;
return {
total,
sufficient,
insufficient: total - sufficient,
};
}, [jobOrderLines, inventoryData]);
const status = processData?.status?.toLowerCase?.() ?? "";

const handleRelease = useCallback(() => {
// TODO: 替换为实际的 release 调用
console.log("Release clicked for jobOrderId:", jobOrderId);
}, [jobOrderId]);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
@@ -139,6 +168,7 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
);
}
// InfoCard 组件内容
const InfoCardContent = () => (
<Card sx={{ display: "block", mt: 2 }}>
@@ -153,21 +183,13 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
value={processData?.jobOrderCode || ""}
/>
</Grid>
<Grid item xs={6}/>
<Grid item xs={6}>
<TextField
label={t("Item Code")}
fullWidth
disabled={true}
value={processData?.itemCode || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Item Name")}
fullWidth
disabled={true}
value={processData?.itemName || ""}
value={processData?.itemCode+"-"+processData?.itemName || ""}
/>
</Grid>
<Grid item xs={6}>
@@ -175,17 +197,10 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
label={t("Req. Qty")}
fullWidth
disabled={true}
value={processData?.outputQty ? integerFormatter.format(processData.outputQty) : ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("UoM")}
fullWidth
disabled={true}
value={processData?.outputQtyUom || ""}
value={processData?.outputQty + "(" + processData?.outputQtyUom + ")" || ""}
/>
</Grid>

<Grid item xs={6}>
<TextField
value={processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""}
@@ -204,49 +219,19 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Dark")}
label={t("Is Dark | Dense | Float")}
fullWidth
disabled={true}
value={processData?.isDark || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Dense")}
fullWidth
disabled={true}
value={processData?.isDense || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Float")}
fullWidth
disabled={true}
value={processData?.isFloat || ""}
value={processData?.isDark + " | " + processData?.isDense + " | " + processData?.isFloat || ""}
/>
</Grid>

</Grid>
</Box>
</CardContent>
</Card>
);

// PickTable 组件内容
const getStockAvailable = (line: JobOrderLine) => {
const inventory = inventoryData.find(inv =>
inv.itemCode === line.itemCode || inv.itemName === line.itemName
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return line.stockQty || 0;
};

const isStockSufficient = (line: JobOrderLine) => {
const stockAvailable = getStockAvailable(line);
return stockAvailable >= line.reqQty;
};
const productionProcessesLineRemarkTableColumns: GridColDef[] = [
{
field: "seqNo",
@@ -321,11 +306,28 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
},
},
{
field: "stockStatus",
headerName: t("Stock Status"),
field: "bomProcessSeqNo",
headerName: t("Seq No"),
flex: 0.5,
align: "right",
headerAlign: "right",
type: "number",
},

{
field: "seqNoRemark",
headerName: t("Seq No Remark"),
flex: 1,
align: "left",
headerAlign: "left",
type: "string",
},
{
field: "stockStatus",
headerName: t("Stock Status"),
flex: 0.5,
align: "center",
headerAlign: "center",
type: "boolean",
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
return isStockSufficient(params.row)
@@ -342,14 +344,44 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp

const PickTableContent = () => (
<Box sx={{ mt: 2 }}>
<Card sx={{ mb: 2 }}>
<CardContent>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
spacing={2}
>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Total lines: ")}<strong>{stockCounts.total}</strong>
</Typography>

<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Lines with sufficient stock: ")}<strong style={{ color: "green" }}>{stockCounts.sufficient}</strong>
</Typography>

<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Lines with insufficient stock: ")}<strong style={{ color: "red" }}>{stockCounts.insufficient}</strong>
</Typography>

<Button
variant="contained"
color="primary"
onClick={handleRelease}
disabled={stockCounts.insufficient > 0 || status !== "planning"}
>
{t("Release")}
</Button>
</Stack>
</CardContent>
</Card>

<StyledDataGrid
sx={{
"--DataGrid-overlayHeight": "100px",
}}
sx={{ "--DataGrid-overlayHeight": "100px" }}
disableColumnMenu
rows={pickTableRows}
columns={pickTableColumns}
getRowHeight={() => 'auto'}
getRowHeight={() => "auto"}
/>
</Box>
);
@@ -381,7 +413,7 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
<Box sx={{ borderBottom: '1px solid #e0e0e0' }}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Job Order Info")} />
<Tab label={t("Job Order Lines")} />
<Tab label={t("BoM Material")} />
<Tab label={t("Production Process")} />
<Tab label={t("Production Process Line Remark")} />
<Tab label={t("Matching Stock")} />


+ 13
- 9
src/components/ProductionProcess/ProductionProcessList.tsx 查看文件

@@ -34,7 +34,7 @@ interface ProductProcessListProps {
const PER_PAGE = 6;

const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo }) => {
const { t } = useTranslation();
const { t } = useTranslation( ["common", "production","purchaseOrder"]);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const sessionToken = session as SessionWithTokens | null;
const [loading, setLoading] = useState(false);
@@ -144,16 +144,22 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Box sx={{ minWidth: 0 }}>
<Typography variant="subtitle1">
{process.productProcessCode}
{t("Job Order")}: {jobOrderCode}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Job Order")}: {jobOrderCode}
</Typography>

</Box>
<Chip size="small" label={t(status)} color={statusColor as any} />
</Stack>

<Typography variant="body2" color="text.secondary">
{t("Item Name")}: {process.itemName}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Required Qty")}: {process.requiredQty}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Production date")}: {process.date ? dayjs(process.date as any).format(OUTPUT_DATE_FORMAT) : "-"}
</Typography>
{statusLower !== "pending" && linesWithStatus.length > 0 && (
<Box sx={{ mt: 1 }}>
<Typography variant="body2" fontWeight={600}>
@@ -184,9 +190,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
</Button>
)}
<Box sx={{ flex: 1 }} />
<Typography variant="caption" color="text.secondary">
{t("Lines")}: {totalCount}
</Typography>
</CardActions>
</Card>
</Grid>


+ 39
- 23
src/components/ProductionProcess/ProductionProcessStepExecution.tsx 查看文件

@@ -63,7 +63,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
const [showOutputTable, setShowOutputTable] = useState(false);
const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-";
const [remainingTime, setRemainingTime] = useState<string | null>(null);
// 检查是否两个都已扫描
//const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId;
@@ -98,6 +98,27 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
setLineDetail(null);
});
}, [lineId]);
useEffect(() => {
if (!lineDetail?.durationInMinutes || !lineDetail?.startTime) {
setRemainingTime(null);
return;
}
const start = new Date(lineDetail.startTime as any);
const end = new Date(start.getTime() + lineDetail.durationInMinutes * 60_000);
const update = () => {
const diff = end.getTime() - Date.now();
if (diff <= 0) {
setRemainingTime("00:00");
return;
}
const minutes = Math.floor(diff / 60000).toString().padStart(2, "0");
const seconds = Math.floor((diff % 60000) / 1000).toString().padStart(2, "0");
setRemainingTime(`${minutes}:${seconds}`);
};
update();
const timer = setInterval(update, 1000);
return () => clearInterval(timer);
}, [lineDetail?.durationInMinutes, lineDetail?.startTime]);
const handleSubmitOutput = async () => {
if (!lineDetail?.id) return;

@@ -255,7 +276,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
</TableRow>
{/* By-product */}
{lineDetail?.byproductQty && lineDetail.byproductQty > 0 && (
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("By-product")}</Typography>
@@ -272,10 +293,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
<Typography>{lineDetail.byproductUom || "-"}</Typography>
</TableCell>
</TableRow>
)}

{/* Defect */}
{lineDetail?.defectQty && lineDetail.defectQty > 0 && (
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
@@ -287,10 +307,8 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
<Typography>{lineDetail.defectUom || "-"}</Typography>
</TableCell>
</TableRow>
)}

{/* Scrap */}
{lineDetail?.scrapQty && lineDetail.scrapQty > 0 && (
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
@@ -302,7 +320,6 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
<Typography>{lineDetail.scrapUom || "-"}</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
@@ -311,8 +328,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
<>
{/* 如果未完成,显示原来的两个部分 */}
{/* 当前步骤信息 */}
{!showOutputTable && (
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Grid item xs={12} >
<Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
@@ -327,6 +345,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {equipmentName}
</Typography>

<Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}>
<Button
variant="contained"
@@ -355,27 +374,24 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
{t("Continue")}
</Button>
)}
<Button
sx={{ mt: 2, alignSelf: "flex-end" }}
variant="outlined"
onClick={() => setShowOutputTable(true)}
>
{t("Order Complete")}
</Button>
</Stack>
</CardContent>
</Card>
</Grid>
</Grid>

)}
{/* ========== 产出输入表单 ========== */}
{showOutputTable && (
<Box>
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" fontWeight={600}>
{t("Production Output Data Entry")}
</Typography>
<Button
variant="outlined"
onClick={() => setShowOutputTable(!showOutputTable)}
>
{showOutputTable ? t("Hide Table") : t("Show Table")}
</Button>
</Box>
{showOutputTable && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Table size="small">
<TableHead>
@@ -526,8 +542,8 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
</Button>
</Box>
</Paper>
)}
</Box>
</Box>
)}
</>
)}
</Box>


+ 89
- 3
src/i18n/zh/common.json 查看文件

@@ -16,7 +16,6 @@
"Add Record": "新增",
"Clean Record": "重置",
"Dashboard": "資訊展示面板",
"Store Management": "原材料",
"Stock Take Management": "盤點管理",
"Store Management": "倉庫管理",
"Delivery": "送貨訂單",
@@ -27,7 +26,7 @@
"User Group": "用戶群組",
"Items": "物料",
"Demand Forecast Setting": "需求預測設定",
"Equipment Type": "設備類型",
"Equipment Type/Code": "使用設備-編號",
"Equipment": "設備",
"Warehouse": "倉庫",
"Supplier": "供應商",
@@ -107,6 +106,93 @@
"Row per page": "每頁行數",
"No data available": "沒有資料",
"jodetail": "工單細節",
"Sign out": "登出"
"Sign out": "登出",


"By-product": "副產品",
"Complete Step": "完成步驟",
"Defect": "缺陷",
"Output from Process": "流程輸出",
"Quantity": "數量",
"Scrap": "廢料",
"Unit": "單位",
"Back to List": "返回列表",
"Production Output Data Entry": "生產輸出數據輸入",
"Step": "步驟",
"Quality Check": "品質檢查",
"Action": "操作",
"Changeover Time (mins)": "生產後轉換時間(分鐘)",
"Completed": "完成",
"completed": "完成",
"Date": "日期",
"Failed to submit scan data. Please try again.": "掃碼數據提交失敗. 請重試.",
"In Progress": "進行中",
"Is Dark": "是否黑暗",
"Is Dense": "是否密集",
"Is Float": "是否浮動",
"Job Order Code": "工單編號",
"Operator": "操作員",
"Output Qty": "輸出數量",
"Pending": "待處理",
"pending": "待處理",

"Please scan equipment code (optional if not required)": "請掃描設備編號(可選)",
"Please scan operator code": "請掃描操作員編號",
"Please scan operator code first": "請先掃描操作員編號",
"Processing Time (mins)": "步驟時間(分鐘)",
"Production Process Information": "生產流程信息",
"Production Process Steps": "生產流程步驟",
"Scan Operator & Equipment": "掃描操作員和設備",
"Seq": "序號",
"Setup Time (mins)": "生產前預備時間(分鐘)",
"Start": "開始",
"Start QR Scan": "開始掃碼",
"Status": "狀態",
"in_progress": "進行中",
"In_Progress": "進行中",
"inProgress": "進行中",
"Step Name": "步驟名稱",
"Stop QR Scan": "停止掃碼",
"Submit & Start": "提交並開始",
"Total Steps": "總步驟數",
"Unknown": "",
"Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.",
"View": "查看",
"Back": "返回",
"BoM Material": "物料清單",
"Is Dark | Dense | Float": "是否黑暗 | 密集 | 浮動",
"Item Code": "物料編號",
"Item Name": "物料名稱",
"Job Order Info": "工單信息",
"Matching Stock": "匹配庫存",
"No data found": "沒有找到資料",
"Production Priority": "生產優先級",
"Production Process": "工藝流程",
"Production Process Line Remark": "工藝明細",
"Remark": "明細",
"Req. Qty": "需求數量",
"Seq No": "序號",
"Seq No Remark": "序號明細",
"Stock Available": "庫存可用",
"Stock Status": "庫存狀態",
"Target Production Date": "目標生產日期",
"id": "ID",
"Finished lines": "完成行",
"Invalid Stock In Line Id": "無效庫存行ID",
"Production date": "生產日期",
"Required Qty": "需求數量",
"Total processes": "總流程數",
"View Details": "查看詳情",
"view stockin": "查看入庫",
"Completed Step": "完成步驟",
"Continue": "繼續",
"Executing": "執行中",
"Order Complete": "訂單完成",
"Pause": "暫停",
"Production Output Data": "生產輸出數據",
"Step Information": "步驟信息",
"Stop": "停止",
"Putaway Detail": "上架詳情"

}

正在加载...
取消
保存