- MTMS
+ FP-MTMS
Food Production
diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx
index f47d72a..18e9072 100644
--- a/src/components/NavigationContent/NavigationContent.tsx
+++ b/src/components/NavigationContent/NavigationContent.tsx
@@ -96,7 +96,7 @@ const NavigationContent: React.FC = () => {
{
icon:
,
label: "Stock Take Management",
- requiredAbility: [AUTH.STOCK_TAKE, AUTH.ADMIN],
+ requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.ADMIN],
path: "/stocktakemanagement",
},
{
@@ -120,7 +120,7 @@ const NavigationContent: React.FC = () => {
{
icon:
,
label: "Stock Record",
- requiredAbility: [AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.STOCK_FG, AUTH.ADMIN],
+ requiredAbility: [AUTH.STOCK, AUTH.STOCK_TAKE, AUTH.STOCK_IN_BIND, AUTH.STOCK_FG, AUTH.ADMIN],
path: "/stockRecord",
},
],
diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx
index 115a86b..b9cb8d9 100644
--- a/src/components/PoDetail/PoInputGrid.tsx
+++ b/src/components/PoDetail/PoInputGrid.tsx
@@ -525,7 +525,7 @@ const closeNewModal = useCallback(() => {
width: 150,
// flex: 0.5,
renderCell: (params) => {
- return params.row.uom?.udfudesc;
+ return itemDetail.uom?.udfudesc;
},
},
{
diff --git a/src/components/ProductionProcess/BagConsumptionForm.tsx b/src/components/ProductionProcess/BagConsumptionForm.tsx
index afee258..2de6881 100644
--- a/src/components/ProductionProcess/BagConsumptionForm.tsx
+++ b/src/components/ProductionProcess/BagConsumptionForm.tsx
@@ -140,7 +140,17 @@ const BagConsumptionForm: React.FC
= ({
alert(t("Please select at least one bag"));
return;
}
-
+ for (const row of validRows) {
+ const selectedBag = bagList.find((b) => b.id === row.bagLotLineId);
+ const available = selectedBag?.balanceQty ?? 0;
+ const requested = row.consumedQty + row.scrapQty;
+ if (requested > available) {
+ alert(
+ `${selectedBag?.bagName ?? "Bag"}: ${t("Insufficient balance")}. ${t("Available")}: ${available}, ${t("Requested")}: ${requested}`
+ );
+ return;
+ }
+ }
// 提交每个 bag consumption
const promises = validRows.map((row) => {
const selectedBag = bagList.find((b) => b.id === row.bagLotLineId);
diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
index b569b70..f00ddf5 100644
--- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
+++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
@@ -422,7 +422,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
const productionProcessesLineRemarkTableColumns: GridColDef[] = [
{
field: "seqNo",
- headerName: t("Seq"),
+ headerName: t("SEQ"),
flex: 0.2,
align: "left",
headerAlign: "left",
diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx
index 2190567..dcdde39 100644
--- a/src/components/ProductionProcess/ProductionProcessList.tsx
+++ b/src/components/ProductionProcess/ProductionProcessList.tsx
@@ -220,7 +220,7 @@ const ProductProcessList: React.FC = ({ onSelectProcess
display: "flex",
flexDirection: "column",
border: "1px solid",
- borderColor: "success.main",
+ borderColor: "blue",
}}
>
= ({ onSelectProcess
-
- {t("Item Name")}: {process.itemCode} {process.itemName}
+
+ {/* {t("Item Name")}: */}
+ {process.itemCode} {process.itemName}
{t("Production Priority")}: {process.productionPriority}
@@ -306,7 +307,7 @@ const ProductProcessList: React.FC = ({ onSelectProcess
)}
{statusLower === "completed" && (
-
- setShowOnlyWithDifference(!showOnlyWithDifference)}
- startIcon={
+ setVariancePercentTolerance(e.target.value)}
+ label={t("Variance %")}
+ sx={{ width: 100 }}
+ inputProps={{ min: 0, max: 100, step: 0.1 }}
+ />
+ {/*
+ setShowOnlyWithDifference(e.target.checked)}
- sx={{ p: 0, pointerEvents: 'none' }}
/>
}
- sx={{ textTransform: 'none' }}
- >
- {t("Only Variance")}
+ label={t("Only Variance")}
+ />
+ */}
+
+ {t("Batch Save All")}
-
- {t("Batch Save All")}
-
-
+
{loadingDetails ? (
@@ -454,9 +495,10 @@ const ApproverStockTake: React.FC = ({
{t("Warehouse Location")}
{t("Item-lotNo-ExpiryDate")}
+ {t("UOM")}
{t("Stock Take Qty(include Bad Qty)= Available Qty")}
{t("Remark")}
- {t("UOM")}
+
{t("Record Status")}
{t("Action")}
@@ -492,25 +534,27 @@ const ApproverStockTake: React.FC = ({
{detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"}
-
+ {detail.uom || "-"}
- {detail.finalQty != null ? (
-
- {(() => {
- const finalDifference = (detail.finalQty || 0) - (detail.availableQty || 0);
- const differenceColor = finalDifference > 0
- ? 'error.main'
- : finalDifference < 0
- ? 'error.main'
- : 'success.main';
-
- return (
-
- {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(detail.availableQty)} = {formatNumber(finalDifference)}
-
- );
- })()}
-
+ {detail.finalQty != null ? (
+
+ {(() => {
+ // 若有 bookQty(盤點當時帳面),用它來算差異;否則用 availableQty
+ const bookQtyToUse = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0);
+ const finalDifference = (detail.finalQty || 0) - bookQtyToUse;
+ const differenceColor = detail.stockTakeRecordStatus === "completed"
+ ? 'text.secondary'
+ : finalDifference !== 0
+ ? 'error.main'
+ : 'success.main';
+
+ return (
+
+ {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(bookQtyToUse)} = {formatNumber(finalDifference)}
+
+ );
+ })()}
+
) : (
{hasFirst && (
@@ -581,7 +625,7 @@ const ApproverStockTake: React.FC = ({
disabled={selection !== "approver"}
/>
- ={(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))}
+ = {formatNumber(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))}
)}
@@ -597,12 +641,12 @@ const ApproverStockTake: React.FC = ({
selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))|| 0;
}
- const bookQty = detail.availableQty || 0;
+ const bookQty = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0);
const difference = selectedQty - bookQty;
- const differenceColor = difference > 0
- ? 'error.main'
- : difference < 0
- ? 'error.main'
+ const differenceColor = detail.stockTakeRecordStatus === "completed"
+ ? 'text.secondary'
+ : difference !== 0
+ ? 'error.main'
: 'success.main';
return (
@@ -621,11 +665,13 @@ const ApproverStockTake: React.FC = ({
- {detail.uom || "-"}
+
- {detail.stockTakeRecordStatus === "pass" ? (
+ {detail.stockTakeRecordStatus === "completed" ? (
+ ) : detail.stockTakeRecordStatus === "pass" ? (
+
) : detail.stockTakeRecordStatus === "notMatch" ? (
) : (
diff --git a/src/components/StockTakeManagement/PickerCardList.tsx b/src/components/StockTakeManagement/PickerCardList.tsx
index 15c437a..6c6f0ca 100644
--- a/src/components/StockTakeManagement/PickerCardList.tsx
+++ b/src/components/StockTakeManagement/PickerCardList.tsx
@@ -13,6 +13,11 @@ import {
TablePagination,
Grid,
LinearProgress,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogContentText,
+ DialogActions,
} from "@mui/material";
import { useState, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
@@ -41,7 +46,7 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT
const [stockTakeSessions, setStockTakeSessions] = useState([]);
const [page, setPage] = useState(0);
const [creating, setCreating] = useState(false);
-
+ const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
const fetchStockTakeSessions = useCallback(async () => {
setLoading(true);
try {
@@ -64,6 +69,7 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT
const paged = stockTakeSessions.slice(startIdx, startIdx + PER_PAGE);
const handleCreateStockTake = useCallback(async () => {
+ setOpenConfirmDialog(false);
setCreating(true);
try {
const result = await createStockTakeForSections();
@@ -177,7 +183,7 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT
setOpenConfirmDialog(true)}
disabled={creating}
>
{creating ? : t("Create Stock Take for All Sections")}
@@ -263,6 +269,33 @@ const PickerCardList: React.FC = ({ onCardClick, onReStockT
rowsPerPageOptions={[PER_PAGE]}
/>
)}
+ {/* Create Stock Take 確認 Dialog */}
+
);
};
diff --git a/src/components/StockTakeManagement/PickerReStockTake.tsx b/src/components/StockTakeManagement/PickerReStockTake.tsx
index 9233ca8..1ff1001 100644
--- a/src/components/StockTakeManagement/PickerReStockTake.tsx
+++ b/src/components/StockTakeManagement/PickerReStockTake.tsx
@@ -21,7 +21,6 @@ import { useState, useCallback, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
AllPickedStockTakeListReponse,
- getInventoryLotDetailsBySection,
InventoryLotDetailResponse,
saveStockTakeRecord,
SaveStockTakeRecordRequest,
@@ -51,13 +50,13 @@ const PickerReStockTake: React.FC = ({
const [inventoryLotDetails, setInventoryLotDetails] = useState([]);
const [loadingDetails, setLoadingDetails] = useState(false);
- // 编辑状态
- const [editingRecord, setEditingRecord] = useState(null);
- const [firstQty, setFirstQty] = useState("");
- const [secondQty, setSecondQty] = useState("");
- const [firstBadQty, setFirstBadQty] = useState("");
- const [secondBadQty, setSecondBadQty] = useState("");
- const [remark, setRemark] = useState("");
+ const [recordInputs, setRecordInputs] = useState>({});
const [saving, setSaving] = useState(false);
const [batchSaving, setBatchSaving] = useState(false);
const [shortcutInput, setShortcutInput] = useState("");
@@ -115,28 +114,36 @@ const PickerReStockTake: React.FC = ({
}
}, [selectedSession, total]);
+ useEffect(() => {
+ const inputs: Record = {};
+ inventoryLotDetails.forEach((detail) => {
+ const firstTotal = detail.firstStockTakeQty != null
+ ? (detail.firstStockTakeQty + (detail.firstBadQty ?? 0)).toString()
+ : "";
+ const secondTotal = detail.secondStockTakeQty != null
+ ? (detail.secondStockTakeQty + (detail.secondBadQty ?? 0)).toString()
+ : "";
+ inputs[detail.id] = {
+ firstQty: firstTotal,
+ secondQty: secondTotal,
+ firstBadQty: detail.firstBadQty?.toString() || "",
+ secondBadQty: detail.secondBadQty?.toString() || "",
+ remark: detail.remarks || "",
+ };
+ });
+ setRecordInputs(inputs);
+ }, [inventoryLotDetails]);
+
useEffect(() => {
loadDetails(page, pageSize);
}, [page, pageSize, loadDetails]);
-
- const handleStartEdit = useCallback((detail: InventoryLotDetailResponse) => {
- setEditingRecord(detail);
- setFirstQty(detail.firstStockTakeQty?.toString() || "");
- setSecondQty(detail.secondStockTakeQty?.toString() || "");
- setFirstBadQty(detail.firstBadQty?.toString() || "");
- setSecondBadQty(detail.secondBadQty?.toString() || "");
- setRemark(detail.remarks || "");
- }, []);
-
- const handleCancelEdit = useCallback(() => {
- setEditingRecord(null);
- setFirstQty("");
- setSecondQty("");
- setFirstBadQty("");
- setSecondBadQty("");
- setRemark("");
- }, []);
-
+ const formatNumber = (num: number | null | undefined): string => {
+ if (num == null || Number.isNaN(num)) return "0";
+ return num.toLocaleString("en-US", {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ });
+ };
const handleSaveStockTake = useCallback(async (detail: InventoryLotDetailResponse) => {
if (!selectedSession || !currentUserId) {
return;
@@ -145,41 +152,69 @@ const PickerReStockTake: React.FC = ({
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) {
+ // 用戶輸入為 total 和 bad,需計算 available = total - bad(與 PickerStockTake 一致)
+ const totalQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstQty : recordInputs[detail.id]?.secondQty;
+ const badQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstBadQty : recordInputs[detail.id]?.secondBadQty;
+
+ if (!totalQtyStr) {
onSnackbar(
isFirstSubmit
- ? t("Please enter QTY and Bad QTY")
- : t("Please enter Second QTY and Bad QTY"),
+ ? t("Please enter QTY")
+ : t("Please enter Second QTY"),
"error"
);
return;
}
-
+
+ const totalQty = parseFloat(totalQtyStr);
+ const badQty = parseFloat(badQtyStr || "0") || 0;
+
+ if (Number.isNaN(totalQty)) {
+ onSnackbar(t("Invalid 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: parseFloat(qty),
- badQty: parseFloat(badQty),
- remark: isSecondSubmit ? (remark || null) : null,
+ qty: availableQty,
+ badQty: badQty,
+ remark: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : null,
};
- console.log('handleSaveStockTake: request:', request);
- console.log('handleSaveStockTake: selectedSession.stockTakeId:', selectedSession.stockTakeId);
- console.log('handleSaveStockTake: currentUserId:', currentUserId);
- await saveStockTakeRecord(
+ const result = await saveStockTakeRecord(
request,
selectedSession.stockTakeId,
currentUserId
);
onSnackbar(t("Stock take record saved successfully"), "success");
- handleCancelEdit();
-
- await loadDetails(page, pageSize);
+
+ const savedId = result?.id ?? detail.stockTakeRecordId;
+ setInventoryLotDetails((prev) =>
+ prev.map((d) =>
+ d.id === detail.id
+ ? {
+ ...d,
+ stockTakeRecordId: savedId ?? d.stockTakeRecordId,
+ firstStockTakeQty: isFirstSubmit ? availableQty : d.firstStockTakeQty,
+ firstBadQty: isFirstSubmit ? badQty : d.firstBadQty ?? null,
+ secondStockTakeQty: isSecondSubmit ? availableQty : d.secondStockTakeQty,
+ secondBadQty: isSecondSubmit ? badQty : d.secondBadQty ?? null,
+ remarks: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : d.remarks,
+ stockTakeRecordStatus: "pass",
+ }
+ : d
+ )
+ );
} catch (e: any) {
console.error("Save stock take record error:", e);
let errorMessage = t("Failed to save stock take record");
@@ -199,15 +234,13 @@ const PickerReStockTake: React.FC = ({
} finally {
setSaving(false);
}
- }, [selectedSession, firstQty, secondQty, firstBadQty, secondBadQty, remark, handleCancelEdit, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
+ }, [selectedSession, recordInputs, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
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 = {
@@ -217,7 +250,6 @@ const PickerReStockTake: React.FC = ({
};
const result = await batchSaveStockTakeRecords(request);
- console.log('handleBatchSubmitAll: Result:', result);
onSnackbar(
t("Batch save completed: {{success}} success, {{errors}} errors", {
@@ -273,31 +305,19 @@ const PickerReStockTake: React.FC = ({
const newInput = prev + e.key;
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);
});
- } else {
- console.error('handleBatchSubmitAllRef.current is null');
}
}, 0);
return "";
}
- if (newInput.length > 15) {
- return "";
- }
-
- if (newInput.length > 0 && !newInput.startsWith('{')) {
- return "";
- }
-
- if (newInput.length > 5 && !newInput.startsWith('{2fi')) {
- return "";
- }
+ if (newInput.length > 15) return "";
+ if (newInput.length > 0 && !newInput.startsWith('{')) return "";
+ if (newInput.length > 5 && !newInput.startsWith('{2fi')) return "";
return newInput;
});
@@ -315,11 +335,15 @@ const PickerReStockTake: React.FC = ({
}, []);
const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => {
- if (detail.stockTakeRecordStatus === "pass") {
+ if (selectedSession?.status?.toLowerCase() === "completed") {
+ return true;
+ }
+ const recordStatus = detail.stockTakeRecordStatus?.toLowerCase();
+ if (recordStatus === "pass" || recordStatus === "completed") {
return true;
}
return false;
- }, []);
+ }, [selectedSession?.status]);
const uniqueWarehouses = Array.from(
new Set(
@@ -328,6 +352,9 @@ const PickerReStockTake: React.FC = ({
.filter(warehouse => warehouse && warehouse.trim() !== "")
)
).join(", ");
+
+ const defaultInputs = { firstQty: "", secondQty: "", firstBadQty: "", secondBadQty: "", remark: "" };
+
return (
@@ -339,42 +366,31 @@ const PickerReStockTake: React.FC = ({
<> {t("Warehouse")}: {uniqueWarehouses}>
)}
- {/*
- {shortcutInput && (
-
-
- {t("Shortcut Input")}: {shortcutInput}
-
-
- )}
- */}
{loadingDetails ? (
) : (
<>
-
+
-
+
{t("Warehouse Location")}
{t("Item-lotNo-ExpiryDate")}
- {t("Qty")}
- {t("Bad Qty")}
- {t("Remark")}
{t("UOM")}
+ {t("Stock Take Qty(include Bad Qty)= Available Qty")}
+ {t("Remark")}
{t("Record Status")}
{t("Action")}
@@ -382,7 +398,7 @@ const PickerReStockTake: React.FC = ({
{inventoryLotDetails.length === 0 ? (
-
+
{t("No data")}
@@ -390,99 +406,156 @@ const PickerReStockTake: 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 inputs = recordInputs[detail.id] ?? defaultInputs;
return (
{detail.warehouseArea || "-"}{detail.warehouseSlot || "-"}
+ maxWidth: 150,
+ wordBreak: 'break-word',
+ whiteSpace: 'normal',
+ lineHeight: 1.5
+ }}>
{detail.itemCode || "-"} {detail.itemName || "-"}
{detail.lotNo || "-"}
{detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"}
-
-
-
- {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 ? (
+ {detail.uom || "-"}
+
+
+ {/* First */}
+ {!submitDisabled && isFirstSubmit ? (
+
+ {t("First")}:
+ {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: { ...(prev[detail.id] ?? defaultInputs), firstQty: val }
+ }));
+ }}
+ sx={{
+ width: 130,
+ minWidth: 130,
+ "& .MuiInputBase-input": {
+ height: "1.4375em",
+ padding: "4px 8px",
+ },
+ }}
+ placeholder={t("Stock Take Qty")}
+ />
+ {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: { ...(prev[detail.id] ?? defaultInputs), firstBadQty: val }
+ }));
+ }}
+ sx={{
+ width: 130,
+ minWidth: 130,
+ "& .MuiInputBase-input": {
+ height: "1.4375em",
+ padding: "4px 8px",
+ },
+ }}
+ placeholder={t("Bad Qty")}
+ />
+
+ = {formatNumber(parseFloat(inputs.firstQty || "0") - parseFloat(inputs.firstBadQty || "0"))}
+
+
+ ) : detail.firstStockTakeQty != null ? (
- {t("Second")}: {detail.secondStockTakeQty.toFixed(2)}
+ {t("First")}:{" "}
+ {formatNumber((detail.firstStockTakeQty ?? 0) + (detail.firstBadQty ?? 0))}{" "}
+ ({formatNumber(detail.firstBadQty ?? 0)}) ={" "}
+ {formatNumber(detail.firstStockTakeQty ?? 0)}
) : null}
-
- {!detail.firstStockTakeQty && !detail.secondStockTakeQty && !isEditing && (
-
- -
-
- )}
-
-
-
-
- {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 ? (
+
+ {/* Second */}
+ {!submitDisabled && isSecondSubmit ? (
+
+ {t("Second")}:
+ {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: { ...(prev[detail.id] ?? defaultInputs), secondQty: val }
+ }));
+ }}
+ sx={{
+ width: 130,
+ minWidth: 130,
+ "& .MuiInputBase-input": {
+ height: "1.4375em",
+ padding: "4px 8px",
+ },
+ }}
+ placeholder={t("Stock Take Qty")}
+ />
+ {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: { ...(prev[detail.id] ?? defaultInputs), secondBadQty: val }
+ }));
+ }}
+ sx={{
+ width: 130,
+ minWidth: 130,
+ "& .MuiInputBase-input": {
+ height: "1.4375em",
+ padding: "4px 8px",
+ },
+ }}
+ placeholder={t("Bad Qty")}
+ />
+
+ = {formatNumber(parseFloat(inputs.secondQty || "0") - parseFloat(inputs.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 && !submitDisabled && (
-
@@ -490,13 +563,16 @@ const PickerReStockTake: React.FC = ({
- {isEditing && isSecondSubmit ? (
+ {!submitDisabled && isSecondSubmit ? (
<>
{t("Remark")}
setRemark(e.target.value)}
+ value={inputs.remark}
+ onChange={(e) => setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: { ...(prev[detail.id] ?? defaultInputs), remark: e.target.value }
+ }))}
sx={{ width: 150 }}
/>
>
@@ -506,49 +582,30 @@ const PickerReStockTake: React.FC = ({
)}
- {detail.uom || "-"}
+
- {detail.stockTakeRecordStatus === "pass" ? (
-
- ) : detail.stockTakeRecordStatus === "notMatch" ? (
-
- ) : (
-
- )}
-
+ {detail.stockTakeRecordStatus === "completed" ? (
+
+ ) : detail.stockTakeRecordStatus === "pass" ? (
+
+ ) : detail.stockTakeRecordStatus === "notMatch" ? (
+
+ ) : (
+
+ )}
+
- {isEditing ? (
-
- handleSaveStockTake(detail)}
- disabled={saving || submitDisabled}
- >
- {t("Save")}
-
-
- {t("Cancel")}
-
-
- ) : (
+
handleStartEdit(detail)}
- disabled={submitDisabled}
+ variant="contained"
+ onClick={() => handleSaveStockTake(detail)}
+ disabled={saving || submitDisabled }
>
- {!detail.stockTakeRecordId
- ? t("Input")
- : detail.stockTakeRecordStatus === "notMatch"
- ? t("Input")
- : t("View")}
+ {t("Save")}
- )}
+
);
diff --git a/src/components/StockTakeManagement/PickerStockTake.tsx b/src/components/StockTakeManagement/PickerStockTake.tsx
index cda39cf..f2f4b2d 100644
--- a/src/components/StockTakeManagement/PickerStockTake.tsx
+++ b/src/components/StockTakeManagement/PickerStockTake.tsx
@@ -55,13 +55,14 @@ const PickerStockTake: React.FC = ({
const [inventoryLotDetails, setInventoryLotDetails] = useState([]);
const [loadingDetails, setLoadingDetails] = useState(false);
- // 编辑状态
- const [editingRecord, setEditingRecord] = useState(null);
- // firstQty / secondQty 保存的是 total = available + bad
- const [firstQty, setFirstQty] = useState("");
- const [secondQty, setSecondQty] = useState("");
- const [firstBadQty, setFirstBadQty] = useState("");
- const [secondBadQty, setSecondBadQty] = useState("");
+ const [recordInputs, setRecordInputs] = useState>({});
+ const [savingRecordId, setSavingRecordId] = useState(null);
const [remark, setRemark] = useState("");
const [saving, setSaving] = useState(false);
const [batchSaving, setBatchSaving] = useState(false);
@@ -91,7 +92,11 @@ const PickerStockTake: React.FC = ({
}
setPage(0);
}, []);
- const loadDetails = useCallback(async (pageNum: number, size: number | string) => {
+ const loadDetails = useCallback(async (
+ pageNum: number,
+ size: number | string,
+ options?: { silent?: boolean }
+ ) => {
console.log('loadDetails called with:', { pageNum, size, selectedSessionTotal: selectedSession.totalInventoryLotNumber });
setLoadingDetails(true);
try {
@@ -132,44 +137,34 @@ const PickerStockTake: React.FC = ({
setLoadingDetails(false);
}
}, [selectedSession, total]);
-
useEffect(() => {
- loadDetails(page, pageSize);
- }, [page, pageSize, loadDetails]);
- const handleStartEdit = useCallback((detail: InventoryLotDetailResponse) => {
- setEditingRecord(detail);
-
- // 编辑时,输入 total = qty + badQty
- const firstTotal =
- detail.firstStockTakeQty != null
+ const inputs: Record = {};
+ inventoryLotDetails.forEach((detail) => {
+ const firstTotal = detail.firstStockTakeQty != null
? (detail.firstStockTakeQty + (detail.firstBadQty ?? 0)).toString()
: "";
- const secondTotal =
- detail.secondStockTakeQty != null
+ 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 || "");
- }, []);
-
- const handleCancelEdit = useCallback(() => {
- setEditingRecord(null);
- setFirstQty("");
- setSecondQty("");
- setFirstBadQty("");
- setSecondBadQty("");
- setRemark("");
- }, []);
+ inputs[detail.id] = {
+ firstQty: firstTotal,
+ secondQty: secondTotal,
+ firstBadQty: detail.firstBadQty?.toString() || "",
+ secondBadQty: detail.secondBadQty?.toString() || "",
+ remark: detail.remarks || "",
+ };
+ });
+ setRecordInputs(inputs);
+ }, [inventoryLotDetails]);
+ useEffect(() => {
+ loadDetails(page, pageSize);
+ }, [page, pageSize, loadDetails]);
const formatNumber = (num: number | null | undefined): string => {
- if (num == null || Number.isNaN(num)) return "0.00";
+ if (num == null || Number.isNaN(num)) return "0";
return num.toLocaleString("en-US", {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
});
};
@@ -184,24 +179,25 @@ const PickerStockTake: React.FC = ({
detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty;
// 现在用户输入的是 total 和 bad,需要算 available = total - bad
- const totalQtyStr = isFirstSubmit ? firstQty : secondQty;
- const badQtyStr = isFirstSubmit ? firstBadQty : secondBadQty;
-
- if (!totalQtyStr || !badQtyStr) {
+ const totalQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstQty : recordInputs[detail.id]?.secondQty;
+ const badQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstBadQty : recordInputs[detail.id]?.secondBadQty;
+
+ // 只檢查 totalQty,Bad Qty 未輸入時預設為 0
+ if (!totalQtyStr) {
onSnackbar(
isFirstSubmit
- ? t("Please enter QTY and Bad QTY")
- : t("Please enter Second QTY and Bad QTY"),
+ ? t("Please enter QTY")
+ : t("Please enter Second 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");
+ const badQty = parseFloat(badQtyStr || "0") || 0; // 空字串時為 0
+
+ if (Number.isNaN(totalQty)) {
+ onSnackbar(t("Invalid QTY"), "error");
return;
}
@@ -219,7 +215,7 @@ const PickerStockTake: React.FC = ({
inventoryLotLineId: detail.id,
qty: availableQty, // 保存 available qty
badQty: badQty, // 保存 bad qty
- remark: isSecondSubmit ? (remark || null) : null,
+ remark: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : null,
};
console.log("handleSaveStockTake: request:", request);
console.log("handleSaveStockTake: selectedSession.stockTakeId:", selectedSession.stockTakeId);
@@ -228,10 +224,24 @@ const PickerStockTake: React.FC = ({
await saveStockTakeRecord(request, selectedSession.stockTakeId, currentUserId);
onSnackbar(t("Stock take record saved successfully"), "success");
- handleCancelEdit();
-
- await loadDetails(page, pageSize);
+ //await loadDetails(page, pageSize, { silent: true });
+ setInventoryLotDetails((prev) =>
+ prev.map((d) =>
+ d.id === detail.id
+ ? {
+ ...d,
+ stockTakeRecordId: d.stockTakeRecordId ?? null, // 首次儲存後可從 response 取得,此處先保留
+ firstStockTakeQty: isFirstSubmit ? availableQty : d.firstStockTakeQty,
+ firstBadQty: isFirstSubmit ? badQty : d.firstBadQty ?? null,
+ secondStockTakeQty: isSecondSubmit ? availableQty : d.secondStockTakeQty,
+ secondBadQty: isSecondSubmit ? badQty : d.secondBadQty ?? null,
+ remarks: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : d.remarks,
+ stockTakeRecordStatus: "pass",
+ }
+ : d
+ )
+ );
} catch (e: any) {
console.error("Save stock take record error:", e);
let errorMessage = t("Failed to save stock take record");
@@ -254,18 +264,11 @@ const PickerStockTake: React.FC = ({
},
[
selectedSession,
- firstQty,
- secondQty,
- firstBadQty,
- secondBadQty,
+ recordInputs,
remark,
- handleCancelEdit,
t,
currentUserId,
onSnackbar,
- loadDetails,
- page,
- pageSize,
]
);
@@ -387,11 +390,15 @@ const PickerStockTake: React.FC = ({
}, []);
const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => {
- if (detail.stockTakeRecordStatus === "pass") {
+ if (selectedSession?.status?.toLowerCase() === "completed") {
+ return true;
+ }
+ const recordStatus = detail.stockTakeRecordStatus?.toLowerCase();
+ if (recordStatus === "pass" || recordStatus === "completed") {
return true;
}
return false;
- }, []);
+ }, [selectedSession?.status]);
const uniqueWarehouses = Array.from(
new Set(
@@ -460,9 +467,10 @@ const PickerStockTake: React.FC = ({
{t("Warehouse Location")}
{t("Item-lotNo-ExpiryDate")}
+ {t("UOM")}
{t("Stock Take Qty(include Bad Qty)= Available Qty")}
{t("Remark")}
- {t("UOM")}
+
{t("Record Status")}
{t("Action")}
@@ -478,7 +486,6 @@ const PickerStockTake: React.FC = ({
) : (
inventoryLotDetails.map((detail) => {
- const isEditing = editingRecord?.id === detail.id;
const submitDisabled = isSubmitDisabled(detail);
const isFirstSubmit =
!detail.stockTakeRecordId || !detail.firstStockTakeQty;
@@ -513,19 +520,24 @@ const PickerStockTake: React.FC = ({
-
+ {detail.uom || "-"}
{/* Qty + Bad Qty 合并显示/输入 */}
{/* First */}
- {isEditing && isFirstSubmit ? (
+ {!submitDisabled && isFirstSubmit ? (
{t("First")}:
setFirstQty(e.target.value)}
+ value={recordInputs[detail.id]?.firstQty || ""}
+ inputProps={{ min: 0, step: "any" }}
+ onChange={(e) => {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({ ...prev, [detail.id]: { ...prev[detail.id], firstQty: val } }));
+ }}
sx={{
width: 130,
minWidth: 130,
@@ -533,14 +545,23 @@ const PickerStockTake: React.FC = ({
height: "1.4375em",
padding: "4px 8px",
},
+ "& .MuiInputBase-input::placeholder": {
+ color: "grey.400", // MUI light grey
+ opacity: 1,
+ },
}}
placeholder={t("Stock Take Qty")}
/>
setFirstBadQty(e.target.value)}
+ value={recordInputs[detail.id]?.firstBadQty || ""}
+ inputProps={{ min: 0, step: "any" }}
+ onChange={(e) => {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({ ...prev, [detail.id]: { ...prev[detail.id], firstBadQty: val } }));
+ }}
sx={{
width: 130,
minWidth: 130,
@@ -548,14 +569,18 @@ const PickerStockTake: React.FC = ({
height: "1.4375em",
padding: "4px 8px",
},
+ "& .MuiInputBase-input::placeholder": {
+ color: "grey.400", // MUI light grey
+ opacity: 1,
+ },
}}
placeholder={t("Bad Qty")}
/>
=
{formatNumber(
- parseFloat(firstQty || "0") -
- parseFloat(firstBadQty || "0")
+ parseFloat(recordInputs[detail.id]?.firstQty || "0") -
+ parseFloat(recordInputs[detail.id]?.firstBadQty || "0")
)}
@@ -576,14 +601,19 @@ const PickerStockTake: React.FC = ({
) : null}
{/* Second */}
- {isEditing && isSecondSubmit ? (
+ {!submitDisabled && isSecondSubmit ? (
{t("Second")}:
setSecondQty(e.target.value)}
+ value={recordInputs[detail.id]?.secondQty || ""}
+ inputProps={{ min: 0, step: "any" }}
+ onChange={(e) => {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({ ...prev, [detail.id]: { ...prev[detail.id], secondQty: val } }));
+ }}
sx={{
width: 130,
minWidth: 130,
@@ -597,8 +627,13 @@ const PickerStockTake: React.FC = ({
setSecondBadQty(e.target.value)}
+ value={recordInputs[detail.id]?.secondBadQty || ""}
+ inputProps={{ min: 0, step: "any" }}
+ onChange={(e) => {
+ const val = e.target.value;
+ if (val.includes("-")) return;
+ setRecordInputs(prev => ({ ...prev, [detail.id]: { ...prev[detail.id], secondBadQty: val } }));
+ }}
sx={{
width: 130,
minWidth: 130,
@@ -612,8 +647,8 @@ const PickerStockTake: React.FC = ({
=
{formatNumber(
- parseFloat(secondQty || "0") -
- parseFloat(secondBadQty || "0")
+ parseFloat(recordInputs[detail.id]?.secondQty || "0") -
+ parseFloat(recordInputs[detail.id]?.secondBadQty || "0")
)}
@@ -635,7 +670,7 @@ const PickerStockTake: React.FC = ({
{!detail.firstStockTakeQty &&
!detail.secondStockTakeQty &&
- !isEditing && (
+ !submitDisabled && (
= ({
{/* Remark */}
- {isEditing && isSecondSubmit ? (
+ {!submitDisabled && isSecondSubmit ? (
<>
{t("Remark")}
setRemark(e.target.value)}
+ value={recordInputs[detail.id]?.remark || ""}
+ onChange={(e) => setRecordInputs(prev => ({
+ ...prev,
+ [detail.id]: {
+ ...(prev[detail.id] ?? { firstQty: "", secondQty: "", firstBadQty: "", secondBadQty: "", remark: "" }),
+ remark: e.target.value
+ }
+ }))}
sx={{ width: 150 }}
/>
>
@@ -665,32 +706,38 @@ const PickerStockTake: React.FC = ({
)}
- {detail.uom || "-"}
+
- {detail.stockTakeRecordStatus === "pass" ? (
-
- ) : detail.stockTakeRecordStatus === "notMatch" ? (
-
- ) : (
-
- )}
-
+ {detail.stockTakeRecordStatus === "completed" ? (
+
+ ) : detail.stockTakeRecordStatus === "pass" ? (
+
+ ) : detail.stockTakeRecordStatus === "notMatch" ? (
+
+ ) : (
+
+ )}
+
- {isEditing ? (
+
= ({
>
{t("Save")}
-
- {t("Cancel")}
-
+
- ) : (
- handleStartEdit(detail)}
- disabled={submitDisabled}
- >
- {!detail.stockTakeRecordId
- ? t("Input")
- : detail.stockTakeRecordStatus === "notMatch"
- ? t("Input")
- : t("View")}
-
- )}
+
);
diff --git a/src/components/StyledDataGrid/StyledDataGrid.tsx b/src/components/StyledDataGrid/StyledDataGrid.tsx
index 7899dc4..0ea456c 100644
--- a/src/components/StyledDataGrid/StyledDataGrid.tsx
+++ b/src/components/StyledDataGrid/StyledDataGrid.tsx
@@ -1,6 +1,8 @@
import { styled } from "@mui/material";
import { DataGrid ,DataGridProps,zhTW} from "@mui/x-data-grid";
import { forwardRef } from "react";
+import { useTranslation } from "react-i18next";
+
const StyledDataGridBase = styled(DataGrid)(({ theme }) => ({
"--unstable_DataGrid-radius": 0,
"& .MuiDataGrid-columnHeaders": {
@@ -29,12 +31,14 @@ const StyledDataGridBase = styled(DataGrid)(({ theme }) => ({
},
}));
const StyledDataGrid = forwardRef((props, ref) => {
+ const { t } = useTranslation();
return (
diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json
index c883594..5d551e5 100644
--- a/src/i18n/zh/common.json
+++ b/src/i18n/zh/common.json
@@ -13,7 +13,7 @@
"Overall Time Remaining": "總剩餘時間",
"Reset": "重置",
"Search": "搜索",
- "This lot is rejected, please scan another lot.": "此批次已封存,請掃描另一個批號。",
+ "This lot is rejected, please scan another lot.": "此批次發現問題,請掃描另一個批號。",
"Process Start Time": "工序開始時間",
"Stock Req. Qty": "需求數",
"Staff No Required": "員工編號必填",
@@ -124,6 +124,7 @@
"Today": "今天",
"Yesterday": "昨天",
+ "Two Days Ago": "前天",
"Input Equipment is not match with process": "輸入的設備與流程不匹配",
"Staff No is required": "員工編號必填",
@@ -132,6 +133,8 @@
"Production Date": "生產日期",
"QC Check Item": "QC品檢項目",
"QC Category": "QC品檢模板",
+ "QC Item All": "QC 綜合管理",
+ "qcItemAll": "QC 綜合管理",
"qcCategory": "品檢模板",
"QC Check Template": "QC檢查模板",
"Mail": "郵件",
@@ -148,6 +151,7 @@
"Production Date":"生產日期",
"QC Check Item":"QC品檢項目",
"QC Category":"QC品檢模板",
+ "QC Item All":"QC 綜合管理",
"qcCategory":"品檢模板",
"QC Check Template":"QC檢查模板",
"QR Code Handle":"二維碼列印及下載",
@@ -284,7 +288,8 @@
"Please scan equipment code": "請掃描設備編號",
"Equipment Code": "設備編號",
"Seq": "步驟",
- "Item Name": "物料名稱",
+ "SEQ": "步驟",
+ "Item Name": "產品名稱",
"Job Order Info": "工單信息",
"Matching Stock": "工單對料",
"No data found": "沒有找到資料",
@@ -469,5 +474,29 @@
"Delete Success": "刪除成功",
"Delete Failed": "刪除失敗",
"Create Printer": "新增列印機",
- "Report": "報告"
+ "Report": "報告",
+ "Issue": "問題",
+ "Note:": "注意:",
+ "Required Qty": "需求數量",
+ "Verified Qty": "確認數量",
+ "Max": "最大值",
+ "Min": "最小值",
+ "Max": "最大值",
+ "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。",
+ "Pick Execution Issue Form": "提料問題表單",
+ "Missing items": "缺少物品",
+ "Total (Verified + Bad + Missing) must equal Required quantity": "總數必須等於需求數量",
+ "Missing item Qty": "缺少物品數量",
+ "seq": "序號",
+ "Job Order Pick Execution": "工單提料",
+ "Bad Item Qty": "不良物品數量",
+ "Issue Remark": "問題備註",
+ "At least one issue must be reported": "至少需要報告一個問題",
+ "Qty is required": "數量是必填項",
+ "Verified quantity cannot exceed received quantity": "確認數量不能超過接收數量",
+ "Handled By": "處理者",
+ "submit": "提交",
+ "Received Qty": "接收數量"
+
+
}
\ No newline at end of file
diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json
index a222604..a2a2fd8 100644
--- a/src/i18n/zh/inventory.json
+++ b/src/i18n/zh/inventory.json
@@ -7,14 +7,26 @@
"Qty": "盤點數量",
"UoM": "單位",
"mat": "物料",
+ "variance": "差異",
+ "Variance %": "差異百分比",
"fg": "成品",
"Back to List": "返回列表",
"Record Status": "記錄狀態",
"Stock take record status updated to not match": "盤點記錄狀態更新為數值不符",
"available": "可用",
+ "Issue Qty": "問題數量",
+ "tke": "盤點",
+ "Submit Bad Item": "提交不良品",
+ "Remain available Quantity": "剩餘可用數量",
+ "Submitting...": "提交中...",
"Item-lotNo-ExpiryDate": "貨品-批號-到期日",
+ "Submit Miss Item": "提交缺貨",
+ "Confirm": "確認",
+ "Confirm create stock take for all sections?": "確認為所有區域創建盤點?",
"Item-lotNo-ExpiryDate": "貨品-批號-到期日",
"not available": "不可用",
+ "Book Qty": "帳面庫存",
+ "Submit Quantity": "實際問題數量",
"Batch Submit All": "批量提交所有",
"Batch Save All": "批量保存所有",
"Batch Submit All": "批量提交所有",
@@ -39,6 +51,7 @@
"DO Order Code": "送貨單編號",
"JO Order Code": "工單編號",
"Picker Name": "提料員",
+ "Rows per page": "每頁行數",
"rejected": "已拒絕",
"miss": "缺貨",
@@ -206,6 +219,9 @@
"Loading": "加載中",
"adj": "調整",
"nor": "正常",
+ "trf": "轉倉",
+
+
"Stock transfer successful": "轉倉成功",
"Failed to transfer stock": "轉倉失敗",
@@ -218,6 +234,27 @@
"Target Location": "目標倉位",
"Original Qty": "原有數量",
"Qty To Be Transferred": "待轉數量",
- "Submit": "提交"
+ "Submit": "提交",
+
+ "Printer": "列印機",
+ "Print Qty": "列印數量",
+ "Print": "列印",
+ "Print sent": "已送出列印",
+ "Print failed": "列印失敗",
+
+ "Stock Adjustment": "庫存調整",
+ "Edit mode": "編輯模式",
+ "Add entry": "新增倉存",
+
+ "productLotNo": "產品批號",
+ "dnNo": "送貨單編號",
+ "Optional - system will generate": "選填,系統將自動生成",
+ "Add": "新增",
+ "Opening Inventory": "開倉",
+ "Reason for adjustment": "調整原因",
+ "No lot no entered, will be generated by system.": "未輸入批號,將由系統生成。",
+ "Reason for removal": "移除原因",
+ "Confirm remove": "確認移除",
+ "Adjusted Qty": "調整後倉存"
}
diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json
index 7ff6959..bc5312b 100644
--- a/src/i18n/zh/jo.json
+++ b/src/i18n/zh/jo.json
@@ -93,6 +93,11 @@
"Bag Code": "包裝袋編號",
"Sequence": "序",
+ "Seq": "步驟",
+ "SEQ": "步驟",
+ "Today": "今天",
+ "Yesterday": "昨天",
+ "Two Days Ago": "前天",
"Item Code": "成品/半成品編號",
"Paused": "已暫停",
"paused": "已暫停",
@@ -115,7 +120,7 @@
"Pick Order Detail": "提料單細節",
"Finished Job Order Record": "已完成工單記錄",
"No. of Items to be Picked": "需提料數量",
- "No. of Items with Issue During Pick": "提料過程中出現問題的數量",
+ "No. of Items with Issue During Pick": "問題數量",
"Pick Start Time": "提料開始時間",
"Pick End Time": "提料結束時間",
"FG / WIP Item": "成品/半成品",
@@ -141,7 +146,7 @@
"Start QR Scan": "開始QR掃碼",
"Stop QR Scan": "停止QR掃碼",
"Rows per page": "每頁行數",
- "Job Order Item Name": "工單物料名稱",
+ "Job Order Item Name": "工單產品名稱",
"Job Order Code": "工單編號",
"View Details": "查看詳情",
"Skip": "跳過",
@@ -322,7 +327,7 @@
"acceptedQty": "接受數量",
"bind": "綁定",
"expiryDate": "有效期",
- "itemName": "物料名稱",
+ "itemName": "產品名稱",
"itemNo": "成品編號",
"not default warehosue": "不是默認倉庫",
"printQty": "打印數量",
@@ -347,7 +352,7 @@
"receivedQty": "接收數量",
"stock in information": "庫存信息",
"No Uom": "沒有單位",
- "Print Pick Record": "打印板頭紙",
+ "Print Pick Record": "打印版頭紙",
"Printed Successfully.": "成功列印",
"Submit All Scanned": "提交所有已掃描項目",
"Submitting...": "提交中...",
@@ -557,5 +562,15 @@
"Production Time Remaining": "生產剩餘時間",
"Process": "工序",
"Start": "開始",
+ "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。",
+ "Pick Execution Issue Form": "提料問題表單",
+ "Missing items": "缺少物品",
+ "Total (Verified + Bad + Missing) must equal Required quantity": "總數必須等於需求數量",
+ "Missing item Qty": "缺少物品數量",
+ "Bad Item Qty": "不良物品數量",
+ "Issue Remark": "問題備註",
+ "seq": "序號",
+ "Handled By": "處理者",
+ "Job Order Pick Execution": "工單提料",
"Finish": "完成"
}
diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json
index e51fd5d..e7a3f00 100644
--- a/src/i18n/zh/pickOrder.json
+++ b/src/i18n/zh/pickOrder.json
@@ -367,6 +367,7 @@
"View Details": "查看詳情",
"No Item": "沒有貨品",
"None": "沒有",
+ "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。",
"Add Selected Items to Created Items": "將已選擇的貨品添加到已建立的貨品中",
"All pick orders created successfully": "所有提料單建立成功",
"Failed to create group": "建立分組失敗",
diff --git a/src/i18n/zh/qcItemAll.json b/src/i18n/zh/qcItemAll.json
index 370113b..1599e01 100644
--- a/src/i18n/zh/qcItemAll.json
+++ b/src/i18n/zh/qcItemAll.json
@@ -42,7 +42,7 @@
"Select Qc Item": "選擇品檢項目",
"Select Type": "選擇類型",
"Item Code": "物料編號",
- "Item Name": "物料名稱",
+ "Item Name": "產品名稱",
"Qc Category Code": "品檢模板編號",
"Qc Category Name": "品檢模板名稱",
"Qc Item Code": "品檢項目編號",
diff --git a/tailwind.config.js b/tailwind.config.js
index a2f9e87..af40323 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -18,6 +18,11 @@ module.exports = {
border: "var(--border)",
muted: "var(--muted)",
},
+ fontSize: {
+ xs: ["0.8125rem", { lineHeight: "1.25rem" }],
+ sm: ["0.9375rem", { lineHeight: "1.375rem" }],
+ base: ["1.0625rem", { lineHeight: "1.625rem" }],
+ },
},
},
plugins: [],