FPSMS-frontend
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

476 lignes
15 KiB

  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. export interface RecordsRes<T> {
  7. records: T[];
  8. total: number;
  9. }
  10. export interface InventoryLotDetailResponse {
  11. id: number;
  12. inventoryLotId: number;
  13. itemId: number;
  14. itemCode: string;
  15. itemName: string;
  16. lotNo: string;
  17. expiryDate: string;
  18. productionDate: string;
  19. stockInDate: string;
  20. inQty: number;
  21. outQty: number;
  22. holdQty: number;
  23. availableQty: number;
  24. uom: string;
  25. warehouseCode: string;
  26. warehouseName: string;
  27. warehouseSlot: string;
  28. warehouseArea: string;
  29. warehouse: string;
  30. varianceQty: number | null;
  31. status: string;
  32. remarks: string | null;
  33. stockTakeRecordStatus: string;
  34. stockTakeRecordId: number | null;
  35. firstStockTakeQty: number | null;
  36. secondStockTakeQty: number | null;
  37. firstBadQty: number | null;
  38. secondBadQty: number | null;
  39. approverQty: number | null;
  40. approverBadQty: number | null;
  41. finalQty: number | null;
  42. bookQty: number | null;
  43. }
  44. export const getInventoryLotDetailsBySection = async (
  45. stockTakeSection: string,
  46. stockTakeId?: number | null,
  47. pageNum?: number,
  48. pageSize?: number
  49. ) => {
  50. console.log('🌐 [API] getInventoryLotDetailsBySection called with:', {
  51. stockTakeSection,
  52. stockTakeId,
  53. pageNum,
  54. pageSize
  55. });
  56. const encodedSection = encodeURIComponent(stockTakeSection);
  57. let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySection?stockTakeSection=${encodedSection}&pageNum=${pageNum}&pageSize=${pageSize}`;
  58. if (stockTakeId != null && stockTakeId > 0) {
  59. url += `&stockTakeId=${stockTakeId}`;
  60. }
  61. console.log(' [API] Full URL:', url);
  62. const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
  63. url,
  64. {
  65. method: "GET",
  66. },
  67. );
  68. console.log('[API] Response received:', response);
  69. return response;
  70. }
  71. export interface SaveStockTakeRecordRequest {
  72. stockTakeRecordId?: number | null;
  73. inventoryLotLineId: number;
  74. qty: number;
  75. badQty: number;
  76. //stockTakerName: string;
  77. remark?: string | null;
  78. }
  79. export interface AllPickedStockTakeListReponse {
  80. id: number;
  81. stockTakeSession: string;
  82. lastStockTakeDate: string | null;
  83. status: string|null;
  84. approverName: string | null;
  85. currentStockTakeItemNumber: number;
  86. totalInventoryLotNumber: number;
  87. stockTakeId: number;
  88. stockTakeRoundId: number | null;
  89. stockTakerName: string | null;
  90. totalItemNumber: number;
  91. startTime: string | null;
  92. endTime: string | null;
  93. planStartDate: string | null;
  94. stockTakeSectionDescription: string | null;
  95. reStockTakeTrueFalse: boolean;
  96. }
  97. export const getApproverInventoryLotDetailsAll = async (
  98. stockTakeId?: number | null,
  99. pageNum: number = 0,
  100. pageSize: number = 100
  101. ) => {
  102. const params = new URLSearchParams();
  103. params.append("pageNum", String(pageNum));
  104. params.append("pageSize", String(pageSize));
  105. if (stockTakeId != null && stockTakeId > 0) {
  106. params.append("stockTakeId", String(stockTakeId));
  107. }
  108. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`;
  109. const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
  110. url,
  111. {
  112. method: "GET",
  113. },
  114. );
  115. return response;
  116. }
  117. export const getApproverInventoryLotDetailsAllPending = async (
  118. stockTakeId?: number | null,
  119. pageNum: number = 0,
  120. pageSize: number = 2147483647
  121. ) => {
  122. const params = new URLSearchParams();
  123. params.append("pageNum", String(pageNum));
  124. params.append("pageSize", String(pageSize));
  125. if (stockTakeId != null && stockTakeId > 0) {
  126. params.append("stockTakeId", String(stockTakeId));
  127. }
  128. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`;
  129. return serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(url, { method: "GET" });
  130. }
  131. export const getApproverInventoryLotDetailsAllApproved = async (
  132. stockTakeId?: number | null,
  133. pageNum: number = 0,
  134. pageSize: number = 50
  135. ) => {
  136. const params = new URLSearchParams();
  137. params.append("pageNum", String(pageNum));
  138. params.append("pageSize", String(pageSize));
  139. if (stockTakeId != null && stockTakeId > 0) {
  140. params.append("stockTakeId", String(stockTakeId));
  141. }
  142. const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`;
  143. return serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(url, { method: "GET" });
  144. }
  145. export const importStockTake = async (data: FormData) => {
  146. const importStockTake = await serverFetchJson<string>(
  147. `${BASE_API_URL}/stockTake/import`,
  148. {
  149. method: "POST",
  150. body: data,
  151. },
  152. );
  153. return importStockTake;
  154. }
  155. export const getStockTakeRecords = async () => {
  156. const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 serverFetchJson
  157. `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList`,
  158. {
  159. method: "GET",
  160. },
  161. );
  162. return stockTakeRecords;
  163. }
  164. export const getStockTakeRecordsPaged = async (
  165. pageNum: number,
  166. pageSize: number,
  167. params?: { sectionDescription?: string; stockTakeSections?: string }
  168. ) => {
  169. const searchParams = new URLSearchParams();
  170. searchParams.set("pageNum", String(pageNum));
  171. searchParams.set("pageSize", String(pageSize));
  172. if (params?.sectionDescription && params.sectionDescription !== "All") {
  173. searchParams.set("sectionDescription", params.sectionDescription);
  174. }
  175. if (params?.stockTakeSections?.trim()) {
  176. searchParams.set("stockTakeSections", params.stockTakeSections.trim());
  177. }
  178. const url = `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList?${searchParams.toString()}`;
  179. const res = await serverFetchJson<RecordsRes<AllPickedStockTakeListReponse>>(url, { method: "GET" });
  180. return res;
  181. };
  182. export const getApproverStockTakeRecords = async () => {
  183. const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 serverFetchJson
  184. `${BASE_API_URL}/stockTakeRecord/AllApproverStockTakeList`,
  185. {
  186. method: "GET",
  187. },
  188. );
  189. return stockTakeRecords;
  190. }
  191. export const getLatestApproverStockTakeHeader = async () => {
  192. return serverFetchJson<AllPickedStockTakeListReponse>(
  193. `${BASE_API_URL}/stockTakeRecord/LatestApproverStockTakeHeader`,
  194. { method: "GET" }
  195. );
  196. }
  197. export const createStockTakeForSections = async () => {
  198. const createStockTakeForSections = await serverFetchJson<Map<string, string>>(
  199. `${BASE_API_URL}/stockTake/createForSections`,
  200. {
  201. method: "POST",
  202. },
  203. );
  204. return createStockTakeForSections;
  205. }
  206. export const saveStockTakeRecord = async (
  207. request: SaveStockTakeRecordRequest,
  208. stockTakeId: number,
  209. stockTakerId: number
  210. ) => {
  211. try {
  212. const result = await serverFetchJson<any>(
  213. `${BASE_API_URL}/stockTakeRecord/saveStockTakeRecord?stockTakeId=${stockTakeId}&stockTakerId=${stockTakerId}`,
  214. {
  215. method: "POST",
  216. headers: {
  217. "Content-Type": "application/json",
  218. },
  219. body: JSON.stringify(request),
  220. },
  221. );
  222. console.log('saveStockTakeRecord: request:', request);
  223. console.log('saveStockTakeRecord: stockTakeId:', stockTakeId);
  224. console.log('saveStockTakeRecord: stockTakerId:', stockTakerId);
  225. return result;
  226. } catch (error: any) {
  227. // 尝试从错误响应中提取消息
  228. if (error?.response) {
  229. try {
  230. const errorData = await error.response.json();
  231. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save stock take record");
  232. (errorWithMessage as any).response = error.response;
  233. throw errorWithMessage;
  234. } catch {
  235. throw error;
  236. }
  237. }
  238. throw error;
  239. }
  240. }
  241. export interface BatchSaveStockTakeRecordRequest {
  242. stockTakeId: number;
  243. stockTakeSection: string;
  244. stockTakerId: number;
  245. //stockTakerName: string;
  246. }
  247. export interface BatchSaveStockTakeRecordResponse {
  248. successCount: number;
  249. errorCount: number;
  250. errors: string[];
  251. }
  252. export const batchSaveStockTakeRecords = cache(async (data: BatchSaveStockTakeRecordRequest) => {
  253. return serverFetchJson<BatchSaveStockTakeRecordResponse>(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`,
  254. {
  255. method: "POST",
  256. body: JSON.stringify(data),
  257. headers: { "Content-Type": "application/json" },
  258. })
  259. })
  260. // Add these interfaces and functions
  261. export interface SaveApproverStockTakeRecordRequest {
  262. stockTakeRecordId?: number | null;
  263. qty: number;
  264. badQty: number;
  265. approverId?: number | null;
  266. approverQty?: number | null;
  267. approverBadQty?: number | null;
  268. }
  269. export interface BatchSaveApproverStockTakeRecordRequest {
  270. stockTakeId: number;
  271. stockTakeSection: string;
  272. approverId: number;
  273. variancePercentTolerance?: number | null;
  274. }
  275. export interface BatchSaveApproverStockTakeRecordResponse {
  276. successCount: number;
  277. errorCount: number;
  278. errors: string[];
  279. }
  280. export interface BatchSaveApproverStockTakeAllRequest {
  281. stockTakeId: number;
  282. approverId: number;
  283. variancePercentTolerance?: number | null;
  284. }
  285. export const saveApproverStockTakeRecord = async (
  286. request: SaveApproverStockTakeRecordRequest,
  287. stockTakeId: number
  288. ) => {
  289. try {
  290. const result = await serverFetchJson<any>(
  291. `${BASE_API_URL}/stockTakeRecord/saveApproverStockTakeRecord?stockTakeId=${stockTakeId}`,
  292. {
  293. method: "POST",
  294. headers: {
  295. "Content-Type": "application/json",
  296. },
  297. body: JSON.stringify(request),
  298. },
  299. );
  300. return result;
  301. } catch (error: any) {
  302. if (error?.response) {
  303. try {
  304. const errorData = await error.response.json();
  305. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save approver stock take record");
  306. (errorWithMessage as any).response = error.response;
  307. throw errorWithMessage;
  308. } catch {
  309. throw error;
  310. }
  311. }
  312. throw error;
  313. }
  314. }
  315. export const batchSaveApproverStockTakeRecords = cache(async (data: BatchSaveApproverStockTakeRecordRequest) => {
  316. return serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
  317. `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecords`,
  318. {
  319. method: "POST",
  320. body: JSON.stringify(data),
  321. headers: { "Content-Type": "application/json" },
  322. }
  323. )
  324. }
  325. )
  326. export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSaveApproverStockTakeAllRequest) => {
  327. return serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
  328. `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecordsAll`,
  329. {
  330. method: "POST",
  331. body: JSON.stringify(data),
  332. headers: { "Content-Type": "application/json" },
  333. }
  334. )
  335. })
  336. export const updateStockTakeRecordStatusToNotMatch = async (
  337. stockTakeRecordId: number
  338. ) => {
  339. try {
  340. const result = await serverFetchJson<any>(
  341. `${BASE_API_URL}/stockTakeRecord/updateStockTakeRecordStatusToNotMatch?stockTakeRecordId=${stockTakeRecordId}`,
  342. {
  343. method: "POST",
  344. },
  345. );
  346. return result;
  347. } catch (error: any) {
  348. if (error?.response) {
  349. try {
  350. const errorData = await error.response.json();
  351. const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to update stock take record status");
  352. (errorWithMessage as any).response = error.response;
  353. throw errorWithMessage;
  354. } catch {
  355. throw error;
  356. }
  357. }
  358. throw error;
  359. }
  360. }
  361. export const getInventoryLotDetailsBySectionNotMatch = async (
  362. stockTakeSection: string,
  363. stockTakeId?: number | null,
  364. pageNum: number = 0,
  365. pageSize: number = 10
  366. ) => {
  367. const encodedSection = encodeURIComponent(stockTakeSection);
  368. let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySectionNotMatch?stockTakeSection=${encodedSection}&pageNum=${pageNum}`;
  369. // Only add pageSize if it's not "all" (which would be a large number)
  370. if (pageSize < 100000) {
  371. url += `&pageSize=${pageSize}`;
  372. }
  373. // If pageSize is large (meaning "all"), don't send it - backend will return all
  374. if (stockTakeId != null && stockTakeId > 0) {
  375. url += `&stockTakeId=${stockTakeId}`;
  376. }
  377. const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
  378. url,
  379. {
  380. method: "GET",
  381. },
  382. );
  383. return response;
  384. }
  385. export interface SearchStockTransactionResult {
  386. records: StockTransactionResponse[];
  387. total: number;
  388. }
  389. export interface SearchStockTransactionRequest {
  390. startDate: string | null;
  391. endDate: string | null;
  392. itemCode: string | null;
  393. itemName: string | null;
  394. type: string | null;
  395. pageNum: number;
  396. pageSize: number;
  397. }
  398. export interface StockTransactionResponse {
  399. id: number;
  400. transactionType: string;
  401. itemId: number;
  402. itemCode: string | null;
  403. itemName: string | null;
  404. uomId?: number | null;
  405. uomDesc?: string | null;
  406. balanceQty: number | null;
  407. qty: number;
  408. type: string | null;
  409. status: string;
  410. transactionDate: string | null;
  411. date: string | null; // 添加这个字段
  412. lotNo: string | null;
  413. stockInId: number | null;
  414. stockOutId: number | null;
  415. remarks: string | null;
  416. }
  417. export interface StockTransactionListResponse {
  418. records: RecordsRes<StockTransactionResponse>;
  419. }
  420. export const searchStockTransactions = cache(async (request: SearchStockTransactionRequest) => {
  421. const params = new URLSearchParams();
  422. if (request.itemCode) params.append("itemCode", request.itemCode);
  423. if (request.itemName) params.append("itemName", request.itemName);
  424. if (request.type) params.append("type", request.type);
  425. if (request.startDate) params.append("startDate", request.startDate);
  426. if (request.endDate) params.append("endDate", request.endDate);
  427. params.append("pageNum", String(request.pageNum || 0));
  428. params.append("pageSize", String(request.pageSize || 100));
  429. const queryString = params.toString();
  430. const url = `${BASE_API_URL}/stockTakeRecord/searchStockTransactions${queryString ? `?${queryString}` : ''}`;
  431. const response = await serverFetchJson<RecordsRes<StockTransactionResponse>>(
  432. url,
  433. {
  434. method: "GET",
  435. next: { tags: ["Stock Transaction List"] },
  436. }
  437. );
  438. // 回傳 records 與 total,供分頁正確顯示
  439. return {
  440. records: response?.records || [],
  441. total: response?.total ?? 0,
  442. };
  443. });