FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

584 satır
17 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 PrintDNLabelsReprintRequest{
  93. doPickOrderId: number;
  94. printerId: number;
  95. printQty: number;
  96. fromCarton: number;
  97. toCarton: number;
  98. totalCartonsOnShipment: number;
  99. }
  100. export interface PrintDNLabelsRespone{
  101. success: boolean;
  102. message?: string
  103. }
  104. export interface BatchReleaseRequest {
  105. ids: number[];
  106. }
  107. export interface BatchReleaseResponse {
  108. success: boolean;
  109. message?: string
  110. }
  111. export interface getTicketReleaseTable {
  112. id: number;
  113. storeId: string | null;
  114. ticketNo: string | null;
  115. pickOrderId: number | null;
  116. doOrderId: number | null;
  117. pickOrderCode: string | null;
  118. deliveryOrderCode: string | null;
  119. loadingSequence: number | null;
  120. ticketStatus: string | null;
  121. truckId: number | null;
  122. truckDepartureTime: string | null;
  123. shopId: number | null;
  124. handledBy: number | null;
  125. ticketReleaseTime: string | null;
  126. ticketCompleteDateTime: string | null;
  127. truckLanceCode: string | null;
  128. shopCode: string | null;
  129. shopName: string | null;
  130. requiredDeliveryDate: string | null;
  131. handlerName: string | null;
  132. numberOfFGItems: number;
  133. /** 進行中 do_pick_order 為 true,才可呼叫 force-complete / revert-assignment(id 為 do_pick_order 主鍵) */
  134. isActiveDoPickOrder?: boolean;
  135. }
  136. export interface TruckScheduleDashboardItem {
  137. storeId: string | null;
  138. truckId: number | null;
  139. truckLanceCode: string | null;
  140. truckDepartureTime: string | number[] | null;
  141. numberOfShopsToServe: number;
  142. numberOfPickTickets: number;
  143. totalItemsToPick: number;
  144. numberOfTicketsReleased: number;
  145. firstTicketStartTime: string | number[] | null;
  146. numberOfTicketsCompleted: number;
  147. lastTicketEndTime: string | number[] | null;
  148. pickTimeTakenMinutes: number | null;
  149. }
  150. export interface SearchDeliveryOrderInfoRequest {
  151. code: string;
  152. shopName: string;
  153. status: string;
  154. orderStartDate: string;
  155. orderEndDate: string;
  156. estArrStartDate: string;
  157. estArrEndDate: string;
  158. pageSize: number;
  159. pageNum: number;
  160. }
  161. export interface SearchDeliveryOrderInfoResponse {
  162. records: DeliveryOrderInfo[];
  163. total: number;
  164. }
  165. export interface DeliveryOrderInfo {
  166. id: number;
  167. code: string;
  168. shopName: string;
  169. supplierName: string; // 改为必需字段
  170. status: string;
  171. orderDate: string;
  172. estimatedArrivalDate: string;
  173. deliveryOrderLines: DoDetailLine[];
  174. }
  175. export const fetchDoRecordByPage = cache(async (data?: SearchDeliveryOrderInfoRequest) => {
  176. const queryStr = convertObjToURLSearchParams(data)
  177. console.log("queryStr", queryStr)
  178. const response = serverFetchJson<SearchDeliveryOrderInfoResponse>(
  179. `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
  180. {
  181. method: "GET",
  182. headers: { "Content-Type": "application/json" },
  183. next: {
  184. tags: ["jos"]
  185. }
  186. }
  187. )
  188. return response
  189. })
  190. export const fetchTicketReleaseTable = cache(async (startDate: string, endDate: string)=> {
  191. return await serverFetchJson<getTicketReleaseTable[]>(
  192. `${BASE_API_URL}/doPickOrder/ticket-release-table/${startDate}&${endDate}`,
  193. {
  194. method: "GET",
  195. }
  196. );
  197. });
  198. export const fetchTruckScheduleDashboard = cache(async (date?: string) => {
  199. const url = date
  200. ? `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard?date=${date}`
  201. : `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard`;
  202. return await serverFetchJson<TruckScheduleDashboardItem[]>(
  203. url,
  204. {
  205. method: "GET",
  206. }
  207. );
  208. });
  209. export const startBatchReleaseAsyncSingle = cache(async (data: { doId: number; userId: number }) => {
  210. const { doId, userId } = data;
  211. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  212. `${BASE_API_URL}/doPickOrder/batch-release/async-single?userId=${userId}`,
  213. {
  214. method: "POST",
  215. body: JSON.stringify(doId),
  216. headers: { "Content-Type": "application/json" },
  217. }
  218. );
  219. });
  220. export const startBatchReleaseAsync = cache(async (data: { ids: number[]; userId: number }) => {
  221. const { ids, userId } = data;
  222. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  223. `${BASE_API_URL}/doPickOrder/batch-release/async?userId=${userId}`,
  224. {
  225. method: "POST",
  226. body: JSON.stringify(ids),
  227. headers: { "Content-Type": "application/json" },
  228. }
  229. );
  230. });
  231. export const getBatchReleaseProgress = cache(async (jobId: string) => {
  232. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  233. `${BASE_API_URL}/doPickOrder/batch-release/progress/${jobId}`,
  234. { method: "GET" }
  235. );
  236. });
  237. export const assignPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  238. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/assign-by-store`,
  239. {
  240. method: "POST",
  241. body: JSON.stringify(data),
  242. headers: { "Content-Type": "application/json" },
  243. })
  244. })
  245. export const releaseAssignedPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  246. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/release-assigned-by-store`,
  247. {
  248. method: "POST",
  249. body: JSON.stringify(data),
  250. headers: { "Content-Type": "application/json" },
  251. })
  252. })
  253. export async function releaseDo(input: ReleaseDoRequest) {
  254. const response = await serverFetchJson<ReleaseDoResponse>(`${BASE_API_URL}/do/release`, {
  255. method: 'POST',
  256. body: JSON.stringify(input),
  257. headers: {
  258. 'Content-Type': 'application/json',
  259. },
  260. });
  261. revalidateTag('do');
  262. return response;
  263. }
  264. export const preloadDo = () => {
  265. fetchDoList();
  266. };
  267. export const fetchDoList = cache(async () => {
  268. return serverFetchJson<DoResult[]>(`${BASE_API_URL}/do/list`, {
  269. next: { tags: ["doList"] },
  270. });
  271. });
  272. export const fetchDoDetail = cache(async (id: number) => {
  273. return serverFetchJson<DoDetail>(`${BASE_API_URL}/do/detail/${id}`, {
  274. method: "GET",
  275. headers: { "Content-Type": "application/json" },
  276. next: {
  277. tags: ["doDetail"]
  278. }
  279. });
  280. });
  281. /** 車線搜尋為「車線-X」時改走後端專用 API(只含推算車線為 null/空白之送貨單) */
  282. function isTruckLaneXSearch(truckLanceCode?: string): boolean {
  283. const t = truckLanceCode?.trim().toLowerCase() ?? "";
  284. return t === "車線-x";
  285. }
  286. export async function fetchDoSearch(
  287. code: string,
  288. shopName: string,
  289. status: string,
  290. orderStartDate: string,
  291. orderEndDate: string,
  292. estArrStartDate: string,
  293. estArrEndDate: string,
  294. pageNum?: number,
  295. pageSize?: number,
  296. truckLanceCode?: string
  297. ): Promise<DoSearchLiteResponse> {
  298. // 构建请求体
  299. const requestBody: any = {
  300. code: code || null,
  301. shopName: shopName || null,
  302. status: status || null,
  303. estimatedArrivalDate: estArrStartDate || null, // 使用单个日期字段
  304. truckLanceCode: truckLanceCode || null,
  305. pageNum: pageNum || 1,
  306. pageSize: pageSize || 10,
  307. };
  308. // 如果日期不为空,转换为 LocalDateTime 格式
  309. if (estArrStartDate) {
  310. requestBody.estimatedArrivalDate = estArrStartDate; // 格式: "2026-01-19T00:00:00"
  311. } else {
  312. requestBody.estimatedArrivalDate = null;
  313. }
  314. const useUnassignedTruck = isTruckLaneXSearch(truckLanceCode);
  315. if (useUnassignedTruck) {
  316. delete requestBody.truckLanceCode;
  317. }
  318. const url = useUnassignedTruck
  319. ? `${BASE_API_URL}/do/search-do-lite-unassigned-truck`
  320. : `${BASE_API_URL}/do/search-do-lite`;
  321. const data = await serverFetchJson<DoSearchLiteResponse>(url, {
  322. method: "POST",
  323. headers: { "Content-Type": "application/json" },
  324. body: JSON.stringify(requestBody),
  325. });
  326. return data;
  327. }
  328. export async function fetchDoSearchList(
  329. code: string,
  330. shopName: string,
  331. status: string,
  332. orderStartDate: string,
  333. orderEndDate: string,
  334. etaFrom: string,
  335. etaTo: string,
  336. page = 0,
  337. size = 500
  338. ): Promise<DoSearchAll[]> {
  339. const params = new URLSearchParams();
  340. if (code) params.append("code", code);
  341. if (shopName) params.append("shopName", shopName);
  342. if (status) params.append("status", status);
  343. if (orderStartDate) params.append("orderFrom", orderStartDate);
  344. if (orderEndDate) params.append("orderTo", orderEndDate);
  345. if (etaFrom) params.append("etaFrom", etaFrom);
  346. if (etaTo) params.append("etaTo", etaTo);
  347. params.append("page", String(page));
  348. params.append("size", String(size));
  349. const res = await fetch(`/api/delivery-order/search-do-list?${params.toString()}`);
  350. const pageData = await res.json(); // Spring Page 结构
  351. return pageData.content; // 前端继续沿用你原来的 client-side 分页逻辑
  352. }
  353. export async function printDN(request: PrintDeliveryNoteRequest){
  354. const params = new URLSearchParams();
  355. params.append('doPickOrderId', request.doPickOrderId.toString());
  356. params.append('printerId', request.printerId.toString());
  357. if (request.printQty !== null && request.printQty !== undefined) {
  358. params.append('printQty', request.printQty.toString());
  359. }
  360. params.append('numOfCarton', request.numOfCarton.toString());
  361. params.append('isDraft', request.isDraft.toString());
  362. try {
  363. const response = await serverFetch(`${BASE_API_URL}/do/print-DN?${params.toString()}`, {
  364. method: "GET",
  365. });
  366. if (response.ok) {
  367. return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse;
  368. }
  369. const errorText = await response.text();
  370. console.error("Print DN error:", errorText);
  371. return {
  372. success: false,
  373. message: "No data found for this pick order."
  374. } as PrintDeliveryNoteResponse;
  375. } catch (error) {
  376. console.error("Error in printDN:", error);
  377. return {
  378. success: false,
  379. message: "No data found for this pick order."
  380. } as PrintDeliveryNoteResponse;
  381. }
  382. }
  383. export async function printDNLabels(request: PrintDNLabelsRequest){
  384. const params = new URLSearchParams();
  385. params.append('doPickOrderId', request.doPickOrderId.toString());
  386. params.append('printerId', request.printerId.toString());
  387. if (request.printQty !== null && request.printQty !== undefined) {
  388. params.append('printQty', request.printQty.toString());
  389. }
  390. params.append('numOfCarton', request.numOfCarton.toString());
  391. const response = await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DNLabels?${params.toString()}`,{
  392. method: "GET"
  393. });
  394. return { success: true, message: "Print job sent successfully (labels)"} as PrintDeliveryNoteResponse
  395. }
  396. export async function printDNLabelsReprint(request: PrintDNLabelsReprintRequest){
  397. const params = new URLSearchParams();
  398. params.append('doPickOrderId', request.doPickOrderId.toString());
  399. params.append('printerId', request.printerId.toString());
  400. if (request.printQty !== null && request.printQty !== undefined) {
  401. params.append('printQty', request.printQty.toString());
  402. }
  403. params.append('fromCarton', request.fromCarton.toString());
  404. params.append('toCarton', request.toCarton.toString());
  405. params.append('totalCartonsOnShipment', request.totalCartonsOnShipment.toString());
  406. await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DNLabels-reprint?${params.toString()}`,{
  407. method: "GET"
  408. });
  409. return { success: true, message: "Print job sent successfully (reprint labels)"} as PrintDeliveryNoteResponse
  410. }
  411. /*
  412. export interface PrintWorkbenchDeliveryNoteRequest{
  413. deliveryOrderPickOrderId: number;
  414. printerId: number;
  415. printQty: number;
  416. numOfCarton: number;
  417. isDraft: boolean;
  418. }
  419. export interface PrintWorkbenchDNLabelsRequest{
  420. deliveryOrderPickOrderId: number;
  421. printerId: number;
  422. printQty: number;
  423. numOfCarton: number;
  424. }
  425. export async function printDNWorkbench(request: PrintWorkbenchDeliveryNoteRequest){
  426. const params = new URLSearchParams();
  427. params.append("doPickOrderId", request.deliveryOrderPickOrderId.toString());
  428. params.append("printerId", request.printerId.toString());
  429. if (request.printQty !== null && request.printQty !== undefined) {
  430. params.append("printQty", request.printQty.toString());
  431. }
  432. params.append("numOfCarton", request.numOfCarton.toString());
  433. params.append("isDraft", request.isDraft.toString());
  434. try {
  435. const response = await serverFetch(`${BASE_API_URL}/do/workbench/print-DN?${params.toString()}`, {
  436. method: "GET",
  437. });
  438. if (response.ok) {
  439. return { success: true, message: "Print job sent successfully (workbench DN)" } as PrintDeliveryNoteResponse;
  440. }
  441. const errorText = await response.text();
  442. console.error("Workbench print DN error:", errorText);
  443. return {
  444. success: false,
  445. message: "No workbench data found for this ticket.",
  446. } as PrintDeliveryNoteResponse;
  447. } catch (error) {
  448. console.error("Error in printDNWorkbench:", error);
  449. return {
  450. success: false,
  451. message: "No workbench data found for this ticket.",
  452. } as PrintDeliveryNoteResponse;
  453. }
  454. }
  455. export async function printDNLabelsWorkbench(request: PrintWorkbenchDNLabelsRequest){
  456. const params = new URLSearchParams();
  457. params.append("doPickOrderId", request.deliveryOrderPickOrderId.toString());
  458. params.append("printerId", request.printerId.toString());
  459. if (request.printQty !== null && request.printQty !== undefined) {
  460. params.append("printQty", request.printQty.toString());
  461. }
  462. params.append("numOfCarton", request.numOfCarton.toString());
  463. await serverFetchWithNoContent(`${BASE_API_URL}/do/workbench/print-DNLabels?${params.toString()}`,{
  464. method: "GET"
  465. });
  466. return { success: true, message: "Print job sent successfully (workbench labels)"} as PrintDeliveryNoteResponse
  467. }
  468. */
  469. export interface Check4FTruckBatchResponse {
  470. hasProblem: boolean;
  471. problems: ProblemDoDto[];
  472. }
  473. export interface ProblemDoDto {
  474. deliveryOrderId: number;
  475. deliveryOrderCode: string;
  476. targetDate: string;
  477. availableTrucks: TruckInfoDto[];
  478. }
  479. export interface TruckInfoDto {
  480. id: number;
  481. truckLanceCode: string;
  482. departureTime: string;
  483. storeId: string;
  484. shopCode: string;
  485. shopName: string;
  486. }
  487. export const check4FTrucksBatch = cache(async (doIds: number[]) => {
  488. return await serverFetchJson<Check4FTruckBatchResponse>(`${BASE_API_URL}/do/check-4f-trucks-batch`, {
  489. method: "POST",
  490. body: JSON.stringify(doIds),
  491. headers: { "Content-Type": "application/json" },
  492. });
  493. });
  494. export async function fetchAllDoSearch(
  495. code: string,
  496. shopName: string,
  497. status: string,
  498. estArrStartDate: string,
  499. truckLanceCode?: string // 添加这个参数
  500. ): Promise<DoSearchAll[]> {
  501. // 使用一个很大的 pageSize 来获取所有匹配的记录
  502. const requestBody: any = {
  503. code: code || null,
  504. shopName: shopName || null,
  505. status: status || null,
  506. estimatedArrivalDate: estArrStartDate || null,
  507. truckLanceCode: truckLanceCode || null, // 添加这个字段
  508. pageNum: 1,
  509. pageSize: 10000, // 使用一个很大的值来获取所有记录
  510. };
  511. if (estArrStartDate) {
  512. requestBody.estimatedArrivalDate = estArrStartDate;
  513. } else {
  514. requestBody.estimatedArrivalDate = null;
  515. }
  516. const useUnassignedTruck = isTruckLaneXSearch(truckLanceCode);
  517. if (useUnassignedTruck) {
  518. delete requestBody.truckLanceCode;
  519. }
  520. const url = useUnassignedTruck
  521. ? `${BASE_API_URL}/do/search-do-lite-unassigned-truck`
  522. : `${BASE_API_URL}/do/search-do-lite`;
  523. const data = await serverFetchJson<DoSearchLiteResponse>(url, {
  524. method: "POST",
  525. headers: { "Content-Type": "application/json" },
  526. body: JSON.stringify(requestBody),
  527. });
  528. return data.records;
  529. }