Browse Source

Merge branch 'production' into stockTakeUpdate

stockTakeUpdate
CANCERYS\kw093 6 days ago
parent
commit
be4c90ee89
18 changed files with 880 additions and 368 deletions
  1. +1
    -1
      src/app/api/jo/actions.ts
  2. +2
    -0
      src/app/api/shop/actions.ts
  3. +7
    -7
      src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx
  4. +36
    -19
      src/components/ProductionProcess/ProductionOutputForm.tsx
  5. +35
    -24
      src/components/ProductionProcess/ProductionOutputFormPage.tsx
  6. +10
    -1
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  7. +14
    -7
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  8. +1
    -1
      src/components/ProductionProcess/ProductionProcessList.tsx
  9. +68
    -37
      src/components/ProductionProcess/ProductionProcessStepExecution.tsx
  10. +678
    -259
      src/components/Shop/RouteBoard.tsx
  11. +2
    -0
      src/i18n/en/jo.json
  12. +2
    -0
      src/i18n/en/productionProcess.json
  13. +1
    -1
      src/i18n/en/routeboard.json
  14. +9
    -5
      src/i18n/en/shop.json
  15. +2
    -0
      src/i18n/zh/jo.json
  16. +2
    -0
      src/i18n/zh/productionProcess.json
  17. +1
    -1
      src/i18n/zh/routeboard.json
  18. +9
    -5
      src/i18n/zh/shop.json

+ 1
- 1
src/app/api/jo/actions.ts View File

@@ -906,7 +906,7 @@ export const fetchAllJoborderProductProcessInfo = cache(async (type?: string | n
);
});

