Selaa lähdekoodia

update

MergeProblem1
CANCERYS\kw093 11 tuntia sitten
vanhempi
commit
b826baabe2
4 muutettua tiedostoa jossa 185 lisäystä ja 14 poistoa
  1. +40
    -3
      src/app/api/stockTake/actions.ts
  2. +139
    -9
      src/components/StockTakeManagement/ApproverStockTakeAll.tsx
  3. +3
    -1
      src/i18n/en/inventory.json
  4. +3
    -1
      src/i18n/zh/inventory.json

+ 40
- 3
src/app/api/stockTake/actions.ts Näytä tiedosto

@@ -135,10 +135,42 @@ export interface AllPickedStockTakeListReponse {
reStockTakeTrueFalse: boolean;
}

/** 與 Picker 列表一致:區域描述、盤點區域、貨品編號/名稱(逗號多關鍵字由後端解析) */
export type ApproverInventoryLotDetailsQuery = {
itemCode?: string | null;
itemName?: string | null;
sectionDescription?: string | null;
stockTakeSections?: string | null;
};

function appendApproverInventoryLotQueryParams(
params: URLSearchParams,
query?: ApproverInventoryLotDetailsQuery | null
) {
if (!query) return;
if (query.itemCode != null && query.itemCode.trim() !== "") {
params.append("itemCode", query.itemCode.trim());
}
if (query.itemName != null && query.itemName.trim() !== "") {
params.append("itemName", query.itemName.trim());
}
if (
query.sectionDescription != null &&
query.sectionDescription !== "" &&
query.sectionDescription !== "All"
) {
params.append("sectionDescription", query.sectionDescription.trim());
}
if (query.stockTakeSections != null && query.stockTakeSections.trim() !== "") {
params.append("stockTakeSections", query.stockTakeSections.trim());
}
}

