FPSMS-frontend
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 

473 рядки
13 KiB

  1. "use server";
  2. import { BASE_API_URL } from "@/config/api";
  3. // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
  4. import { revalidateTag } from "next/cache";
  5. import { cache } from "react";
  6. import { serverFetch, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
  7. import { QcItemResult } from "../settings/qcItem";
  8. import { RecordsRes } from "../utils";
  9. import { DoResult } from ".";
  10. import { GridRowId, GridRowSelectionModel } from "@mui/x-data-grid";
  11. import { GET } from "../auth/[...nextauth]/route";
  12. import { stringify } from "querystring";
  13. import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
  14. export interface CreateConsoDoInput {
  15. ids: GridRowSelectionModel;
  16. }
  17. export interface DoDetail {
  18. id: number;
  19. code: string;
  20. supplierCode: string;
  21. shopCode: string;
  22. shopName: string;
  23. currencyCode: string;
  24. orderDate: string;
  25. estimatedArrivalDate: string;
  26. completeDate: string;
  27. status: string;
  28. deliveryOrderLines: DoDetailLine[];
  29. }
  30. export interface DoDetailLine {
  31. id: number;
  32. itemNo: string;
  33. qty: number;
  34. price: number;
  35. status: string;
  36. itemName?: string;
  37. uomCode?: string;
  38. uom?: string;
  39. shortUom?: string;
  40. }
  41. export interface DoSearchAll {
  42. id: number;
  43. code: string;
  44. status: string;
  45. estimatedArrivalDate: number[];
  46. orderDate: number[];
  47. supplierName: string;
  48. shopName: string;
  49. shopAddress?: string;
  50. }
  51. export interface DoSearchLiteResponse {
  52. records: DoSearchAll[];
  53. total: number;
  54. }
  55. export interface ReleaseDoRequest {
  56. id: number;
  57. }
  58. export interface ReleaseDoResponse {
  59. id: number;
  60. entity: { status: string }
  61. }
  62. export interface AssignByStoreRequest {
  63. storeId: string; // "2/F" or "4/F"
  64. assignTo: number;
  65. }
  66. export interface AssignByStoreResponse {
  67. id: number;
  68. code: string;
  69. name: string;
  70. type: string;
  71. message: string;
  72. errorPosition: string;
  73. entity: any;
  74. }
  75. export interface PrintDeliveryNoteRequest{
  76. doPickOrderId: number;
  77. printerId: number;
  78. printQty: number;
  79. numOfCarton: number;
  80. isDraft: boolean;
  81. }
  82. export interface PrintDeliveryNoteResponse{
  83. success: boolean;
  84. message?: string
  85. }
  86. export interface PrintDNLabelsRequest{
  87. doPickOrderId: number;
  88. printerId: number;
  89. printQty: number;
  90. numOfCarton: number;
  91. }
  92. export interface PrintDNLabelsRespone{
  93. success: boolean;
  94. message?: string
  95. }
  96. export interface BatchReleaseRequest {
  97. ids: number[];
  98. }
  99. export interface BatchReleaseResponse {
  100. success: boolean;
  101. message?: string
  102. }
  103. export interface getTicketReleaseTable {
  104. id: number;
  105. storeId: string | null;
  106. ticketNo: string | null;
  107. pickOrderId: number | null;
  108. doOrderId: number | null;
  109. pickOrderCode: string | null;
  110. deliveryOrderCode: string | null;
  111. loadingSequence: number | null;
  112. ticketStatus: string | null;
  113. truckId: number | null;
  114. truckDepartureTime: string | null;
  115. shopId: number | null;
  116. handledBy: number | null;
  117. ticketReleaseTime: string | null;
  118. ticketCompleteDateTime: string | null;
  119. truckLanceCode: string | null;
  120. shopCode: string | null;
  121. shopName: string | null;
  122. requiredDeliveryDate: string | null;
  123. handlerName: string | null;
  124. numberOfFGItems: number;
  125. /** 進行中 do_pick_order 為 true,才可呼叫 force-complete / revert-assignment(id 為 do_pick_order 主鍵) */
  126. isActiveDoPickOrder?: boolean;
  127. }
  128. export interface TruckScheduleDashboardItem {
  129. storeId: string | null;
  130. truckId: number | null;
  131. truckLanceCode: string | null;
  132. truckDepartureTime: string | number[] | null;
  133. numberOfShopsToServe: number;
  134. numberOfPickTickets: number;
  135. totalItemsToPick: number;
  136. numberOfTicketsReleased: number;
  137. firstTicketStartTime: string | number[] | null;
  138. numberOfTicketsCompleted: number;
  139. lastTicketEndTime: string | number[] | null;
  140. pickTimeTakenMinutes: number | null;
  141. }
  142. export interface SearchDeliveryOrderInfoRequest {
  143. code: string;
  144. shopName: string;
  145. status: string;
  146. orderStartDate: string;
  147. orderEndDate: string;
  148. estArrStartDate: string;
  149. estArrEndDate: string;
  150. pageSize: number;
  151. pageNum: number;
  152. }
  153. export interface SearchDeliveryOrderInfoResponse {
  154. records: DeliveryOrderInfo[];
  155. total: number;
  156. }
  157. export interface DeliveryOrderInfo {
  158. id: number;
  159. code: string;
  160. shopName: string;
  161. supplierName: string; // 改为必需字段
  162. status: string;
  163. orderDate: string;
  164. estimatedArrivalDate: string;
  165. deliveryOrderLines: DoDetailLine[];
  166. }
  167. export const fetchDoRecordByPage = cache(async (data?: SearchDeliveryOrderInfoRequest) => {
  168. const queryStr = convertObjToURLSearchParams(data)
  169. console.log("queryStr", queryStr)
  170. const response = serverFetchJson<SearchDeliveryOrderInfoResponse>(
  171. `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
  172. {
  173. method: "GET",
  174. headers: { "Content-Type": "application/json" },
  175. next: {
  176. tags: ["jos"]
  177. }
  178. }
  179. )
  180. return response
  181. })
  182. export const fetchTicketReleaseTable = cache(async (startDate: string, endDate: string)=> {
  183. return await serverFetchJson<getTicketReleaseTable[]>(
  184. `${BASE_API_URL}/doPickOrder/ticket-release-table/${startDate}&${endDate}`,
  185. {
  186. method: "GET",
  187. }
  188. );
  189. });
  190. export const fetchTruckScheduleDashboard = cache(async (date?: string) => {
  191. const url = date
  192. ? `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard?date=${date}`
  193. : `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard`;
  194. return await serverFetchJson<TruckScheduleDashboardItem[]>(
  195. url,
  196. {
  197. method: "GET",
  198. }
  199. );
  200. });
  201. export const startBatchReleaseAsyncSingle = cache(async (data: { doId: number; userId: number }) => {
  202. const { doId, userId } = data;
  203. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  204. `${BASE_API_URL}/doPickOrder/batch-release/async-single?userId=${userId}`,
  205. {
  206. method: "POST",
  207. body: JSON.stringify(doId),
  208. headers: { "Content-Type": "application/json" },
  209. }
  210. );
  211. });
  212. export const startBatchReleaseAsync = cache(async (data: { ids: number[]; userId: number }) => {
  213. const { ids, userId } = data;
  214. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  215. `${BASE_API_URL}/doPickOrder/batch-release/async?userId=${userId}`,
  216. {
  217. method: "POST",
  218. body: JSON.stringify(ids),
  219. headers: { "Content-Type": "application/json" },
  220. }
  221. );
  222. });
  223. export const getBatchReleaseProgress = cache(async (jobId: string) => {
  224. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  225. `${BASE_API_URL}/doPickOrder/batch-release/progress/${jobId}`,
  226. { method: "GET" }
  227. );
  228. });
  229. export const assignPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  230. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/assign-by-store`,
  231. {
  232. method: "POST",
  233. body: JSON.stringify(data),
  234. headers: { "Content-Type": "application/json" },
  235. })
  236. })
  237. export const releaseAssignedPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  238. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/release-assigned-by-store`,
  239. {
  240. method: "POST",
  241. body: JSON.stringify(data),
  242. headers: { "Content-Type": "application/json" },
  243. })
  244. })
  245. export async function releaseDo(input: ReleaseDoRequest) {
  246. const response = await serverFetchJson<ReleaseDoResponse>(`${BASE_API_URL}/do/release`, {
  247. method: 'POST',
  248. body: JSON.stringify(input),
  249. headers: {
  250. 'Content-Type': 'application/json',
  251. },
  252. });
  253. revalidateTag('do');
  254. return response;
  255. }
  256. export const preloadDo = () => {
  257. fetchDoList();
  258. };
  259. export const fetchDoList = cache(async () => {
  260. return serverFetchJson<DoResult[]>(`${BASE_API_URL}/do/list`, {
  261. next: { tags: ["doList"] },
  262. });
  263. });
  264. export const fetchDoDetail = cache(async (id: number) => {
  265. return serverFetchJson<DoDetail>(`${BASE_API_URL}/do/detail/${id}`, {
  266. method: "GET",
  267. headers: { "Content-Type": "application/json" },
  268. next: {
  269. tags: ["doDetail"]
  270. }
  271. });
  272. });
  273. export async function fetchDoSearch(
  274. code: string,
  275. shopName: string,
  276. status: string,
  277. orderStartDate: string,
  278. orderEndDate: string,
  279. estArrStartDate: string,
  280. estArrEndDate: string,
  281. pageNum?: number,
  282. pageSize?: number,
  283. truckLanceCode?: string
  284. ): Promise<DoSearchLiteResponse> {
  285. // 构建请求体
  286. const requestBody: any = {
  287. code: code || null,
  288. shopName: shopName || null,
  289. status: status || null,
  290. estimatedArrivalDate: estArrStartDate || null, // 使用单个日期字段
  291. truckLanceCode: truckLanceCode || null,
  292. pageNum: pageNum || 1,
  293. pageSize: pageSize || 10,
  294. };
  295. // 如果日期不为空,转换为 LocalDateTime 格式
  296. if (estArrStartDate) {
  297. requestBody.estimatedArrivalDate = estArrStartDate; // 格式: "2026-01-19T00:00:00"
  298. } else {
  299. requestBody.estimatedArrivalDate = null;
  300. }
  301. const url = `${BASE_API_URL}/do/search-do-lite`;
  302. const data = await serverFetchJson<DoSearchLiteResponse>(url, {
  303. method: "POST",
  304. headers: { "Content-Type": "application/json" },
  305. body: JSON.stringify(requestBody),
  306. });
  307. return data;
  308. }
  309. export async function fetchDoSearchList(
  310. code: string,
  311. shopName: string,
  312. status: string,
  313. orderStartDate: string,
  314. orderEndDate: string,
  315. etaFrom: string,
  316. etaTo: string,
  317. page = 0,
  318. size = 500
  319. ): Promise<DoSearchAll[]> {
  320. const params = new URLSearchParams();
  321. if (code) params.append("code", code);
  322. if (shopName) params.append("shopName", shopName);
  323. if (status) params.append("status", status);
  324. if (orderStartDate) params.append("orderFrom", orderStartDate);
  325. if (orderEndDate) params.append("orderTo", orderEndDate);
  326. if (etaFrom) params.append("etaFrom", etaFrom);
  327. if (etaTo) params.append("etaTo", etaTo);
  328. params.append("page", String(page));
  329. params.append("size", String(size));
  330. const res = await fetch(`/api/delivery-order/search-do-list?${params.toString()}`);
  331. const pageData = await res.json(); // Spring Page 结构
  332. return pageData.content; // 前端继续沿用你原来的 client-side 分页逻辑
  333. }
  334. export async function printDN(request: PrintDeliveryNoteRequest){
  335. const params = new URLSearchParams();
  336. params.append('doPickOrderId', request.doPickOrderId.toString());
  337. params.append('printerId', request.printerId.toString());
  338. if (request.printQty !== null && request.printQty !== undefined) {
  339. params.append('printQty', request.printQty.toString());
  340. }
  341. params.append('numOfCarton', request.numOfCarton.toString());
  342. params.append('isDraft', request.isDraft.toString());
  343. try {
  344. const response = await serverFetch(`${BASE_API_URL}/do/print-DN?${params.toString()}`, {
  345. method: "GET",
  346. });
  347. if (response.ok) {
  348. return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse;
  349. }
  350. const errorText = await response.text();
  351. console.error("Print DN error:", errorText);
  352. return {
  353. success: false,
  354. message: "No data found for this pick order."
  355. } as PrintDeliveryNoteResponse;
  356. } catch (error) {
  357. console.error("Error in printDN:", error);
  358. return {
  359. success: false,
  360. message: "No data found for this pick order."
  361. } as PrintDeliveryNoteResponse;
  362. }
  363. }
  364. export async function printDNLabels(request: PrintDNLabelsRequest){
  365. const params = new URLSearchParams();
  366. params.append('doPickOrderId', request.doPickOrderId.toString());
  367. params.append('printerId', request.printerId.toString());
  368. if (request.printQty !== null && request.printQty !== undefined) {
  369. params.append('printQty', request.printQty.toString());
  370. }
  371. params.append('numOfCarton', request.numOfCarton.toString());
  372. const response = await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DNLabels?${params.toString()}`,{
  373. method: "GET"
  374. });
  375. return { success: true, message: "Print job sent successfully (labels)"} as PrintDeliveryNoteResponse
  376. }
  377. export interface Check4FTruckBatchResponse {
  378. hasProblem: boolean;
  379. problems: ProblemDoDto[];
  380. }
  381. export interface ProblemDoDto {
  382. deliveryOrderId: number;
  383. deliveryOrderCode: string;
  384. targetDate: string;
  385. availableTrucks: TruckInfoDto[];
  386. }
  387. export interface TruckInfoDto {
  388. id: number;
  389. truckLanceCode: string;
  390. departureTime: string;
  391. storeId: string;
  392. shopCode: string;
  393. shopName: string;
  394. }
  395. export const check4FTrucksBatch = cache(async (doIds: number[]) => {
  396. return await serverFetchJson<Check4FTruckBatchResponse>(`${BASE_API_URL}/do/check-4f-trucks-batch`, {
  397. method: "POST",
  398. body: JSON.stringify(doIds),
  399. headers: { "Content-Type": "application/json" },
  400. });
  401. });
  402. export async function fetchAllDoSearch(
  403. code: string,
  404. shopName: string,
  405. status: string,
  406. estArrStartDate: string,
  407. truckLanceCode?: string // 添加这个参数
  408. ): Promise<DoSearchAll[]> {
  409. // 使用一个很大的 pageSize 来获取所有匹配的记录
  410. const requestBody: any = {
  411. code: code || null,
  412. shopName: shopName || null,
  413. status: status || null,
  414. estimatedArrivalDate: estArrStartDate || null,
  415. truckLanceCode: truckLanceCode || null, // 添加这个字段
  416. pageNum: 1,
  417. pageSize: 10000, // 使用一个很大的值来获取所有记录
  418. };
  419. if (estArrStartDate) {
  420. requestBody.estimatedArrivalDate = estArrStartDate;
  421. } else {
  422. requestBody.estimatedArrivalDate = null;
  423. }
  424. const url = `${BASE_API_URL}/do/search-do-lite`;
  425. const data = await serverFetchJson<DoSearchLiteResponse>(url, {
  426. method: "POST",
  427. headers: { "Content-Type": "application/json" },
  428. body: JSON.stringify(requestBody),
  429. });
  430. return data.records;
  431. }