/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */
/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */
export const fetchJoborderProductProcessesPage = cache(async (params: {
/** Job order / process date(YYYY-MM-DD) */
date?: string | null;


+ 2
- 0
src/app/api/shop/actions.ts View File

@@ -965,6 +965,8 @@ export interface TruckLaneRenameLogResponse {
oldDepartureTime?: string | null;
newDepartureTime?: string | null;
ticketCount: number;
/** `truck.id` of shops in the renamed lane, comma-separated. */
truckIds?: string | null;
}

export const previewTruckLaneRenameAction = async (


+ 7
- 7
src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx View File

@@ -203,13 +203,13 @@ const TruckScheduleDashboard: React.FC = () => {
return '-';
};

// Compare a timestamp against the selected date (today/tomorrow/...) and return a short label.
// Used to avoid confusion when picking crosses midnight.
// Compare a timestamp against calendar today. Same-day picks show no chip;
// only midnight-crossing (yesterday / tomorrow / N days) gets a label.
// Does not filter rows — trucklane tickets for the selected delivery date still show.
const relativeDayLabel = useCallback(
(dateTimeData: string | number[] | null, dateOption: string): string | null => {
(dateTimeData: string | number[] | null): string | null => {
if (!dateTimeData) return null;
const dateOffset = getDateOffset(dateOption);
const baseDate = dayjs().startOf('day').add(dateOffset, 'day');
const baseDate = dayjs().startOf('day');

let dt: dayjs.Dayjs;
if (Array.isArray(dateTimeData)) {
@@ -518,8 +518,8 @@ const TruckScheduleDashboard: React.FC = () => {
filteredData.map((row, index) => {
const timeRemaining = calculateTimeRemaining(row, selectedDate);
const chipColor = getTimeChipColor(row, selectedDate);
const startRel = relativeDayLabel(row.firstTicketStartTime, selectedDate);
const endRel = relativeDayLabel(row.lastTicketEndTime, selectedDate);
const startRel = relativeDayLabel(row.firstTicketStartTime);
const endRel = relativeDayLabel(row.lastTicketEndTime);
return (
<TableRow


+ 36
- 19
src/components/ProductionProcess/ProductionOutputForm.tsx View File

@@ -17,25 +17,42 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { useTranslation } from "react-i18next";
import { UpdateProductProcessLineQtyRequest } from "@/app/api/jo/actions";

type QtyInput = number | "";

export type ProductionOutputFormData = Omit<
UpdateProductProcessLineQtyRequest,
| "outputFromProcessQty"
| "byproductQty"
| "defectQty"
| "defect2Qty"
| "defect3Qty"
| "scrapQty"
> & {
outputFromProcessQty: QtyInput;
byproductQty: QtyInput;
defectQty: QtyInput;
defect2Qty: QtyInput;
defect3Qty: QtyInput;
scrapQty: QtyInput;
};

const parseQtyInput = (raw: string): QtyInput => {
if (raw === "") return "";
const n = parseInt(raw, 10);
return Number.isNaN(n) ? "" : n;
};

export const toSubmitQty = (v: QtyInput | null | undefined): number =>
v === "" || v == null || Number.isNaN(Number(v)) ? 0 : Number(v);

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

/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */
const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
outputData,
setOutputData,
@@ -68,7 +85,7 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
onChange={(e) =>
setOutputData({
...outputData,
outputFromProcessQty: parseInt(e.target.value) || 0,
outputFromProcessQty: parseQtyInput(e.target.value),
})
}
/>
@@ -103,7 +120,7 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
onChange={(e) =>
setOutputData({
...outputData,
byproductQty: parseInt(e.target.value) || 0,
byproductQty: parseQtyInput(e.target.value),
})
}
/>
@@ -138,7 +155,7 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
onChange={(e) =>
setOutputData({
...outputData,
defectQty: parseInt(e.target.value) || 0,
defectQty: parseQtyInput(e.target.value),
})
}
/>
@@ -173,7 +190,7 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
onChange={(e) =>
setOutputData({
...outputData,
scrapQty: parseInt(e.target.value) || 0,
scrapQty: parseQtyInput(e.target.value),
})
}
/>
@@ -207,4 +224,4 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({
);
};

export default ProductionOutputForm;
export default ProductionOutputForm;

+ 35
- 24
src/components/ProductionProcess/ProductionOutputFormPage.tsx View File

@@ -5,47 +5,47 @@ import {
Button,
Typography,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useTranslation } from "react-i18next";
import {
fetchProductProcessLineDetail,
updateProductProcessLineQty,
UpdateProductProcessLineQtyRequest,
JobOrderProcessLineDetailResponse,
} from "@/app/api/jo/actions";
import ProductionOutputForm from "./ProductionOutputForm";
import ProductionOutputForm, {
ProductionOutputFormData,
toSubmitQty,
} from "./ProductionOutputForm";

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

/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */
const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
lineId,
onBack,
}) => {
const { t } = useTranslation();
const { t } = useTranslation(["common", "jo", "productionProcess"]);
const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
byproductUom: string;
}>({
const [outputData, setOutputData] = useState<ProductionOutputFormData>({
productProcessLineId: lineId ?? 0,
outputFromProcessQty: 0,
outputFromProcessQty: "",
outputFromProcessUom: "",
defectQty: 0,
defectQty: "",
defectUom: "",
defect2Qty: 0,
defect2Qty: "",
defect2Uom: "",
defect3Qty: 0,
defect3Qty: "",
defect3Uom: "",
defectDescription: "",
defectDescription2: "",
defectDescription3: "",
scrapQty: 0,
scrapQty: "",
scrapUom: "",
byproductName: "",
byproductQty: 0,
byproductQty: "",
byproductUom: "",
});

@@ -60,14 +60,14 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
setOutputData((prev) => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessQty: (detail as any).outputFromProcessQty || "",
outputFromProcessUom: (detail as any).outputFromProcessUom || "",
defectQty: detail.defectQty || 0,
defectQty: detail.defectQty || "",
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
scrapQty: detail.scrapQty || "",
scrapUom: detail.scrapUom || "",
byproductName: detail.byproductName || "",
byproductQty: detail.byproductQty || 0,
byproductQty: detail.byproductQty || "",
byproductUom: detail.byproductUom || "",
}));
})
@@ -84,6 +84,12 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
await updateProductProcessLineQty({
...outputData,
productProcessLineId: lineDetail.id || 0,
outputFromProcessQty: toSubmitQty(outputData.outputFromProcessQty),
byproductQty: toSubmitQty(outputData.byproductQty),
defectQty: toSubmitQty(outputData.defectQty),
defect2Qty: toSubmitQty(outputData.defect2Qty),
defect3Qty: toSubmitQty(outputData.defect3Qty),
scrapQty: toSubmitQty(outputData.scrapQty),
});

console.log("Output data submitted successfully");
@@ -94,14 +100,14 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
setOutputData((prev) => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessQty: (detail as any).outputFromProcessQty || "",
outputFromProcessUom: (detail as any).outputFromProcessUom || "",
defectQty: detail.defectQty || 0,
defectQty: detail.defectQty || "",
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
scrapQty: detail.scrapQty || "",
scrapUom: detail.scrapUom || "",
byproductName: detail.byproductName || "",
byproductQty: detail.byproductQty || 0,
byproductQty: detail.byproductQty || "",
byproductUom: detail.byproductUom || "",
}));

@@ -116,8 +122,13 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({
return (
<Box>
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
<Button
variant="outlined"
size="small"
onClick={onBack}
startIcon={<ArrowBackIcon />}
>
{t("Back to Process Steps")}
</Button>
</Box>



+ 10
- 1
src/components/ProductionProcess/ProductionProcessDetail.tsx View File

@@ -64,13 +64,16 @@ interface ProductProcessDetailProps {
jobOrderId: number;
onBack: () => void;
fromJosave?: boolean;
/** 進入步驟執行頁時通知父層(用來隱藏「返回列表」) */
onStepViewChange?: (inStepView: boolean) => void;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.1 | 2026-08-10 */
/** FP-MTMS Version Checklist | Functions Ref. No. 50 | v1.0.2 | 2026-10-29 */
const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
jobOrderId,
onBack,
fromJosave,
onStepViewChange,
}) => {
// console.log(" ProductionProcessDetail RENDER", { jobOrderId, fromJosave });
@@ -143,6 +146,12 @@ const fetchProcessDetailRef = useRef<() => Promise<void>>();
useEffect(() => {
onBackRef.current = onBack;
}, [onBack]);

useEffect(() => {
const inStepView = isExecutingLine && selectedLineId != null;
onStepViewChange?.(inStepView);
return () => onStepViewChange?.(false);
}, [isExecutingLine, selectedLineId, onStepViewChange]);
// 获取 process 和 lines 数据
const fetchProcessDetail = useCallback(async () => {
// console.log(" fetchProcessDetail CALLED", { jobOrderId, timestamp: new Date().toISOString() });


+ 14
- 7
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx View File

@@ -54,7 +54,7 @@ interface ProductProcessJobOrderDetailProps {
initialTabIndex?: number;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */
/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */
const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProps> = ({
jobOrderId,
onBack,
@@ -68,6 +68,7 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]);
const [tabIndex, setTabIndex] = useState(initialTabIndex);
const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
const [inStepView, setInStepView] = useState(false);
const [operationPriority, setOperationPriority] = useState<number>(50);
const [openOperationPriorityDialog, setOpenOperationPriorityDialog] = useState(false);
const [openPlanStartDialog, setOpenPlanStartDialog] = useState(false);
@@ -320,6 +321,9 @@ const handleRelease = useCallback(async (jobOrderId: number) => {
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
if (newValue !== 2) {
setInStepView(false);
}
},
[],
);
@@ -796,12 +800,14 @@ const handleRelease = useCallback(async (jobOrderId: number) => {

return (
<Box>
{/* 返回按钮 */}
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
{t("Back to List")}
</Button>
</Box>
{/* 步驟執行頁不顯示「返回列表」,避免誤離開工單 */}
{!inStepView && (
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
{t("Back to List")}
</Button>
</Box>
)}
{/* 标签页 */}
<Box sx={{ borderBottom: '1px solid #e0e0e0' }}>
@@ -833,6 +839,7 @@ const handleRelease = useCallback(async (jobOrderId: number) => {

}}
fromJosave={Boolean(fromJosave && !isPutAwayed)}
onStepViewChange={setInStepView}
/>
)}
{tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />}


+ 1
- 1
src/components/ProductionProcess/ProductionProcessList.tsx View File

@@ -179,7 +179,7 @@ function isWaitingQcPutAway(
return s !== "completed" && s !== "rejected";
}

/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.10 | 2026-10-15 */
/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.11 | 2026-10-29 */
const ProductProcessList: React.FC<ProductProcessListProps> = ({
onSelectProcess,
printerCombo,


+ 68
- 37
src/components/ProductionProcess/ProductionProcessStepExecution.tsx View File

@@ -28,6 +28,7 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import StopIcon from "@mui/icons-material/Stop";
import PauseIcon from "@mui/icons-material/Pause";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useTranslation } from "react-i18next";
import {
JobOrderProcessLineDetailResponse,
@@ -47,6 +48,35 @@ import { fetchNameList, NameList } from "@/app/api/user/actions";
import BagConsumptionForm from "./BagConsumptionForm"; // ✅ 添加导入
import OverallTimeRemainingCard from "./OverallTimeRemainingCard"; // ✅ 添加导入
import dayjs from "dayjs";

type QtyInput = number | "";

type OutputFormState = Omit<
UpdateProductProcessLineQtyRequest,
| "outputFromProcessQty"
| "byproductQty"
| "defectQty"
| "defect2Qty"
| "defect3Qty"
| "scrapQty"
> & {
outputFromProcessQty: QtyInput;
byproductQty: QtyInput;
defectQty: QtyInput;
defect2Qty: QtyInput;
defect3Qty: QtyInput;
scrapQty: QtyInput;
};

const parseQtyInput = (raw: string): QtyInput => {
if (raw === "") return "";
const n = parseInt(raw, 10);
return Number.isNaN(n) ? "" : n;
};

const toSubmitQty = (v: QtyInput | null | undefined): number =>
v === "" || v == null || Number.isNaN(Number(v)) ? 0 : Number(v);

interface ProductionProcessStepExecutionProps {
lineId: number | null;
onBack: () => void;
@@ -55,7 +85,7 @@ interface ProductionProcessStepExecutionProps {
jobOrderId?: number; // ✅ 添加
}

/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.0 | 2026-08-10 */
/** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.1 | 2026-10-29 */
const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
lineId,
onBack,
@@ -75,24 +105,20 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
lineStatusNorm === "autopass";
const isPassStatus = lineStatusNorm === "pass";
const isAutoPassStatus = lineStatusNorm === "autopass";
const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
byproductUom: string;
}>({
const [outputData, setOutputData] = useState<OutputFormState>({
productProcessLineId: lineId ?? 0,
outputFromProcessQty: 0,
outputFromProcessQty: "",
outputFromProcessUom: "",
defectQty: 0,
defectQty: "",
defectUom: "",
scrapQty: 0,
scrapQty: "",
scrapUom: "",
byproductName: "",
byproductQty: 0,
byproductQty: "",
byproductUom: "",
defect2Qty: 0,
defect2Qty: "",
defect2Uom: "",
defect3Qty: 0,
defect3Qty: "",
defect3Uom: "",
defectDescription: "",
defectDescription2: "",
@@ -153,14 +179,14 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
setOutputData(prev => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessQty: (detail as any).outputFromProcessQty || "",
outputFromProcessUom: (detail as any).outputFromProcessUom || "",
defectQty: detail.defectQty || 0,
defectQty: detail.defectQty || "",
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
scrapQty: detail.scrapQty || "",
scrapUom: detail.scrapUom || "",
byproductName: detail.byproductName || "",
byproductQty: detail.byproductQty || 0,
byproductQty: detail.byproductQty || "",
byproductUom: detail.byproductUom || ""
}));
})
@@ -351,20 +377,20 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
await updateProductProcessLineQty({
productProcessLineId: lineDetail?.id || 0 as number,
byproductName: outputData.byproductName,
byproductQty: outputData.byproductQty,
byproductQty: toSubmitQty(outputData.byproductQty),
byproductUom: outputData.byproductUom,
outputFromProcessQty: outputData.outputFromProcessQty,
outputFromProcessQty: toSubmitQty(outputData.outputFromProcessQty),
outputFromProcessUom: outputData.outputFromProcessUom,
defectQty: outputData.defectQty,
defectQty: toSubmitQty(outputData.defectQty),
defectUom: outputData.defectUom,
defect2Qty: outputData.defect2Qty,
defect2Qty: toSubmitQty(outputData.defect2Qty),
defect2Uom: outputData.defect2Uom,
defect3Qty: outputData.defect3Qty,
defect3Qty: toSubmitQty(outputData.defect3Qty),
defect3Uom: outputData.defect3Uom,
defectDescription: outputData.defectDescription,
defectDescription2: outputData.defectDescription2,
defectDescription3: outputData.defectDescription3,
scrapQty: outputData.scrapQty,
scrapQty: toSubmitQty(outputData.scrapQty),
scrapUom: outputData.scrapUom,
});

@@ -383,21 +409,21 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
setOutputData(prev => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
outputFromProcessQty: (detail as any).outputFromProcessQty || "",
outputFromProcessUom: (detail as any).outputFromProcessUom || "",
defectQty: detail.defectQty || 0,
defectQty: detail.defectQty || "",
defectUom: detail.defectUom || "",
defectDescription: detail.defectDescription || "",
defectDescription2: detail.defectDescription2 || "",
defectDescription3: detail.defectDescription3 || "",
defectQty2: detail.defectQty2 || 0,
defectUom2: detail.defectUom2 || "",
defectQty3: detail.defectQty3 || 0,
defectUom3: detail.defectUom3 || "",
scrapQty: detail.scrapQty || 0,
defect2Qty: (detail as any).defect2Qty || "",
defect2Uom: (detail as any).defect2Uom || "",
defect3Qty: (detail as any).defect3Qty || "",
defect3Uom: (detail as any).defect3Uom || "",
scrapQty: detail.scrapQty || "",
scrapUom: detail.scrapUom || "",
byproductName: detail.byproductName || "",
byproductQty: detail.byproductQty || 0,
byproductQty: detail.byproductQty || "",
byproductUom: detail.byproductUom || ""
}));
})
@@ -561,8 +587,13 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
return (
<Box>
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
<Button
variant="outlined"
size="small"
onClick={onBack}
startIcon={<ArrowBackIcon />}
>
{t("Back to Process Steps")}
</Button>
</Box>
{processData && (
@@ -812,7 +843,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
value={outputData.outputFromProcessQty}
onChange={(e) => setOutputData({
...outputData,
outputFromProcessQty: parseInt(e.target.value) || 0
outputFromProcessQty: parseQtyInput(e.target.value)
})}
/>
</TableCell>
@@ -854,7 +885,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
value={outputData.defectQty}
onChange={(e) => setOutputData({
...outputData,
defectQty: parseInt(e.target.value) || 0
defectQty: parseQtyInput(e.target.value)
})}
/>
</TableCell>
@@ -902,7 +933,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
value={outputData.defect2Qty}
onChange={(e) => setOutputData({
...outputData,
defect2Qty: parseInt(e.target.value) || 0
defect2Qty: parseQtyInput(e.target.value)
})}
/>
</TableCell>
@@ -950,7 +981,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
value={outputData.defect3Qty}
onChange={(e) => setOutputData({
...outputData,
defect3Qty: parseInt(e.target.value) || 0
defect3Qty: parseQtyInput(e.target.value)
})}
/>
</TableCell>
@@ -998,7 +1029,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
value={outputData.scrapQty}
onChange={(e) => setOutputData({
...outputData,
scrapQty: parseInt(e.target.value) || 0
scrapQty: parseQtyInput(e.target.value)
})}
/>
</TableCell>


+ 678
- 259
src/components/Shop/RouteBoard.tsx
File diff suppressed because it is too large
View File


+ 2
- 0
src/i18n/en/jo.json View File

@@ -32,6 +32,8 @@
"BOM Type": "BOM Type",
"Back": "Back",
"Back to List": "Back to List",
"Back to Process": "Back to Process Steps",
"Back to Process Steps": "Back to Process Steps",
"Bad Item Qty": "Bad Item Qty",
"Bad Package Qty": "Bad Package Qty",
"Bag": "Bag",


+ 2
- 0
src/i18n/en/productionProcess.json View File

@@ -10,6 +10,8 @@
"Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes",
"Back": "Back",
"Back to List": "Back to List",
"Back to Process": "Back to Process Steps",
"Back to Process Steps": "Back to Process Steps",
"Bag": "Bag",
"Bag Consumption": "Bag Consumption",
"Balance": "Balance",


+ 1
- 1
src/i18n/en/routeboard.json View File

@@ -106,7 +106,7 @@
"lane_noMatchFilter": "No lanes match (clear search or floor filter)",
"floor_label": "Floor",
"floor_all": "All",
"filter_clear": "Clear",
"filter_clear": "Clear all",
"filter_apply": "OK",
"btn_addLane": "Add lane",
"tools_title": "Tools",


+ 9
- 5
src/i18n/en/shop.json View File

@@ -132,13 +132,12 @@
"confirm_restoreDiscardsEdits": "Scheduling a version restore will discard other unsaved board changes (drags, deletes, pending shops/lanes, logistics fields, etc.). Continue?",
"confirm_restoreSaveWillDropStaging": "Save will apply the snapshot restore first; other staged edits in this save will be skipped. Continue?",
"confirm_schedule_removeShop": "Remove this shop from the lane? It will be scheduled for deletion when you submit.",
"departureDialog_hint": "Name and departure apply to every master row on this lane. Press \"Save changes\" to write them. Completed delivery tickets keep the old name.",
"departureDialog_hint": "Name and departure apply to every master row on this lane. Press \"Save changes\" to write them. Existing delivery tickets keep the old name; later releases use the new name.",
"departureDialog_title": "Edit lane",
"departureDialog_nameLabel": "Lane name",
"departureDialog_remarkReadonly": "Remark (not edited here)",
"laneEditTooltip": "Edit lane name and departure time (written when you press Save changes)",
"confirm_weekdayToken": "The weekday token in the lane name will change or become unreadable. 4F assignment matches Mon–Sun inside the name. Apply anyway?",
"confirm_renameLane": "Rename \"{{from}}\" to \"{{to}}\".\n{{unfinished}} unfinished delivery ticket(s) will follow (handlers: {{handlers}}).\n{{completed}} completed ticket(s) keep the old name.\n{{schedules}} open schedule line(s) will change from/to.\nNothing is written until you press Save changes.",
"confirm_renameLane": "Rename \"{{from}}\" to \"{{to}}\".\n{{unfinished}} unfinished delivery ticket(s) stay on the old name (handlers: {{handlers}}).\n{{completed}} completed ticket(s) stay on the old name.\nLater releases use the new name.\n{{schedules}} open schedule line(s) will change from/to.\nNothing is written until you press Save changes.",
"confirm_restoreKeepsTickets": "Restore puts lane master data, and open schedules that point at the current name, back to this snapshot. Unfinished delivery tickets stay on the current name.",
"rename_noHandlers": "none",
"rename_none": "none",
@@ -152,7 +151,7 @@
"renameErr_LANE_NOT_FOUND": "No live lane rows were found, so nothing was written",
"renameErr_LANE_TIME_INVALID": "Departure time is not a valid time",
"renameLog_title": "Lane renames",
"renameLog_line": "{{when}} {{who}}: {{from}} → {{to}} ({{floor}}, tickets {{tickets}})",
"renameLog_line": "{{when}} {{who}}: {{from}} → {{to}} ({{floor}})",
"departureEditAria": "Edit departure time",
"departureTooltipEditSave": "Edit departure time (saved with \"Save changes\")",
"departureTooltipNeedShops": "Add shops before setting departure time",
@@ -231,9 +230,14 @@
"err_save": "Save failed",
"exportRoutes": "Export routes",
"filter_apply": "OK",
"filter_clear": "Clear",
"filter_clear": "Clear all",
"floor_all": "All",
"floor_label": "Floor",
"lane_filter": "Filter",
"laneFilter_special": "Lane code",
"laneFilter_zone": "Zone",
"laneFilter_weekday": "Weekday",
"laneFilter_area": "Area",
"id": "id",
"importRoutes": "Import routes",
"import_staged_preview": "Import preview loaded: {{file}} ({{sheets}} sheet(s) / {{rows}} rows). Press \"Save changes\" to persist.",


+ 2
- 0
src/i18n/zh/jo.json View File

@@ -34,6 +34,8 @@
"BOM Type": "BOM 類型",
"Back": "返回",
"Back to List": "返回列表",
"Back to Process": "返回列表",
"Back to Process Steps": "返回列表",
"Bad Item Qty": "不良物品數量",
"Bad Package Qty": "不良包裝數量",
"Bag": "包裝袋",


+ 2
- 0
src/i18n/zh/productionProcess.json View File

@@ -10,6 +10,8 @@
"Auto-refresh every 10 minutes": "每10分鐘自動刷新",
"Back": "返回",
"Back to List": "返回列表",
"Back to Process": "返回列表",
"Back to Process Steps": "返回列表",
"Bag": "包裝袋",
"Bag Consumption": "包裝袋消耗",
"Balance": "可用數量",


+ 1
- 1
src/i18n/zh/routeboard.json View File

@@ -106,7 +106,7 @@
"lane_noMatchFilter": "無符合條件的車線(清除搜索或樓層篩選)",
"floor_label": "樓層",
"floor_all": "全部",
"filter_clear": "清除",
"filter_clear": "清除全部",
"filter_apply": "確定",
"btn_addLane": "新增車線",
"tools_title": "操作工具",


+ 9
- 5
src/i18n/zh/shop.json View File

@@ -106,7 +106,12 @@
"lane_selectAll": "全選",
"floor_label": "樓層",
"floor_all": "全部",
"filter_clear": "清除",
"lane_filter": "篩選",
"laneFilter_special": "車線",
"laneFilter_zone": "區",
"laneFilter_weekday": "星期",
"laneFilter_area": "地區",
"filter_clear": "清除全部",
"filter_apply": "確定",
"btn_addLane": "新增車線",
"tools_title": "操作工具",
@@ -202,11 +207,10 @@
"addShop_confirm": "確認",
"departureDialog_title": "編輯車線",
"departureDialog_nameLabel": "車線名稱",
"departureDialog_remarkReadonly": "備註(這次不改)",
"departureDialog_hint": "名稱與出車時間會套用到此車線全部主檔。按上方「儲存更改」才寫入。已完成的出倉票不會改名。",
"departureDialog_hint": "名稱與出車時間會套用到此車線全部店鋪。\n 既有送貨訂單維持舊名稱;之後放單才用新名稱。\n 按上方「儲存更改」才寫入。",
"laneEditTooltip": "編輯車線名稱與出車時間(按「儲存更改」才寫入)",
"confirm_weekdayToken": "車線名稱裡的星期代碼會改變,或改完後無法辨認。4F 派車是用名稱裡的 Mon–Sun 對日子。仍要套用?",
"confirm_renameLane": "把「{{from}}」改成「{{to}}」。\n未完成出倉票 {{unfinished}} 張會改成新名稱(負責人:{{handlers}})。\n已完成 {{completed}} 張維持舊名稱。\n未完成排程 {{schedules}} 筆的起迄名稱會改。\n按「儲存更改」後才寫入。",
"confirm_renameLane": "把「{{from}}」改成「{{to}}」。\n未完成出倉票 {{unfinished}} 張維持舊名稱(負責人:{{handlers}})。\n已完成 {{completed}} 張維持舊名稱。\n之後放單才用新名稱。\n未完成排程 {{schedules}} 筆的起迄名稱會改。\n按「儲存更改」後才寫入。",
"confirm_restoreKeepsTickets": "還原會把車線主檔,以及指向目前名稱的未完成排程,改回此版本的名稱。未完成的出倉票會留在現在的名稱,不會跟回去。",
"rename_noHandlers": "無",
"rename_none": "無",
@@ -220,7 +224,7 @@
"renameErr_LANE_NOT_FOUND": "找不到這條車線的主檔,沒有寫入",
"renameErr_LANE_TIME_INVALID": "出車時間格式不正確",
"renameLog_title": "車線改名紀錄",
"renameLog_line": "{{when}} {{who}}:{{from}} → {{to}}({{floor}},票 {{tickets}})",
"renameLog_line": "{{when}} {{who}}:{{from}} → {{to}}({{floor}})",
"seqDialog_title": "編輯裝車順序",
"logistics_colLaneCount": "{{count}} 條車線",
"tooltip_openLaneBoard": "在車線看板開此車線",


Loading…
Cancel
Save