FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

actions.ts 19 KiB

3 miesięcy temu
3 miesięcy temu
2 tygodni temu
2 miesięcy temu
3 miesięcy temu
1 miesiąc temu
2 tygodni temu
2 tygodni temu
2 tygodni temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
2 miesięcy temu
1 dzień temu
3 miesięcy temu
2 miesięcy temu
1 dzień temu
2 miesięcy temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
1 dzień temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
3 miesięcy temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
3 miesięcy temu
1 miesiąc temu
3 miesięcy temu
3 miesięcy temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
1 tydzień temu
2 tygodni temu
2 tygodni temu
2 tygodni temu
1 tydzień temu
2 tygodni temu
1 tydzień temu
2 tygodni temu
2 tygodni temu
2 tygodni temu
1 tydzień temu
2 tygodni temu
1 tydzień temu
2 tygodni temu
2 tygodni temu
2 tygodni temu
3 miesięcy temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
1 miesiąc temu
1 miesiąc temu
1 miesiąc temu
3 miesięcy temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
2 tygodni temu
2 tygodni temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
1 miesiąc temu
3 miesięcy temu
1 tydzień temu
1 tydzień temu
3 miesięcy temu
2 tygodni temu
3 miesięcy temu
2 tygodni temu
2 tygodni temu
2 tygodni temu
3 miesięcy temu
2 miesięcy temu
1 dzień temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
1 dzień temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
2 miesięcy temu
3 miesięcy temu
1 miesiąc temu
2 miesięcy temu
2 tygodni temu
2 miesięcy temu
1 miesiąc temu
2 miesięcy temu
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. // actions.ts
  2. "use server";
  3. import { cache } from 'react';
  4. import { serverFetchJson } from "@/app/utils/fetchUtil"; // 改为 serverFetchJson
  5. import { BASE_API_URL } from "@/config/api";
  6. //import { stockTakeDebugLog } from "@/components/StockTakeManagement/stockTakeDebugLog";
  7. export interface RecordsRes<T> {
  8. records: T[];
  9. total: number;
  10. }
  11. export interface InventoryLotDetailResponse {
  12. id: number;
  13. inventoryLotId: number;
  14. itemId: number;
  15. itemCode: string;
  16. itemName: string;
  17. lotNo: string;
  18. expiryDate: string;
  19. productionDate: string;
  20. stockInDate: string;
  21. inQty: number;
  22. outQty: number;
  23. holdQty: number;
  24. availableQty: number;
  25. uom: string;
  26. warehouseCode: string;
  27. warehouseName: string;
  28. warehouseSlot: string;
  29. warehouseArea: string;
  30. warehouse: string;
  31. varianceQty: number | null;
  32. status: string;
  33. remarks: string | null;
  34. stockTakeRecordStatus: string;
  35. stockTakeRecordId: number | null;
  36. firstStockTakeQty: number | null;
  37. secondStockTakeQty: number | null;
  38. firstBadQty: number | null;
  39. secondBadQty: number | null;
  40. approverQty: number | null;
  41. approverBadQty: number | null;
  42. finalQty: number | null;
  43. bookQty: number | null;
  44. lastSelect?: number | null;
  45. stockTakeSection?: string | null;
  46. stockTakeSectionDescription?: string | null;
  47. stockTakerName?: string | null;
  48. /** ISO string or backend LocalDateTime array */
  49. stockTakeEndTime?: string | string[] | null;
  50. /** ISO string or backend LocalDateTime array */
  51. approverTime?: string | string[] | null;
  52. }
  53. /**
  54. * `approverInventoryLotDetailsAll*`:
  55. * - `total` = 全域 `inventory_lot_line` 中 `status = available` 筆數(與 DB COUNT 一致)
  56. * - `filteredRecordCount` = 目前 tab/篩選後筆數(分頁用)
  57. */
  58. export interface ApproverInventoryLotDetailsRecordsRes extends RecordsRes<InventoryLotDetailResponse> {
  59. filteredRecordCount?: number;
  60. totalWaitingForApprover?: number;
  61. totalApproved?: number;
  62. }
  63. function normalizeApproverInventoryLotDetailsRes(
  64. raw: ApproverInventoryLotDetailsRecordsRes
  65. ): ApproverInventoryLotDetailsRecordsRes {
  66. const waiting = Number(raw.totalWaitingForApprover ?? 0) || 0;
  67. const approved = Number(raw.totalApproved ?? 0) || 0;
  68. return {
  69. records: Array.isArray(raw.records) ? raw.records : [],
  70. total: Number(raw.total ?? 0) || 0,
  71. filteredRecordCount: Number(raw.filteredRecordCount ?? 0) || 0,
  72. totalWaitingForApprover: waiting,
  73. totalApproved: approved,
  74. };
  75. }
  76. export const getInventoryLotDetailsBySection = async (
  77. stockTakeSection: string,
  78. stockTakeId?: number | null,
  79. pageNum?: number,
  80. pageSize?: number,
  81. stockTakeRoundId?: number | null
  82. ) => {
  83. console.log('🌐 [API] getInventoryLotDetailsBySection called with:', {
  84. stockTakeSection,
  85. stockTakeId,
  86. stockTakeRoundId,
  87. pageNum,
  88. pageSize
  89. });
  90. const encodedSection = encodeURIComponent(stockTakeSection);
  91. let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySection?stockTakeSection=${encodedSection}&pageNum=${pageNum}&pageSize=${pageSize}`;
  92. if (stockTakeId != null && stockTakeId > 0) {
  93. url += `&stockTakeId=${stockTakeId}`;
  94. }
  95. if (stockTakeRoundId != null && stockTakeRoundId > 0) {
  96. url += `&stockTakeRoundId=${stockTakeRoundId}`;
  97. }
  98. console.log(' [API] Full URL:', url);
  99. const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
  100. url,
  101. {
  102. method: "GET",
  103. },
  104. );
  105. console.log('[API] Response received:', response);
  106. return response;
  107. }
  108. export interface SaveStockTakeRecordRequest {
  109. stockTakeRecordId?: number | null;
  110. inventoryLotLineId: number;
  111. qty: number;
  112. badQty: number;
  113. //stockTakerName: string;
  114. remark?: string | null;
  115. }
  116. export interface AllPickedStockTakeListReponse {
  117. id: number;
  118. stockTakeSession: string;
  119. lastStockTakeDate: string | null;
  120. status: string|null;
  121. approverName: string | null;
  122. currentStockTakeItemNumber: number;
  123. totalInventoryLotNumber: number;
  124. stockTakeId: number;
  125. stockTakeRoundId: number | null;
  126. stockTakerName: string | null;
  127. totalItemNumber: number;
  128. startTime: string | null;
  129. endTime: string | null;
  130. planStartDate: string | null;
  131. stockTakeSectionDescription: string | null;
  132. reStockTakeTrueFalse: boolean;
  133. }
  134. /** 與 Picker 列表一致:區域描述、盤點區域、貨品編號/名稱(逗號多關鍵字由後端解析) */
  135. export type ApproverInventoryLotDetailsQuery = {
  136. itemKeyword?: string | null;
  137. //itemKeyword?: string | null;
  138. sectionDescription?: string | null;
  139. stockTakeSections?: string | null;
  140. warehouseKeyword?: string | null;
  141. };
  142. function appendApproverInventoryLotQueryParams(
  143. params: URLSearchParams,
  144. query?: ApproverInventoryLotDetailsQuery | null
  145. ) {
  146. if (!query) return;
  147. if (query.itemKeyword != null && query.itemKeyword.trim() !== "") {
  148. params.append("itemKeyword", query.itemKeyword.trim());
  149. }
  150. if (query.warehouseKeyword != null && query.warehouseKeyword.trim() !== "") {
  151. params.append("warehouseKeyword", query.warehouseKeyword.trim());
  152. }
  153. if (
  154. query.sectionDescription != null &&
  155. query.sectionDescription !== "" &&
  156. query.sectionDescription !== "All"
  157. ) {
  158. params.append("sectionDescription", query.sectionDescription.trim());
  159. }
  160. if (query.stockTakeSections != null && query.stockTakeSections.trim() !== "") {
  161. params.append("stockTakeSections", query.stockTakeSections.trim());
  162. }
  163. }
  164. export const getApproverInventoryLotDetailsAll = async (
  165. stockTakeId?: number | null,
  166. pageNum: number = 0,
  167. pageSize: number = 100,
  168. query?: ApproverInventoryLotDetailsQuery | null
  169. ) => {
  170. const params = new URLSearchParams();
  171. params.append("pageNum", String(pageNum));
  172. params.append("pageSize", String(pageSize));
  173. if (stockTakeId != null && stockTakeId > 0) {
  174. params.append("stockTakeId", String(stockTakeId));
  175. }
  176. appendApproverInventoryLotQueryParams(params, query);
  177. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`;
  178. const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(
  179. url,
  180. {
  181. method: "GET",
  182. },
  183. );
  184. return normalizeApproverInventoryLotDetailsRes(response);
  185. }
  186. export const getApproverInventoryLotDetailsAllPending = async (
  187. stockTakeId?: number | null,
  188. pageNum: number = 0,
  189. pageSize: number = 50,
  190. query?: ApproverInventoryLotDetailsQuery | null
  191. ) => {
  192. const params = new URLSearchParams();
  193. params.append("pageNum", String(pageNum));
  194. params.append("pageSize", String(pageSize));
  195. if (stockTakeId != null && stockTakeId > 0) {
  196. params.append("stockTakeId", String(stockTakeId));
  197. }
  198. appendApproverInventoryLotQueryParams(params, query);
  199. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`;
  200. const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
  201. return normalizeApproverInventoryLotDetailsRes(response);
  202. }
  203. export const getApproverInventoryLotDetailsAllApproved = async (
  204. stockTakeId?: number | null,
  205. pageNum: number = 0,
  206. pageSize: number = 50,
  207. query?: ApproverInventoryLotDetailsQuery | null
  208. ) => {
  209. const params = new URLSearchParams();
  210. params.append("pageNum", String(pageNum));
  211. params.append("pageSize", String(pageSize));
  212. if (stockTakeId != null && stockTakeId > 0) {
  213. params.append("stockTakeId", String(stockTakeId));
  214. }
  215. appendApproverInventoryLotQueryParams(params, query);
  216. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`;
  217. const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
  218. return normalizeApproverInventoryLotDetailsRes(response);
  219. }
  220. export const importStockTake = async (data: FormData) => {
  221. const importStockTake = await serverFetchJson<string>(
  222. `${BASE_API_URL}/stockTake/import`,
  223. {
  224. method: "POST",
  225. body: data,
  226. },
  227. );
  228. return importStockTake;
  229. }
  230. export const getStockTakeRecords = async () => {
  231. const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 serverFetchJson
  232. `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList`,
  233. {
  234. method: "GET",
  235. },
  236. );
  237. return stockTakeRecords;
  238. }
  239. export const getStockTakeRecordsPaged = async (
  240. pageNum: number,
  241. pageSize: number,
  242. params?: { sectionDescription?: string; stockTakeSections?: string }
  243. ) => {
  244. const searchParams = new URLSearchParams();
  245. searchParams.set("pageNum", String(pageNum));
  246. searchParams.set("pageSize", String(pageSize));
  247. if (params?.sectionDescription && params.sectionDescription !== "All") {
  248. searchParams.set("sectionDescription", params.sectionDescription);
  249. }
  250. if (params?.stockTakeSections?.trim()) {
  251. searchParams.set("stockTakeSections", params.stockTakeSections.trim());
  252. }
  253. const url = `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList?${searchParams.toString()}`;
  254. const res = await serverFetchJson<RecordsRes<AllPickedStockTakeListReponse>>(url, { method: "GET" });
  255. return res;
  256. };
  257. export const getApproverStockTakeRecords = async () => {
  258. const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 serverFetchJson
  259. `${BASE_API_URL}/stockTakeRecord/AllApproverStockTakeList`,
  260. {
  261. method: "GET",
  262. },
  263. );
  264. return stockTakeRecords;
  265. }
  266. export const getLatestApproverStockTakeHeader = async () => {
  267. return serverFetchJson<AllPickedStockTakeListReponse>(
  268. `${BASE_API_URL}/stockTakeRecord/LatestApproverStockTakeHeader`,
  269. { method: "GET" }
  270. );
  271. }
  272. export const createStockTakeForSections = async () => {
  273. const createStockTakeForSections = await serverFetchJson<Map<string, string>>(
  274. `${BASE_API_URL}/stockTake/createForSections`,
  275. {
  276. method: "POST",
  277. },
  278. );
  279. return createStockTakeForSections;
  280. }
  281. export const saveStockTakeRecord = async (
  282. request: SaveStockTakeRecordRequest,
  283. stockTakeId: number,
  284. stockTakerId: number
  285. ) => {
  286. try {
  287. const result = await serverFetchJson<any>(
  288. `${BASE_API_URL}/stockTakeRecord/saveStockTakeRecord?stockTakeId=${stockTakeId}&stockTakerId=${stockTakerId}`,
  289. {
  290. method: "POST",
  291. headers: {
  292. "Content-Type": "application/json",
  293. },
  294. body: JSON.stringify(request),
  295. },
  296. );
  297. console.log('saveStockTakeRecord: request:', request);
  298. console.log('saveStockTakeRecord: stockTakeId:', stockTakeId);
  299. console.log('saveStockTakeRecord: stockTakerId:', stockTakerId);
  300. return result;
  301. } catch (error: any) {
  302. // 尝试从错误响应中提取消息
  303. if (error?.response) {
  304. try {
  305. const errorData = await error.response.json();
  306. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save stock take record");
  307. (errorWithMessage as any).response = error.response;
  308. throw errorWithMessage;
  309. } catch {
  310. throw error;
  311. }
  312. }
  313. throw error;
  314. }
  315. }
  316. export interface BatchSaveStockTakeRecordRequest {
  317. stockTakeId: number;
  318. stockTakeSection: string;
  319. stockTakerId: number;
  320. //stockTakerName: string;
  321. }
  322. export interface BatchSaveStockTakeRecordResponse {
  323. successCount: number;
  324. errorCount: number;
  325. errors: string[];
  326. }
  327. export const batchSaveStockTakeRecords = cache(async (data: BatchSaveStockTakeRecordRequest) => {
  328. const r = await serverFetchJson<BatchSaveStockTakeRecordResponse>(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`,
  329. {
  330. method: "POST",
  331. body: JSON.stringify(data),
  332. headers: { "Content-Type": "application/json" },
  333. })
  334. return r
  335. })
  336. // Add these interfaces and functions
  337. export interface SaveApproverStockTakeRecordRequest {
  338. stockTakeRecordId?: number | null;
  339. qty: number;
  340. badQty: number;
  341. approverId?: number | null;
  342. approverQty?: number | null;
  343. approverBadQty?: number | null;
  344. lastSelect?: number | null;
  345. }
  346. export interface BatchSaveApproverStockTakeRecordRequest {
  347. stockTakeId: number;
  348. stockTakeSection: string;
  349. approverId: number;
  350. variancePercentTolerance?: number | null;
  351. }
  352. export interface BatchSaveApproverStockTakeRecordResponse {
  353. successCount: number;
  354. errorCount: number;
  355. errors: string[];
  356. }
  357. /*
  358. export interface BatchSaveApproverStockTakeAllRequest {
  359. stockTakeId: number;
  360. approverId: number;
  361. variancePercentTolerance?: number | null;
  362. }
  363. */
  364. export interface BatchSaveApproverStockTakeAllRequest {
  365. stockTakeId: number;
  366. approverId: number;
  367. // UI 用,batch 不應該用它來 skip
  368. variancePercentTolerance?: number | null;
  369. // 新增:讓 batch 只處理搜尋結果那批
  370. itemKeyword?: string | null;
  371. warehouseKeyword?: string | null;
  372. sectionDescription?: string | null;
  373. stockTakeSections?: string | null; // 逗號字串
  374. }
  375. export const saveApproverStockTakeRecord = async (
  376. request: SaveApproverStockTakeRecordRequest,
  377. stockTakeId: number
  378. ) => {
  379. try {
  380. const result = await serverFetchJson<any>(
  381. `${BASE_API_URL}/stockTakeRecord/saveApproverStockTakeRecord?stockTakeId=${stockTakeId}`,
  382. {
  383. method: "POST",
  384. headers: {
  385. "Content-Type": "application/json",
  386. },
  387. body: JSON.stringify(request),
  388. },
  389. );
  390. return result;
  391. } catch (error: any) {
  392. if (error?.response) {
  393. try {
  394. const errorData = await error.response.json();
  395. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save approver stock take record");
  396. (errorWithMessage as any).response = error.response;
  397. throw errorWithMessage;
  398. } catch {
  399. throw error;
  400. }
  401. }
  402. throw error;
  403. }
  404. }
  405. export const batchSaveApproverStockTakeRecords = cache(async (data: BatchSaveApproverStockTakeRecordRequest) => {
  406. return serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
  407. `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecords`,
  408. {
  409. method: "POST",
  410. body: JSON.stringify(data),
  411. headers: { "Content-Type": "application/json" },
  412. }
  413. )
  414. }
  415. )
  416. export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSaveApproverStockTakeAllRequest) => {
  417. const r = await serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
  418. `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecordsAll`,
  419. {
  420. method: "POST",
  421. body: JSON.stringify(data),
  422. headers: { "Content-Type": "application/json" },
  423. }
  424. )
  425. return r
  426. })
  427. export const updateStockTakeRecordStatusToNotMatch = async (
  428. stockTakeRecordId: number
  429. ) => {
  430. try {
  431. const result = await serverFetchJson<any>(
  432. `${BASE_API_URL}/stockTakeRecord/updateStockTakeRecordStatusToNotMatch?stockTakeRecordId=${stockTakeRecordId}`,
  433. {
  434. method: "POST",
  435. },
  436. );
  437. return result;
  438. } catch (error: any) {
  439. if (error?.response) {
  440. try {
  441. const errorData = await error.response.json();
  442. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to update stock take record status");
  443. (errorWithMessage as any).response = error.response;
  444. throw errorWithMessage;
  445. } catch {
  446. throw error;
  447. }
  448. }
  449. throw error;
  450. }
  451. }
  452. export const getInventoryLotDetailsBySectionNotMatch = async (
  453. stockTakeSection: string,
  454. stockTakeId?: number | null,
  455. pageNum: number = 0,
  456. pageSize: number = 10,
  457. stockTakeRoundId?: number | null
  458. ) => {
  459. const encodedSection = encodeURIComponent(stockTakeSection);
  460. let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySectionNotMatch?stockTakeSection=${encodedSection}&pageNum=${pageNum}`;
  461. // Only add pageSize if it's not "all" (which would be a large number)
  462. if (pageSize < 100000) {
  463. url += `&pageSize=${pageSize}`;
  464. }
  465. // If pageSize is large (meaning "all"), don't send it - backend will return all
  466. if (stockTakeId != null && stockTakeId > 0) {
  467. url += `&stockTakeId=${stockTakeId}`;
  468. }
  469. if (stockTakeRoundId != null && stockTakeRoundId > 0) {
  470. url += `&stockTakeRoundId=${stockTakeRoundId}`;
  471. }
  472. const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
  473. url,
  474. {
  475. method: "GET",
  476. },
  477. );
  478. return response;
  479. }
  480. export interface SearchStockTransactionResult {
  481. records: StockTransactionResponse[];
  482. total: number;
  483. }
  484. export interface SearchStockTransactionRequest {
  485. startDate: string | null;
  486. endDate: string | null;
  487. itemCode: string | null;
  488. itemName: string | null;
  489. type: string | null;
  490. pageNum: number;
  491. pageSize: number;
  492. }
  493. export interface StockTransactionResponse {
  494. id: number;
  495. transactionType: string;
  496. itemId: number;
  497. itemCode: string | null;
  498. itemName: string | null;
  499. uomId?: number | null;
  500. uomDesc?: string | null;
  501. balanceQty: number | null;
  502. qty: number;
  503. type: string | null;
  504. status: string;
  505. transactionDate: string | null;
  506. date: string | null; // 添加这个字段
  507. lotNo: string | null;
  508. stockInId: number | null;
  509. stockOutId: number | null;
  510. remarks: string | null;
  511. }
  512. export interface StockTransactionListResponse {
  513. records: RecordsRes<StockTransactionResponse>;
  514. }
  515. export const searchStockTransactions = cache(async (request: SearchStockTransactionRequest) => {
  516. const params = new URLSearchParams();
  517. if (request.itemCode) params.append("itemCode", request.itemCode);
  518. if (request.itemName) params.append("itemName", request.itemName);
  519. if (request.type) params.append("type", request.type);
  520. if (request.startDate) params.append("startDate", request.startDate);
  521. if (request.endDate) params.append("endDate", request.endDate);
  522. params.append("pageNum", String(request.pageNum || 0));
  523. params.append("pageSize", String(request.pageSize || 100));
  524. const queryString = params.toString();
  525. const url = `${BASE_API_URL}/stockTakeRecord/searchStockTransactions${queryString ? `?${queryString}` : ''}`;
  526. const response = await serverFetchJson<RecordsRes<StockTransactionResponse>>(
  527. url,
  528. {
  529. method: "GET",
  530. next: { tags: ["Stock Transaction List"] },
  531. }
  532. );
  533. // 回傳 records 與 total,供分頁正確顯示
  534. return {
  535. records: response?.records || [],
  536. total: response?.total ?? 0,
  537. };
  538. });