From 9c9888a44f475422a4889d5450a0f6de231ac8c0 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 1 Apr 2026 16:14:46 +0800 Subject: [PATCH] update --- src/app/api/stockTake/actions.ts | 13 +- .../ApproverStockTakeAll.tsx | 1126 ++++++++--------- src/i18n/zh/common.json | 4 + src/i18n/zh/inventory.json | 13 +- 4 files changed, 537 insertions(+), 619 deletions(-) diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index 2712b2c..34285e4 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -137,10 +137,11 @@ export interface AllPickedStockTakeListReponse { /** 與 Picker 列表一致:區域描述、盤點區域、貨品編號/名稱(逗號多關鍵字由後端解析) */ export type ApproverInventoryLotDetailsQuery = { - itemCode?: string | null; - itemName?: string | null; + itemKeyword?: string | null; + //itemKeyword?: string | null; sectionDescription?: string | null; stockTakeSections?: string | null; + warehouseKeyword?: string | null; }; function appendApproverInventoryLotQueryParams( @@ -148,11 +149,11 @@ function appendApproverInventoryLotQueryParams( query?: ApproverInventoryLotDetailsQuery | null ) { if (!query) return; - if (query.itemCode != null && query.itemCode.trim() !== "") { - params.append("itemCode", query.itemCode.trim()); + if (query.itemKeyword != null && query.itemKeyword.trim() !== "") { + params.append("itemKeyword", query.itemKeyword.trim()); } - if (query.itemName != null && query.itemName.trim() !== "") { - params.append("itemName", query.itemName.trim()); + if (query.warehouseKeyword != null && query.warehouseKeyword.trim() !== "") { + params.append("warehouseKeyword", query.warehouseKeyword.trim()); } if ( query.sectionDescription != null && diff --git a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx index cd8f76e..bc19a0f 100644 --- a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx +++ b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx @@ -20,6 +20,11 @@ import { TableSortLabel, } from "@mui/material"; import { useState, useCallback, useEffect, useMemo } from "react"; +import { Collapse } from "@mui/material"; +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { useTranslation } from "react-i18next"; import { AllPickedStockTakeListReponse, @@ -39,7 +44,13 @@ import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { DataGrid, GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; +import StyledDataGrid from "@/components/StyledDataGrid/StyledDataGrid"; +import type { + GridPaginationModel, + GridSortModel, +} from "@mui/x-data-grid"; interface ApproverStockTakeAllProps { selectedSession: AllPickedStockTakeListReponse; mode: "pending" | "approved"; @@ -56,21 +67,21 @@ type ApprovedSortKey = | "stockTakerName" | "variance"; -type ApproverSearchKey = "sectionDescription" | "stockTakeSession" | "itemCode" | "itemName"; +type ApproverSearchKey = "sectionDescription" | "stockTakeSession" | "itemKeyword" | "warehouseKeyword"; type ApproverSearchFilters = { sectionDescription: string; stockTakeSession: string; - itemCode: string; - itemName: string; + itemKeyword: string; + warehouseKeyword: string; }; function buildApproverInventoryQuery(filters: ApproverSearchFilters): ApproverInventoryLotDetailsQuery { return { sectionDescription: filters.sectionDescription !== "All" ? filters.sectionDescription : undefined, stockTakeSections: filters.stockTakeSession.trim() ? filters.stockTakeSession.trim() : undefined, - itemCode: filters.itemCode.trim() ? filters.itemCode.trim() : undefined, - itemName: filters.itemName.trim() ? filters.itemName.trim() : undefined, + itemKeyword: filters.itemKeyword.trim() ? filters.itemKeyword.trim() : undefined, + warehouseKeyword: filters.warehouseKeyword.trim() ? filters.warehouseKeyword.trim() : undefined, }; } @@ -78,8 +89,8 @@ function hasAnyApproverSearchCriterion(f: ApproverSearchFilters): boolean { return ( (f.sectionDescription && f.sectionDescription !== "All") || f.stockTakeSession.trim() !== "" || - f.itemCode.trim() !== "" || - f.itemName.trim() !== "" + f.itemKeyword.trim() !== "" || + f.warehouseKeyword.trim() !== "" ); } @@ -121,6 +132,7 @@ const ApproverStockTakeAll: React.FC = ({ const [sectionDescriptionAutocompleteOptions, setSectionDescriptionAutocompleteOptions] = useState< { value: string; label: string }[] >([]); + const [showFilters, setShowFilters] = useState(true) const [appliedFilters, setAppliedFilters] = useState(null); const currentUserId = session?.id ? parseInt(session.id) : undefined; @@ -147,13 +159,15 @@ const ApproverStockTakeAll: React.FC = ({ const next: ApproverSearchFilters = { sectionDescription: inputs.sectionDescription || "All", stockTakeSession: inputs.stockTakeSession || "", - itemCode: inputs.itemCode || "", - itemName: inputs.itemName || "", + itemKeyword: inputs.itemKeyword || "", + warehouseKeyword: inputs.warehouseKeyword || "", }; + /* if (!hasAnyApproverSearchCriterion(next)) { onSnackbar(t("Please set at least one search criterion"), "warning"); return; } + */ setAppliedFilters(next); setPage(0); }, @@ -184,70 +198,60 @@ const ApproverStockTakeAll: React.FC = ({ }, { type: "text", - label: t("Item Code"), - paramName: "itemCode", + label: t("Item"), + paramName: "itemKeyword", placeholder: "", }, + { type: "text", - label: t("Item Name"), - paramName: "itemName", + label: t("Warehouse"), + paramName: "warehouseKeyword", placeholder: "", }, ], [t, sectionDescriptionAutocompleteOptions] ); - const loadDetails = useCallback( - async (pageNum: number, size: number | string, filters: ApproverSearchFilters) => { - setLoadingDetails(true); - try { - let actualSize: number; - if (size === "all") { - if (total > 0) { - actualSize = total; - } else if (selectedSession.totalInventoryLotNumber > 0) { - actualSize = selectedSession.totalInventoryLotNumber; - } else { - actualSize = 10000; - } - } else { - actualSize = typeof size === "string" ? parseInt(size, 10) : size; - } + const loadDetails = useCallback(async (filters: ApproverSearchFilters) => { + setLoadingDetails(true); + console.time("🔥 Total time from API call to DataGrid ready"); - const searchQuery: ApproverInventoryLotDetailsQuery = buildApproverInventoryQuery(filters); - const response = mode === "approved" - ? await getApproverInventoryLotDetailsAllApproved( - selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null, - pageNum, - actualSize, - searchQuery - ) - : await getApproverInventoryLotDetailsAllPending( - selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null, - pageNum, - actualSize, - searchQuery - ); - setInventoryLotDetails(Array.isArray(response.records) ? response.records : []); - setTotal(response.total || 0); - } catch (e) { - console.error(e); - setInventoryLotDetails([]); - setTotal(0); - } finally { - setLoadingDetails(false); - } - }, - [selectedSession, total, mode] - ); + try { + const searchQuery: ApproverInventoryLotDetailsQuery = buildApproverInventoryQuery(filters); + + // Always request ALL rows (max 5000) + const response = mode === "approved" + ? await getApproverInventoryLotDetailsAllApproved( + selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null, + 0, + 5000, + searchQuery + ) + : await getApproverInventoryLotDetailsAllPending( + selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null, + 0, + 5000, + searchQuery + ); - useEffect(() => { - if (!appliedFilters) { - return; + console.timeEnd("🔥 Total time from API call to DataGrid ready"); + + setInventoryLotDetails(Array.isArray(response.records) ? response.records : []); + console.log(`Loaded ${response.records?.length || 0} rows from backend`); + } catch (e) { + console.error(e); + setInventoryLotDetails([]); + } finally { + setLoadingDetails(false); } - loadDetails(page, pageSize, appliedFilters); - }, [page, pageSize, appliedFilters, loadDetails]); + }, [selectedSession, mode]); + + + useEffect(() => { + if (!appliedFilters) return; + loadDetails(appliedFilters); + }, [appliedFilters, loadDetails]); useEffect(() => { fetchStockTakeSections() @@ -399,22 +403,26 @@ const ApproverStockTakeAll: React.FC = ({ const thresholdPercent = isNaN(percent) || percent < 0 ? 0 : percent; return inventoryLotDetails.filter((detail) => { + /* if (detail.finalQty != null || detail.stockTakeRecordStatus === "completed") { return true; } + */ const selection = qtySelection[detail.id] ?? (detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0 ? "second" : "first"); // 避免 Approver 手动输入过程中被 variance 过滤掉,导致“输入后行消失无法提交” + if (selection === "approver") { return true; } + const difference = calculateDifference(detail, selection); const bookQty = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0); - if (bookQty === 0) return difference !== 0; + //if (bookQty === 0) return difference !== 0; const threshold = Math.abs(bookQty) * (thresholdPercent / 100); return Math.abs(difference) >= threshold; }); @@ -473,7 +481,37 @@ const ApproverStockTakeAll: React.FC = ({ return cmp * mul; }); }, [filteredDetails, mode, approvedSortKey, approvedSortDir]); + useEffect(() => { + if (inventoryLotDetails.length === 0) return; + + console.group(` ApproverStockTakeAll - ${inventoryLotDetails.length} rows`); + console.time("1. After API response"); + + // Timing point 1: right after data is set + console.timeEnd("1. After API response"); + + // Timing point 2: after inferSelection + console.time("2. inferSelection useEffect"); + }, [inventoryLotDetails]); + + // Add this useEffect to measure inferSelection + useEffect(() => { + if (inventoryLotDetails.length === 0) return; + console.timeEnd("2. inferSelection useEffect"); + console.time("3. filteredDetails memo"); + }, [inventoryLotDetails, qtySelection]); + // Add this to measure filteredDetails + useEffect(() => { + console.timeEnd("3. filteredDetails memo"); + console.time("4. sortedDetails memo"); + }, [filteredDetails]); + + // Add this to measure sortedDetails + useEffect(() => { + console.timeEnd("4. sortedDetails memo"); + console.time("5. Before DataGrid render"); + }, [sortedDetails]); const handleApprovedSort = useCallback((property: ApprovedSortKey) => { if (approvedSortKey === property) { setApprovedSortDir((d) => (d === "asc" ? "desc" : "asc")); @@ -648,7 +686,7 @@ const ApproverStockTakeAll: React.FC = ({ ); if (appliedFilters) { - await loadDetails(page, pageSize, appliedFilters); + await loadDetails(appliedFilters); } } catch (e: any) { console.error("handleBatchSubmitAll (all): Error:", e); @@ -695,7 +733,369 @@ const ApproverStockTakeAll: React.FC = ({ if (!ms) return "-"; return dayjs(ms).format("YYYY-MM-DD HH:mm"); }; + const rows = sortedDetails; + const columns = useMemo[]>(() => { + const cols: GridColDef[] = []; + + if (mode === "approved") { + cols.push({ + field: "approverTime", + headerName: t("Approver Time"), + minWidth: 150, + flex: 0.6, + sortable: false, + renderCell: (params) => ( + + {formatRecordEndTime(params.row)} + + ), + }); + } + + cols.push( + { + field: "warehouseLocation", + headerName: t("Warehouse Location"), + minWidth: 220, + flex: 1, + sortable: false, + renderCell: (params) => ( + + + + {params.row.stockTakeSection || "-"}{" "} + {params.row.stockTakeSectionDescription || "-"} + + + {params.row.warehouseCode || "-"} + + ), + }, + { + field: "itemLot", + headerName: t("Item-lotNo-ExpiryDate"), + minWidth: 160, + flex: 1, + sortable: false, + renderCell: (params) => ( + + + {params.row.itemCode || "-"} {params.row.itemName || "-"} + + {params.row.lotNo || "-"} + + {params.row.expiryDate + ? dayjs(params.row.expiryDate).format(OUTPUT_DATE_FORMAT) + : "-"} + + + ), + }, + { + field: "uom", + headerName: t("UOM"), + minWidth: 90, + flex: 0.4, + sortable: false, + valueGetter: (params) => params.row.uom || "-", + }, + { + field: "qtyBlock", + headerName: t("Stock Take Qty(include Bad Qty)= Available Qty"), + minWidth: 420, + flex: 3, + sortable: false, + renderCell: (params: GridRenderCellParams) => { + const detail = params.row; + + const hasFirst = + detail.firstStockTakeQty != null && detail.firstStockTakeQty >= 0; + const hasSecond = + detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0; + + const selection = + qtySelection[detail.id] || (hasSecond ? "second" : "first"); + + const canApprover = + mode === "pending" + ? true + : selection === "approver" && + (detail.approverQty != null || detail.approverBadQty != null); + + const showRadioBlock = mode === "approved" || detail.finalQty == null; + const bookQty = detail.bookQty ?? detail.availableQty ?? 0; + const selectedQty = + selection === "first" + ? (detail.firstStockTakeQty ?? 0) + : selection === "second" + ? (detail.secondStockTakeQty ?? 0) + : (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0")) || 0; + const difference = selectedQty - bookQty; + const approverQtyNum = parseFloat(approverQty[detail.id] || "0") || 0; + const approverBadQtyNum = parseFloat(approverBadQty[detail.id] || "0") || 0; + const approverGoodQty = approverQtyNum - approverBadQtyNum; + const variancePercentage = (difference / bookQty) * 100; + return ( + + {!showRadioBlock ? ( + + - + + ) : ( + + {hasFirst && ( + + + setQtySelection({ + ...qtySelection, + [detail.id]: "first", + }) + } + /> + + {t("First")}:{" "} + {formatNumber( + (detail.firstStockTakeQty ?? 0) + (detail.firstBadQty ?? 0) + )}{" "} + ({detail.firstBadQty ?? 0}) ={" "} + {formatNumber(detail.firstStockTakeQty ?? 0)} + + + )} + + {hasSecond && ( + + + setQtySelection({ + ...qtySelection, + [detail.id]: "second", + }) + } + /> + + {t("Second")}:{" "} + {formatNumber( + (detail.secondStockTakeQty ?? 0) + + (detail.secondBadQty ?? 0) + )}{" "} + ({detail.secondBadQty ?? 0}) ={" "} + {formatNumber(detail.secondStockTakeQty ?? 0)} + + + )} + + {canApprover && ( + + + setQtySelection({ + ...qtySelection, + [detail.id]: "approver", + }) + } + /> + {t("Approver Input")}: + + { + const clean = sanitizeIntegerInput(e.target.value); + setApproverQty({ + ...approverQty, + [detail.id]: clean, + }); + }} + sx={{ width: 90, minWidth: 90 }} + placeholder={t("Stock Take Qty")} + disabled={mode === "approved" || selection !== "approver"} + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} + /> + + { + const clean = sanitizeIntegerInput(e.target.value); + setApproverBadQty({ + ...approverBadQty, + [detail.id]: clean, + }); + }} + sx={{ width: 90, minWidth: 90 }} + placeholder={t("Bad Qty")} + disabled={mode === "approved" || selection !== "approver"} + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} + /> + + = {formatNumber(approverGoodQty)} + + + )} + + + + + {t("Selected Qty")}: {formatNumber(selectedQty)}{" "} + - {t("Book Qty")}: {formatNumber(bookQty)}{" "} + = {t("Difference")}: {formatNumber(difference)} + + + + {t("variance Percentage")}: {variancePercentage.toFixed(0) + "%"} + + + + + )} + + ); + }, + } + ); + + if (mode === "approved") { + cols.push({ + field: "varianceQty", + headerName: t("Variance"), + minWidth: 110, + flex: 0.5, + sortable: false, + valueGetter: (params) => String(params.row.varianceQty ?? 0), + }); + } + + cols.push( + { + field: "remarks", + headerName: t("Remark"), + minWidth: 120, + flex: 0.8, + sortable: false, + renderCell: (params) => ( + + {params.row.remarks || "-"} + + ), + }, + { + field: "stockTakeRecordStatus", + headerName: t("Record Status"), + minWidth: 130, + flex: 0.6, + sortable: false, + renderCell: (params) => ( + + ), + }, + { + field: "stockTakerName", + headerName: t("Picker"), + minWidth: 140, + flex: 0.7, + sortable: false, + valueGetter: (params) => params.row.stockTakerName || "-", + }, + { + field: "actions", + headerName: t("Action"), + minWidth: 110, + flex: 0.9, + sortable: false, + renderCell: (params) => { + const detail = params.row; + const hasSecond = + detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0; + const selection = + qtySelection[detail.id] || (hasSecond ? "second" : "first"); + + return ( + + {mode === "pending" && ( + <> + + + + + + + + )} + + {mode === "approved" && ( + + {selection === "first" + ? t("First") + : selection === "second" + ? t("Second") + : t("Approver Input")} + + )} + + ); + }, + } + ); + + return cols; + }, [ + mode, + t, + qtySelection, + approverQty, + approverBadQty, + saving, + updatingStatus, + blockNonIntegerKeys, + sanitizeIntegerInput, + handleSaveApproverStockTake, + handleUpdateStatusToNotMatch, + formatRecordEndTime, + ]); + const effectivePageSize = + pageSize === "all" ? Math.max(total || 0, 0) : (pageSize as number); return ( {onBack && ( @@ -720,6 +1120,11 @@ const ApproverStockTakeAll: React.FC = ({ + + {t("-{{Variance}}≤Variance Percentage ≤{{Variance}} will be filtered out", { + Variance: variancePercentTolerance || "0", + })} + = ({ )} - - - - criteria={approverSearchCriteria} - onSearch={handleApproverSearchBoxSearch} - onReset={handleApproverSearchBoxReset} - /> + setShowFilters(expanded)} + elevation={0} + sx={{ mb: 2, border: "1px solid", borderColor: "divider", borderRadius: 1 }} +> + }> + + {({t("Search Criteria")})} + + + + + + + criteria={approverSearchCriteria} + onSearch={handleApproverSearchBoxSearch} + onReset={handleApproverSearchBoxReset} + /> + + + + + {t("Total")}: {total}{" "} + | {t("Shown")}: {sortedDetails.length}{" "} + | {t("Filtered out")}: {Math.max(0, inventoryLotDetails.length - sortedDetails.length)} + + {loadingDetails ? ( + + - - {loadingDetails ? ( - - - - ) : ( - <> - - - - - - {mode === "approved" && ( - - handleApprovedSort("stockTakeEndTime")} - > - {t("Approver Time")} - - - )} - - {mode === "approved" ? ( - handleApprovedSort("stockTakeSection")} - > - {t("Warehouse Location")} - - ) : ( - t("Warehouse Location") - )} - - - {mode === "approved" ? ( - handleApprovedSort("item")} - > - {t("Item-lotNo-ExpiryDate")} - - ) : ( - t("Item-lotNo-ExpiryDate") - )} - - {t("UOM")} - - {t("Stock Take Qty(include Bad Qty)= Available Qty")} - - {mode === "approved" && ( - - handleApprovedSort("variance")} - > - {t("Variance")} - - - )} - {t("Remark")} - {t("Record Status")} - - {mode === "approved" ? ( - handleApprovedSort("stockTakerName")} - > - {t("Picker")} - - ) : ( - t("Picker") - )} - - {t("Action")} - - - - {sortedDetails.length === 0 ? ( - - - - {!appliedFilters - ? t("Approver search empty hint") - : t("No data")} - - - - ) : ( - sortedDetails.map((detail) => { - const hasFirst = - detail.firstStockTakeQty != null && detail.firstStockTakeQty >= 0; - const hasSecond = - detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0; - const selection = - qtySelection[detail.id] || (hasSecond ? "second" : "first"); - - // approved 视图下,只有存在已保存的 approver 结果才显示 approver 输入区块 - const canApprover = - mode === "pending" - ? true - : selection === "approver" && - (detail.approverQty != null || - detail.approverBadQty != null); - - // approved 模式下:即使 finalQty 已存在,也需要展示 radio 用于查看选择 - const showRadioBlock = - mode === "approved" || detail.finalQty == null; - - return ( - - {mode === "approved" && ( - - - - - {formatRecordEndTime(detail)} - - - - )} - - - {detail.stockTakeSection || "-"} {detail.stockTakeSectionDescription || "-"} - - {detail.warehouseCode || "-"} - - - - - - {detail.itemCode || "-"} {detail.itemName || "-"} - - {detail.lotNo || "-"} - - {detail.expiryDate - ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) - : "-"} - - - - {detail.uom || "-"} - - {showRadioBlock ? ( - - {hasFirst && ( - - - setQtySelection({ - ...qtySelection, - [detail.id]: "first", - }) - } - /> - - {t("First")}:{" "} - {formatNumber( - (detail.firstStockTakeQty ?? 0) + - (detail.firstBadQty ?? 0) - )}{" "} - ({detail.firstBadQty ?? 0}) ={" "} - {formatNumber(detail.firstStockTakeQty ?? 0)} - - - )} - - {hasSecond && ( - - - setQtySelection({ - ...qtySelection, - [detail.id]: "second", - }) - } - /> - - {t("Second")}:{" "} - {formatNumber( - (detail.secondStockTakeQty ?? 0) + - (detail.secondBadQty ?? 0) - )}{" "} - ({detail.secondBadQty ?? 0}) ={" "} - {formatNumber(detail.secondStockTakeQty ?? 0)} - - - )} - - {canApprover && ( - - - setQtySelection({ - ...qtySelection, - [detail.id]: "approver", - }) - } - /> - - {t("Approver Input")}: - - { - const clean = sanitizeIntegerInput(e.target.value); - setApproverQty({ - ...approverQty, - [detail.id]: clean, - }); - }} - sx={{ - width: 130, - minWidth: 130, - "& .MuiInputBase-input": { - height: "1.4375em", - padding: "4px 8px", - }, - }} - placeholder={t("Stock Take Qty")} - disabled={mode === "approved" || selection !== "approver"} - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} - /> - - { - const clean = sanitizeIntegerInput(e.target.value); - setApproverBadQty({ - ...approverBadQty, - [detail.id]: clean, - }); - }} - sx={{ - width: 130, - minWidth: 130, - "& .MuiInputBase-input": { - height: "1.4375em", - padding: "4px 8px", - }, - }} - placeholder={t("Bad Qty")} - disabled={mode === "approved" || selection !== "approver"} - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} - /> - - ={" "} - {formatNumber( - parseFloat(approverQty[detail.id] || "0") - - parseFloat( - approverBadQty[detail.id] || "0" - ) - )} - - - )} - - {detail.finalQty != null ? ( - - {(() => { - 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)} - - ); - })()} - - ) : ( - (() => { - let selectedQty = 0; - - if (selection === "first") { - selectedQty = detail.firstStockTakeQty || 0; - } else if (selection === "second") { - selectedQty = detail.secondStockTakeQty || 0; - } else if (selection === "approver") { - selectedQty = - (parseFloat(approverQty[detail.id] || "0") - - parseFloat( - approverBadQty[detail.id] || "0" - )) || 0; - } - - const bookQty = - detail.bookQty != null - ? detail.bookQty - : detail.availableQty || 0; - const difference = selectedQty - bookQty; - const differenceColor = - detail.stockTakeRecordStatus === "completed" - ? "text.secondary" - : difference !== 0 - ? "error.main" - : "success.main"; - - return ( - - {t("Difference")}:{" "} - {t("selected stock take qty")}( - {formatNumber(selectedQty)}) -{" "} - {t("book qty")}( - {formatNumber(bookQty)}) ={" "} - {formatNumber(difference)} - - ); - })() - )} - - ) : ( - - {(() => { - 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)} - - ); - })()} - - )} - - - {mode === "approved" && ( - - - {formatNumber(detail.varianceQty)} - - - )} - - - - {detail.remarks || "-"} - - - - - {detail.stockTakeRecordStatus === "completed" ? ( - - ) : detail.stockTakeRecordStatus === "pass" ? ( - - ) : detail.stockTakeRecordStatus === "notMatch" ? ( - - ) : ( - - )} - - {detail.stockTakerName || "-"} - - {mode === "pending" && detail.stockTakeRecordId && - detail.stockTakeRecordStatus !== "notMatch" && ( - - - - )} -
- {mode === "pending" && detail.finalQty == null && ( - - - - )} -
-
- ); - }) - )} -
-
-
- + row.id} + loading={loadingDetails} + disableRowSelectionOnClick + paginationMode="client" // ← important: client-side + pageSizeOptions={[50, 100, 200, 500]} + getRowHeight={() => "auto"} + sx={{ + "& .MuiDataGrid-cell": { py: 1, alignItems: "flex-start" }, + "& .MuiDataGrid-row": { minHeight: 80 }, + }} /> - - )} -
- ); +
+ )} + +); }; export default ApproverStockTakeAll; diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 10ae893..1645263 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -407,6 +407,7 @@ "Lines with insufficient stock: ": "未能提料項目數量: ", "Total lines: ": "總數量:", "Balance": "可用數量", + "Selected Qty": "選擇數量", "Submitting...": "提交中...", "Batch Count": "批數", "Shop": "店鋪", @@ -429,6 +430,9 @@ "District Reference": "區域參考", "Store ID": "樓層", "Remark": "備註", + "Not Match": "數值不符", + "Pass": "通過", + "pass": "通過", "Actions": "操作", "View Detail": "查看詳情", "Back": "返回", diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index 337fcf3..daaf142 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -49,13 +49,20 @@ "Batch Submit All": "批量提交所有", "Batch Save All": "批量保存所有", "not match": "數值不符", - "Stock Take Qty(include Bad Qty)= Available Qty": "盤點數(含壞品)= 可用數", + "Not Match": "數值不符", + "Pass": "通過", + "Selected Qty": "選擇數量", + "Show Search Filters": "顯示搜索器", + "Hide Search Filters": "隱藏搜索器", "Stock Take Qty(include Bad Qty)= Available Qty": "盤點數(含壞品)= 可用數", "View ReStockTake": "查看重新盤點", "Stock Take Qty": "盤點數", - + "variance Percentage": "差異百分比", + "-{{Variance}}≤Variance Percentage ≤{{Variance}} will be filtered out": "-{{Variance}}%≤差異百分比≤{{Variance}}%將被過濾掉", "Stock Take Qty": "盤點數", - + "Total": "總數", + "Shown": "顯示", + "Filtered out": "過濾掉", "ReStockTake": "重新盤點", "Stock Taker": "盤點員", "Total Item Number": "貨品數量",