| @@ -1,5 +1,5 @@ | |||||
| import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage"; | import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage"; | ||||
| import { getServerI18n } from "../../../i18n"; | |||||
| import { I18nProvider, getServerI18n } from "../../../i18n"; | |||||
| import Add from "@mui/icons-material/Add"; | import Add from "@mui/icons-material/Add"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| @@ -15,7 +15,7 @@ export const metadata: Metadata = { | |||||
| }; | }; | ||||
| const production: React.FC = async () => { | const production: React.FC = async () => { | ||||
| const { t } = await getServerI18n("claims"); | |||||
| const { t } = await getServerI18n("common"); | |||||
| const printerCombo = await fetchPrinterCombo(); | const printerCombo = await fetchPrinterCombo(); | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -38,7 +38,9 @@ const production: React.FC = async () => { | |||||
| {t("Create Process")} | {t("Create Process")} | ||||
| </Button> */} | </Button> */} | ||||
| </Stack> | </Stack> | ||||
| <ProductionProcessPage printerCombo={printerCombo} /> {/* Use new component */} | |||||
| <I18nProvider namespaces={["common", "production","purchaseOrder"]}> | |||||
| <ProductionProcessPage printerCombo={printerCombo} /> {/* Use new component */} | |||||
| </I18nProvider> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -263,6 +263,7 @@ export interface AllJoborderProductProcessInfoResponse { | |||||
| date: string; | date: string; | ||||
| bomId?: number; | bomId?: number; | ||||
| itemName: string; | itemName: string; | ||||
| requiredQty: number; | |||||
| jobOrderId: number; | jobOrderId: number; | ||||
| stockInLineId: number; | stockInLineId: number; | ||||
| jobOrderCode: string; | jobOrderCode: string; | ||||
| @@ -332,6 +333,7 @@ export interface JobOrderProcessLineDetailResponse { | |||||
| operatorName: string; | operatorName: string; | ||||
| handlerId: number; | handlerId: number; | ||||
| seqNo: number; | seqNo: number; | ||||
| durationInMinutes: number; | |||||
| name: string; | name: string; | ||||
| description: string; | description: string; | ||||
| equipmentId: number; | equipmentId: number; | ||||
| @@ -360,7 +362,10 @@ export interface JobOrderLineInfo { | |||||
| stockQty: number, | stockQty: number, | ||||
| uom: string, | uom: string, | ||||
| shortUom: string, | shortUom: string, | ||||
| availableStatus: string | |||||
| availableStatus: string, | |||||
| bomProcessId: number, | |||||
| bomProcessSeqNo: number, | |||||
| } | } | ||||
| export interface ProductProcessLineInfoResponse { | export interface ProductProcessLineInfoResponse { | ||||
| id: number, | id: number, | ||||
| @@ -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; | |||||
| @@ -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; | |||||
| @@ -48,7 +48,7 @@ import { | |||||
| } from "@/app/api/jo/actions"; | } from "@/app/api/jo/actions"; | ||||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | import { fetchNameList, NameList } from "@/app/api/user/actions"; | ||||
| import ProductionProcessStepExecution from "./ProductionProcessStepExecution"; | import ProductionProcessStepExecution from "./ProductionProcessStepExecution"; | ||||
| import ProductionOutputFormPage from "./ProductionOutputFormPage"; | |||||
| interface ProductProcessDetailProps { | interface ProductProcessDetailProps { | ||||
| jobOrderId: number; | jobOrderId: number; | ||||
| @@ -63,7 +63,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ | |||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const [showOutputPage, setShowOutputPage] = useState(false); | |||||
| // 基本信息 | // 基本信息 | ||||
| const [processData, setProcessData] = useState<any>(null); | const [processData, setProcessData] = useState<any>(null); | ||||
| const [lines, setLines] = useState<ProductProcessLineInfoResponse[]>([]); | const [lines, setLines] = useState<ProductProcessLineInfoResponse[]>([]); | ||||
| @@ -102,6 +102,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ | |||||
| await fetchProcessDetail(); // 重新拉取最新的 process/lines | await fetchProcessDetail(); // 重新拉取最新的 process/lines | ||||
| setIsExecutingLine(false); | setIsExecutingLine(false); | ||||
| setSelectedLineId(null); | setSelectedLineId(null); | ||||
| setShowOutputPage(false); | |||||
| }; | }; | ||||
| // 获取 process 和 lines 数据 | // 获取 process 和 lines 数据 | ||||
| @@ -486,11 +487,12 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ | |||||
| <TableCell>{t("Seq")}</TableCell> | <TableCell>{t("Seq")}</TableCell> | ||||
| <TableCell>{t("Step Name")}</TableCell> | <TableCell>{t("Step Name")}</TableCell> | ||||
| <TableCell>{t("Description")}</TableCell> | <TableCell>{t("Description")}</TableCell> | ||||
| <TableCell>{t("Equipment Type/Code")}</TableCell> | |||||
| <TableCell>{t("Operator")}</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("Status")}</TableCell> | ||||
| <TableCell align="center">{t("Action")}</TableCell> | <TableCell align="center">{t("Action")}</TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| @@ -512,16 +514,30 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ | |||||
| <Typography fontWeight={500}>{line.name}</Typography> | <Typography fontWeight={500}>{line.name}</Typography> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell><Typography fontWeight={500}>{line.description || "-"}</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}>{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"> | <TableCell align="center"> | ||||
| {isCompleted ? ( | {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 ? ( | ) : 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 ? ( | ) : isPending ? ( | ||||
| <Chip label={t("Pending")} color="default" size="small" /> | <Chip label={t("Pending")} color="default" size="small" /> | ||||
| ) : ( | ) : ( | ||||
| @@ -1,5 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import React, { useCallback, useEffect, useState } from "react"; | |||||
| import React, { useCallback, useEffect, useState, useMemo } from "react"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -99,7 +99,36 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchData(); | fetchData(); | ||||
| }, [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"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
| (_e, newValue) => { | (_e, newValue) => { | ||||
| setTabIndex(newValue); | setTabIndex(newValue); | ||||
| @@ -139,6 +168,7 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| ); | ); | ||||
| } | } | ||||
| // InfoCard 组件内容 | // InfoCard 组件内容 | ||||
| const InfoCardContent = () => ( | const InfoCardContent = () => ( | ||||
| <Card sx={{ display: "block", mt: 2 }}> | <Card sx={{ display: "block", mt: 2 }}> | ||||
| @@ -153,21 +183,13 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| value={processData?.jobOrderCode || ""} | value={processData?.jobOrderCode || ""} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}/> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Item Code")} | label={t("Item Code")} | ||||
| fullWidth | fullWidth | ||||
| disabled={true} | 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> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| @@ -175,17 +197,10 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| label={t("Req. Qty")} | label={t("Req. Qty")} | ||||
| fullWidth | fullWidth | ||||
| disabled={true} | 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> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| value={processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""} | value={processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""} | ||||
| @@ -204,49 +219,19 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Is Dark")} | |||||
| label={t("Is Dark | Dense | Float")} | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | 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> | ||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </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[] = [ | const productionProcessesLineRemarkTableColumns: GridColDef[] = [ | ||||
| { | { | ||||
| field: "seqNo", | 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, | flex: 0.5, | ||||
| align: "right", | align: "right", | ||||
| headerAlign: "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", | type: "boolean", | ||||
| renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | ||||
| return isStockSufficient(params.row) | return isStockSufficient(params.row) | ||||
| @@ -342,14 +344,44 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| const PickTableContent = () => ( | const PickTableContent = () => ( | ||||
| <Box sx={{ mt: 2 }}> | <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 | <StyledDataGrid | ||||
| sx={{ | |||||
| "--DataGrid-overlayHeight": "100px", | |||||
| }} | |||||
| sx={{ "--DataGrid-overlayHeight": "100px" }} | |||||
| disableColumnMenu | disableColumnMenu | ||||
| rows={pickTableRows} | rows={pickTableRows} | ||||
| columns={pickTableColumns} | columns={pickTableColumns} | ||||
| getRowHeight={() => 'auto'} | |||||
| getRowHeight={() => "auto"} | |||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -381,7 +413,7 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| <Box sx={{ borderBottom: '1px solid #e0e0e0' }}> | <Box sx={{ borderBottom: '1px solid #e0e0e0' }}> | ||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("Job Order Info")} /> | <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")} /> | ||||
| <Tab label={t("Production Process Line Remark")} /> | <Tab label={t("Production Process Line Remark")} /> | ||||
| <Tab label={t("Matching Stock")} /> | <Tab label={t("Matching Stock")} /> | ||||
| @@ -34,7 +34,7 @@ interface ProductProcessListProps { | |||||
| const PER_PAGE = 6; | const PER_PAGE = 6; | ||||
| const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo }) => { | 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 { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const sessionToken = session as SessionWithTokens | null; | const sessionToken = session as SessionWithTokens | null; | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| @@ -144,16 +144,22 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess | |||||
| <Stack direction="row" justifyContent="space-between" alignItems="center"> | <Stack direction="row" justifyContent="space-between" alignItems="center"> | ||||
| <Box sx={{ minWidth: 0 }}> | <Box sx={{ minWidth: 0 }}> | ||||
| <Typography variant="subtitle1"> | <Typography variant="subtitle1"> | ||||
| {process.productProcessCode} | |||||
| {t("Job Order")}: {jobOrderCode} | |||||
| </Typography> | </Typography> | ||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("Job Order")}: {jobOrderCode} | |||||
| </Typography> | |||||
| </Box> | </Box> | ||||
| <Chip size="small" label={t(status)} color={statusColor as any} /> | <Chip size="small" label={t(status)} color={statusColor as any} /> | ||||
| </Stack> | </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 && ( | {statusLower !== "pending" && linesWithStatus.length > 0 && ( | ||||
| <Box sx={{ mt: 1 }}> | <Box sx={{ mt: 1 }}> | ||||
| <Typography variant="body2" fontWeight={600}> | <Typography variant="body2" fontWeight={600}> | ||||
| @@ -184,9 +190,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Box sx={{ flex: 1 }} /> | <Box sx={{ flex: 1 }} /> | ||||
| <Typography variant="caption" color="text.secondary"> | |||||
| {t("Lines")}: {totalCount} | |||||
| </Typography> | |||||
| </CardActions> | </CardActions> | ||||
| </Card> | </Card> | ||||
| </Grid> | </Grid> | ||||
| @@ -63,7 +63,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| const [showOutputTable, setShowOutputTable] = useState(false); | const [showOutputTable, setShowOutputTable] = useState(false); | ||||
| const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-"; | const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-"; | ||||
| const [remainingTime, setRemainingTime] = useState<string | null>(null); | |||||
| // 检查是否两个都已扫描 | // 检查是否两个都已扫描 | ||||
| //const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId; | //const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId; | ||||
| @@ -98,6 +98,27 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| setLineDetail(null); | setLineDetail(null); | ||||
| }); | }); | ||||
| }, [lineId]); | }, [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 () => { | const handleSubmitOutput = async () => { | ||||
| if (!lineDetail?.id) return; | if (!lineDetail?.id) return; | ||||
| @@ -255,7 +276,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| </TableRow> | </TableRow> | ||||
| {/* By-product */} | {/* By-product */} | ||||
| {lineDetail?.byproductQty && lineDetail.byproductQty > 0 && ( | |||||
| <TableRow> | <TableRow> | ||||
| <TableCell> | <TableCell> | ||||
| <Typography fontWeight={500}>{t("By-product")}</Typography> | <Typography fontWeight={500}>{t("By-product")}</Typography> | ||||
| @@ -272,10 +293,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| <Typography>{lineDetail.byproductUom || "-"}</Typography> | <Typography>{lineDetail.byproductUom || "-"}</Typography> | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| )} | |||||
| {/* Defect */} | {/* Defect */} | ||||
| {lineDetail?.defectQty && lineDetail.defectQty > 0 && ( | |||||
| <TableRow sx={{ bgcolor: 'warning.50' }}> | <TableRow sx={{ bgcolor: 'warning.50' }}> | ||||
| <TableCell> | <TableCell> | ||||
| <Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography> | <Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography> | ||||
| @@ -287,10 +307,8 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| <Typography>{lineDetail.defectUom || "-"}</Typography> | <Typography>{lineDetail.defectUom || "-"}</Typography> | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| )} | |||||
| {/* Scrap */} | {/* Scrap */} | ||||
| {lineDetail?.scrapQty && lineDetail.scrapQty > 0 && ( | |||||
| <TableRow sx={{ bgcolor: 'error.50' }}> | <TableRow sx={{ bgcolor: 'error.50' }}> | ||||
| <TableCell> | <TableCell> | ||||
| <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography> | <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography> | ||||
| @@ -302,7 +320,6 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| <Typography>{lineDetail.scrapUom || "-"}</Typography> | <Typography>{lineDetail.scrapUom || "-"}</Typography> | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| )} | |||||
| </TableBody> | </TableBody> | ||||
| </Table> | </Table> | ||||
| </CardContent> | </CardContent> | ||||
| @@ -311,8 +328,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| <> | <> | ||||
| {/* 如果未完成,显示原来的两个部分 */} | {/* 如果未完成,显示原来的两个部分 */} | ||||
| {/* 当前步骤信息 */} | {/* 当前步骤信息 */} | ||||
| {!showOutputTable && ( | |||||
| <Grid container spacing={2} sx={{ mb: 3 }}> | <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%' }}> | <Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}> | ||||
| <CardContent> | <CardContent> | ||||
| <Typography variant="h6" color="primary.main" gutterBottom> | <Typography variant="h6" color="primary.main" gutterBottom> | ||||
| @@ -327,6 +345,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| <Typography variant="body2" color="text.secondary"> | <Typography variant="body2" color="text.secondary"> | ||||
| {t("Equipment")}: {equipmentName} | {t("Equipment")}: {equipmentName} | ||||
| </Typography> | </Typography> | ||||
| <Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}> | <Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| @@ -355,27 +374,24 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| {t("Continue")} | {t("Continue")} | ||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | |||||
| sx={{ mt: 2, alignSelf: "flex-end" }} | |||||
| variant="outlined" | |||||
| onClick={() => setShowOutputTable(true)} | |||||
| > | |||||
| {t("Order Complete")} | |||||
| </Button> | |||||
| </Stack> | </Stack> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| )} | |||||
| {/* ========== 产出输入表单 ========== */} | {/* ========== 产出输入表单 ========== */} | ||||
| {showOutputTable && ( | |||||
| <Box> | <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' }}> | <Paper sx={{ p: 3, bgcolor: 'grey.50' }}> | ||||
| <Table size="small"> | <Table size="small"> | ||||
| <TableHead> | <TableHead> | ||||
| @@ -526,8 +542,8 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| </Paper> | </Paper> | ||||
| )} | |||||
| </Box> | |||||
| </Box> | |||||
| )} | |||||
| </> | </> | ||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| @@ -16,7 +16,6 @@ | |||||
| "Add Record": "新增", | "Add Record": "新增", | ||||
| "Clean Record": "重置", | "Clean Record": "重置", | ||||
| "Dashboard": "資訊展示面板", | "Dashboard": "資訊展示面板", | ||||
| "Store Management": "原材料", | |||||
| "Stock Take Management": "盤點管理", | "Stock Take Management": "盤點管理", | ||||
| "Store Management": "倉庫管理", | "Store Management": "倉庫管理", | ||||
| "Delivery": "送貨訂單", | "Delivery": "送貨訂單", | ||||
| @@ -27,7 +26,7 @@ | |||||
| "User Group": "用戶群組", | "User Group": "用戶群組", | ||||
| "Items": "物料", | "Items": "物料", | ||||
| "Demand Forecast Setting": "需求預測設定", | "Demand Forecast Setting": "需求預測設定", | ||||
| "Equipment Type": "設備類型", | |||||
| "Equipment Type/Code": "使用設備-編號", | |||||
| "Equipment": "設備", | "Equipment": "設備", | ||||
| "Warehouse": "倉庫", | "Warehouse": "倉庫", | ||||
| "Supplier": "供應商", | "Supplier": "供應商", | ||||
| @@ -107,6 +106,93 @@ | |||||
| "Row per page": "每頁行數", | "Row per page": "每頁行數", | ||||
| "No data available": "沒有資料", | "No data available": "沒有資料", | ||||
| "jodetail": "工單細節", | "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": "上架詳情" | |||||
| } | } | ||||