From 986edbf6d480e5ca9d931a6e91a31519913a0bd0 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 10 Aug 2026 23:29:31 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=93=A1=E5=B7=A5=E7=99=BC=E8=B2=A8?= =?UTF-8?q?=E7=B8=BE=E6=95=88=E5=9C=96=E8=A1=A8=202.=20=20=E6=96=B0?= =?UTF-8?q?=E5=A0=B1=E8=A1=A8=EF=BC=9A=E9=80=81=E8=B2=A8=E8=A8=82=E5=96=AE?= =?UTF-8?q?=E8=88=87=E5=80=89=E5=AD=98=E5=96=AE=E4=BD=8D=E4=B8=8D=E7=AC=A6?= =?UTF-8?q?=203=20=E5=BA=97=E9=8B=AA=E8=A3=9C=E8=B2=A8=E5=A0=B1=E8=A1=A8?= =?UTF-8?q?=20=E5=8A=A0=E3=80=8C=E5=8E=9F=E5=96=AE=E6=8F=90=E6=96=99?= =?UTF-8?q?=E4=BA=BA=E3=80=8D=E3=80=8C=E5=AF=A6=E9=9A=9B=E8=A3=9C=E8=B2=A8?= =?UTF-8?q?=E6=8F=90=E6=96=99=E4=BA=BA=E3=80=8D=EF=BC=9BExcel=20=E6=AC=84?= =?UTF-8?q?=E4=BD=8D=E6=94=B9=E7=B4=94=E4=B8=AD=E6=96=87=E8=A1=A8=E9=A0=AD?= =?UTF-8?q?=204=20=20=E5=8C=85=E8=A3=9D=20autoPass=205.=20JO=20=E6=8F=90?= =?UTF-8?q?=E6=96=99=20re-suggest=20JO=20=E9=A1=9E=E5=9E=8B=20storeId=20?= =?UTF-8?q?=E4=BF=9D=E6=8C=81=20null=EF=BC=88=E9=81=BF=E5=85=8D=E8=A2=AB?= =?UTF-8?q?=E9=A0=90=E8=A8=AD=E6=88=90=202/F=20=E6=BF=BE=E6=8E=89=203F?= =?UTF-8?q?=EF=BC=89=20=20=E5=89=8D=E7=AB=AF=E5=B0=8D=E9=BD=8A=20DO?= =?UTF-8?q?=EF=BC=9A=E7=94=A8=E5=BE=8C=E7=AB=AF=20stockout=20qty=E3=80=81?= =?UTF-8?q?=E5=90=8C=20POL=20=E4=BE=9D=20status=20=E6=8E=92=E5=BA=8F=206.?= =?UTF-8?q?=20=E4=BD=BF=E7=94=A8=E8=80=85=E5=BB=BA=E7=AB=8B=EF=BC=8F?= =?UTF-8?q?=E9=A9=97=E8=AD=89=207.=20=20BOM=20=E8=A8=AD=E5=82=99=208?= =?UTF-8?q?=E3=80=8C=E4=B8=8D=E5=90=88=E7=94=A8=EF=BC=8F=E4=B8=8D=E9=81=A9?= =?UTF-8?q?=E7=94=A8=E3=80=8D=E7=B5=B1=E4=B8=80=E4=B8=8D=E6=8E=9B=20equipm?= =?UTF-8?q?ent=20data=209=20hilde=20some=20useless=20console=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/update-chart-i18n.js | 8 ++- src/app/(main)/chart/delivery/page.tsx | 45 ++++++++++++-- .../report/ReportSelectionDashboard.tsx | 3 + src/app/(main)/report/page.tsx | 27 ++++++++ src/app/(main)/report/reportCategories.ts | 2 +- .../report/shopOrderReplenishmentReportApi.ts | 62 ++++++++++--------- src/app/api/chart/client.ts | 5 ++ src/app/api/user/client.ts | 11 +++- src/components/CreateUser/CreateUser.tsx | 35 +++++++++-- src/components/CreateUser/UserDetail.tsx | 5 ++ src/components/JoSearch/JoCreateFormModal.tsx | 4 +- src/components/JoSearch/JoSearch.tsx | 8 +-- .../JoWorkbench/JoWorkbenchSearch.tsx | 8 +-- .../JoWorkbench/newJobPickExecution.tsx | 62 +++++++++++-------- .../ProductionProcessDetail.tsx | 9 ++- .../ProductionProcessList.tsx | 8 +-- .../ProductionProcessStepExecution.tsx | 34 ++++++++-- .../UserSearch/UserExcelSheetView.tsx | 24 ++++++- src/config/reportConfig.ts | 29 +++++++++ src/i18n/en/chart.json | 4 +- src/i18n/en/itemTracing.json | 3 +- src/i18n/en/jo.json | 4 +- src/i18n/en/productionProcess.json | 3 +- src/i18n/en/user.json | 9 ++- src/i18n/zh/chart.json | 4 +- src/i18n/zh/itemTracing.json | 3 +- src/i18n/zh/jo.json | 5 +- src/i18n/zh/productionProcess.json | 5 +- src/i18n/zh/user.json | 9 ++- 29 files changed, 334 insertions(+), 104 deletions(-) diff --git a/scripts/update-chart-i18n.js b/scripts/update-chart-i18n.js index 05fa7c08..2d915d52 100644 --- a/scripts/update-chart-i18n.js +++ b/scripts/update-chart-i18n.js @@ -42,9 +42,11 @@ const en = { "delivery_store": "Store", "delivery_staff": "Staff", "delivery_staffPlaceholder": "Leave empty for all", - "delivery_staffPerfCaption": "Per-person pick count & total duration for period", + "delivery_staffPerfCaption": "Per-person pick count, item kinds, picked qty & total duration for period", "delivery_colStaff": "Staff", "delivery_colPickCount": "Pick Count", + "delivery_colItemKindCount": "Item Kind Count", + "delivery_colItemQtyPicked": "Item Qty Picked", "delivery_colTotalMin": "Total Min", "delivery_colAvgMin": "Avg Min/Order", "delivery_dailyByStaff": "Daily by Staff", @@ -167,9 +169,11 @@ const zh = { "delivery_store": "倉別", "delivery_staff": "員工", "delivery_staffPlaceholder": "不選則全部", - "delivery_staffPerfCaption": "週期內每人揀單數及總耗時(首揀至完成)", + "delivery_staffPerfCaption": "週期內每人揀單數、品項數、揀貨數量及總耗時(首揀至完成)", "delivery_colStaff": "員工", "delivery_colPickCount": "揀單數", + "delivery_colItemKindCount": "品項數", + "delivery_colItemQtyPicked": "揀貨數量", "delivery_colTotalMin": "總分鐘", "delivery_colAvgMin": "平均分鐘/單", "delivery_dailyByStaff": "每日按員工單數", diff --git a/src/app/(main)/chart/delivery/page.tsx b/src/app/(main)/chart/delivery/page.tsx index c8ba2a22..bbbf3bb8 100644 --- a/src/app/(main)/chart/delivery/page.tsx +++ b/src/app/(main)/chart/delivery/page.tsx @@ -66,6 +66,7 @@ const defaultCriteria: Criteria = { }, }; +/** FP-MTMS Version Checklist | Functions Ref. No. 61 | v1.0.0 | 2026-08-10 */ export default function DeliveryChartPage() { const [criteria, setCriteria] = useState(defaultCriteria); const [topItemsSelected, setTopItemsSelected] = useState([]); @@ -76,7 +77,14 @@ export default function DeliveryChartPage() { const [chartData, setChartData] = useState<{ delivery: { date: string; orderCount: number; totalQty: number }[]; topItems: { itemCode: string; itemName: string; totalQty: number }[]; - staffPerf: { date: string; staffName: string; orderCount: number; totalMinutes: number }[]; + staffPerf: { + date: string; + staffName: string; + orderCount: number; + totalMinutes: number; + itemKindCount: number; + itemQtyPicked: number; + }[]; }>({ delivery: [], topItems: [], staffPerf: [] }); const [loadingCharts, setLoadingCharts] = useState>({}); @@ -146,6 +154,8 @@ export default function DeliveryChartPage() { staffName: string; orderCount: number; totalMinutes: number; + itemKindCount: number; + itemQtyPicked: number; }[], })) ) @@ -164,18 +174,30 @@ export default function DeliveryChartPage() { }, [criteria.topItems.rangeDays]); const staffPerfByStaff = useMemo(() => { - const map = new Map(); + const map = new Map< + string, + { orderCount: number; totalMinutes: number; itemKindCount: number; itemQtyPicked: number } + >(); for (const r of chartData.staffPerf) { const name = r.staffName || "Unknown"; - const cur = map.get(name) ?? { orderCount: 0, totalMinutes: 0 }; + const cur = map.get(name) ?? { + orderCount: 0, + totalMinutes: 0, + itemKindCount: 0, + itemQtyPicked: 0, + }; map.set(name, { orderCount: cur.orderCount + r.orderCount, totalMinutes: cur.totalMinutes + r.totalMinutes, + itemKindCount: cur.itemKindCount + r.itemKindCount, + itemQtyPicked: cur.itemQtyPicked + r.itemQtyPicked, }); } return Array.from(map.entries()).map(([staffName, v]) => ({ staffName, orderCount: v.orderCount, + itemKindCount: v.itemKindCount, + itemQtyPicked: v.itemQtyPicked, totalMinutes: v.totalMinutes, avgMinutesPerOrder: v.orderCount > 0 ? Math.round(v.totalMinutes / v.orderCount) : 0, })); @@ -298,7 +320,14 @@ export default function DeliveryChartPage() { ({ 日期: r.date, 員工: r.staffName, 揀單數: r.orderCount, 總分鐘: r.totalMinutes }))} + exportData={chartData.staffPerf.map((r) => ({ + 日期: r.date, + 員工: r.staffName, + 揀單數: r.orderCount, + 總揀貨款數: r.itemKindCount, + 總揀貨件數: r.itemQtyPicked, + 總分鐘: r.totalMinutes, + }))} filters={ <> - 週期內每人揀單數及總耗時(首揀至完成) + 週期內每人揀單數、總揀貨款數、總揀貨件數及總耗時(首揀至完成) 員工 揀單數 + 總揀貨款數 + 總揀貨件數 總分鐘 平均分鐘/單 @@ -416,13 +447,15 @@ export default function DeliveryChartPage() { {staffPerfByStaff.length === 0 ? ( - 無數據 + 無數據 ) : ( staffPerfByStaff.map((row) => ( {row.staffName} {row.orderCount} + {row.itemKindCount} + {row.itemQtyPicked} {row.totalMinutes} {row.avgMinutesPerOrder} diff --git a/src/app/(main)/report/ReportSelectionDashboard.tsx b/src/app/(main)/report/ReportSelectionDashboard.tsx index bc256f73..5c7ac845 100644 --- a/src/app/(main)/report/ReportSelectionDashboard.tsx +++ b/src/app/(main)/report/ReportSelectionDashboard.tsx @@ -30,6 +30,9 @@ const REPORT_ICON_MAP: Record = { "rep-008": OutboundOutlinedIcon, "rep-009": OutboundOutlinedIcon, "rep-013": LocalShippingOutlinedIcon, + "rep-016": OutboundOutlinedIcon, + "rep-017": LocalShippingOutlinedIcon, + "rep-018": SearchOutlinedIcon, "rep-006": BarChartOutlinedIcon, "rep-005": PieChartOutlineOutlinedIcon, "rep-015": LayersOutlinedIcon, diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index 266020a5..ddc3c912 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -44,6 +44,7 @@ interface ItemCodeWithName { } /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.0 | 2026-08-10 */ export default function ReportPage() { const { data: session } = useSession() as { data: SessionWithTokens | null }; const includeGrnFinancialColumns = @@ -213,6 +214,22 @@ export default function ReportPage() { return false; } + // Date fields with minDate: 'today' must not be before local today + const today = new Date(); + const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; + const beforeToday = currentReport.fields + .filter((field) => field.type === 'date' && field.minDate === 'today') + .filter((field) => { + const v = (criteria[field.name] || '').trim(); + return v && v < todayStr; + }) + .map((field) => field.label); + + if (beforeToday.length > 0) { + alert(`日期不可早於今天:\n- ${beforeToday.join('\n- ')}`); + return false; + } + return true; }; @@ -421,6 +438,11 @@ export default function ReportPage() { // Use larger grid size for 成品/半成品生產分析報告 const gridSize = currentReport.id === 'rep-005' ? { xs: 12, sm: 12, md: 6 } : { xs: 12, sm: 6 }; + const todayLocal = (() => { + const d = new Date(); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; + })(); + const disabledByCheckedCheckbox = currentReport.fields.some((f) => { if (f.type !== 'checkbox' || criteria[f.name] !== 'true') return false; return f.disablesFieldsWhenChecked?.includes(field.name) ?? false; @@ -546,6 +568,11 @@ export default function ReportPage() { placeholder={field.placeholder} disabled={disabledByCheckedCheckbox || disabledRep012Status} InputLabelProps={field.type === 'date' ? { shrink: true } : {}} + inputProps={ + field.type === 'date' && field.minDate === 'today' + ? { min: todayLocal } + : undefined + } sx={currentReport.id === 'rep-005' ? { '& .MuiOutlinedInput-root': { minHeight: '64px', diff --git a/src/app/(main)/report/reportCategories.ts b/src/app/(main)/report/reportCategories.ts index 1c86308b..d6013bc4 100644 --- a/src/app/(main)/report/reportCategories.ts +++ b/src/app/(main)/report/reportCategories.ts @@ -25,7 +25,7 @@ export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ headerBg: "#b3d4f0", bodyBg: "#eef5fc", accent: "#1565c0", - reportIds: ["rep-004", "rep-014", "rep-008", "rep-009", "rep-013", "rep-016", "rep-017"], + reportIds: ["rep-004", "rep-014", "rep-008", "rep-009", "rep-013", "rep-016", "rep-017", "rep-018"], }, { id: "production", diff --git a/src/app/(main)/report/shopOrderReplenishmentReportApi.ts b/src/app/(main)/report/shopOrderReplenishmentReportApi.ts index bca78f71..9392b25b 100644 --- a/src/app/(main)/report/shopOrderReplenishmentReportApi.ts +++ b/src/app/(main)/report/shopOrderReplenishmentReportApi.ts @@ -13,10 +13,12 @@ export interface ShopOrderReplenishmentReportRow { itemName?: string; firstOrderQty?: number | string; firstOrderActualPickQty?: number | string; + firstOrderPickerHandler?: string; reorderQty?: number | string; reorderDate?: string; reason?: string; actualDeliveredQty?: number | string; + actualDeliveredHandler?: string; deliveredDate?: string; [key: string]: unknown; } @@ -27,24 +29,25 @@ export interface ShopOrderReplenishmentReportResponse { const SHEET_NAME = "店鋪訂單補貨記錄"; -const NO_DATA_NOTE = - "(篩選範圍內無資料 / No records in the selected range)"; +const NO_DATA_NOTE = "(篩選範圍內無資料)"; function emptySheetRow(note: string = NO_DATA_NOTE): Record { return { - "Shop No. / 店鋪編號": note, - "Shop Name / 店鋪名稱": "", - "Shop Order Date / 店鋪訂單日期": "", - "Shop Order No. / 店鋪訂單編號": "", - "Item No. / 貨品編號": "", - "Item Name / 貨品名稱": "", - "First Order Qty / 原訂單數量": "", - "First Order Actual Pick Qty / 原單實際提料數量": "", - "Reorder Qty / 補貨數量": "", - "Reorder Date / 補貨日期": "", - "Reason / 補貨原因": "", - "Actual Delivered Qty / 實際補貨數量": "", - "Delivered Date / 送貨日期": "", + "店鋪編號": note, + "店鋪名稱": "", + "店鋪訂單日期": "", + "店鋪訂單編號": "", + "貨品編號": "", + "貨品名稱": "", + "原訂單數量": "", + "原單實際提料數量": "", + "原單提料人": "", + "補貨數量": "", + "補貨日期": "", + "補貨原因": "", + "實際補貨數量": "", + "實際補貨提料人": "", + "送貨日期": "", }; } @@ -92,19 +95,21 @@ function toExcelRow(r: ShopOrderReplenishmentReportRow): Record const base = emptySheetRow(""); return { ...base, - "Shop No. / 店鋪編號": r.shopNo ?? "", - "Shop Name / 店鋪名稱": r.shopName ?? "", - "Shop Order Date / 店鋪訂單日期": formatDateCell(r.shopOrderDate), - "Shop Order No. / 店鋪訂單編號": r.shopOrderNo ?? "", - "Item No. / 貨品編號": r.itemNo ?? "", - "Item Name / 貨品名稱": r.itemName ?? "", - "First Order Qty / 原訂單數量": formatQty(r.firstOrderQty), - "First Order Actual Pick Qty / 原單實際提料數量": formatQty(r.firstOrderActualPickQty), - "Reorder Qty / 補貨數量": formatQty(r.reorderQty), - "Reorder Date / 補貨日期": formatDateCell(r.reorderDate), - "Reason / 補貨原因": formatReason(r.reason), - "Actual Delivered Qty / 實際補貨數量": formatQty(r.actualDeliveredQty), - "Delivered Date / 送貨日期": formatDateCell(r.deliveredDate), + "店鋪編號": r.shopNo ?? "", + "店鋪名稱": r.shopName ?? "", + "店鋪訂單日期": formatDateCell(r.shopOrderDate), + "店鋪訂單編號": r.shopOrderNo ?? "", + "貨品編號": r.itemNo ?? "", + "貨品名稱": r.itemName ?? "", + "原訂單數量": formatQty(r.firstOrderQty), + "原單實際提料數量": formatQty(r.firstOrderActualPickQty), + "原單提料人": r.firstOrderPickerHandler ?? "", + "補貨數量": formatQty(r.reorderQty), + "補貨日期": formatDateCell(r.reorderDate), + "補貨原因": formatReason(r.reason), + "實際補貨數量": formatQty(r.actualDeliveredQty), + "實際補貨提料人": r.actualDeliveredHandler ?? "", + "送貨日期": formatDateCell(r.deliveredDate), }; } @@ -132,6 +137,7 @@ export async function fetchShopOrderReplenishmentReportData( } /** + * FP-MTMS Version Checklist | Functions Ref. No. 49 | v1.0.1 | 2026-08-10 * Generate and download Shop Orders Replenishment Records as Excel. */ export async function generateShopOrderReplenishmentReportExcel( diff --git a/src/app/api/chart/client.ts b/src/app/api/chart/client.ts index defade07..96823649 100644 --- a/src/app/api/chart/client.ts +++ b/src/app/api/chart/client.ts @@ -131,6 +131,8 @@ export interface StaffDeliveryPerformanceRow { staffName: string; orderCount: number; totalMinutes: number; + itemKindCount: number; + itemQtyPicked: number; } export interface StaffOption { @@ -577,6 +579,7 @@ export async function fetchPlannedOutputByDateAndItem( /** Warehouse / lane filter for staff delivery performance chart (delivery_order_pick_order.store_id). */ export type StaffDeliveryPerformanceStoreFilter = "all" | "2/F" | "4/F" | "null_only"; +/** FP-MTMS Version Checklist | Functions Ref. No. 61 | v1.0.0 | 2026-08-10 */ export async function fetchStaffDeliveryPerformance( startDate?: string, endDate?: string, @@ -604,6 +607,8 @@ export async function fetchStaffDeliveryPerformance( staffName: String(row.staffName ?? row.staffname ?? ""), orderCount: Number(row.orderCount ?? row.ordercount ?? 0), totalMinutes: Number(row.totalMinutes ?? row.totalminutes ?? 0), + itemKindCount: Number(row.itemKindCount ?? row.itemkindcount ?? 0), + itemQtyPicked: Number(row.itemQtyPicked ?? row.itemqtypicked ?? 0), }; }); } diff --git a/src/app/api/user/client.ts b/src/app/api/user/client.ts index 2040da72..648e8d95 100644 --- a/src/app/api/user/client.ts +++ b/src/app/api/user/client.ts @@ -164,6 +164,15 @@ export const updateUser = async ( if (response.status === 401) { throw new Error("Unauthorized: Please log in again"); } - throw new Error(`Failed to update user: ${response.status} ${response.statusText}`); + let detail = ""; + try { + const body = await response.json(); + detail = body?.error || body?.message || ""; + } catch { + // ignore parse errors + } + throw new Error( + `Failed to update user: ${response.status} ${response.statusText}${detail ? `. ${detail}` : ""}`, + ); } }; \ No newline at end of file diff --git a/src/components/CreateUser/CreateUser.tsx b/src/components/CreateUser/CreateUser.tsx index 2ad4c6b0..3166fed0 100644 --- a/src/components/CreateUser/CreateUser.tsx +++ b/src/components/CreateUser/CreateUser.tsx @@ -29,7 +29,7 @@ import { useForm, useFormContext, } from "react-hook-form"; -import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; +import { Check, Close, Error as ErrorIcon, RestartAlt } from "@mui/icons-material"; import { UserInputs, adminChangePassword, @@ -46,8 +46,9 @@ interface Props { auths: auth[]; } +/** FP-MTMS Version Checklist | Functions Ref. No. 62 | v1.0.0 | 2026-08-10 */ const CreateUser: React.FC = ({ rules, auths }) => { - console.log(auths); + // console.log(auths); const { t } = useTranslation("user"); const formProps = useForm(); const searchParams = useSearchParams(); @@ -172,7 +173,33 @@ const CreateUser: React.FC = ({ rules, auths }) => { router.replace("/settings/user"); } catch (e) { console.log(e); - setServerError(t("An error has occurred. Please try again later.")); + const msg = e instanceof Error ? e.message : String(e); + + if (msg.includes("USERNAME_NOT_AVAILABLE")) { + const text = t("Username is already taken"); + setServerError(text); + formProps.setError("username", { message: text, type: "validate" }); + } else if (msg.includes("NAME_NOT_AVAILABLE")) { + const text = t("Name is already taken"); + setServerError(text); + formProps.setError("name", { message: text, type: "validate" }); + } else if (msg.includes("STAFF_NO_NOT_AVAILABLE")) { + const text = t("Staff No is already taken"); + setServerError(text); + formProps.setError("staffNo", { message: text, type: "validate" }); + } else if (msg.includes("USER_WRONG_NEW_PWD")) { + setServerError(t("New password does not meet the rules")); + } else if (/\b400\b/.test(msg)) { + setServerError(t("Invalid request. Please check your input")); + } else if (/\b401\b/.test(msg) || /\b403\b/.test(msg)) { + setServerError(t("Unauthorized or no permission")); + } else if (/\b404\b/.test(msg)) { + setServerError(t("User Not Found")); + } else if (/\b500\b/.test(msg)) { + setServerError(t("Server error. Please try again later")); + } else { + setServerError(t("An error has occurred. Please try again later.")); + } } }, [router], @@ -212,7 +239,7 @@ const CreateUser: React.FC = ({ rules, auths }) => { label={t("User Detail")} icon={ hasErrorsInTab(0, errors) ? ( - + ) : undefined } iconPosition="end" diff --git a/src/components/CreateUser/UserDetail.tsx b/src/components/CreateUser/UserDetail.tsx index d8f5c133..6778d505 100644 --- a/src/components/CreateUser/UserDetail.tsx +++ b/src/components/CreateUser/UserDetail.tsx @@ -35,6 +35,11 @@ const UserDetail: React.FC = () => { required: "username required!", })} error={Boolean(errors.username)} + helperText={ + Boolean(errors.username) && errors.username?.message + ? t(errors.username.message) + : "" + } /> diff --git a/src/components/JoSearch/JoCreateFormModal.tsx b/src/components/JoSearch/JoCreateFormModal.tsx index 033e3480..41ddfb3f 100644 --- a/src/components/JoSearch/JoCreateFormModal.tsx +++ b/src/components/JoSearch/JoCreateFormModal.tsx @@ -57,7 +57,7 @@ const JoCreateFormModal: React.FC = ({ /* const handleAutoCompleteChange = useCallback( (event: SyntheticEvent, value: BomCombo, onChange: (...event: any[]) => void) => { - console.log("BOM changed to:", value); + // console.log("BOM changed to:", value); onChange(value.id); // 重置倍数为 1 @@ -98,7 +98,7 @@ const JoCreateFormModal: React.FC = ({ }, [bomCombo]); const handleAutoCompleteChange = useCallback( (event: SyntheticEvent, value: BomCombo, onChange: (...event: any[]) => void) => { - console.log("BOM changed to:", value); + // console.log("BOM changed to:", value); onChange(value.id); if (value.outputQty != null) { diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx index 46638b18..c6bbe1f9 100644 --- a/src/components/JoSearch/JoSearch.tsx +++ b/src/components/JoSearch/JoSearch.tsx @@ -265,13 +265,13 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT pageSize: pagingController.pageSize, }; const response = await fetchJos(params); - console.log("newPageFetch params:", params) - console.log("newPageFetch response:", response) + // console.log("newPageFetch params:", params) + // console.log("newPageFetch response:", response) if (response && response.records) { - console.log("newPageFetch - setting filteredJos with", response.records.length, "records"); + // console.log("newPageFetch - setting filteredJos with", response.records.length, "records"); setTotalCount(response.total); setFilteredJos(response.records); - console.log("newPageFetch - filteredJos set, first record id:", response.records[0]?.id); + // console.log("newPageFetch - filteredJos set, first record id:", response.records[0]?.id); } else { console.warn("newPageFetch - no response or no records"); setFilteredJos([]); diff --git a/src/components/JoWorkbench/JoWorkbenchSearch.tsx b/src/components/JoWorkbench/JoWorkbenchSearch.tsx index 0da4da8a..54792aa8 100644 --- a/src/components/JoWorkbench/JoWorkbenchSearch.tsx +++ b/src/components/JoWorkbench/JoWorkbenchSearch.tsx @@ -266,13 +266,13 @@ const JoWorkbenchSearch: React.FC = ({ defaultInputs, bomCombo, printerCo pageSize: pagingController.pageSize, }; const response = await fetchJosForWorkbench(params); - console.log("newPageFetch params:", params) - console.log("newPageFetch response:", response) + // console.log("newPageFetch params:", params) + // console.log("newPageFetch response:", response) if (response && response.records) { - console.log("newPageFetch - setting filteredJos with", response.records.length, "records"); + // console.log("newPageFetch - setting filteredJos with", response.records.length, "records"); setTotalCount(response.total); setFilteredJos(response.records); - console.log("newPageFetch - filteredJos set, first record id:", response.records[0]?.id); + // console.log("newPageFetch - filteredJos set, first record id:", response.records[0]?.id); } else { console.warn("newPageFetch - no response or no records"); setFilteredJos([]); diff --git a/src/components/JoWorkbench/newJobPickExecution.tsx b/src/components/JoWorkbench/newJobPickExecution.tsx index 10a4c661..e62f9d5a 100644 --- a/src/components/JoWorkbench/newJobPickExecution.tsx +++ b/src/components/JoWorkbench/newJobPickExecution.tsx @@ -618,7 +618,7 @@ const QrCodeModal: React.FC<{ ); }; -/** FP-MTMS Version Checklist | Functions Ref. No. 43 | v1.0.0 | 2026-08-03 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 43 | v1.0.1 | 2026-08-10 */ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCombo = [] }) => { const workbenchMode = true; const { t } = useTranslation("jo"); @@ -896,10 +896,8 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo data.pickOrderLines.forEach((line) => { // 用来记录这一行已经通过 lots 出现过的 lotId(避免 stockouts 再渲染一次) const lotIdSet = new Set(); - /** 已由有批次建議分配的量(加總後與 pick_order_line.requiredQty 的差額 = 無批次列應顯示的數),對齊 DO Workbench */ - let lotsAllocatedSumForLine = 0; - // lots:按 lotId 去重并合并 requiredQty(对齐 GoodPickExecutiondetail) + // lots:按 lotId 去重并合并 requiredQty(对齐 DO Workbench / GoodPickExecutiondetail) if (line.lots && line.lots.length > 0) { const lotMap = new Map(); @@ -916,7 +914,6 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo }); lotMap.forEach((lot: any) => { - lotsAllocatedSumForLine += Number(lot.requiredQty) || 0; if (lot.lotId != null) lotIdSet.add(lot.lotId); allLots.push({ @@ -945,20 +942,8 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo }); } - /** 工單 API 常在有揀貨後仍回傳 lots: [],缺口只在 stockouts;此時用非 noLot 的已揀量扣 POL(對齊實際剩餘) */ - const stockoutsPickedSumNonNoLot = (line.stockouts ?? []).reduce( - (acc: number, s: any) => { - if (!s || s.noLot) return acc; - return acc + (Number(s.qty) || 0); - }, - 0, - ); - const noLotRemainingBasis = - lotsAllocatedSumForLine > 0 - ? lotsAllocatedSumForLine - : stockoutsPickedSumNonNoLot; - // stockouts:用于“无 suggested lot / noLot”场景也显示并可 submit 0 闭环 + // 批號需求數:對齊 DO Workbench——用後端 stockout/SPL qty,不前端推 gap if (line.stockouts && line.stockouts.length > 0) { line.stockouts.forEach((stockout: any) => { const hasLot = stockout.lotId != null; @@ -970,6 +955,17 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo return; } + const stockoutRequiredQty = Number( + stockout?.requiredQty ?? + stockout?.suggestedPickQty ?? + stockout?.suggestedPickLotQty, + ); + const effectiveStockoutRequiredQty = Number.isFinite( + stockoutRequiredQty, + ) + ? stockoutRequiredQty + : Number(line.requiredQty) || 0; + allLots.push({ pickOrderLineId: line.id, itemId: line.itemId, @@ -996,19 +992,13 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo expiryDate: null, location: stockout.location || null, availableQty: stockout.availableQty ?? 0, - // 無批次列:有 SPL 時扣 suggested 合計;僅有 stockouts(lots 空)時扣已揀量(對齊 DO + workbench 僅 SOL 情境) - requiredQty: stockout.noLot - ? Math.max( - 0, - (Number(line.requiredQty) || 0) - noLotRemainingBasis, - ) - : Number(line.requiredQty) || 0, + requiredQty: effectiveStockoutRequiredQty, actualPickQty: stockout.qty ?? 0, processingStatus: stockout.status || "pending", lotAvailability: stockout.noLot ? "insufficient_stock" : "available", - suggestedPickLotId: null, + suggestedPickLotId: stockout.suggestedPickLotId ?? null, stockOutLineId: stockout.id || null, stockOutLineQty: stockout.qty ?? 0, stockOutLineStatus: stockout.status || null, @@ -3655,6 +3645,22 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo const isNoLotTailRow = (lot: any) => lot.noLot === true || lot.lotId == null || lot.lotId === undefined; + /** 同 POL 內對齊 DO Workbench:已掃/已完成在前,resuggest pending 在後 */ + const statusRank = (lot: any) => { + const st = String(lot?.stockOutLineStatus ?? "").toLowerCase(); + if ( + st === "completed" || + st === "partially_completed" || + st === "partially_complete" + ) { + return 0; + } + if (st === "checked") return 1; + if (st === "pending") return 2; + if (st === "rejected") return 3; + return 9; + }; + const sortedData = [...sourceData].sort((a, b) => { const efA = effectiveFloorOrder(a); const efB = effectiveFloorOrder(b); @@ -3674,6 +3680,10 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList, printerCo const bName = String(b.itemName || ""); if (aName !== bName) return aName.localeCompare(bName); + const ra = statusRank(a); + const rb = statusRank(b); + if (ra !== rb) return ra - rb; + const tailA = isNoLotTailRow(a) ? 1 : 0; const tailB = isNoLotTailRow(b) ? 1 : 0; if (tailA !== tailB) return tailA - tailB; diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 87b13fa5..7700330f 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -66,7 +66,7 @@ interface ProductProcessDetailProps { fromJosave?: boolean; } -/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.0 | 2026-08-05 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.1 | 2026-08-10 */ const ProductionProcessDetail: React.FC = ({ jobOrderId, onBack, @@ -77,7 +77,7 @@ const ProductionProcessDetail: React.FC = ({ const { t } = useTranslation(["productionProcess", "common"]); const { data: session } = useSession() as { data: SessionWithTokens | null }; const abilities = session?.abilities ?? session?.user?.abilities ?? []; - /** 「已完成」(Just Pass):僅 ADMIN */ + /** 「跳過」(Just Pass):僅 ADMIN */ const canAdminPass = hasAbility(abilities, AUTH.ADMIN); const currentUserId = session?.id ? parseInt(session.id) : undefined; const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); @@ -666,7 +666,8 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { const isPaused = statusLower === 'paused'; const isPending = statusLower === 'pending' || status === ''; const isPass = statusLower === 'pass'; - const isPassDisabled = isCompleted || isPass || !canAdminPass; + const isAutoPass = statusLower === 'autopass' || statusLower === 'auto pass'; + const isPassDisabled = isCompleted || isPass || isAutoPass || !canAdminPass; return ( @@ -773,6 +774,8 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { ) : isPaused ? ( + ) : isAutoPass ? ( + ) : isPass ? ( ) : ( diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index e8a85f9a..665d6ba9 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -178,7 +178,7 @@ function isWaitingQcPutAway( return s !== "completed" && s !== "rejected"; } -/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.8 | 2026-08-09 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.9 | 2026-08-10 */ const ProductProcessList: React.FC = ({ onSelectProcess, printerCombo, @@ -447,7 +447,7 @@ const ProductProcessList: React.FC = ({ return productionCache; }, [listTab, productionCache, pendingQcCache, putawayedCache, offPlanRows]); - // QC ready: same JO — all lines Completed/Pass (sibling 包裝 auto-Pass on backend) + has SIL + // QC ready: same JO — all lines Completed/Pass/autoPass (sibling 包裝 autoPass on backend) + has SIL const jobOrderQcReadyById = useMemo(() => { const byJobOrder = new Map(); for (const p of tabProcesses) { @@ -459,8 +459,8 @@ const ProductProcessList: React.FC = ({ const result = new Map(); const isDone = (status: unknown) => { - const s = String(status ?? "").trim().toLowerCase(); - return s === "completed" || s === "pass"; + const s = String(status ?? "").trim().toLowerCase().replace(/\s+/g, ""); + return s === "completed" || s === "pass" || s === "autopass"; }; byJobOrder.forEach((jobOrderProcesses, jobOrderId) => { diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx index 954dc843..42b9b7b8 100644 --- a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx +++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx @@ -55,6 +55,7 @@ interface ProductionProcessStepExecutionProps { jobOrderId?: number; // ✅ 添加 } +/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.0 | 2026-08-10 */ const ProductionProcessStepExecution: React.FC = ({ lineId, onBack, @@ -62,9 +63,18 @@ const ProductionProcessStepExecution: React.FC { - const { t } = useTranslation( ["common","jo"]); + const { t } = useTranslation( ["common","jo","productionProcess"]); const [lineDetail, setLineDetail] = useState(null); - const isCompleted = lineDetail?.status === "Completed" || lineDetail?.status === "Pass"; + const lineStatusNorm = String(lineDetail?.status ?? "") + .trim() + .toLowerCase() + .replace(/\s+/g, ""); + const isCompleted = + lineStatusNorm === "completed" || + lineStatusNorm === "pass" || + lineStatusNorm === "autopass"; + const isPassStatus = lineStatusNorm === "pass"; + const isAutoPassStatus = lineStatusNorm === "autopass"; const [outputData, setOutputData] = useState { - // Don't show time remaining if completed - if (lineDetail?.status === "Completed" || lineDetail?.status === "Pass") { + // Don't show time remaining if completed / pass / autoPass + const statusNorm = String(lineDetail?.status ?? "") + .trim() + .toLowerCase() + .replace(/\s+/g, ""); + if ( + statusNorm === "completed" || + statusNorm === "pass" || + statusNorm === "autopass" + ) { console.log("Line is completed"); setRemainingTime(null); setIsOverTime(false); @@ -553,9 +571,13 @@ const ProductionProcessStepExecution: React.FC - {lineDetail?.status === "Pass" ? ( + {isAutoPassStatus ? ( - {t("Passed Step")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo}) + {t("Auto Pass")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo}) + + ) : isPassStatus ? ( + + {t("Just Pass")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo}) ) : ( diff --git a/src/components/UserSearch/UserExcelSheetView.tsx b/src/components/UserSearch/UserExcelSheetView.tsx index 23107c30..86ceae45 100644 --- a/src/components/UserSearch/UserExcelSheetView.tsx +++ b/src/components/UserSearch/UserExcelSheetView.tsx @@ -328,7 +328,29 @@ const UserExcelSheetView: React.FC = ({ users }) => { } catch (error) { console.error("Failed to save user authorities", error); setAllUsers(cloneUserList(savedUsers)); - alert(t("Save failed. Please try again.", { defaultValue: "儲存失敗,請再試一次。" })); + + const msg = error instanceof Error ? error.message : String(error); + let text = t("Save failed. Please try again.", { + defaultValue: "儲存失敗,請再試一次。", + }); + if (msg.includes("USERNAME_NOT_AVAILABLE")) { + text = t("Username is already taken"); + } else if (msg.includes("USER_WRONG_NEW_PWD")) { + text = t("New password does not meet the rules"); + } else if (/\b400\b/.test(msg)) { + text = t("Invalid request. Please check your input"); + } else if ( + msg.includes("Unauthorized") || + /\b401\b/.test(msg) || + /\b403\b/.test(msg) + ) { + text = t("Unauthorized or no permission"); + } else if (/\b404\b/.test(msg)) { + text = t("User Not Found"); + } else if (/\b500\b/.test(msg)) { + text = t("Server error. Please try again later"); + } + alert(text); } finally { setIsSaving(false); saveInFlightRef.current = false; diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts index 47dd05f7..4f3156d3 100644 --- a/src/config/reportConfig.ts +++ b/src/config/reportConfig.ts @@ -17,6 +17,8 @@ export interface ReportField { allowInput?: boolean; // Allow user to input custom values (for select types) /** When checkbox is checked, disable these field names (by `name`) */ disablesFieldsWhenChecked?: string[]; + /** For date fields: restrict picker so value cannot be before today */ + minDate?: 'today'; } export type ReportResponseType = 'pdf' | 'excel'; @@ -383,4 +385,31 @@ export const REPORTS: ReportDefinition[] = [ { label: "店鋪編號 Shop Code", name: "shopCode", type: "text", required: false, placeholder: "e.g. S001" }, ], }, + { + /** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.0 | 2026-08-10 */ + id: "rep-018", + title: "送貨訂單與倉存單位不符報告", + apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-do-inventory-uom-mismatch`, + responseType: "excel", + fields: [ + { + label: "預計到貨日期 Estimated Arrival Date", + name: "deliveryDate", + type: "date", + required: true, + minDate: "today", + }, + { + label: "送貨訂單樓層 Floor", + name: "storeId", + type: "select", + required: false, + options: [ + { label: "全部 All (2F+4F)", value: "All" }, + { label: "2F", value: "2F" }, + { label: "4F", value: "4F" }, + ], + }, + ], + }, ] \ No newline at end of file diff --git a/src/i18n/en/chart.json b/src/i18n/en/chart.json index 0eb11e4d..189903a2 100644 --- a/src/i18n/en/chart.json +++ b/src/i18n/en/chart.json @@ -7,6 +7,8 @@ "board_processLive": "Process Live Board", "dateRange_lastDays": "Last {{d}} days", "delivery_colAvgMin": "Avg Min/Order", + "delivery_colItemKindCount": "Item Kind Count", + "delivery_colItemQtyPicked": "Item Qty Picked", "delivery_colPickCount": "Pick Count", "delivery_colStaff": "Staff", "delivery_colTotalMin": "Total Min", @@ -19,7 +21,7 @@ "delivery_ordersByDate": "Delivery Orders by Date", "delivery_ordersByDate_export": "Delivery_Orders_By_Date", "delivery_staff": "Staff", - "delivery_staffPerfCaption": "Per-person pick count & total duration for period", + "delivery_staffPerfCaption": "Per-person pick count, item kinds, picked qty & total duration for period", "delivery_staffPerfDateError": "Staff performance start date cannot be later than end date", "delivery_staffPerformanceTitle": "Staff Delivery Performance (Daily Pick Count & Duration)", "delivery_staffPlaceholder": "Leave empty for all", diff --git a/src/i18n/en/itemTracing.json b/src/i18n/en/itemTracing.json index 281b85ac..5a218103 100644 --- a/src/i18n/en/itemTracing.json +++ b/src/i18n/en/itemTracing.json @@ -286,12 +286,13 @@ "code.joStatus.storing": "Storing", "code.joStatus.PARTIAL": "Partial", "code.joStatus.partial": "Partial", - "code.productionStatus.Pass": "Pass", + "code.productionStatus.Pass": "Skip", "code.productionStatus.Completed": "Completed", "code.productionStatus.Pending": "Pending", "code.productionStatus.Paused": "Paused", "code.productionStatus.InProgress": "In progress", "code.productionStatus.Skip": "Skip", + "code.productionStatus.autoPass": "Auto Skipped", "continuousScanBlocked": "Finish current scan first", "nodeJoOut": "Job order material issue", "nodePoOut": "Purchase pick", diff --git a/src/i18n/en/jo.json b/src/i18n/en/jo.json index 17b9e286..06a212c3 100644 --- a/src/i18n/en/jo.json +++ b/src/i18n/en/jo.json @@ -272,7 +272,9 @@ "Overview": "Overview", "Packaging": "Packaging", "Partial quantity submitted. Please submit more or complete the order.": "Partial quantity submitted. Please submit more or complete the order.", - "Pass": "Pass", + "Pass": "Skip", + "Just Pass": "Skip", + "Auto Pass": "Auto Skipped", "Passed Step": "Passed Step", "Pause": "Pause", "Pause Reason": "Pause Reason", diff --git a/src/i18n/en/productionProcess.json b/src/i18n/en/productionProcess.json index af69d1f0..60b32361 100644 --- a/src/i18n/en/productionProcess.json +++ b/src/i18n/en/productionProcess.json @@ -123,7 +123,8 @@ "Job process detail: handler": "Handler", "Job process detail: process name": "Process Name", "Job process detail: time": "Time", - "Just Pass": "Just Pass", + "Just Pass": "Skip", + "Auto Pass": "Auto Skipped", "Last updated": "Last updated", "Lines with insufficient stock: ": "Lines with insufficient stock: ", "Lines with sufficient stock: ": "Lines with sufficient stock: ", diff --git a/src/i18n/en/user.json b/src/i18n/en/user.json index ff056e65..4ebea06d 100644 --- a/src/i18n/en/user.json +++ b/src/i18n/en/user.json @@ -46,5 +46,12 @@ "Failed to search by name": "Failed to search by name", "Failed to search by username": "Failed to search by username", "Staff No is required": "Staff No is required", - "User Not Found": "User Not Found" + "User Not Found": "User Not Found", + "Username is already taken": "Username is already taken. Please choose another.", + "Name is already taken": "Name is already taken. Please choose another.", + "Staff No is already taken": "Staff No is already taken. Please choose another.", + "New password does not meet the rules": "New password does not meet the rules. Please try again.", + "Invalid request. Please check your input": "Invalid request. Please check your input.", + "Unauthorized or no permission": "Unauthorized or no permission.", + "Server error. Please try again later": "Server error. Please try again later." } diff --git a/src/i18n/zh/chart.json b/src/i18n/zh/chart.json index d0fc2ddb..1c964bb7 100644 --- a/src/i18n/zh/chart.json +++ b/src/i18n/zh/chart.json @@ -7,6 +7,8 @@ "board_processLive": "工序即時看板", "dateRange_lastDays": "最近 {{d}} 天", "delivery_colAvgMin": "平均分鐘/單", + "delivery_colItemKindCount": "總揀貨款數", + "delivery_colItemQtyPicked": "揀貨數量", "delivery_colPickCount": "揀單數", "delivery_colStaff": "員工", "delivery_colTotalMin": "總分鐘", @@ -19,7 +21,7 @@ "delivery_ordersByDate": "按日期發貨單數量", "delivery_ordersByDate_export": "發貨單數量_按日期", "delivery_staff": "員工", - "delivery_staffPerfCaption": "週期內每人揀單數及總耗時(首揀至完成)", + "delivery_staffPerfCaption": "週期內每人揀單數、總揀貨款數、揀貨數量及總耗時(首揀至完成)", "delivery_staffPerfDateError": "員工發貨績效的起始日期不能晚於結束日期", "delivery_staffPerformanceTitle": "員工發貨績效(每日揀貨數量與耗時)", "delivery_staffPlaceholder": "不選則全部", diff --git a/src/i18n/zh/itemTracing.json b/src/i18n/zh/itemTracing.json index 031dee0a..d2a81834 100644 --- a/src/i18n/zh/itemTracing.json +++ b/src/i18n/zh/itemTracing.json @@ -286,12 +286,13 @@ "code.joStatus.storing": "待QC上架", "code.joStatus.PARTIAL": "部分完成", "code.joStatus.partial": "部分完成", - "code.productionStatus.Pass": "通過", + "code.productionStatus.Pass": "跳過", "code.productionStatus.Completed": "完成", "code.productionStatus.Pending": "待處理", "code.productionStatus.Paused": "已暫停", "code.productionStatus.InProgress": "進行中", "code.productionStatus.Skip": "跳過", + "code.productionStatus.autoPass": "已自動跳過", "continuousScanBlocked": "請先完成目前掃描", "nodeJoOut": "工單提料", "nodePoOut": "採購提料", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index e5a34dd8..84135db3 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -10,7 +10,8 @@ "Actual Pick Qty": "實際提料數量", "Add Bag": "新增包裝袋", "Add Record": "添加記錄", - "Just Pass": "通過", + "Just Pass": "跳過", + "Auto Pass": "已自動跳過", "Add Selected Items to Created Items": "將已選擇的物品添加到創建的物品中", "Add some entries!": "請添加條目", "All": "全部", @@ -275,7 +276,7 @@ "Overview": "總覽", "Packaging": "提料中", "Partial quantity submitted. Please submit more or complete the order.": "已提料部分數量。請提交更多或完成訂單。", - "Pass": "通過", + "Pass": "跳過", "Passed Step": "通過步驟", "Pause": "暫停", "Pause Reason": "暫停原因", diff --git a/src/i18n/zh/productionProcess.json b/src/i18n/zh/productionProcess.json index 2c339f30..fd1e0261 100644 --- a/src/i18n/zh/productionProcess.json +++ b/src/i18n/zh/productionProcess.json @@ -32,7 +32,7 @@ "Confirm": "確認", "Confirm cancel job order": "確認取消工單", "Confirm delete job order": "確認刪除工單", - "Confirm to Pass this Process?": "確認要通過此工序嗎?", + "Confirm to Pass this Process?": "確認要跳過此工序嗎?", "Confirm to update this Job Order?": "確認要完成此工單嗎?", "Consumed Qty": "消耗數量", "Continue": "繼續", @@ -123,7 +123,8 @@ "Job process detail: handler": "員工", "Job process detail: process name": "工序", "Job process detail: time": "時間", - "Just Pass": "已完成", + "Just Pass": "跳過", + "Auto Pass": "已自動跳過", "Last updated": "最後更新", "Lines with insufficient stock: ": "未能提料項目數量: ", "Lines with sufficient stock: ": "可提料項目數量: ", diff --git a/src/i18n/zh/user.json b/src/i18n/zh/user.json index 2f93027c..3eb62410 100644 --- a/src/i18n/zh/user.json +++ b/src/i18n/zh/user.json @@ -46,5 +46,12 @@ "Failed to search by name": "依名稱搜尋失敗", "Failed to search by username": "依使用者名稱搜尋失敗", "Staff No is required": "員工編號必填", - "User Not Found": "用戶不存在" + "User Not Found": "用戶不存在", + "Username is already taken": "用戶名稱已被使用,請換一個。", + "Name is already taken": "姓名已被使用,請換一個。", + "Staff No is already taken": "員工編號已被使用,請換一個。", + "New password does not meet the rules": "新密碼不符合規則,請重新輸入。", + "Invalid request. Please check your input": "請求資料不正確,請檢查後再試。", + "Unauthorized or no permission": "未授權或沒有權限。", + "Server error. Please try again later": "伺服器錯誤,請稍後再試。" }