From b6f41ecd3c793d383f7ef26192003f6c21b9f232 Mon Sep 17 00:00:00 2001 From: Harry Groves Date: Thu, 8 Oct 2026 12:20:33 +0800 Subject: [PATCH] EN support for scheduling page --- src/app/(main)/ps/PsRulesTab.tsx | 123 +++++++++-------- src/app/(main)/ps/page.tsx | 221 ++++++++++++++++--------------- src/i18n/en/scheduling.json | 122 ++++++++++++++++- src/i18n/zh/scheduling.json | 122 ++++++++++++++++- 4 files changed, 426 insertions(+), 162 deletions(-) diff --git a/src/app/(main)/ps/PsRulesTab.tsx b/src/app/(main)/ps/PsRulesTab.tsx index 51168f5f..793e4680 100644 --- a/src/app/(main)/ps/PsRulesTab.tsx +++ b/src/app/(main)/ps/PsRulesTab.tsx @@ -3,17 +3,18 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { CircularProgress } from "@mui/material"; import Clear from "@mui/icons-material/Clear"; +import { useTranslation } from "react-i18next"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; const WEEKDAYS = [ - { id: "MON", label: "一" }, - { id: "TUE", label: "二" }, - { id: "WED", label: "三" }, - { id: "THU", label: "四" }, - { id: "FRI", label: "五" }, - { id: "SAT", label: "六" }, - { id: "SUN", label: "日" }, + { id: "MON", labelKey: "weekdayMon" }, + { id: "TUE", labelKey: "weekdayTue" }, + { id: "WED", labelKey: "weekdayWed" }, + { id: "THU", labelKey: "weekdayThu" }, + { id: "FRI", labelKey: "weekdayFri" }, + { id: "SAT", labelKey: "weekdaySat" }, + { id: "SUN", labelKey: "weekdaySun" }, ] as const; type WeekdayId = (typeof WEEKDAYS)[number]["id"]; @@ -74,6 +75,7 @@ function numText(value: unknown, fallback: string): string { } export default function PsRulesTab({ items }: { items: ItemOption[] }) { + const { t } = useTranslation("scheduling"); const [plant, setPlant] = useState(emptyPlant); const [overrides, setOverrides] = useState>({}); const [loading, setLoading] = useState(true); @@ -91,7 +93,7 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { ); if (response.status === 401 || response.status === 403) return; if (!response.ok) { - alert("讀取排期規則失敗"); + alert(t("loadRulesFail")); return; } const data = await response.json(); @@ -138,7 +140,7 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { setOverrides(next); } catch (e) { console.error("schedule-rules Error:", e); - alert("讀取排期規則失敗"); + alert(t("loadRulesFail")); } finally { setLoading(false); } @@ -155,15 +157,15 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { const prepRed = Number(plant.prepareMoreRedLineDays); const prepSafety = Number(plant.prepareMoreSafetyStockDays); if (![red, safety, prepRed, prepSafety].every((n) => Number.isFinite(n) && n > 0)) { - alert("可用日必須是大於 0 的數字。"); + alert(t("daysMustBePositive")); return; } if (red > safety || prepRed > prepSafety) { - alert("觸發日不可大於補貨日。"); + alert(t("triggerNotAboveReplenish")); return; } if (!plant.coffeeDays.length || !plant.teaDays.length || !plant.lemonDays.length) { - alert("類別 A / B / C 至少要各選一天可生產。"); + alert(t("categoryNeedsDay")); return; } plantLock.current = true; @@ -186,13 +188,13 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { if (response.status === 401 || response.status === 403) return; if (!response.ok) { const body = await response.json().catch(() => ({})); - alert(body.error || "儲存失敗"); + alert(body.error || t("saveFail")); return; } - alert("已儲存。下次按「預測排期」會用新規則。"); + alert(t("rulesSaved")); } catch (e) { console.error("save schedule-rules Error:", e); - alert("儲存失敗"); + alert(t("saveFail")); } finally { setSavingPlant(false); plantLock.current = false; @@ -222,21 +224,21 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { return (

- 可用日 = 存貨 ÷ 每日平均出貨量。低於觸發日就安排生產,並補到補貨日。留空的物料跟隨這裡的預設。已產生的排期要再按「預測排期」才會更新。 + {t("rulesIntro")}

-

預設規則

+

{t("defaultRules")}

setPlant((prev) => ({ ...prev, redLineDays: value }))} onSafety={(value) => setPlant((prev) => ({ ...prev, safetyStockDays: value }))} /> setPlant((prev) => ({ ...prev, prepareMoreRedLineDays: value }))} @@ -247,33 +249,33 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) {
setPlant((prev) => ({ ...prev, prepareMoreDays: days }))} /> setPlant((prev) => ({ ...prev, noProductionDays: days }))} />
setPlant((prev) => ({ ...prev, coffeeDays: days }))} onSkip={(skip) => setPlant((prev) => ({ ...prev, coffeeSkipPrepareMore: skip }))} /> setPlant((prev) => ({ ...prev, teaDays: days }))} onSkip={(skip) => setPlant((prev) => ({ ...prev, teaSkipPrepareMore: skip }))} /> setPlant((prev) => ({ ...prev, lemonDays: days }))} @@ -288,18 +290,18 @@ export default function PsRulesTab({ items }: { items: ItemOption[] }) { className="inline-flex items-center gap-2 rounded-lg bg-blue-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-600 disabled:opacity-50" > {savingPlant && } - 儲存預設 + {t("saveDefaults")}
-

個別物料

+

{t("perItem")}

setFilter(e.target.value)} - placeholder="搜尋物料編號或名稱" + placeholder={t("searchItem")} className="w-56 rounded border border-slate-300 bg-white px-2 py-1 text-sm dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100" />
- - - - - - + + + + + + @@ -361,15 +363,21 @@ function DayPair({ onRed: (value: string) => void; onSafety: (value: string) => void; }) { + const { t } = useTranslation("scheduling"); return (
{title}
-
- 低於 - - 日生產,補至 - - 日 +
+
+ {t("below")} + + {t("produceUpTo")} +
+
+ {t("topUpTo")} + + {t("days")} +
); @@ -399,6 +407,7 @@ function DayChecks({ onChange: (days: WeekdayId[]) => void; disabled?: boolean; }) { + const { t } = useTranslation("scheduling"); return (
{label ? ( @@ -417,13 +426,13 @@ function DayChecks({ on ? selected.filter((id) => id !== day.id) : [...selected, day.id] ) } - className={`h-8 w-8 rounded border text-sm ${ + className={`h-8 min-w-8 rounded border px-1.5 text-xs ${ on ? "border-blue-500 bg-blue-500 text-white" : "border-slate-300 bg-white text-slate-600 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300" } disabled:opacity-40`} > - {day.label} + {t(day.labelKey)} ); })} @@ -445,13 +454,14 @@ function CategoryDays({ onDays: (days: WeekdayId[]) => void; onSkip: (skip: boolean) => void; }) { + const { t } = useTranslation("scheduling"); return (
-
{label}
+
{label}
); @@ -474,6 +484,7 @@ function ItemRuleRow({ produceDays: [], skipPrepareMore: null, }); + const { t } = useTranslation("scheduling"); const [draft, setDraft] = useState(value ?? blank()); const [saving, setSaving] = useState(false); const lock = useRef(false); @@ -491,15 +502,15 @@ function ItemRuleRow({ const red = next.redLineDays.trim() === "" ? null : Number(next.redLineDays); const safety = next.safetyStockDays.trim() === "" ? null : Number(next.safetyStockDays); if ((red != null && (!Number.isFinite(red) || red <= 0)) || (safety != null && (!Number.isFinite(safety) || safety <= 0))) { - alert("可用日必須是大於 0 的數字,或留空跟隨預設。"); + alert(t("daysBlankFollowDefault")); return; } if (red != null && safety != null && red > safety) { - alert("觸發日不可大於補貨日。"); + alert(t("triggerNotAboveReplenish")); return; } if (next.useCustomProduceDays && next.produceDays.length === 0) { - alert("自訂可生產日至少要選一天。"); + alert(t("customDaysRequired")); return; } const empty = @@ -522,13 +533,13 @@ function ItemRuleRow({ if (response.status === 401 || response.status === 403) return; if (!response.ok) { const body = await response.json().catch(() => ({})); - alert(body.error || "儲存失敗"); + alert(body.error || t("saveFail")); return; } onSaved(empty ? null : next); } catch (e) { console.error("setItemPsRule Error:", e); - alert("儲存失敗"); + alert(t("saveFail")); } finally { setSaving(false); lock.current = false; @@ -547,7 +558,7 @@ function ItemRuleRow({ type="number" min={0} step={0.1} - placeholder="預設" + placeholder={t("defaultPlaceholder")} value={draft.redLineDays} onChange={(e) => setDraft((prev) => ({ ...prev, redLineDays: e.target.value }))} onBlur={() => void persist(draft)} @@ -559,7 +570,7 @@ function ItemRuleRow({ type="number" min={0} step={0.1} - placeholder="預設" + placeholder={t("defaultPlaceholder")} value={draft.safetyStockDays} onChange={(e) => setDraft((prev) => ({ ...prev, safetyStockDays: e.target.value }))} onBlur={() => void persist(draft)} @@ -581,7 +592,7 @@ function ItemRuleRow({ if (!e.target.checked) void persist(next); }} /> - 自訂 + {t("custom")} - - - + + +
@@ -761,8 +771,10 @@ export default function ProductionSchedulePage() {

- 排期詳細: {selectedPs?.id} ( - {formatBackendDate(selectedPs?.produceAt)}) + {t("detailTitle", { + id: selectedPs?.id, + date: formatBackendDate(selectedPs?.produceAt), + })}

@@ -770,34 +782,34 @@ export default function ProductionSchedulePage() {
@@ -856,7 +868,7 @@ export default function ProductionSchedulePage() { ) : ( )} - 自動生成工單 + {t("generateJobs")} @@ -884,7 +896,7 @@ export default function ProductionSchedulePage() { />

- 準備生成預計排期 + {t("forecastDialogTitle")}

@@ -892,7 +904,7 @@ export default function ProductionSchedulePage() { htmlFor="forecast-start" className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300" > - 開始日期 + {t("startDate")} - 排期日數 + {t("scheduleDays")} setIsForecastDialogOpen(false)} className="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600" > - 取消 + {t("cancel")}
@@ -971,10 +983,10 @@ export default function ProductionSchedulePage() { />

- 匯出送貨單數量 + {t("exportDoQty")}

- 選擇日期範圍(僅含具 BOM 的物料,數量為庫存單位) + {t("exportDoQtyHint")}

@@ -982,7 +994,7 @@ export default function ProductionSchedulePage() { htmlFor="do-qty-export-from" className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300" > - 開始日期 + {t("startDate")} - 結束日期 + {t("endDate")} - 取消 + {t("cancel")}
@@ -1052,16 +1064,16 @@ export default function ProductionSchedulePage() { />

- 匯出排期/物料用量預計 + {t("exportPlanDialogTitle")}

- 選擇要匯出的起始日期 + {t("exportPlanHint")}

setIsExportDialogOpen(false)} className="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600" > - 取消 + {t("cancel")}
@@ -1115,7 +1127,7 @@ export default function ProductionSchedulePage() { id="settings-panel-title" className="text-lg font-semibold text-slate-900 dark:text-white" > - 排期設定 + {t("scheduleSettings")}
{settingsTab === "items" && ( @@ -1131,18 +1143,18 @@ export default function ProductionSchedulePage() { ) : ( )} - 刷新系統庫存 + {t("refreshStock")}
@@ -1177,7 +1189,7 @@ export default function ProductionSchedulePage() { : "border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400" }`} > - 物料設定 + {t("tabItems")} {settingsTab === "items" && (

- 預設為過去 30 天(含今日)。設定排期每天出貨量、設定排期庫存可編輯並按列儲存。 + {t("itemsHint")}

)} {settingsTab === "rules" ? ( @@ -1215,16 +1227,16 @@ export default function ProductionSchedulePage() {
物料編號物料名稱觸發日補貨日可生產日多備{t("itemCode")}{t("itemName")}{t("triggerDays")}{t("replenishDays")}{t("productionDays")}{t("extraStock")}
@@ -617,7 +628,7 @@ function ItemRuleRow({ ) : value ? ( } @@ -675,7 +685,7 @@ export default function ProductionSchedulePage() { {/* Query Bar */}
- 搜索 + {t("search")}
@@ -701,16 +711,16 @@ export default function ProductionSchedulePage() {
- 詳細 + {t("detail")} - 生產日期 + {t("productionDate")} - 預計生產數 + {t("estProdQty")} - 成品款數 + {t("fgCount")}
- 工單號 + {t("jobOrder")} - 物料編號 + {t("itemCode")} - 物料名稱 + {t("itemName")} - 每日平均出貨量 + {t("avgDailyOut")} - 出貨前預計存貨量 + {t("stockBeforeOut")} - 單位 + {t("unit")} - 可用日 + {t("daysOfCover")} - 生產量(批) + {t("batchQty")} - 預計生產包數 + {t("estPacks")} - 優先度 + {t("priority")}
- - - - - - - - - - + + + + + + + + + + @@ -1284,6 +1296,7 @@ function DailyOutRow({ fakeOnHandClearing: boolean; formatNum: (n: any) => string; }) { + const { t } = useTranslation("scheduling"); const [editQty, setEditQty] = useState( row.dailyQty != null ? String(row.dailyQty) : "" ); @@ -1335,7 +1348,7 @@ function DailyOutRow({ type="button" disabled={fakeOnHandClearing} onClick={() => onClearFakeOnHand(row.itemCode)} - title="清除設定排期庫存" + title={t("clearScheduleStock")} className="rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600 disabled:opacity-50 dark:hover:bg-slate-600 dark:hover:text-slate-200" > {fakeOnHandClearing ? : } @@ -1365,7 +1378,7 @@ function DailyOutRow({ type="button" disabled={clearing} onClick={() => onClear(row.itemCode)} - title="清除設定排期每天出貨量" + title={t("clearDailyOut")} className="rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600 disabled:opacity-50 dark:hover:bg-slate-600 dark:hover:text-slate-200" > {clearing ? : } diff --git a/src/i18n/en/scheduling.json b/src/i18n/en/scheduling.json index 3531f16b..711c0340 100644 --- a/src/i18n/en/scheduling.json +++ b/src/i18n/en/scheduling.json @@ -1,5 +1,125 @@ { "title": "Scheduling", "Scheduling": "Scheduling", - "ps": "Scheduling" + "ps": "Scheduling", + "pageTitle": "Production schedule", + "scheduleSettings": "Schedule settings", + "exportPlan": "Export plan / material demand", + "exportDoQty": "Export delivery qty", + "forecast": "Forecast schedule", + "productionDate": "Production date", + "search": "Search", + "detail": "Detail", + "estProdQty": "Planned qty", + "fgCount": "Finished goods", + "detailTitle": "Schedule {{id}} ({{date}})", + "jobOrder": "Job order", + "itemCode": "Item code", + "itemName": "Item name", + "avgDailyOut": "Avg daily outbound", + "stockBeforeOut": "Stock before outbound", + "unit": "Unit", + "daysOfCover": "Days of cover", + "batchQty": "Batches", + "estPacks": "Planned packs", + "priority": "Priority", + "generateJobs": "Generate job orders", + "close": "Close", + "forecastDialogTitle": "Generate forecast", + "startDate": "Start date", + "scheduleDays": "Days to schedule", + "cancel": "Cancel", + "calculateForecast": "Calculate forecast", + "exportDoQtyHint": "Choose a date range. Only items with a BOM are included, and quantity is in the stock unit.", + "endDate": "End date", + "export": "Export", + "exportPlanDialogTitle": "Export schedule and material usage", + "exportPlanHint": "Choose the start date to export.", + "fromDate": "From date", + "refreshStock": "Refresh stock", + "exportScheduleStock": "Export stock", + "importScheduleStock": "Import stock (overwrite)", + "tabItems": "Items", + "tabRules": "Rules", + "itemsHint": "Defaults to the past 30 days, including today. Schedule stock and daily outbound can be edited and are saved per row.", + "onHand": "On hand", + "scheduleStock": "Schedule stock", + "pastAvgOut": "Past avg outbound", + "dailyOutOverride": "Daily outbound override", + "categoryA": "Category A", + "categoryB": "Category B", + "categoryC": "Category C", + "clearScheduleStock": "Clear schedule stock", + "clearDailyOut": "Clear daily outbound override", + "forecastInvalid": "Enter a start date and a number of days (1 or more).", + "forecastOk": "Forecast calculated.", + "forecastError": "Calculation failed: {{status}} - {{detail}}", + "forecastUnknown": "Something went wrong.", + "doQtyDatesRequired": "Select a start date and an end date.", + "doQtyDateOrder": "The start date cannot be after the end date.", + "doQtyExportFail": "Could not export delivery quantities.", + "exportFromRequired": "Select a from date.", + "exportFail": "Could not export the file.", + "detailMissingId": "Cannot open this schedule because the schedule ID is missing.", + "serverError": "Server error {{status}}: {{detail}}", + "networkError": "Could not load the schedule. Check the connection and try again.", + "operationDone": "Done.", + "jobsCreated": "Created {{count}} job orders.", + "jobsNone": "No new job orders were created. These finished goods may already have a job in progress.", + "generateJobsFail": "Could not generate job orders.", + "refreshStockFailStatus": "Could not refresh stock ({{status}}){{detail}}", + "refreshStockFail": "Could not refresh stock.", + "excelNoSheet": "The Excel file has no worksheet.", + "excelEmpty": "The Excel file is empty.", + "excelNoItemCode": "No valid item codes were found.", + "importOutsideBom": "These item codes are outside the schedule BOM. The forecast will not use them; they will only be saved as stock overrides.\n\n{{preview}}", + "importOutsideBomMore": " ({{count}} in total)", + "sessionExpired": "Your session has expired, or you do not have permission. Sign in and try again.", + "importFailStatus": "Import failed (status {{status}}).{{detail}}", + "serverMessage": "\nServer message: {{message}}", + "importOk": "Imported {{count}} schedule stock rows.", + "importFailFormat": "Import failed. Check the file format.", + "saveFail": "Could not save.", + "confirmClearDailyOut": "Clear the daily outbound override for {{itemCode}}?", + "clearFail": "Could not clear.", + "updateFail": "Could not update.", + "confirmClearScheduleStock": "Clear the schedule stock override for this item?", + "loadRulesFail": "Could not load the schedule rules.", + "daysMustBePositive": "Days of cover must be a number greater than 0.", + "triggerNotAboveReplenish": "Trigger days cannot be greater than replenish days.", + "categoryNeedsDay": "Categories A, B, and C each need at least one production day.", + "rulesSaved": "Saved. Run Forecast schedule again to apply these rules.", + "rulesIntro": "Days of cover = stock ÷ average daily outbound. Production is scheduled when cover falls below the trigger, and stock is topped up to the replenish days. Items left blank follow these defaults. Schedules already created change only after you run Forecast schedule again.", + "defaultRules": "Default rules", + "normalDays": "Normal days", + "extraDays": "Extra-stock days", + "below": "Below", + "produceUpTo": "days", + "topUpTo": "Top up to", + "days": "days", + "extraWeekdays": "Extra-stock weekdays", + "noProductionUncategorised": "No production for unclassified items", + "skipExtra": "Skip extra stock", + "saveDefaults": "Save defaults", + "perItem": "Per item", + "searchItem": "Search item code or name", + "customOnly": "Custom rules only", + "triggerDays": "Trigger days", + "replenishDays": "Replenish days", + "productionDays": "Production days", + "extraStock": "Extra stock", + "daysBlankFollowDefault": "Days of cover must be greater than 0, or leave the field blank to follow the default.", + "customDaysRequired": "Choose at least one custom production day.", + "defaultPlaceholder": "Default", + "custom": "Custom", + "followDefault": "Follow default", + "applyExtra": "Apply extra stock", + "clearItemRule": "Clear this item's custom rule", + "weekdayMon": "Mon", + "weekdayTue": "Tue", + "weekdayWed": "Wed", + "weekdayThu": "Thu", + "weekdayFri": "Fri", + "weekdaySat": "Sat", + "weekdaySun": "Sun" } diff --git a/src/i18n/zh/scheduling.json b/src/i18n/zh/scheduling.json index 7d8af610..29180b11 100644 --- a/src/i18n/zh/scheduling.json +++ b/src/i18n/zh/scheduling.json @@ -1,5 +1,125 @@ { "title": "排程", "Scheduling": "排程", - "ps": "排程" + "ps": "排程", + "pageTitle": "生產排程", + "scheduleSettings": "排期設定", + "exportPlan": "匯出計劃/物料需求Excel", + "exportDoQty": "匯出送貨單數量", + "forecast": "預測排期", + "productionDate": "生產日期", + "search": "搜索", + "detail": "詳細", + "estProdQty": "預計生產數", + "fgCount": "成品款數", + "detailTitle": "排期詳細: {{id}} ({{date}})", + "jobOrder": "工單號", + "itemCode": "物料編號", + "itemName": "物料名稱", + "avgDailyOut": "每日平均出貨量", + "stockBeforeOut": "出貨前預計存貨量", + "unit": "單位", + "daysOfCover": "可用日", + "batchQty": "生產量(批)", + "estPacks": "預計生產包數", + "priority": "優先度", + "generateJobs": "自動生成工單", + "close": "關閉", + "forecastDialogTitle": "準備生成預計排期", + "startDate": "開始日期", + "scheduleDays": "排期日數", + "cancel": "取消", + "calculateForecast": "計算預測排期", + "exportDoQtyHint": "選擇日期範圍(僅含具 BOM 的物料,數量為庫存單位)", + "endDate": "結束日期", + "export": "匯出", + "exportPlanDialogTitle": "匯出排期/物料用量預計", + "exportPlanHint": "選擇要匯出的起始日期", + "fromDate": "起始日期", + "refreshStock": "刷新系統庫存", + "exportScheduleStock": "匯出排期庫存 Excel", + "importScheduleStock": "匯入排期庫存 Excel(覆蓋設定)", + "tabItems": "物料設定", + "tabRules": "排期規則", + "itemsHint": "預設為過去 30 天(含今日)。設定排期每天出貨量、設定排期庫存可編輯並按列儲存。", + "onHand": "庫存", + "scheduleStock": "設定排期庫存", + "pastAvgOut": "過去平均出貨量", + "dailyOutOverride": "設定排期每天出貨量", + "categoryA": "類別 A", + "categoryB": "類別 B", + "categoryC": "類別 C", + "clearScheduleStock": "清除設定排期庫存", + "clearDailyOut": "清除設定排期每天出貨量", + "forecastInvalid": "請輸入有效的開始日期及日數(1 或以上)。", + "forecastOk": "成功計算排期!", + "forecastError": "計算錯誤: {{status}} - {{detail}}", + "forecastUnknown": "發生不明狀況.", + "doQtyDatesRequired": "請選擇開始及結束日期。", + "doQtyDateOrder": "開始日期不可晚於結束日期。", + "doQtyExportFail": "匯出送貨單數量失敗。", + "exportFromRequired": "請選擇起始日期。", + "exportFail": "匯出失敗。", + "detailMissingId": "無法開啟詳細資料:缺少排期編號。", + "serverError": "伺服器錯誤 {{status}}: {{detail}}", + "networkError": "無法載入排期,請檢查連線後再試。", + "operationDone": "操作完成。", + "jobsCreated": "成功建立 {{count}} 張工單!", + "jobsNone": "沒有建立新工單(可能因為成品已有生產中的工單).", + "generateJobsFail": "生成工單失敗。", + "refreshStockFailStatus": "刷新庫存失敗({{status}}){{detail}}", + "refreshStockFail": "刷新庫存失敗。", + "excelNoSheet": "Excel 沒有工作表。", + "excelEmpty": "Excel 內容為空。", + "excelNoItemCode": "找不到任何有效的物料編號。", + "importOutsideBom": "注意:以下物料編號不在排期 BOM 範圍內,預測不會受影響,只會寫入覆蓋表。\n\n{{preview}}", + "importOutsideBomMore": " 等共 {{count}} 筆", + "sessionExpired": "登入已過期或沒有權限,請重新登入後再試。", + "importFailStatus": "匯入失敗(狀態碼 {{status}})。{{detail}}", + "serverMessage": "\n伺服器訊息:{{message}}", + "importOk": "已成功匯入並更新 {{count}} 筆排期庫存。", + "importFailFormat": "匯入失敗,請檢查檔案格式。", + "saveFail": "儲存失敗", + "confirmClearDailyOut": "確定要清除{{itemCode}}的設定排期每天出貨量嗎?", + "clearFail": "清除失敗", + "updateFail": "設定失敗", + "confirmClearScheduleStock": "確定要清除此物料的設定排期庫存嗎?", + "loadRulesFail": "讀取排期規則失敗", + "daysMustBePositive": "可用日必須是大於 0 的數字。", + "triggerNotAboveReplenish": "觸發日不可大於補貨日。", + "categoryNeedsDay": "類別 A / B / C 至少要各選一天可生產。", + "rulesSaved": "已儲存。下次按「預測排期」會用新規則。", + "rulesIntro": "可用日 = 存貨 ÷ 每日平均出貨量。低於觸發日就安排生產,並補到補貨日。留空的物料跟隨這裡的預設。已產生的排期要再按「預測排期」才會更新。", + "defaultRules": "預設規則", + "normalDays": "一般日", + "extraDays": "多備日", + "below": "低於", + "produceUpTo": "日生產", + "topUpTo": "補至", + "days": "日", + "extraWeekdays": "多備星期", + "noProductionUncategorised": "未分類物料不生產", + "skipExtra": "不套用多備", + "saveDefaults": "儲存預設", + "perItem": "個別物料", + "searchItem": "搜尋物料編號或名稱", + "customOnly": "只顯示已自訂", + "triggerDays": "觸發日", + "replenishDays": "補貨日", + "productionDays": "可生產日", + "extraStock": "多備", + "daysBlankFollowDefault": "可用日必須是大於 0 的數字,或留空跟隨預設。", + "customDaysRequired": "自訂可生產日至少要選一天。", + "defaultPlaceholder": "預設", + "custom": "自訂", + "followDefault": "跟隨預設", + "applyExtra": "套用多備", + "clearItemRule": "清除此物料的自訂規則", + "weekdayMon": "一", + "weekdayTue": "二", + "weekdayWed": "三", + "weekdayThu": "四", + "weekdayFri": "五", + "weekdaySat": "六", + "weekdaySun": "日" }
物料編號物料名稱單位庫存設定排期庫存過去平均出貨量設定排期每天出貨量類別 A類別 B類別 C{t("itemCode")}{t("itemName")}{t("unit")}{t("onHand")}{t("scheduleStock")}{t("pastAvgOut")}{t("dailyOutOverride")}{t("categoryA")}{t("categoryB")}{t("categoryC")}