diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index 5e0c0e6..c092195 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -77,11 +77,15 @@ export interface AllPickedStockTakeListReponse { stockTakeSession: string; lastStockTakeDate: string | null; status: string|null; + approverName: string | null; currentStockTakeItemNumber: number; totalInventoryLotNumber: number; stockTakeId: number; stockTakerName: string | null; totalItemNumber: number; + startTime: string | null; + endTime: string | null; + reStockTakeTrueFalse: boolean; } export const importStockTake = async (data: FormData) => { diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 99b1d6f..56a41e4 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -552,7 +552,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO try { const userIdToUse = userId || currentUserId; - console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); + console.log(" fetchAllCombinedLotData called with userId:", userIdToUse); if (!userIdToUse) { console.warn("⚠️ No userId available, skipping API call"); @@ -620,9 +620,9 @@ const fgOrder: FGPickOrderResponse = { }; setFgPickOrders([fgOrder]); - console.log("🔍 DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder); -console.log("🔍 DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes); -console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); + console.log(" DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder); +console.log(" DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes); +console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); // 移除:不需要 doPickOrderDetail 和 switcher 逻辑 // if (hierarchicalData.pickOrders.length > 1) { ... } @@ -749,7 +749,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); }); console.log(" Transformed flat lot data:", flatLotData); - console.log("🔍 Total items (including null stock):", flatLotData.length); + console.log(" Total items (including null stock):", flatLotData.length); setCombinedLotData(flatLotData); setOriginalCombinedData(flatLotData); @@ -766,7 +766,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); }, [currentUserId, checkAllLotsCompleted]); // 移除 selectedPickOrderId 依赖 // Add effect to check completion when lot data changes const handleManualLotConfirmation = useCallback(async (currentLotNo: string, newLotNo: string) => { - console.log(`🔍 Manual lot confirmation: Current=${currentLotNo}, New=${newLotNo}`); + console.log(` Manual lot confirmation: Current=${currentLotNo}, New=${newLotNo}`); // 使用第一个输入框的 lot number 查找当前数据 const currentLot = combinedLotData.find(lot => @@ -1387,7 +1387,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); + console.log(` Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); setSelectedLotForQr(expectedLot); handleLotMismatch( { @@ -1424,7 +1424,7 @@ useEffect(() => { return; } if (latestQr === "{2fic}") { - console.log("🔍 Detected {2fic} shortcut - opening manual lot confirmation form"); + console.log(" Detected {2fic} shortcut - opening manual lot confirmation form"); setManualLotConfirmationOpen(true); resetScan(); setLastProcessedQr(latestQr); @@ -1432,7 +1432,7 @@ useEffect(() => { return; // 直接返回,不继续处理其他逻辑 } if (latestQr && latestQr !== lastProcessedQr) { - console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); + console.log(` Processing new QR code with enhanced validation: ${latestQr}`); setLastProcessedQr(latestQr); setProcessedQrCodes(prev => new Set(prev).add(latestQr)); @@ -1921,7 +1921,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe setPickOrderSwitching(true); try { - console.log("🔍 Switching to pick order:", pickOrderId); + console.log(" Switching to pick order:", pickOrderId); setSelectedPickOrderId(pickOrderId); // 强制刷新数据,确保显示正确的 pick order 数据 diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index a614269..861e922 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -106,6 +106,11 @@ const NavigationContent: React.FC = () => { label: "Finished Good Order", path: "/finishedGood", }, + { + icon: , + label: "Stock Record", + path: "/stockRecord", + }, ], }, // { diff --git a/src/components/PickOrderSearch/PickExecution.tsx b/src/components/PickOrderSearch/PickExecution.tsx index 486b52e..50f6d92 100644 --- a/src/components/PickOrderSearch/PickExecution.tsx +++ b/src/components/PickOrderSearch/PickExecution.tsx @@ -362,8 +362,8 @@ const PickExecution: React.FC = ({ filterArgs }) => { // FIXED: 计算累计拣货数量 const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty; console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0); - console.log("🔍 DEBUG - Current submit:", qty); - console.log("🔍 DEBUG - Total picked:", totalPickedForThisLot); + console.log(" DEBUG - Current submit:", qty); + console.log(" DEBUG - Total picked:", totalPickedForThisLot); console.log("�� DEBUG - Required qty:", selectedLot.requiredQty); // FIXED: 状态应该基于累计拣货数量 @@ -428,7 +428,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { if (currentConsoCode) { try { - console.log(`🔍 Checking completion for consoCode: ${currentConsoCode}`); + console.log(` Checking completion for consoCode: ${currentConsoCode}`); const completionResponse = await checkAndCompletePickOrderByConsoCode(currentConsoCode); console.log("�� Completion response:", completionResponse); @@ -788,7 +788,7 @@ const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number) const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId); if (foundLine) { correctConsoCode = pickOrder.consoCode; - console.log(`🔍 Found consoCode for line ${selectedRowId}: ${correctConsoCode} (from pick order ${pickOrder.id})`); + console.log(` Found consoCode for line ${selectedRowId}: ${correctConsoCode} (from pick order ${pickOrder.id})`); break; } } diff --git a/src/components/StockTakeManagement/ApproverCardList.tsx b/src/components/StockTakeManagement/ApproverCardList.tsx index 1baad2a..153f5a7 100644 --- a/src/components/StockTakeManagement/ApproverCardList.tsx +++ b/src/components/StockTakeManagement/ApproverCardList.tsx @@ -58,7 +58,66 @@ const ApproverCardList: React.FC = ({ onCardClick }) => { const startIdx = page * PER_PAGE; const paged = stockTakeSessions.slice(startIdx, startIdx + PER_PAGE); + const TimeDisplay: React.FC<{ startTime: string | null; endTime: string | null }> = ({ startTime, endTime }) => { + const [currentTime, setCurrentTime] = useState(dayjs()); + useEffect(() => { + if (!endTime && startTime) { + const interval = setInterval(() => { + setCurrentTime(dayjs()); + }, 1000); // 每秒更新一次 + + return () => clearInterval(interval); + } + }, [startTime, endTime]); + + if (endTime && startTime) { + // 当有结束时间时,计算从开始到结束的持续时间 + const start = dayjs(startTime); + const end = dayjs(endTime); + const duration = dayjs.duration(end.diff(start)); + const hours = Math.floor(duration.asHours()); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); + } else if (startTime) { + // 当没有结束时间时,显示实时计时器 + const start = dayjs(startTime); + const duration = dayjs.duration(currentTime.diff(start)); + const hours = Math.floor(duration.asHours()); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); + } else { + return <>-; + } + }; + const startTimeDisplay = (startTime: string | null) => { + if (startTime) { + const start = dayjs(startTime); + return start.format("HH:mm"); + } else { + return "-"; + } + }; + const endTimeDisplay = (endTime: string | null) => { + if (endTime) { + const end = dayjs(endTime); + return end.format("HH:mm"); + } else { + return "-"; + } + }; const getStatusColor = (status: string | null) => { if (!status) return "default"; @@ -126,17 +185,22 @@ const ApproverCardList: React.FC = ({ onCardClick }) => { {t("Section")}: {session.stockTakeSession} - {session.status ? ( - - ) : ( - - )} + + {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} + + + {t("Stock Taker")}: {session.stockTakerName || "-"} + {t("Approver")}: {session.approverName || "-"} + + + {t("start time")}: {startTimeDisplay(session.startTime) || "-"} + {t("end time")}: {endTimeDisplay(session.endTime) || "-"} + - {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} + {t("Control Time")}: - {session.totalInventoryLotNumber > 0 && ( @@ -156,7 +220,7 @@ const ApproverCardList: React.FC = ({ onCardClick }) => { )} - + + {session.status ? ( + + ) : ( + + )} diff --git a/src/components/StockTakeManagement/ApproverStockTake.tsx b/src/components/StockTakeManagement/ApproverStockTake.tsx index 4426965..a036bd0 100644 --- a/src/components/StockTakeManagement/ApproverStockTake.tsx +++ b/src/components/StockTakeManagement/ApproverStockTake.tsx @@ -252,7 +252,20 @@ const ApproverStockTake: React.FC = ({ useEffect(() => { handleBatchSubmitAllRef.current = handleBatchSubmitAll; }, [handleBatchSubmitAll]); - + const formatNumber = (num: number | null | undefined): string => { + if (num == null) return "0.00"; + return num.toLocaleString('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + }; + const uniqueWarehouses = Array.from( + new Set( + inventoryLotDetails + .map(detail => detail.warehouse) + .filter(warehouse => warehouse && warehouse.trim() !== "") + ) + ).join(", "); const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => { // Only allow editing if there's a first stock take qty if (!detail.firstStockTakeQty || detail.firstStockTakeQty === 0) { @@ -266,9 +279,18 @@ const ApproverStockTake: React.FC = ({ + {t("Stock Take Section")}: {selectedSession.stockTakeSession} + {uniqueWarehouses && ( + <> {t("Warehouse")}: {uniqueWarehouses} + )} + + + {loadingDetails ? ( @@ -279,8 +301,8 @@ const ApproverStockTake: React.FC = ({ {t("Warehouse Location")} - {t("Item")} - {t("Stock Take Qty")} + {t("Item-lotNo-ExpiryDate")} + {t("Stock Take Qty(include Bad Qty)= Available Qty")} {t("Remark")} {t("UOM")} {t("Record Status")} @@ -316,21 +338,21 @@ const ApproverStockTake: React.FC = ({ {detail.itemCode || "-"} {detail.itemName || "-"} {detail.lotNo || "-"} {detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"} - + {/**/} {detail.finalQty != null ? ( - // 提交后只显示差异行 + - {t("Difference")}: {detail.finalQty?.toFixed(2) || "0.00"} - {(detail.availableQty || 0).toFixed(2)} = {((detail.finalQty || 0) - (detail.availableQty || 0)).toFixed(2)} + {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(detail.availableQty)} = {formatNumber((detail.finalQty || 0) - (detail.availableQty || 0))} ) : ( - {/* 第一行:First Qty(默认选中) */} + {hasFirst && ( = ({ onChange={() => setQtySelection({ ...qtySelection, [detail.id]: "first" })} /> - {t("First")}: {(detail.firstStockTakeQty??0)+(detail.firstBadQty??0) || "0.00"} ({detail.firstBadQty??0}) + {t("First")}: {formatNumber((detail.firstStockTakeQty??0)+(detail.firstBadQty??0))} ({detail.firstBadQty??0}) = {formatNumber(detail.firstStockTakeQty??0)} )} - {/* 第二行:Second Qty(如果存在) */} + {hasSecond && ( = ({ onChange={() => setQtySelection({ ...qtySelection, [detail.id]: "second" })} /> - {t("Second")}: {(detail.secondStockTakeQty??0)+(detail.secondBadQty??0) || "0.00"} ({detail.secondBadQty??0}) + {t("Second")}: {formatNumber((detail.secondStockTakeQty??0)+(detail.secondBadQty??0))} ({detail.secondBadQty??0}) = {formatNumber(detail.secondStockTakeQty??0)} )} - {/* 第三行:Approver Input(仅在 second qty 存在时显示) */} + {hasSecond && ( = ({ type="number" value={approverQty[detail.id] || ""} onChange={(e) => setApproverQty({ ...approverQty, [detail.id]: e.target.value })} - sx={{ width: 100 }} + sx={{ + width: 130, + minWidth: 130, + '& .MuiInputBase-input': { + height: '1.4375em', + + padding: '4px 8px' + } + }} + placeholder={t("Stock Take Qty") } disabled={selection !== "approver"} /> - - + setApproverBadQty({ ...approverBadQty, [detail.id]: e.target.value })} - sx={{ width: 100 }} + sx={{ + width: 130, + minWidth: 130, + '& .MuiInputBase-input': { + height: '1.4375em', + padding: '4px 8px' + } + }} + placeholder={t("Bad Qty")} disabled={selection !== "approver"} /> + + ={(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))} + )} - {/* 差异行:显示 selected qty - bookqty = result */} + {(() => { let selectedQty = 0; @@ -396,7 +438,7 @@ const ApproverStockTake: React.FC = ({ } else if (selection === "second") { selectedQty = detail.secondStockTakeQty || 0; } else if (selection === "approver") { - selectedQty = parseFloat(approverQty[detail.id] || "0") || 0; + selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))|| 0; } const bookQty = detail.availableQty || 0; @@ -404,7 +446,7 @@ const ApproverStockTake: React.FC = ({ return ( - {t("Difference")}: {selectedQty.toFixed(2)} - {bookQty.toFixed(2)} = {difference.toFixed(2)} + {t("Difference")}: {t("selected stock take qty")}({formatNumber(selectedQty)}) - {t("book qty")}({formatNumber(bookQty)}) = {formatNumber(difference)} ); })()} @@ -431,6 +473,7 @@ const ApproverStockTake: React.FC = ({ {detail.stockTakeRecordId && detail.stockTakeRecordStatus !== "notMatch" && ( + + )} +
{detail.finalQty == null && ( + + )}
diff --git a/src/components/StockTakeManagement/PickerCardList.tsx b/src/components/StockTakeManagement/PickerCardList.tsx index 9b10871..a6affe8 100644 --- a/src/components/StockTakeManagement/PickerCardList.tsx +++ b/src/components/StockTakeManagement/PickerCardList.tsx @@ -16,6 +16,7 @@ import { } from "@mui/material"; import { useState, useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import duration from "dayjs/plugin/duration"; import { getStockTakeRecords, AllPickedStockTakeListReponse, @@ -33,7 +34,9 @@ interface PickerCardListProps { const PickerCardList: React.FC = ({ onCardClick, onReStockTakeClick }) => { const { t } = useTranslation(["inventory", "common"]); + dayjs.extend(duration); + const PER_PAGE = 6; const [loading, setLoading] = useState(false); const [stockTakeSessions, setStockTakeSessions] = useState([]); const [page, setPage] = useState(0); @@ -88,10 +91,70 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT if (statusLower === "completed") return "success"; if (statusLower === "in_progress" || statusLower === "processing") return "primary"; if (statusLower === "approving") return "info"; + if (statusLower === "stockTaking") return "primary"; if (statusLower === "no_cycle") return "default"; return "warning"; }; - + const TimeDisplay: React.FC<{ startTime: string | null; endTime: string | null }> = ({ startTime, endTime }) => { + const [currentTime, setCurrentTime] = useState(dayjs()); + + useEffect(() => { + if (!endTime && startTime) { + const interval = setInterval(() => { + setCurrentTime(dayjs()); + }, 1000); // 每秒更新一次 + + return () => clearInterval(interval); + } + }, [startTime, endTime]); + + if (endTime && startTime) { + // 当有结束时间时,计算从开始到结束的持续时间 + const start = dayjs(startTime); + const end = dayjs(endTime); + const duration = dayjs.duration(end.diff(start)); + const hours = Math.floor(duration.asHours()); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); + } else if (startTime) { + // 当没有结束时间时,显示实时计时器 + const start = dayjs(startTime); + const duration = dayjs.duration(currentTime.diff(start)); + const hours = Math.floor(duration.asHours()); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); + } else { + return <>-; + } + }; + const startTimeDisplay = (startTime: string | null) => { + if (startTime) { + const start = dayjs(startTime); + return start.format("HH:mm"); + } else { + return "-"; + } + }; + const endTimeDisplay = (endTime: string | null) => { + if (endTime) { + const end = dayjs(endTime); + return end.format("HH:mm"); + } else { + return "-"; + } + }; const getCompletionRate = (session: AllPickedStockTakeListReponse): number => { if (session.totalInventoryLotNumber === 0) return 0; return Math.round((session.currentStockTakeItemNumber / session.totalInventoryLotNumber) * 100); @@ -145,13 +208,21 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT {t("Section")}: {session.stockTakeSession} - + + {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} + + + {t("Stock Taker")}: {session.stockTakerName} + + + {t("start time")}: {startTimeDisplay(session.startTime) || "-"} + {t("end time")}: {endTimeDisplay(session.endTime) || "-"} + - {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} + {t("Control Time")}: - {t("Stock Taker")}: {session.stockTakerName} {t("Total Item Number")}: {session.totalItemNumber} {session.totalInventoryLotNumber > 0 && ( @@ -172,7 +243,8 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT )} - + + + + diff --git a/src/components/StockTakeManagement/PickerReStockTake.tsx b/src/components/StockTakeManagement/PickerReStockTake.tsx index fee9a6b..e47dbe8 100644 --- a/src/components/StockTakeManagement/PickerReStockTake.tsx +++ b/src/components/StockTakeManagement/PickerReStockTake.tsx @@ -292,14 +292,24 @@ const PickerStockTake: React.FC = ({ } return false; }, []); - + + const uniqueWarehouses = Array.from( + new Set( + inventoryLotDetails + .map(detail => detail.warehouse) + .filter(warehouse => warehouse && warehouse.trim() !== "") + ) + ).join(", "); return ( - {t("Stock Take Section")}: {selectedSession.stockTakeSession} + {t("Stock Take Section")}: {selectedSession.stockTakeSession} + {uniqueWarehouses && ( + <> {t("Warehouse")}: {uniqueWarehouses} + )} {/* {shortcutInput && ( @@ -320,17 +330,15 @@ const PickerStockTake: React.FC = ({ {t("Warehouse Location")} - {t("Item")} - {/*{t("Item Name")}*/} - {/*{t("Lot No")}*/} - {t("Expiry Date")} + {t("Item-lotNo-ExpiryDate")} + {t("Qty")} {t("Bad Qty")} {/*{inventoryLotDetails.some(d => editingRecord?.id === d.id) && (*/} {t("Remark")} {t("UOM")} - {t("Status")} + {t("Record Status")} {t("Action")} @@ -353,29 +361,19 @@ const PickerStockTake: React.FC = ({ return ( - {detail.warehouseCode || "-"} + {detail.warehouseArea || "-"}{detail.warehouseSlot || "-"} {detail.itemCode || "-"}{detail.lotNo || "-"}{detail.itemName ? ` - ${detail.itemName}` : ""} - {/* - - {detail.itemName || "-"} - */} - {/*{detail.lotNo || "-"}*/} - - {detail.expiryDate - ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) - : "-"} + }}> + + {detail.itemCode || "-"} {detail.itemName || "-"} + {detail.lotNo || "-"} + {detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"} + {/**/} + @@ -418,73 +416,69 @@ const PickerStockTake: React.FC = ({ - - {isEditing && isFirstSubmit ? ( - setFirstBadQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.firstBadQty ? ( - - {t("First")}: {detail.firstBadQty.toFixed(2)} - - ) : null} - - {isEditing && isSecondSubmit ? ( - setSecondBadQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.secondBadQty ? ( - - {t("Second")}: {detail.secondBadQty.toFixed(2)} - - ) : null} - - {!detail.firstBadQty && !detail.secondBadQty && !isEditing && ( - - - - - )} - - - - - {isEditing && isSecondSubmit ? ( - <> - {t("Remark")} - setRemark(e.target.value)} - sx={{ width: 150 }} - // If you want a single-line input, remove multiline/rows: - // multiline - // rows={2} - /> - - ) : ( - - {detail.remarks || "-"} - - )} - - {detail.uom || "-"} - - {detail.status ? ( - + + {isEditing && isFirstSubmit ? ( + setFirstBadQty(e.target.value)} + sx={{ width: 100 }} + /> + ) : detail.firstBadQty != null && detail.firstBadQty > 0 ? ( + + {t("First")}: {detail.firstBadQty.toFixed(2)} + ) : ( - "-" + + + {t("First")}: 0.00 + )} - + + {isEditing && isSecondSubmit ? ( + setSecondBadQty(e.target.value)} + sx={{ width: 100 }} + /> + ) : detail.secondBadQty != null && detail.secondBadQty > 0 ? ( + + {t("Second")}: {detail.secondBadQty.toFixed(2)} + + ) : null} + + {!detail.firstBadQty && !detail.secondBadQty && !isEditing && ( + + - + + )} + + + + {isEditing && isSecondSubmit ? ( + <> + {t("Remark")} + setRemark(e.target.value)} + sx={{ width: 150 }} + // If you want a single-line input, remove multiline/rows: + // multiline + // rows={2} + /> + + ) : ( + + {detail.remarks || "-"} + + )} + + {detail.uom || "-"} + {detail.stockTakeRecordStatus === "pass" ? ( diff --git a/src/components/StockTakeManagement/PickerStockTake.tsx b/src/components/StockTakeManagement/PickerStockTake.tsx index d480718..e1dfa1b 100644 --- a/src/components/StockTakeManagement/PickerStockTake.tsx +++ b/src/components/StockTakeManagement/PickerStockTake.tsx @@ -18,8 +18,8 @@ import { } from "@mui/material"; import { useState, useCallback, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { - AllPickedStockTakeListReponse, +import { + AllPickedStockTakeListReponse, getInventoryLotDetailsBySection, InventoryLotDetailResponse, saveStockTakeRecord, @@ -51,6 +51,7 @@ const PickerStockTake: React.FC = ({ // 编辑状态 const [editingRecord, setEditingRecord] = useState(null); + // firstQty / secondQty 保存的是 total = available + bad const [firstQty, setFirstQty] = useState(""); const [secondQty, setSecondQty] = useState(""); const [firstBadQty, setFirstBadQty] = useState(""); @@ -84,8 +85,19 @@ const PickerStockTake: React.FC = ({ const handleStartEdit = useCallback((detail: InventoryLotDetailResponse) => { setEditingRecord(detail); - setFirstQty(detail.firstStockTakeQty?.toString() || ""); - setSecondQty(detail.secondStockTakeQty?.toString() || ""); + + // 编辑时,输入 total = qty + badQty + const firstTotal = + detail.firstStockTakeQty != null + ? (detail.firstStockTakeQty + (detail.firstBadQty ?? 0)).toString() + : ""; + const secondTotal = + detail.secondStockTakeQty != null + ? (detail.secondStockTakeQty + (detail.secondBadQty ?? 0)).toString() + : ""; + + setFirstQty(firstTotal); + setSecondQty(secondTotal); setFirstBadQty(detail.firstBadQty?.toString() || ""); setSecondBadQty(detail.secondBadQty?.toString() || ""); setRemark(detail.remarks || ""); @@ -100,125 +112,164 @@ const PickerStockTake: React.FC = ({ setRemark(""); }, []); - const handleSaveStockTake = useCallback(async (detail: InventoryLotDetailResponse) => { - if (!selectedSession || !currentUserId) { - return; - } - - const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; - const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; - - const qty = isFirstSubmit ? firstQty : secondQty; - const badQty = isFirstSubmit ? firstBadQty : secondBadQty; - - if (!qty || !badQty) { - onSnackbar( - isFirstSubmit - ? t("Please enter QTY and Bad QTY") - : t("Please enter Second QTY and Bad QTY"), - "error" - ); - return; - } - - setSaving(true); - try { - const request: SaveStockTakeRecordRequest = { - stockTakeRecordId: detail.stockTakeRecordId || null, - inventoryLotLineId: detail.id, - qty: parseFloat(qty), - badQty: parseFloat(badQty), - remark: isSecondSubmit ? (remark || null) : null, - }; - console.log('handleSaveStockTake: request:', request); - console.log('handleSaveStockTake: selectedSession.stockTakeId:', selectedSession.stockTakeId); - console.log('handleSaveStockTake: currentUserId:', currentUserId); - await saveStockTakeRecord( - request, - selectedSession.stockTakeId, - currentUserId - ); - - onSnackbar(t("Stock take record saved successfully"), "success"); - handleCancelEdit(); - - const details = await getInventoryLotDetailsBySection( - selectedSession.stockTakeSession, - selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null - ); - setInventoryLotDetails(Array.isArray(details) ? details : []); - } catch (e: any) { - console.error("Save stock take record error:", e); - let errorMessage = t("Failed to save stock take record"); - - if (e?.message) { - errorMessage = e.message; - } else if (e?.response) { - try { - const errorData = await e.response.json(); - errorMessage = errorData.message || errorData.error || errorMessage; - } catch { - // ignore + const formatNumber = (num: number | null | undefined): string => { + if (num == null || Number.isNaN(num)) return "0.00"; + return num.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + }; + + const handleSaveStockTake = useCallback( + async (detail: InventoryLotDetailResponse) => { + if (!selectedSession || !currentUserId) { + return; + } + + const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; + const isSecondSubmit = + detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; + + // 现在用户输入的是 total 和 bad,需要算 available = total - bad + const totalQtyStr = isFirstSubmit ? firstQty : secondQty; + const badQtyStr = isFirstSubmit ? firstBadQty : secondBadQty; + + if (!totalQtyStr || !badQtyStr) { + onSnackbar( + isFirstSubmit + ? t("Please enter QTY and Bad QTY") + : t("Please enter Second QTY and Bad QTY"), + "error" + ); + return; + } + + const totalQty = parseFloat(totalQtyStr); + const badQty = parseFloat(badQtyStr); + + if (Number.isNaN(totalQty) || Number.isNaN(badQty)) { + onSnackbar(t("Invalid QTY or Bad QTY"), "error"); + return; + } + + const availableQty = totalQty - badQty; + + if (availableQty < 0) { + onSnackbar(t("Available QTY cannot be negative"), "error"); + return; + } + + setSaving(true); + try { + const request: SaveStockTakeRecordRequest = { + stockTakeRecordId: detail.stockTakeRecordId || null, + inventoryLotLineId: detail.id, + qty: availableQty, // 保存 available qty + badQty: badQty, // 保存 bad qty + remark: isSecondSubmit ? (remark || null) : null, + }; + console.log("handleSaveStockTake: request:", request); + console.log("handleSaveStockTake: selectedSession.stockTakeId:", selectedSession.stockTakeId); + console.log("handleSaveStockTake: currentUserId:", currentUserId); + + await saveStockTakeRecord(request, selectedSession.stockTakeId, currentUserId); + + onSnackbar(t("Stock take record saved successfully"), "success"); + handleCancelEdit(); + + const details = await getInventoryLotDetailsBySection( + selectedSession.stockTakeSession, + selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null + ); + setInventoryLotDetails(Array.isArray(details) ? details : []); + } catch (e: any) { + console.error("Save stock take record error:", e); + let errorMessage = t("Failed to save stock take record"); + + if (e?.message) { + errorMessage = e.message; + } else if (e?.response) { + try { + const errorData = await e.response.json(); + errorMessage = errorData.message || errorData.error || errorMessage; + } catch { + // ignore + } } + + onSnackbar(errorMessage, "error"); + } finally { + setSaving(false); } - - onSnackbar(errorMessage, "error"); - } finally { - setSaving(false); - } - }, [selectedSession, firstQty, secondQty, firstBadQty, secondBadQty, remark, handleCancelEdit, t, currentUserId, onSnackbar]); + }, + [ + selectedSession, + firstQty, + secondQty, + firstBadQty, + secondBadQty, + remark, + handleCancelEdit, + t, + currentUserId, + onSnackbar, + ] + ); - const handleBatchSubmitAll = useCallback(async () => { - if (!selectedSession || !currentUserId) { - console.log('handleBatchSubmitAll: Missing selectedSession or currentUserId'); - return; - } + const handleBatchSubmitAll = useCallback( + async () => { + if (!selectedSession || !currentUserId) { + console.log("handleBatchSubmitAll: Missing selectedSession or currentUserId"); + return; + } + + console.log("handleBatchSubmitAll: Starting batch save..."); + setBatchSaving(true); + try { + const request: BatchSaveStockTakeRecordRequest = { + stockTakeId: selectedSession.stockTakeId, + stockTakeSection: selectedSession.stockTakeSession, + stockTakerId: currentUserId, + }; + + const result = await batchSaveStockTakeRecords(request); + console.log("handleBatchSubmitAll: Result:", result); + + onSnackbar( + t("Batch save completed: {{success}} success, {{errors}} errors", { + success: result.successCount, + errors: result.errorCount, + }), + result.errorCount > 0 ? "warning" : "success" + ); - console.log('handleBatchSubmitAll: Starting batch save...'); - setBatchSaving(true); - try { - const request: BatchSaveStockTakeRecordRequest = { - stockTakeId: selectedSession.stockTakeId, - stockTakeSection: selectedSession.stockTakeSession, - stockTakerId: currentUserId, - }; - - const result = await batchSaveStockTakeRecords(request); - console.log('handleBatchSubmitAll: Result:', result); - - onSnackbar( - t("Batch save completed: {{success}} success, {{errors}} errors", { - success: result.successCount, - errors: result.errorCount, - }), - result.errorCount > 0 ? "warning" : "success" - ); - - const details = await getInventoryLotDetailsBySection( - selectedSession.stockTakeSession, - selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null - ); - setInventoryLotDetails(Array.isArray(details) ? details : []); - } catch (e: any) { - console.error("handleBatchSubmitAll: Error:", e); - let errorMessage = t("Failed to batch save stock take records"); - - if (e?.message) { - errorMessage = e.message; - } else if (e?.response) { - try { - const errorData = await e.response.json(); - errorMessage = errorData.message || errorData.error || errorMessage; - } catch { - // ignore + const details = await getInventoryLotDetailsBySection( + selectedSession.stockTakeSession, + selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null + ); + setInventoryLotDetails(Array.isArray(details) ? details : []); + } catch (e: any) { + console.error("handleBatchSubmitAll: Error:", e); + let errorMessage = t("Failed to batch save stock take records"); + + if (e?.message) { + errorMessage = e.message; + } else if (e?.response) { + try { + const errorData = await e.response.json(); + errorMessage = errorData.message || errorData.error || errorMessage; + } catch { + // ignore + } } + + onSnackbar(errorMessage, "error"); + } finally { + setBatchSaving(false); } - - onSnackbar(errorMessage, "error"); - } finally { - setBatchSaving(false); - } - }, [selectedSession, t, currentUserId, onSnackbar]); + }, + [selectedSession, t, currentUserId, onSnackbar] + ); useEffect(() => { handleBatchSubmitAllRef.current = handleBatchSubmitAll; @@ -227,11 +278,12 @@ const PickerStockTake: React.FC = ({ useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { const target = e.target as HTMLElement; - if (target && ( - target.tagName === 'INPUT' || - target.tagName === 'TEXTAREA' || - target.isContentEditable - )) { + if ( + target && + (target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable) + ) { return; } @@ -240,48 +292,48 @@ const PickerStockTake: React.FC = ({ } if (e.key.length === 1) { - setShortcutInput(prev => { + setShortcutInput((prev) => { const newInput = prev + e.key; - - if (newInput === '{2fitestall}') { - console.log('✅ Shortcut {2fitestall} detected!'); + + if (newInput === "{2fitestall}") { + console.log("✅ Shortcut {2fitestall} detected!"); setTimeout(() => { if (handleBatchSubmitAllRef.current) { - console.log('Calling handleBatchSubmitAll...'); - handleBatchSubmitAllRef.current().catch(err => { - console.error('Error in handleBatchSubmitAll:', err); + console.log("Calling handleBatchSubmitAll..."); + handleBatchSubmitAllRef.current().catch((err) => { + console.error("Error in handleBatchSubmitAll:", err); }); } else { - console.error('handleBatchSubmitAllRef.current is null'); + console.error("handleBatchSubmitAllRef.current is null"); } }, 0); return ""; } - + if (newInput.length > 15) { return ""; } - - if (newInput.length > 0 && !newInput.startsWith('{')) { + + if (newInput.length > 0 && !newInput.startsWith("{")) { return ""; } - - if (newInput.length > 5 && !newInput.startsWith('{2fi')) { + + if (newInput.length > 5 && !newInput.startsWith("{2fi")) { return ""; } - + return newInput; }); - } else if (e.key === 'Backspace') { - setShortcutInput(prev => prev.slice(0, -1)); - } else if (e.key === 'Escape') { + } else if (e.key === "Backspace") { + setShortcutInput((prev) => prev.slice(0, -1)); + } else if (e.key === "Escape") { setShortcutInput(""); } }; - window.addEventListener('keydown', handleKeyPress); + window.addEventListener("keydown", handleKeyPress); return () => { - window.removeEventListener('keydown', handleKeyPress); + window.removeEventListener("keydown", handleKeyPress); }; }, []); @@ -292,19 +344,46 @@ const PickerStockTake: React.FC = ({ return false; }, []); + const uniqueWarehouses = Array.from( + new Set( + inventoryLotDetails + .map((detail) => detail.warehouse) + .filter((warehouse) => warehouse && warehouse.trim() !== "") + ) + ).join(", "); + return ( - {t("Stock Take Section")}: {selectedSession.stockTakeSession} + {uniqueWarehouses && ( + <> {t("Warehouse")}: {uniqueWarehouses} + )} + {/* 如果需要显示快捷键输入,可以把这块注释打开 */} {/* {shortcutInput && ( - + - {t("Shortcut Input")}: {shortcutInput} + {t("Shortcut Input")}:{" "} + + {shortcutInput} + )} @@ -319,17 +398,10 @@ const PickerStockTake: React.FC = ({ {t("Warehouse Location")} - {t("Item")} - {/*{t("Item Name")}*/} - {/*{t("Lot No")}*/} - {t("Expiry Date")} - {t("Qty")} - {t("Bad Qty")} - {/*{inventoryLotDetails.some(d => editingRecord?.id === d.id) && (*/} - {t("Remark")} - + {t("Item-lotNo-ExpiryDate")} + {t("Stock Take Qty(include Bad Qty)= Available Qty")} + {t("Remark")} {t("UOM")} - {t("Status")} {t("Record Status")} {t("Action")} @@ -337,7 +409,7 @@ const PickerStockTake: React.FC = ({ {inventoryLotDetails.length === 0 ? ( - + {t("No data")} @@ -347,152 +419,215 @@ const PickerStockTake: React.FC = ({ inventoryLotDetails.map((detail) => { const isEditing = editingRecord?.id === detail.id; const submitDisabled = isSubmitDisabled(detail); - const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; - const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; + const isFirstSubmit = + !detail.stockTakeRecordId || !detail.firstStockTakeQty; + const isSecondSubmit = + detail.stockTakeRecordId && + detail.firstStockTakeQty && + !detail.secondStockTakeQty; return ( - {detail.warehouseCode || "-"} - {detail.itemCode || "-"}{detail.lotNo || "-"}{detail.itemName ? ` - ${detail.itemName}` : ""} - {/* - - {detail.itemName || "-"} - */} - {/*{detail.lotNo || "-"}*/} - {detail.expiryDate - ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) - : "-"} + {detail.warehouseArea || "-"} + {detail.warehouseSlot || "-"} - - + - {isEditing && isFirstSubmit ? ( - setFirstQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.firstStockTakeQty ? ( - - {t("First")}: {detail.firstStockTakeQty.toFixed(2)} - - ) : null} - - {isEditing && isSecondSubmit ? ( - setSecondQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.secondStockTakeQty ? ( - - {t("Second")}: {detail.secondStockTakeQty.toFixed(2)} - - ) : null} - - {!detail.firstStockTakeQty && !detail.secondStockTakeQty && !isEditing && ( - - - - - )} + + {detail.itemCode || "-"} {detail.itemName || "-"} + + {detail.lotNo || "-"} + + {detail.expiryDate + ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) + : "-"} + - - + + {/* Qty + Bad Qty 合并显示/输入 */} + + + {/* First */} {isEditing && isFirstSubmit ? ( - setFirstBadQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.firstBadQty ? ( + + {t("First")}: + setFirstQty(e.target.value)} + sx={{ + width: 130, + minWidth: 130, + "& .MuiInputBase-input": { + height: "1.4375em", + padding: "4px 8px", + }, + }} + placeholder={t("Stock Take Qty")} + /> + setFirstBadQty(e.target.value)} + sx={{ + width: 130, + minWidth: 130, + "& .MuiInputBase-input": { + height: "1.4375em", + padding: "4px 8px", + }, + }} + placeholder={t("Bad Qty")} + /> + + = + {formatNumber( + parseFloat(firstQty || "0") - + parseFloat(firstBadQty || "0") + )} + + + ) : detail.firstStockTakeQty != null ? ( - {t("First")}: {detail.firstBadQty.toFixed(2)} + {t("First")}:{" "} + {formatNumber( + (detail.firstStockTakeQty ?? 0) + + (detail.firstBadQty ?? 0) + )}{" "} + ( + {formatNumber( + detail.firstBadQty ?? 0 + )} + ) ={" "} + {formatNumber(detail.firstStockTakeQty ?? 0)} ) : null} - + + {/* Second */} {isEditing && isSecondSubmit ? ( - setSecondBadQty(e.target.value)} - sx={{ width: 100 }} - - /> - ) : detail.secondBadQty ? ( + + {t("Second")}: + setSecondQty(e.target.value)} + sx={{ + width: 130, + minWidth: 130, + "& .MuiInputBase-input": { + height: "1.4375em", + padding: "4px 8px", + }, + }} + placeholder={t("Stock Take Qty")} + /> + setSecondBadQty(e.target.value)} + sx={{ + width: 130, + minWidth: 130, + "& .MuiInputBase-input": { + height: "1.4375em", + padding: "4px 8px", + }, + }} + placeholder={t("Bad Qty")} + /> + + = + {formatNumber( + parseFloat(secondQty || "0") - + parseFloat(secondBadQty || "0") + )} + + + ) : detail.secondStockTakeQty != null ? ( - {t("Second")}: {detail.secondBadQty.toFixed(2)} + {t("Second")}:{" "} + {formatNumber( + (detail.secondStockTakeQty ?? 0) + + (detail.secondBadQty ?? 0) + )}{" "} + ( + {formatNumber( + detail.secondBadQty ?? 0 + )} + ) ={" "} + {formatNumber(detail.secondStockTakeQty ?? 0)} ) : null} - - {!detail.firstBadQty && !detail.secondBadQty && !isEditing && ( - - - - - )} - + + {!detail.firstStockTakeQty && + !detail.secondStockTakeQty && + !isEditing && ( + + - + + )} + + {/* Remark */} - {isEditing && isSecondSubmit ? ( - <> - {t("Remark")} - setRemark(e.target.value)} - sx={{ width: 150 }} - // If you want a single-line input, remove multiline/rows: - // multiline - // rows={2} - /> - - ) : ( - - {detail.remarks || "-"} - - )} - - {detail.uom || "-"} - - {detail.status ? ( - + {isEditing && isSecondSubmit ? ( + <> + {t("Remark")} + setRemark(e.target.value)} + sx={{ width: 150 }} + /> + ) : ( - "-" + + {detail.remarks || "-"} + )} + + {detail.uom || "-"} + {detail.stockTakeRecordStatus === "pass" ? ( - + ) : detail.stockTakeRecordStatus === "notMatch" ? ( - + ) : ( - + )} + {isEditing ? ( @@ -504,13 +639,9 @@ const PickerStockTake: React.FC = ({ > {t("Save")} - - ) : ( )} diff --git a/src/components/StockTakeManagement/StockTakeManagementWrapper.tsx b/src/components/StockTakeManagement/StockTakeManagementWrapper.tsx index d306255..c39bc1f 100644 --- a/src/components/StockTakeManagement/StockTakeManagementWrapper.tsx +++ b/src/components/StockTakeManagement/StockTakeManagementWrapper.tsx @@ -1,13 +1,13 @@ import React from "react"; import GeneralLoading from "../General/GeneralLoading"; import StockTakeManagement from "./StockTakeManagement"; - +import StockTakeTabs from "./StockTakeTab"; interface SubComponents { Loading: typeof GeneralLoading; } const StockTakeManagementWrapper: React.FC & SubComponents = async () => { - return ; + return ; }; StockTakeManagementWrapper.Loading = GeneralLoading; diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index d7d8f61..901f62c 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -4,7 +4,7 @@ "Job Order Production Process": "工單生產流程", "productionProcess": "生產流程", "Search Criteria": "搜尋條件", - "All": "全部", + "Stock Record": "庫存記錄", "No options": "沒有選項", "Select Another Bag Lot": "選擇另一個包裝袋", "Finished QC Job Orders": "完成QC工單", @@ -28,6 +28,13 @@ "Total finished QC job orders": "總完成QC工單數量", "Over Time": "超時", "Code": "編號", + "Job Order No.": "工單編號", + "FG / WIP Item": "成品/半成品", + "Production Time Remaining": "生產剩餘時間", + "Process": "工序", + "Start": "開始", +"Finish": "完成", +"Wait Time [minutes]": "等待時間(分鐘)", "Staff No": "員工編號", "code": "編號", "Name": "名稱", @@ -41,6 +48,8 @@ "No": "沒有", "Assignment failed: ": "分配失敗: ", "Unknown error": "未知錯誤", + "Job Process Status": "工單流程狀態", + "FG / WIP Item": "成品/半成品", "WIP": "半成品", "R&D": "研發", "STF": "樣品", diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index bb0f8da..6e63f05 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -10,12 +10,29 @@ "fg": "成品", "Back to List": "返回列表", "Record Status": "記錄狀態", + "Stock take record status updated to not match": "盤點記錄狀態更新為數值不符", "available": "可用", + "Item-lotNo-ExpiryDate": "貨品-批號-到期日", "not available": "不可用", + "Batch Submit All": "批量提交所有", + "Batch Save All": "批量保存所有", "not match": "數值不符", - "Stock Take Qty": "盤點數量(含壞盤點數量)", + "Stock Take Qty(include Bad Qty)= Available Qty": "盤點數(含壞品)= 可用數", "View ReStockTake": "查看重新盤點", + "Stock Take Qty": "盤點數", + "ReStockTake": "重新盤點", + "Stock Taker": "盤點員", + "Total Item Number": "貨品數量", + "Start Time": "開始時間", + "Difference": "差異", + "stockTaking": "盤點中", + "selected stock take qty": "已選擇盤點數量", + "book qty": "帳面庫存", + "start time": "開始時間", + "end time": "結束時間", + "Only Variance": "僅差異", + "Control Time": "操作時間", "pass": "通過", "not pass": "不通過", "Available": "可用", @@ -23,7 +40,7 @@ "pending": "待處理", "Last Stock Take Date": "上次盤點日期", "Remark": "備註", - "notMatch": "不匹配", + "notMatch": "數值不符", "Stock take record saved successfully": "盤點記錄保存成功", "View Details": "查看詳細", "Input": "輸入", @@ -133,5 +150,17 @@ "Stock take adjustment confirmed! (Demo only)": "盤點調整確認!(僅演示)", "Stock take adjustment has been confirmed successfully!": "盤點調整確認成功!", "System Qty": "系統數量", - "Variance": "差異" + "Variance": "差異", + + "Stock Record": "庫存記錄", + "Item-lotNo": "貨品-批號", + "In Qty": "入庫數量", + "Out Qty": "出庫數量", + "Balance Qty": "庫存數量", + "Start Date": "開始日期", + "End Date": "結束日期", + "Loading": "加載中", + "adj": "調整", + "nor": "正常" + } diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 3144af4..721fee0 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -101,6 +101,13 @@ "Job Order Pickexcution": "工單提料", "Pick Order Detail": "提料單細節", "Finished Job Order Record": "已完成工單記錄", + "No. of Items to be Picked": "需提料數量", + "No. of Items with Issue During Pick": "提料過程中出現問題的數量", + "Pick Start Time": "提料開始時間", + "Pick End Time": "提料結束時間", + "FG / WIP Item": "成品/半成品", + "Pick Order No.- Job Order No.- Item": "提料單編號-工單編號-成品/半成品", + "Pick Time Taken (minutes)": "提料時間(分鐘)", "Index": "編號", "Route": "路線", "Qty": "數量", @@ -517,6 +524,13 @@ "Start Scan": "開始掃碼", "Stop Scan": "停止掃碼", - - "Sign out": "登出" + "Material Pick Status": "物料提料狀態", + "Job Order Qty": "工單數量", + "Sign out": "登出", + "Job Order No.": "工單編號", + "FG / WIP Item": "成品/半成品", + "Production Time Remaining": "生產剩餘時間", + "Process": "工序", + "Start": "開始", +"Finish": "完成" }