export const getApproverInventoryLotDetailsAll = async (
stockTakeId?: number | null,
pageNum: number = 0,
pageSize: number = 100
pageSize: number = 100,
query?: ApproverInventoryLotDetailsQuery | null
) => {
const params = new URLSearchParams();
params.append("pageNum", String(pageNum));
@@ -146,6 +178,7 @@ export const getApproverInventoryLotDetailsAll = async (
if (stockTakeId != null && stockTakeId > 0) {
params.append("stockTakeId", String(stockTakeId));
}
appendApproverInventoryLotQueryParams(params, query);

const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`;
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(
@@ -159,7 +192,8 @@ export const getApproverInventoryLotDetailsAll = async (
export const getApproverInventoryLotDetailsAllPending = async (
stockTakeId?: number | null,
pageNum: number = 0,
pageSize: number = 2147483647
pageSize: number = 50,
query?: ApproverInventoryLotDetailsQuery | null
) => {
const params = new URLSearchParams();
params.append("pageNum", String(pageNum));
@@ -167,6 +201,7 @@ export const getApproverInventoryLotDetailsAllPending = async (
if (stockTakeId != null && stockTakeId > 0) {
params.append("stockTakeId", String(stockTakeId));
}
appendApproverInventoryLotQueryParams(params, query);
const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`;
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
return normalizeApproverInventoryLotDetailsRes(response);
@@ -174,7 +209,8 @@ export const getApproverInventoryLotDetailsAllPending = async (
export const getApproverInventoryLotDetailsAllApproved = async (
stockTakeId?: number | null,
pageNum: number = 0,
pageSize: number = 50
pageSize: number = 50,
query?: ApproverInventoryLotDetailsQuery | null
) => {
const params = new URLSearchParams();
params.append("pageNum", String(pageNum));
@@ -182,6 +218,7 @@ export const getApproverInventoryLotDetailsAllApproved = async (
if (stockTakeId != null && stockTakeId > 0) {
params.append("stockTakeId", String(stockTakeId));
}
appendApproverInventoryLotQueryParams(params, query);
const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`;
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
return normalizeApproverInventoryLotDetailsRes(response);


+ 139
- 9
src/components/StockTakeManagement/ApproverStockTakeAll.tsx Näytä tiedosto

@@ -31,7 +31,10 @@ import {
BatchSaveApproverStockTakeAllRequest,
batchSaveApproverStockTakeRecordsAll,
updateStockTakeRecordStatusToNotMatch,
type ApproverInventoryLotDetailsQuery,
} from "@/app/api/stockTake/actions";
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";
import { fetchStockTakeSections } from "@/app/api/warehouse/actions";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
@@ -53,6 +56,33 @@ type ApprovedSortKey =
| "stockTakerName"
| "variance";

type ApproverSearchKey = "sectionDescription" | "stockTakeSession" | "itemCode" | "itemName";

type ApproverSearchFilters = {
sectionDescription: string;
stockTakeSession: string;
itemCode: string;
itemName: 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,
};
}

function hasAnyApproverSearchCriterion(f: ApproverSearchFilters): boolean {
return (
(f.sectionDescription && f.sectionDescription !== "All") ||
f.stockTakeSession.trim() !== "" ||
f.itemCode.trim() !== "" ||
f.itemName.trim() !== ""
);
}

function parseDateTimeMs(
v: string | string[] | null | undefined
): number {
@@ -88,6 +118,10 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
const [total, setTotal] = useState(0);
const [approvedSortKey, setApprovedSortKey] = useState<ApprovedSortKey | null>(null);
const [approvedSortDir, setApprovedSortDir] = useState<"asc" | "desc">("asc");
const [sectionDescriptionAutocompleteOptions, setSectionDescriptionAutocompleteOptions] = useState<
{ value: string; label: string }[]
>([]);
const [appliedFilters, setAppliedFilters] = useState<ApproverSearchFilters | null>(null);

const currentUserId = session?.id ? parseInt(session.id) : undefined;

@@ -108,8 +142,64 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
[]
);

const handleApproverSearchBoxSearch = useCallback(
(inputs: Record<ApproverSearchKey | `${ApproverSearchKey}To`, string>) => {
const next: ApproverSearchFilters = {
sectionDescription: inputs.sectionDescription || "All",
stockTakeSession: inputs.stockTakeSession || "",
itemCode: inputs.itemCode || "",
itemName: inputs.itemName || "",
};
if (!hasAnyApproverSearchCriterion(next)) {
onSnackbar(t("Please set at least one search criterion"), "warning");
return;
}
setAppliedFilters(next);
setPage(0);
},
[onSnackbar, t]
);

const handleApproverSearchBoxReset = useCallback(() => {
setAppliedFilters(null);
setPage(0);
setInventoryLotDetails([]);
setTotal(0);
}, []);

const approverSearchCriteria: Criterion<ApproverSearchKey>[] = useMemo(
() => [
{
type: "autocomplete",
label: t("Stock Take Section Description"),
paramName: "sectionDescription",
options: sectionDescriptionAutocompleteOptions,
needAll: true,
},
{
type: "text",
label: t("Stock Take Section (can use , to search multiple sections)"),
paramName: "stockTakeSession",
placeholder: "",
},
{
type: "text",
label: t("Item Code"),
paramName: "itemCode",
placeholder: "",
},
{
type: "text",
label: t("Item Name"),
paramName: "itemName",
placeholder: "",
},
],
[t, sectionDescriptionAutocompleteOptions]
);

const loadDetails = useCallback(
async (pageNum: number, size: number | string) => {
async (pageNum: number, size: number | string, filters: ApproverSearchFilters) => {
setLoadingDetails(true);
try {
let actualSize: number;
@@ -125,16 +215,19 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
actualSize = typeof size === "string" ? parseInt(size, 10) : size;
}

const searchQuery: ApproverInventoryLotDetailsQuery = buildApproverInventoryQuery(filters);
const response = mode === "approved"
? await getApproverInventoryLotDetailsAllApproved(
selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
pageNum,
actualSize
actualSize,
searchQuery
)
: await getApproverInventoryLotDetailsAllPending(
selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
pageNum,
actualSize
actualSize,
searchQuery
);
setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
setTotal(response.total || 0);
@@ -150,14 +243,38 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
);

useEffect(() => {
loadDetails(page, pageSize);
}, [page, pageSize, loadDetails]);
if (!appliedFilters) {
return;
}
loadDetails(page, pageSize, appliedFilters);
}, [page, pageSize, appliedFilters, loadDetails]);

// 切换模式时,清空用户先前的选择与输入,approved 模式需要以后端结果为准。
useEffect(() => {
fetchStockTakeSections()
.then((sections) => {
const descSet = new Set<string>();
sections.forEach((s) => {
const desc = s.stockTakeSectionDescription?.trim();
if (desc) descSet.add(desc);
});
setSectionDescriptionAutocompleteOptions(
Array.from(descSet).map((desc) => ({ value: desc, label: desc }))
);
})
.catch((e) => {
console.error("Failed to load section descriptions for approver search:", e);
});
}, []);

// 切换模式或盘次时,清空选择与搜索;approved 模式需要以后端结果为准。
useEffect(() => {
setQtySelection({});
setApproverQty({});
setApproverBadQty({});
setAppliedFilters(null);
setPage(0);
setInventoryLotDetails([]);
setTotal(0);
}, [mode, selectedSession.stockTakeId]);

useEffect(() => {
@@ -530,7 +647,9 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
result.errorCount > 0 ? "warning" : "success"
);

await loadDetails(page, pageSize);
if (appliedFilters) {
await loadDetails(page, pageSize, appliedFilters);
}
} catch (e: any) {
console.error("handleBatchSubmitAll (all): Error:", e);
let errorMessage = t("Failed to batch save approver stock take records");
@@ -549,7 +668,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
} finally {
setBatchSaving(false);
}
}, [selectedSession, currentUserId, variancePercentTolerance, t, onSnackbar, loadDetails, page, pageSize, mode]);
}, [selectedSession, currentUserId, variancePercentTolerance, t, onSnackbar, loadDetails, page, pageSize, mode, appliedFilters]);

const formatNumber = (num: number | null | undefined): string => {
if (num == null) return "0";
@@ -629,6 +748,15 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
)}
</Stack>
</Stack>

<Box sx={{ width: "100%", mb: 2 }}>
<SearchBox<ApproverSearchKey>
criteria={approverSearchCriteria}
onSearch={handleApproverSearchBoxSearch}
onReset={handleApproverSearchBoxReset}
/>
</Box>

{loadingDetails ? (
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
<CircularProgress />
@@ -754,7 +882,9 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
<TableRow>
<TableCell colSpan={mode === "approved" ? 10 : 8} align="center">
<Typography variant="body2" color="text.secondary">
{t("No data")}
{!appliedFilters
? t("Approver search empty hint")
: t("No data")}
</Typography>
</TableCell>
</TableRow>


+ 3
- 1
src/i18n/en/inventory.json Näytä tiedosto

@@ -3,5 +3,7 @@
"Latest market unit price": "Latest market unit price",
"Add entry for items without inventory": "Add entry for items without inventory",
"Enter item code or name to search": "Enter item code or name to search",
"Current Stock": "Current Stock"
"Current Stock": "Current Stock",
"Please set at least one search criterion": "Please set at least one search criterion",
"Approver search empty hint": "Set search criteria and click Search"
}

+ 3
- 1
src/i18n/zh/inventory.json Näytä tiedosto

@@ -281,6 +281,8 @@
"Search lot by QR code": "尋找批次(掃描二維碼)",
"Please scan...": "請掃描...",
"Stop QR Scan": "停止掃碼",
"No Data": "沒有數據"
"No Data": "沒有數據",
"Please set at least one search criterion": "請至少設定一項搜索條件",
"Approver search empty hint": "請設定搜索條件後點擊搜索"

}

Ladataan…
Peruuta
Tallenna