diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index a5eee0f..f66f658 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1218,7 +1218,6 @@ export interface ProcessStatusInfo { isRequired: boolean; } - export interface JobProcessStatusResponse { jobOrderId: number; jobOrderCode: string; @@ -1244,6 +1243,85 @@ export const fetchJobProcessStatus = cache(async (date?: string) => { next: { tags: ["jobProcessStatus"] }, }); }); + +// ===== Operator KPI Dashboard ===== + +export interface OperatorKpiProcessInfo { + jobOrderId?: number | null; + jobOrderCode?: string | null; + productProcessId?: number | null; + productProcessLineId?: number | null; + processName?: string | null; + equipmentName?: string | null; + equipmentDetailName?: string | null; + startTime?: string | number[] | null; + endTime?: string | number[] | null; + processingTime?: number | null; + itemCode?: string | null; + itemName?: string | null; +} + +export interface OperatorKpiResponse { + operatorId: number; + operatorName?: string | null; + staffNo?: string | null; + totalProcessingMinutes: number; + totalJobOrderCount: number; + currentProcesses: OperatorKpiProcessInfo[]; +} + +export const fetchOperatorKpi = cache(async (date?: string) => { + const params = new URLSearchParams(); + if (date) params.set("date", date); + const qs = params.toString(); + const url = `${BASE_API_URL}/product-process/Demo/OperatorKpi${qs ? `?${qs}` : ""}`; + + return serverFetchJson(url, { + method: "GET", + next: { tags: ["operatorKpi"] }, + }); +}); + +// ===== Equipment Status Dashboard ===== + +export interface EquipmentStatusProcessInfo { + jobOrderId?: number | null; + jobOrderCode?: string | null; + productProcessId?: number | null; + productProcessLineId?: number | null; + processName?: string | null; + operatorName?: string | null; + startTime?: string | number[] | null; + processingTime?: number | null; +} + +export interface EquipmentStatusPerDetail { + equipmentDetailId: number; + equipmentDetailCode?: string | null; + equipmentDetailName?: string | null; + equipmentId?: number | null; + equipmentTypeName?: string | null; + status: string; + repairAndMaintenanceStatus?: boolean | null; + latestRepairAndMaintenanceDate?: string | null; + lastRepairAndMaintenanceDate?: string | null; + repairAndMaintenanceRemarks?: string | null; + currentProcess?: EquipmentStatusProcessInfo | null; +} + +export interface EquipmentStatusByTypeResponse { + equipmentTypeId: number; + equipmentTypeName?: string | null; + details: EquipmentStatusPerDetail[]; +} + +export const fetchEquipmentStatus = cache(async () => { + const url = `${BASE_API_URL}/product-process/Demo/EquipmentStatus`; + return serverFetchJson(url, { + method: "GET", + next: { tags: ["equipmentStatus"] }, + }); +}); export const deleteProductProcessLine = async (lineId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/delete/${lineId}`, diff --git a/src/components/Jodetail/JobPickExecutionForm.tsx b/src/components/Jodetail/JobPickExecutionForm.tsx index 72ed84d..6d6a054 100644 --- a/src/components/Jodetail/JobPickExecutionForm.tsx +++ b/src/components/Jodetail/JobPickExecutionForm.tsx @@ -243,10 +243,12 @@ useEffect(() => { return; } - // ✅ 只允许 Verified>0 且没有问题时,走 normal pick + // 增加 badPackageQty 判断,确保有坏包装会走 issue 流程 + const badPackageQty = Number((formData as any).badPackageQty) || 0; const isNormalPick = verifiedQty > 0 - && formData.missQty == 0 - && formData.badItemQty == 0; + && formData.missQty == 0 + && formData.badItemQty == 0 + && badPackageQty == 0; if (isNormalPick) { if (onNormalPickSubmit) { diff --git a/src/components/ProductionProcess/EquipmentStatusDashboard.tsx b/src/components/ProductionProcess/EquipmentStatusDashboard.tsx new file mode 100644 index 0000000..473c200 --- /dev/null +++ b/src/components/ProductionProcess/EquipmentStatusDashboard.tsx @@ -0,0 +1,363 @@ +"use client"; + +import React, { useState, useEffect, useCallback } from "react"; +import { + Box, + Card, + CardContent, + CircularProgress, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Typography, + Tabs, + Tab, + Chip, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import dayjs from "dayjs"; +import { + fetchEquipmentStatus, + EquipmentStatusByTypeResponse, + EquipmentStatusPerDetail, +} from "@/app/api/jo/actions"; +import { arrayToDayjs } from "@/app/utils/formatUtil"; + +const REFRESH_INTERVAL = 60 * 1000; // 1 分鐘 + +const STATUS_COLORS: Record = { + Processing: "success", + Idle: "default", + Repair: "warning", +}; + +const formatDateTime = (value: any): string => { + if (!value) return "-"; + + if (Array.isArray(value)) { + try { + const parsed = arrayToDayjs(value, true); + if (parsed.isValid()) { + return parsed.format("YYYY-MM-DD HH:mm"); + } + } catch (e) { + console.error("Error parsing datetime array:", e); + } + } + + if (typeof value === "string") { + const parsed = dayjs(value); + if (parsed.isValid()) { + return parsed.format("YYYY-MM-DD HH:mm"); + } + } + + return "-"; +}; + +const formatTime = (value: any): string => { + if (!value) return "-"; + + if (Array.isArray(value)) { + try { + const parsed = arrayToDayjs(value, true); + if (parsed.isValid()) { + return parsed.format("HH:mm"); + } + } catch (e) { + console.error("Error parsing time array:", e); + } + } + + if (typeof value === "string") { + const parsed = dayjs(value); + if (parsed.isValid()) { + return parsed.format("HH:mm"); + } + } + + return "-"; +}; + +// 计算预计完成时间 +const calculateEstimatedCompletionTime = ( + startTime: any, + processingTime: number | null | undefined +): string => { + if (!startTime || !processingTime || processingTime <= 0) return "-"; + + try { + const start = arrayToDayjs(startTime, true); + if (!start.isValid()) return "-"; + + const estimated = start.add(processingTime, "minute"); + return estimated.format("YYYY-MM-DD HH:mm"); + } catch (e) { + console.error("Error calculating estimated completion time:", e); + return "-"; + } +}; + +// 计算剩余时间(分钟) +const calculateRemainingTime = ( + startTime: any, + processingTime: number | null | undefined +): string => { + if (!startTime || !processingTime || processingTime <= 0) return "-"; + + try { + const start = arrayToDayjs(startTime, true); + if (!start.isValid()) return "-"; + + const now = dayjs(); + const estimated = start.add(processingTime, "minute"); + const remainingMinutes = estimated.diff(now, "minute"); + + if (remainingMinutes < 0) { + return `-${Math.abs(remainingMinutes)}`; + } + return remainingMinutes.toString(); + } catch (e) { + console.error("Error calculating remaining time:", e); + return "-"; + } +}; + +const EquipmentStatusDashboard: React.FC = () => { + const { t } = useTranslation(["common", "jo"]); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [tabIndex, setTabIndex] = useState(0); + + const loadData = useCallback(async () => { + setLoading(true); + try { + const result = await fetchEquipmentStatus(); + setData(result || []); + } catch (error) { + console.error("Error fetching equipment status:", error); + setData([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + loadData(); + const interval = setInterval(() => { + loadData(); + }, REFRESH_INTERVAL); + return () => clearInterval(interval); + }, [loadData]); + + // 添加定时更新剩余时间 + useEffect(() => { + const timer = setInterval(() => { + // 触发重新渲染以更新剩余时间 + setData((prev) => [...prev]); + }, 60000); // 每分钟更新一次 + return () => clearInterval(timer); + }, []); + + const handleTabChange = (_: React.SyntheticEvent, newValue: number) => { + setTabIndex(newValue); + }; + + const displayTypes = + tabIndex === 0 + ? data + : data.filter((_, index) => index === tabIndex - 1); + + return ( + + + + {t("Production Equipment Status Dashboard")} + + + + + + {data.map((type, index) => ( + + ))} + + + {loading ? ( + + + + ) : displayTypes.length === 0 ? ( + + {t("No data available")} + + ) : ( + + {displayTypes.map((type) => { + const details = type.details || []; + if (details.length === 0) return null; + + return ( + + + + {type.equipmentTypeName || "-"} + + + + + + + + + {t("Equipment Name and Code")} + + + {details.map((d) => ( + + + + {d.equipmentDetailName || "-"} + + + {d.equipmentDetailCode || "-"} + + + + ))} + + + + {/* 工序 Row */} + + + + {t("Process")} + + + {details.map((d) => ( + + {d.status === "Processing" ? d.currentProcess?.processName || "-" : "-"} + + ))} + + + {/* 狀態 Row - 修改:Processing 时只显示 job order code */} + + + + {t("Status")} + + + {details.map((d) => { + const chipColor = STATUS_COLORS[d.status] || "default"; + const cp = d.currentProcess; + + // Processing 时只显示 job order code,不显示 Chip + if (d.status === "Processing" && cp?.jobOrderCode) { + return ( + + + {cp.jobOrderCode} + + + ); + } + + // 其他状态显示 Chip + return ( + + + + ); + })} + + + + + {/* 開始時間 Row */} + + + + {t("Start Time")} + + + {details.map((d) => ( + + {d.status === "Processing" + ? formatDateTime(d.currentProcess?.startTime) + : "-"} + + ))} + + + {/* 預計完成時間 Row */} + + + + {t("預計完成時間")} + + + {details.map((d) => ( + + {d.status === "Processing" + ? calculateEstimatedCompletionTime( + d.currentProcess?.startTime, + d.currentProcess?.processingTime + ) + : "-"} + + ))} + + + {/* 剩餘時間 Row */} + + + + {t("Remaining Time (min)")} + + + {details.map((d) => ( + + {d.status === "Processing" + ? calculateRemainingTime( + d.currentProcess?.startTime, + d.currentProcess?.processingTime + ) + : "-"} + + ))} + + +
+
+
+
+ ); + })} +
+ )} +
+ ); +}; + +export default EquipmentStatusDashboard; \ No newline at end of file diff --git a/src/components/ProductionProcess/JobProcessStatus.tsx b/src/components/ProductionProcess/JobProcessStatus.tsx index 0d2dc78..4fb3244 100644 --- a/src/components/ProductionProcess/JobProcessStatus.tsx +++ b/src/components/ProductionProcess/JobProcessStatus.tsx @@ -173,7 +173,7 @@ const JobProcessStatus: React.FC = () => { - {t("Job Process Status")} + {t("Job Process Status Dashboard")} @@ -194,21 +194,27 @@ const JobProcessStatus: React.FC = () => { ) : ( - - + +
- + {t("Job Order No.")} - + {t("FG / WIP Item")} - + {t("Production Time Remaining")} @@ -216,8 +222,8 @@ const JobProcessStatus: React.FC = () => { - {[1, 2, 3, 4, 5, 6].map((num) => ( - + {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( + {t("Process")} {num} @@ -225,9 +231,9 @@ const JobProcessStatus: React.FC = () => { ))} - {[1, 2, 3, 4, 5, 6].map((num) => ( - - + {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( + + {t("Start")} @@ -245,21 +251,21 @@ const JobProcessStatus: React.FC = () => { {data.length === 0 ? ( - + {t("No data available")} ) : ( data.map((row) => ( - + {row.jobOrderCode || '-'} - - {row.itemCode || '-'} + + {row.itemCode || '-'} {row.itemName || '-'} - + {row.status === 'pending' ? '-' : calculateRemainingTime(row.planEndTime, row.processingTime, row.setupTime, row.changeoverTime)} @@ -276,7 +282,7 @@ const JobProcessStatus: React.FC = () => { // 如果工序不是必需的,只显示一个 N/A if (!process.isRequired) { return ( - + N/A @@ -290,17 +296,18 @@ const JobProcessStatus: React.FC = () => { ].filter(Boolean).join(" "); // 如果工序是必需的,显示三行(Start、Finish、Wait Time) return ( - - - {label || "-"} - + + + {label || "-"} + {formatTime(process.startTime)} - + {formatTime(process.endTime)} 0 ? 'warning.main' : 'text.primary' + color: waitTime !== '-' && parseInt(waitTime) > 0 ? 'warning.main' : 'text.primary', + py: 0.5 }}> {waitTime} diff --git a/src/components/ProductionProcess/OperatorKpiDashboard.tsx b/src/components/ProductionProcess/OperatorKpiDashboard.tsx new file mode 100644 index 0000000..e9b3769 --- /dev/null +++ b/src/components/ProductionProcess/OperatorKpiDashboard.tsx @@ -0,0 +1,343 @@ +"use client"; + +import React, { useState, useEffect, useCallback, useRef } from "react"; +import { + Box, + Card, + CardContent, + CircularProgress, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Typography, + FormControl, + Select, + MenuItem, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import dayjs from "dayjs"; +import { fetchOperatorKpi, OperatorKpiResponse, OperatorKpiProcessInfo } from "@/app/api/jo/actions"; +import { arrayToDayjs } from "@/app/utils/formatUtil"; + +const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 分鐘 + +const OperatorKpiDashboard: React.FC = () => { + const { t } = useTranslation(["common", "jo"]); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); + const refreshCountRef = useRef(0); + + const formatTime = (timeData: any): string => { + if (!timeData) return "-"; + + if (Array.isArray(timeData)) { + try { + const parsed = arrayToDayjs(timeData, true); + if (parsed.isValid()) { + return parsed.format("HH:mm"); + } + } catch (e) { + console.error("Error parsing time array:", e); + } + } + + if (typeof timeData === "string") { + const parsed = dayjs(timeData); + if (parsed.isValid()) { + return parsed.format("HH:mm"); + } + } + + return "-"; + }; + + const formatMinutesToHHmm = (minutes: number): string => { + if (!minutes || minutes <= 0) return "00:00"; + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`; + }; + + const loadData = useCallback(async () => { + setLoading(true); + try { + const result = await fetchOperatorKpi(selectedDate); + setData(result); + refreshCountRef.current += 1; + } catch (error) { + console.error("Error fetching operator KPI:", error); + setData([]); + } finally { + setLoading(false); + } + }, [selectedDate]); + + useEffect(() => { + loadData(); + const interval = setInterval(() => { + loadData(); + }, REFRESH_INTERVAL); + return () => clearInterval(interval); + }, [loadData]); + + const renderCurrentProcesses = (processes: OperatorKpiProcessInfo[]) => { + if (!processes || processes.length === 0) { + return ( + + - + + ); + } + + // 只顯示目前一個處理中的工序(樣式比照 Excel:欄位名稱縱向排列) + const p = processes[0]; + const jobOrder = p.jobOrderCode ? `[${p.jobOrderCode}]` : "-"; + const itemInfo = p.itemCode && p.itemName + ? `${p.itemCode} - ${p.itemName}` + : p.itemCode || p.itemName || "-"; + + // 格式化所需時間(分鐘轉換為 HH:mm) + const formatRequiredTime = (minutes: number | null | undefined): string => { + if (!minutes || minutes <= 0) return "-"; + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`; + }; + + // 計算預計完成時間 + const calculateEstimatedCompletionTime = (): string => { + if (!p.startTime || !p.processingTime || p.processingTime <= 0) return "-"; + + try { + const start = arrayToDayjs(p.startTime, true); + if (!start.isValid()) return "-"; + + const estimated = start.add(p.processingTime, "minute"); + return estimated.format("HH:mm"); + } catch (e) { + console.error("Error calculating estimated completion time:", e); + return "-"; + } + }; + + return ( + <> + + {t("Job Order and Product")}: {jobOrder} {itemInfo} + + + {t("Process")}: {p.processName || "-"} + + + {t("Start Time")}: {formatTime(p.startTime)} + + + {t("Required Time")}: {formatRequiredTime(p.processingTime)} + + + {t("Estimated Completion Time")}: {calculateEstimatedCompletionTime()} + + + ); + }; + + return ( + + + + + {t("Operator KPI Dashboard")} + + + + + + + + {loading ? ( + + + + ) : ( + +
+ + + + + {t("No.")} + + + + + {t("Operator")} + + + + + {t("Job Details")} + + + + + + {data.length === 0 ? ( + + + {t("No data available")} + + + ) : ( + data.map((row, index) => { + const jobOrderCount = row.totalJobOrderCount || 0; + + return ( + + + {index + 1} + + + + + {t("Operator Name & No.")}:{" "} + + {row.operatorName || "-"}{" "} + {row.staffNo ? `(${row.staffNo})` : ""} + + + + {t("Count of Job Orders")}:{" "} + + {jobOrderCount} + + + + {t("Total Processing Time")}:{" "} + + {formatMinutesToHHmm(row.totalProcessingMinutes || 0)} + + + + + + + {renderCurrentProcesses(row.currentProcesses)} + + + + ); + }) + )} + +
+
+ )} +
+ + ); +}; + +export default OperatorKpiDashboard; + diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 4e8c15f..b569b70 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -246,7 +246,13 @@ const isStockSufficient = (line: JobOrderLineInfo) => { const stockCounts = useMemo(() => { // 过滤掉 consumables 类型的 lines const nonConsumablesLines = jobOrderLines.filter( - line => line.type?.toLowerCase() !== "consumables" && line.type?.toLowerCase() !== "cmb" && line.type?.toLowerCase() !== "nm" + line => { + const type = line.type?.toLowerCase(); + return type !== "consumables" && + type !== "consumable" && // ✅ 添加单数形式 + type !== "cmb" && + type !== "nm" + } ); const total = nonConsumablesLines.length; const sufficient = nonConsumablesLines.filter(isStockSufficient).length; @@ -473,7 +479,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { field: "itemCode", headerName: t("Material Code"), flex: 0.6, - sortable: false, // ✅ 禁用排序 + sortable: false, }, { field: "itemName", @@ -490,11 +496,11 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { flex: 0.7, align: "right", headerAlign: "right", - sortable: false, // ✅ 禁用排序 - // ✅ 将切换功能移到 header + sortable: false, + renderHeader: () => { - const qty = showBaseQty ? t("Base") : t("Req"); - const uom = showBaseQty ? t("Base UOM") : t(" "); + + const uom = showBaseQty ? t("Base UOM") : t("Bom Uom"); return ( { }, }} > - {t("Bom Req. Qty")} ({uom}) + + {t("Bom Req. Qty")}
+ ({uom}) +
); }, @@ -547,7 +556,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { }, }} > - {t("Stock Req. Qty")} ({uom}) + + {t("Stock Req. Qty")}
+ ({uom}) +
); }, @@ -587,7 +599,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { }, }} > - {t("Stock Available")} ({uom}) + + {t("Stock Available")}
+ ({uom}) +
); }, @@ -684,7 +699,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { = ({ printerCo - + + + {tabIndex === 0 && ( @@ -195,6 +199,12 @@ const ProductionProcessPage: React.FC = ({ printerCo {tabIndex === 2 && ( )} + {tabIndex === 3 && ( + + )} + {tabIndex === 4 && ( + + )} ); }; diff --git a/src/components/StockIssue/SubmitIssueForm.tsx b/src/components/StockIssue/SubmitIssueForm.tsx index 4ee5c22..37d6bab 100644 --- a/src/components/StockIssue/SubmitIssueForm.tsx +++ b/src/components/StockIssue/SubmitIssueForm.tsx @@ -76,7 +76,8 @@ const SubmitIssueForm: React.FC = ({ }; const handleSubmit = async () => { - if (!lotId || !submitQty || parseFloat(submitQty) <= 0) { + + if (!lotId || !submitQty || parseFloat(submitQty) < 0) { alert(t("Please enter a valid quantity")); return; } @@ -175,7 +176,7 @@ const SubmitIssueForm: React.FC = ({ diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index b956fc3..d5280d1 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -15,7 +15,7 @@ "Search": "搜索", "This lot is rejected, please scan another lot.": "此批次已封存,請掃描另一個批號。", "Process Start Time": "工序開始時間", - "Stock Req. Qty": "需求數(庫存單位)", + "Stock Req. Qty": "需求數", "Staff No Required": "員工編號必填", "User Not Found": "用戶不存在", "Time Remaining": "剩餘時間", @@ -52,7 +52,7 @@ "No": "沒有", "Assignment failed: ": "分配失敗: ", "Unknown error": "未知錯誤", - "Job Process Status": "工單流程狀態", + "Job Process Status Dashboard": "儀表板 - 工單狀態", "Total Time": "總時間", "Remaining Time": "剩餘時間", @@ -94,7 +94,7 @@ "Deliver Order": "送貨訂單", "Project": "專案", "Product": "產品", - "Material": "材料", + "mat": "原料", "consumables": "消耗品", "non-consumables": "非消耗品", @@ -109,7 +109,7 @@ "Detail Scheduling": "詳細排程", "Customer": "客戶", "qcItem": "品檢項目", - "Item": "成品/半成品", + "Today": "今天", "Yesterday": "昨天", "Input Equipment is not match with process": "輸入的設備與流程不匹配", @@ -209,9 +209,10 @@ "Row per page": "每頁行數", "Select Unit": "選擇單位", "No data available": "沒有資料", - "Bom Req. Qty": "需求數(BOM單位)", + "Bom Req. Qty": "BOM", "Material Name": "材料清單", "Material Code": "材料清單", + "Bom UOM": "使用單位", "Base UOM": "基本單位", "Stock UOM": "庫存單位", "jodetail": "工單細節", @@ -238,7 +239,7 @@ "Is Dense": "濃淡", "Is Float": "浮沉", "Job Order Code": "工單編號", - "Operator": "操作員", + "Output Qty": "輸出數量", "Pending": "待處理", "pending": "待處理", @@ -264,10 +265,10 @@ "Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.", "View": "查看", "Back": "返回", - "BoM Material": "成品/半成品清單", + "BoM Material": "材料清單", "N/A": "不適用", "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間次序 | 複雜度", - "Item Code": "成品/半成品名稱", + "Item Code": "材料名稱", "Please scan equipment code": "請掃描設備編號", "Equipment Code": "設備編號", "Seq": "步驟", @@ -283,7 +284,7 @@ "Seq No": "加入步驟", "Total pick orders": "總提料單數量", "Seq No Remark": "序號明細", - "Stock Available": "庫存可用", + "Stock Available": "庫存數", "Confirm": "確認", "Do you want to delete?": "您確定要刪除嗎?", "Stock Status": "庫存狀態", @@ -300,6 +301,7 @@ "update production priority": "更新生產優先序", "Assume Time Need": "預計所需時間", "Required Qty": "需求數", + "Bom Required Qty": "Bom 使用份量", "Total processes": "總流程數", "View Details": "查看詳情", "view stockin": "品檢", @@ -412,6 +414,22 @@ "Equipment Code": "設備編號", "Yes": "是", "No": "否", + "No.": "編號", + "Operator Name & No.": "操作員名稱及編號", + "Count of Job Orders": "已處理工單", + "Total Processing Time": "總工時", + "Material Pick Status": "物料提料狀態", + "Operator KPI Dashboard": "儀表板 - 操作員KPI概覽", + "Operator": "員工資訊", + "Equipment Name and Code": "設備名稱及編號", + "Remaining Time (min)": "剩餘時間(分鐘)", + "Production Equipment Status Dashboard": "儀表板 - 生產設備狀態", + "Idle": "閒置", + "Process": "工序", + "Job Details": "工單編號及生產產品", + "Required Time": "所需時間", + "Estimated Completion Time": "預計完成時間", + "Job Order and Product": "工單及貨品", "Update Equipment Maintenance and Repair": "更新設備的維護和保養", "Equipment Information": "設備資訊", "Loading": "載入中...", diff --git a/src/i18n/zh/do.json b/src/i18n/zh/do.json index 05c735d..27c924f 100644 --- a/src/i18n/zh/do.json +++ b/src/i18n/zh/do.json @@ -11,9 +11,12 @@ "Status": "來貨狀態", "Order Date From": "訂單日期", "Delivery Order Code": "送貨訂單編號", + "Select Remark": "選擇備註", "Confirm Assignment": "確認分配", "Required Date": "所需日期", + "Submit Miss Item": "提交缺貨品", + "Submit Quantity": "提交數量", "Store": "位置", "Lane Code": "車線號碼", "Available Orders": "可用訂單", diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index 46fdfb7..7d7f502 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -33,6 +33,11 @@ "Start Time": "開始時間", "Difference": "差異", "stockTaking": "盤點中", + "Pick Order Code": "提料單編號", + "DO Order Code": "送貨單編號", + "JO Order Code": "工單編號", + "Picker Name": "提料員", + "rejected": "已拒絕", "miss": "缺貨", "bad": "不良", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 9517120..f481de8 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -11,11 +11,11 @@ "Picked Qty": "已提料數量", "Confirm All": "確認所有提料", "Wait Time [minutes]": "等待時間(分鐘)", - "Job Process Status": "工單流程狀態", + "Job Process Status Dashboard": "儀表板 - 工單狀態", "This lot is rejected, please scan another lot.": "此批次已拒收,請掃描另一個批次。", "Edit": "改數", "Just Complete": "已完成", - "Stock Req. Qty": "需求數(庫存單位)", + "Stock Req. Qty": "需求數", "Bad Package Qty": "不良包裝數量", "Progress": "進度", "Search Job Order/ Create Job Order":"搜尋工單/建立工單", @@ -100,7 +100,7 @@ "Pause Reason": "暫停原因", "Bag Usage": "包裝袋使用記錄", "Reason": "原因", - "Stock Available": "倉庫可用數", + "update production priority": "更新生產優先序", "Staff No": "員工編號", "Please scan staff no": "請掃描員工編號", @@ -108,7 +108,7 @@ "Total lines: ": "所需貨品項目數量: ", "Lines with sufficient stock: ": "可提料項目數量: ", "Lines with insufficient stock: ": "未能提料項目數量: ", - "Item Name": "成品/半成品", + "Item Name": "材料名稱", "Material Code": "材料編號", "Select Unit": "選擇單位", "Job Order Pickexcution": "工單提料", @@ -212,7 +212,8 @@ "No Group": "沒有組", "No created items": "沒有創建物料", "Order Quantity": "需求數", - "Bom Req. Qty": "需求數(BOM單位)", + "Bom Req. Qty": "BOM", + "Bom Uom": "使用單位", "Selected": "已選擇", "Are you sure you want to delete this procoess?": "您確定要刪除此工序嗎?", "Please select item": "請選擇物料", @@ -421,7 +422,7 @@ "View": "查看", "Back": "返回", "N/A": "不適用", - "BoM Material": "成品/半成品清單", + "BoM Material": "材料清單", "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間順序 | 複雜度", "Enter the number of cartons: ": "請輸入箱數:", "Number of cartons": "箱數", @@ -443,7 +444,7 @@ "Req. Qty": "需求數量", "Seq No": "加入步驟", "Seq No Remark": "序號明細", - "Stock Available": "庫存可用", + "Stock Available": "庫存數", "Stock Status": "庫存狀態", "Target Production Date": "目標生產日期", "Description": "描述", @@ -536,13 +537,22 @@ "Finished Good Order": "成品出倉", "finishedGood": "成品", "Router": "執貨路線", - +"Equipment Name and Code": "設備名稱及編號", +"Remaining Time (min)": "剩餘時間(分鐘)", +"Idle": "閒置", +"Repair": "維修", +"Production Equipment Status Dashboard": "儀表板 - 生產設備狀態", +"Operator KPI Dashboard": "儀表板 - 操作員KPI概覽", "Start Scan": "開始掃碼", "Stop Scan": "停止掃碼", + "Operator Name & No.": "操作員名稱及編號", + "Count of Job Orders": "工單數量", + "Total Processing Time": "總生產時間", "Material Pick Status": "物料提料狀態", "Job Order Qty": "工單數量", "Sign out": "登出", "Job Order No.": "工單編號", + "Operator KPI": "操作員KPI", "FG / WIP Item": "成品/半成品", "Production Time Remaining": "生產剩餘時間", "Process": "工序",