Просмотр исходного кода

update stock tkae fix

production
CANCERYS\kw093 3 недель назад
Родитель
Сommit
4b6aeef008
5 измененных файлов: 78 добавлений и 19 удалений
  1. +12
    -0
      src/app/api/stockTake/actions.ts
  2. +5
    -0
      src/app/utils/formatUtil.ts
  3. +6
    -3
      src/components/StockTakeManagement/ApproverStockTakeAll.tsx
  4. +49
    -11
      src/components/StockTakeManagement/PickerCardList.tsx
  5. +6
    -5
      src/i18n/zh/inventory.json

+ 12
- 0
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<LatestStockTakeRoundMeta>(
`${BASE_API_URL}/stockTakeRecord/latestStockTakeRoundMeta`,
{ method: "GET" },
);
};
export const createStockTakeForSections = async (
sections: string[],
stockTakeRoundName?: string | null,


+ 5
- 0
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";


+ 6
- 3
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<ApproverStockTakeAllProps> = ({
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<ApproverStockTakeAllProps> = ({
? 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) => (
<Stack
@@ -1446,6 +1447,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
label={t("Variance filter inclusive only")}
/>
</Grid>
{/*
<Grid item xs={12} md={4} sx={{ display: "flex", alignItems: "center" }}>
<FormControlLabel
control={
@@ -1457,6 +1459,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
label={t("Variance filter strict bounds")}
/>
</Grid>
*/}
<Grid item xs={12}>
<Typography variant="caption" color="text.secondary">
{searchVarianceFilterInclusive


+ 49
- 11
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<PickerCardListProps> = ({
const canManageStockTake = abilities.some((a) => a.trim() === AUTH.ADMIN);
/** 建立盤點後若仍在 page 0,仍強制重新載入 */
const [listRefreshNonce, setListRefreshNonce] = useState(0);
const [globalRoundPlanStartDate, setGlobalRoundPlanStartDate] = useState<string | null>(null);
const [creating, setCreating] = useState(false);
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
const [openCreateStockTakeSummaryConfirm, setOpenCreateStockTakeSummaryConfirm] = useState(false);
@@ -231,6 +232,26 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
};
}, [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<PickerCardListProps> = ({
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 (
<Box>
<Card elevation={0} sx={{ mb: 2 }}>
@@ -714,6 +731,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
<FormControl fullWidth>
<InputLabel>{t("Stock Take Section")}</InputLabel>
<Select
size="small"
value={searchFilters.sectionDescription}
label={t("Stock Take Section")}
onChange={(e) =>
@@ -953,12 +971,16 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
sx: {
width: "100%",
maxWidth: { xs: "100%", sm: 960, md: 1120 },
maxHeight: "90vh",
display: "flex",
flexDirection: "column",
},
}}
>
<DialogTitle
sx={{
pb: 1,
flexShrink: 0,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
@@ -995,13 +1017,24 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
</Box>
</Tooltip>
</DialogTitle>
<DialogContent dividers sx={{ p: 0, overflow: "hidden" }}>
<DialogContent
dividers
sx={{
p: 0,
flex: 1,
minHeight: 0,
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<Box
sx={{
display: "flex",
flex: 1,
minHeight: 0,
flexDirection: { xs: "column", md: "row" },
minHeight: { xs: 460, md: 580 },
maxHeight: { xs: "80vh", md: "75vh" },
height: "100%",
}}
>
{/* 左側:盤點設定與樓層 */}
@@ -1009,6 +1042,9 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
sx={{
width: { xs: "100%", md: 280 },
flexShrink: 0,
minHeight: 0,
maxHeight: { xs: "38vh", md: "100%" },
overflowY: "auto",
borderRight: { md: 1 },
borderBottom: { xs: 1, md: 0 },
borderColor: "divider",
@@ -1148,11 +1184,13 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
<Box
sx={{
flex: 1,
minHeight: 0,
display: "flex",
flexDirection: "column",
minWidth: 0,
p: 2.5,
bgcolor: "background.paper",
overflow: "hidden",
}}
>
{activeCreateFloorKey != null ? (
@@ -1210,7 +1248,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
sx={{
position: "relative",
flex: 1,
minHeight: 200,
minHeight: 0,
mt: 2,
overflowY: "auto",
pr: 0.5,
@@ -1346,7 +1384,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
</Box>
</Box>
</DialogContent>
<DialogActions sx={{ px: 3, py: 2 }}>
<DialogActions sx={{ px: 3, py: 2, flexShrink: 0 }}>
<Button
onClick={() => {
setOpenConfirmDialog(false);


+ 6
- 5
src/i18n/zh/inventory.json Просмотреть файл

@@ -48,8 +48,9 @@
"Issue Qty": "問題數量",
"tke": "盤點",
"Total Stock Takes": "總盤點數量",
"Submit completed: {{success}} success, {{errors}} errors": "提交完成:{{success}} 成功,{{errors}} 錯誤",
"No valid input to submit": "沒有可提交的已輸入行",
"Submit completed: {{success}} success, {{errors}} errors": "提交完成:{{success}} 成功,{{errors}} 失敗",
"No valid input to submit": "請先填寫盤點數量",
"Body is unavailable": "輸入不可用",
"Submit All Inputted": "提交所有輸入",
"Submit Bad Item": "提交不良品",
"Remain available Quantity": "剩餘可用數量",
@@ -69,7 +70,7 @@
"not match": "要求重點",
"Not Match": "要求重點",
"Pass": "已盤點",
"Area": "區域",
"Area": "倉位",
"Selected Qty": "選擇數量",
"Inventory Difference": "庫存差異",
@@ -81,7 +82,7 @@
"Stock Take Qty": "盤點數",
"variance Percentage": "差異百分比",
"-{{Variance}}≤Variance Percentage ≤{{Variance}} will be filtered out": "-{{Variance}}%≤差異百分比≤{{Variance}}%將被過濾掉",
"Variance filter inclusive only": "只顯示差異在範圍內的列",
"Variance filter inclusive only": "反選",
"Variance filter strict bounds": "不使用=",
"Variance filter exclusive range hint": "只顯示 -{{value}}% {{op}} 差異% {{op}} {{value}}% 的列(範圍外)",
"Variance filter inclusive range hint": "只顯示 -{{value}}% {{op}} 差異% {{op}} {{value}}% 的列(範圍內)",
@@ -281,7 +282,7 @@
"Miss Item": "缺貨",
"Bad Item": "不良",
"Expiry Item": "過期",
"Batch save completed: {{success}} success, {{errors}} errors": "批量保存完成:{{success}} 成功,{{errors}} 錯誤",
"Batch save completed: {{success}} success, {{errors}} errors": "批量保存完成:{{success}} 成功,{{errors}} 失敗",
"Batch Save Inputted": "批量保存已輸入",
"Batch Save Completed": "批量保存完成",
"Bad Item Handle": "不良品處理",


Загрузка…
Отмена
Сохранить