diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index c2a971d..6192e73 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -318,6 +318,18 @@ export const getLatestApproverStockTakeHeader = async () => { { method: "GET" } ); } + +export type LatestStockTakeRoundMeta = { + stockTakeRoundId: number | null; + planStartDate: string | null; +}; + +export const getLatestStockTakeRoundMeta = async () => { + return serverFetchJson( + `${BASE_API_URL}/stockTakeRecord/latestStockTakeRoundMeta`, + { method: "GET" }, + ); +}; export const createStockTakeForSections = async ( sections: string[], stockTakeRoundName?: string | null, diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 2a1839e..672ddb5 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -34,6 +34,11 @@ export const priceFormatter = new Intl.NumberFormat("en-HK", { export const integerFormatter = new Intl.NumberFormat("en-HK", {}); +/** 差異% 向零方向捨去小數,與後端 RoundingMode.DOWN 一致(-48.43 → -48) */ +export function roundDownPercent(value: number): number { + return Math.trunc(value); +} + export const INPUT_DATE_FORMAT = "YYYY-MM-DD"; export const OUTPUT_DATE_FORMAT = "YYYY-MM-DD"; diff --git a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx index 81ace7d..0c8022c 100644 --- a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx +++ b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx @@ -56,7 +56,7 @@ import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import dayjs from "dayjs"; -import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { OUTPUT_DATE_FORMAT, roundDownPercent } from "@/app/utils/formatUtil"; import { DataGrid, GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; import StyledDataGrid from "@/components/StyledDataGrid/StyledDataGrid"; import type { @@ -939,7 +939,7 @@ const ApproverStockTakeAll: React.FC = ({ const approverQtyNum = parseFloat(approverQty[detail.id] || "0") || 0; const approverBadQtyNum = parseFloat(approverBadQty[detail.id] || "0") || 0; const approverGoodQty = approverQtyNum - approverBadQtyNum; - const variancePercentage = + const variancePercentageRaw = bookQty !== 0 ? (difference / bookQty) * 100 : difference !== 0 @@ -947,8 +947,9 @@ const ApproverStockTakeAll: React.FC = ({ ? 100 : -100 : 0; + const variancePercentage = roundDownPercent(variancePercentageRaw); const hasVariance = difference !== 0; - const pctLabel = `${variancePercentage >= 0 ? "" : ""}${variancePercentage.toFixed(0)}%`; + const pctLabel = `${variancePercentage}%`; const summaryLine = (label: string, value: string, valueColor?: string) => ( = ({ label={t("Variance filter inclusive only")} /> + {/* = ({ label={t("Variance filter strict bounds")} /> + */} {searchVarianceFilterInclusive diff --git a/src/components/StockTakeManagement/PickerCardList.tsx b/src/components/StockTakeManagement/PickerCardList.tsx index 12d6f35..6068e82 100644 --- a/src/components/StockTakeManagement/PickerCardList.tsx +++ b/src/components/StockTakeManagement/PickerCardList.tsx @@ -41,7 +41,7 @@ import { AllPickedStockTakeListReponse, createStockTakeForSections, getStockTakeRecordsPaged, - + getLatestStockTakeRoundMeta, } from "@/app/api/stockTake/actions"; import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; import { fetchMissingStockTakeSectionIssues } from "@/app/api/warehouse/client"; @@ -138,6 +138,7 @@ const PickerCardList: React.FC = ({ const canManageStockTake = abilities.some((a) => a.trim() === AUTH.ADMIN); /** 建立盤點後若仍在 page 0,仍強制重新載入 */ const [listRefreshNonce, setListRefreshNonce] = useState(0); + const [globalRoundPlanStartDate, setGlobalRoundPlanStartDate] = useState(null); const [creating, setCreating] = useState(false); const [openConfirmDialog, setOpenConfirmDialog] = useState(false); const [openCreateStockTakeSummaryConfirm, setOpenCreateStockTakeSummaryConfirm] = useState(false); @@ -231,6 +232,26 @@ const PickerCardList: React.FC = ({ }; }, [page, pageSize, appliedFilters, listRefreshNonce]); + useEffect(() => { + let cancelled = false; + getLatestStockTakeRoundMeta() + .then((meta) => { + if (cancelled) return; + if (meta?.planStartDate) { + setGlobalRoundPlanStartDate(dayjs(meta.planStartDate).format(OUTPUT_DATE_FORMAT)); + } else { + setGlobalRoundPlanStartDate(null); + } + }) + .catch((e) => { + console.error("Failed to load latest stock take round meta:", e); + if (!cancelled) setGlobalRoundPlanStartDate(null); + }); + return () => { + cancelled = true; + }; + }, [listRefreshNonce]); + //const startIdx = page * PER_PAGE; //const paged = stockTakeSessions.slice(startIdx, startIdx + PER_PAGE); @@ -697,11 +718,7 @@ const PickerCardList: React.FC = ({ if (session.totalInventoryLotNumber === 0) return 0; return Math.round((session.currentStockTakeItemNumber / session.totalInventoryLotNumber) * 100); }; - const planStartDate = (() => { - const first = stockTakeSessions.find(s => s.planStartDate); - if (!first?.planStartDate) return null; - return dayjs(first.planStartDate).format(OUTPUT_DATE_FORMAT); - })(); + const planStartDate = globalRoundPlanStartDate; return ( @@ -714,6 +731,7 @@ const PickerCardList: React.FC = ({ {t("Stock Take Section")}