FPSMS-frontend
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

667 lines
20 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. /** 加單 DO */
  29. isEtra?: boolean;
  30. deliveryOrderLines: DoDetailLine[];
  31. }
  32. export interface DoDetailLine {
  33. id: number;
  34. itemNo: string;
  35. qty: number;
  36. price: number;
  37. status: string;
  38. itemName?: string;
  39. uomCode?: string;
  40. uom?: string;
  41. shortUom?: string;
  42. }
  43. export interface DoSearchAll {
  44. id: number;
  45. code: string;
  46. status: string;
  47. estimatedArrivalDate: number[];
  48. orderDate: number[];
  49. supplierName: string;
  50. shopName: string;
  51. shopAddress?: string;
  52. isEtra?: boolean;
  53. }
  54. export interface DoSearchLiteResponse {
  55. records: DoSearchAll[];
  56. total: number;
  57. }
  58. export interface ReleaseDoRequest {
  59. id: number;
  60. }
  61. export interface ReleaseDoResponse {
  62. id: number;
  63. entity: { status: string }
  64. }
  65. export interface AssignByStoreRequest {
  66. storeId: string; // "2/F" or "4/F"
  67. assignTo: number;
  68. }
  69. export interface AssignByStoreResponse {
  70. id: number;
  71. code: string;
  72. name: string;
  73. type: string;
  74. message: string;
  75. errorPosition: string;
  76. entity: any;
  77. }
  78. export interface PrintDeliveryNoteRequest{
  79. doPickOrderId: number;
  80. printerId: number;
  81. printQty: number;
  82. numOfCarton: number;
  83. isDraft: boolean;
  84. }
  85. export interface PrintDeliveryNoteResponse{
  86. success: boolean;
  87. message?: string
  88. }
  89. export interface PrintDNLabelsRequest{
  90. doPickOrderId: number;
  91. printerId: number;
  92. printQty: number;
  93. numOfCarton: number;
  94. }
  95. export interface PrintDNLabelsReprintRequest{
  96. doPickOrderId: number;
  97. printerId: number;
  98. printQty: number;
  99. fromCarton: number;
  100. toCarton: number;
  101. totalCartonsOnShipment: number;
  102. }
  103. export interface PrintDNLabelsRespone{
  104. success: boolean;
  105. message?: string
  106. }
  107. export interface BatchReleaseRequest {
  108. ids: number[];
  109. }
  110. export interface BatchReleaseResponse {
  111. success: boolean;
  112. message?: string
  113. }
  114. export interface getTicketReleaseTable {
  115. id: number;
  116. storeId: string | null;
  117. ticketNo: string | null;
  118. pickOrderId: number | null;
  119. doOrderId: number | null;
  120. pickOrderCode: string | null;
  121. deliveryOrderCode: string | null;
  122. loadingSequence: number | null;
  123. ticketStatus: string | null;
  124. truckId: number | null;
  125. truckDepartureTime: string | null;
  126. shopId: number | null;
  127. handledBy: number | null;
  128. ticketReleaseTime: string | null;
  129. ticketCompleteDateTime: string | null;
  130. truckLanceCode: string | null;
  131. shopCode: string | null;
  132. shopName: string | null;
  133. requiredDeliveryDate: string | null;
  134. handlerName: string | null;
  135. numberOfFGItems: number;
  136. /** 進行中 do_pick_order 為 true,才可呼叫 force-complete / revert-assignment(id 為 do_pick_order 主鍵) */
  137. isActiveDoPickOrder?: boolean;
  138. }
  139. export interface WorkbenchTicketReleaseTable {
  140. deliveryOrderPickOrderId: number;
  141. storeId: string | null;
  142. ticketNo: string | null;
  143. loadingSequence: number | null;
  144. ticketStatus: string | null;
  145. truckDepartureTime: string | null;
  146. handledBy: number | null;
  147. ticketReleaseTime: string | null;
  148. ticketCompleteDateTime: string | null;
  149. truckLanceCode: string | null;
  150. shopCode: string | null;
  151. shopName: string | null;
  152. requiredDeliveryDate: string | null;
  153. handlerName: string | null;
  154. numberOfFGItems: number;
  155. isActiveWorkbenchTicket?: boolean;
  156. }
  157. export interface WorkbenchTicketOpResponse {
  158. id: number | null;
  159. name: string | null;
  160. code: string;
  161. type: string | null;
  162. message: string | null;
  163. errorPosition: string | null;
  164. entity?: any;
  165. }
  166. export interface TruckScheduleDashboardItem {
  167. storeId: string | null;
  168. truckId: number | null;
  169. truckLanceCode: string | null;
  170. truckDepartureTime: string | number[] | null;
  171. numberOfShopsToServe: number;
  172. numberOfPickTickets: number;
  173. totalItemsToPick: number;
  174. numberOfTicketsReleased: number;
  175. firstTicketStartTime: string | number[] | null;
  176. numberOfTicketsCompleted: number;
  177. lastTicketEndTime: string | number[] | null;
  178. pickTimeTakenMinutes: number | null;
  179. }
  180. export interface SearchDeliveryOrderInfoRequest {
  181. code: string;
  182. shopName: string;
  183. status: string;
  184. orderStartDate: string;
  185. orderEndDate: string;
  186. estArrStartDate: string;
  187. estArrEndDate: string;
  188. pageSize: number;
  189. pageNum: number;
  190. }
  191. export interface SearchDeliveryOrderInfoResponse {
  192. records: DeliveryOrderInfo[];
  193. total: number;
  194. }
  195. export interface DeliveryOrderInfo {
  196. id: number;
  197. code: string;
  198. shopName: string;
  199. supplierName: string; // 改为必需字段
  200. status: string;
  201. orderDate: string;
  202. estimatedArrivalDate: string;
  203. deliveryOrderLines: DoDetailLine[];
  204. }
  205. export const fetchDoRecordByPage = cache(async (data?: SearchDeliveryOrderInfoRequest) => {
  206. const queryStr = convertObjToURLSearchParams(data)
  207. console.log("queryStr", queryStr)
  208. const response = serverFetchJson<SearchDeliveryOrderInfoResponse>(
  209. `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
  210. {
  211. method: "GET",
  212. headers: { "Content-Type": "application/json" },
  213. next: {
  214. tags: ["jos"]
  215. }
  216. }
  217. )
  218. return response
  219. })
  220. export const fetchTicketReleaseTable = cache(async (startDate: string, endDate: string)=> {
  221. return await serverFetchJson<getTicketReleaseTable[]>(
  222. `${BASE_API_URL}/doPickOrder/ticket-release-table/${startDate}&${endDate}`,
  223. {
  224. method: "GET",
  225. }
  226. );
  227. });
  228. export const fetchWorkbenchTicketReleaseTable = cache(async (startDate: string, endDate: string)=> {
  229. return await serverFetchJson<WorkbenchTicketReleaseTable[]>(
  230. `${BASE_API_URL}/doPickOrder/workbench/ticket-release-table/${startDate}&${endDate}`,
  231. {
  232. method: "GET",
  233. }
  234. );
  235. });
  236. export async function forceCompleteWorkbenchTicket(
  237. deliveryOrderPickOrderId: number,
  238. ): Promise<WorkbenchTicketOpResponse> {
  239. return await serverFetchJson<WorkbenchTicketOpResponse>(
  240. `${BASE_API_URL}/doPickOrder/workbench/force-complete/${deliveryOrderPickOrderId}`,
  241. {
  242. method: "POST",
  243. headers: { "Content-Type": "application/json" },
  244. },
  245. );
  246. }
  247. export async function revertWorkbenchTicketAssignment(
  248. deliveryOrderPickOrderId: number,
  249. ): Promise<WorkbenchTicketOpResponse> {
  250. return await serverFetchJson<WorkbenchTicketOpResponse>(
  251. `${BASE_API_URL}/doPickOrder/workbench/revert-assignment/${deliveryOrderPickOrderId}`,
  252. {
  253. method: "POST",
  254. headers: { "Content-Type": "application/json" },
  255. },
  256. );
  257. }
  258. export const fetchTruckScheduleDashboard = cache(async (date?: string) => {
  259. const url = date
  260. ? `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard?date=${date}`
  261. : `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard`;
  262. return await serverFetchJson<TruckScheduleDashboardItem[]>(
  263. url,
  264. {
  265. method: "GET",
  266. }
  267. );
  268. });
  269. export const startBatchReleaseAsyncSingle = cache(async (data: { doId: number; userId: number }) => {
  270. const { doId, userId } = data;
  271. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  272. `${BASE_API_URL}/doPickOrder/batch-release/async-single?userId=${userId}`,
  273. {
  274. method: "POST",
  275. body: JSON.stringify(doId),
  276. headers: { "Content-Type": "application/json" },
  277. }
  278. );
  279. });
  280. export const startBatchReleaseAsync = cache(async (data: { ids: number[]; userId: number }) => {
  281. const { ids, userId } = data;
  282. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  283. `${BASE_API_URL}/doPickOrder/batch-release/async?userId=${userId}`,
  284. {
  285. method: "POST",
  286. body: JSON.stringify(ids),
  287. headers: { "Content-Type": "application/json" },
  288. }
  289. );
  290. });
  291. export const getBatchReleaseProgress = cache(async (jobId: string) => {
  292. return await serverFetchJson<{ id: number|null; code: string; entity?: any }>(
  293. `${BASE_API_URL}/doPickOrder/batch-release/progress/${jobId}`,
  294. { method: "GET" }
  295. );
  296. });
  297. export const assignPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  298. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/assign-by-store`,
  299. {
  300. method: "POST",
  301. body: JSON.stringify(data),
  302. headers: { "Content-Type": "application/json" },
  303. })
  304. })
  305. export const releaseAssignedPickOrderByStore = cache(async (data: AssignByStoreRequest) => {
  306. return await serverFetchJson<AssignByStoreResponse>(`${BASE_API_URL}/doPickOrder/release-assigned-by-store`,
  307. {
  308. method: "POST",
  309. body: JSON.stringify(data),
  310. headers: { "Content-Type": "application/json" },
  311. })
  312. })
  313. export async function releaseDo(input: ReleaseDoRequest) {
  314. const response = await serverFetchJson<ReleaseDoResponse>(`${BASE_API_URL}/do/release`, {
  315. method: 'POST',
  316. body: JSON.stringify(input),
  317. headers: {
  318. 'Content-Type': 'application/json',
  319. },
  320. });
  321. revalidateTag('do');
  322. return response;
  323. }
  324. export const preloadDo = () => {
  325. fetchDoList();
  326. };
  327. export const fetchDoList = cache(async () => {
  328. return serverFetchJson<DoResult[]>(`${BASE_API_URL}/do/list`, {
  329. next: { tags: ["doList"] },
  330. });
  331. });
  332. export const fetchDoDetail = cache(async (id: number) => {
  333. return serverFetchJson<DoDetail>(`${BASE_API_URL}/do/detail/${id}`, {
  334. method: "GET",
  335. headers: { "Content-Type": "application/json" },
  336. next: {
  337. tags: ["doDetail"]
  338. }
  339. });
  340. });
  341. export async function fetchDoSearch(
  342. code: string,
  343. shopName: string,
  344. status: string,
  345. orderStartDate: string,
  346. orderEndDate: string,
  347. estArrStartDate: string,
  348. estArrEndDate: string,
  349. pageNum?: number,
  350. pageSize?: number,
  351. truckLanceCode?: string,
  352. /** 後端:All/null 為全部;2F/4F 依供應商白名單篩選 */
  353. floor?: string | null,
  354. /** null:不篩;true/false:只顯示加單或非加單 DO */
  355. isEtra?: boolean | null,
  356. ): Promise<DoSearchLiteResponse> {
  357. // 构建请求体
  358. const requestBody: any = {
  359. code: code || null,
  360. shopName: shopName || null,
  361. status: status || null,
  362. estimatedArrivalDate: estArrStartDate || null, // 使用单个日期字段
  363. truckLanceCode: truckLanceCode || null,
  364. pageNum: pageNum || 1,
  365. pageSize: pageSize || 10,
  366. floor: floor && floor !== "All" ? floor : null,
  367. ...(isEtra !== undefined && isEtra !== null ? { isEtra } : {}),
  368. };
  369. // 如果日期不为空,转换为 LocalDateTime 格式
  370. if (estArrStartDate) {
  371. requestBody.estimatedArrivalDate = estArrStartDate; // 格式: "2026-01-19T00:00:00"
  372. } else {
  373. requestBody.estimatedArrivalDate = null;
  374. }
  375. /** v2:車線正規化、未指派合併、分批掃描(後端 /do/search-do-lite-v2) */
  376. const data = await serverFetchJson<DoSearchLiteResponse>(
  377. `${BASE_API_URL}/do/search-do-lite-v2`,
  378. {
  379. method: "POST",
  380. headers: { "Content-Type": "application/json" },
  381. body: JSON.stringify(requestBody),
  382. },
  383. );
  384. return data;
  385. }
  386. export async function fetchDoSearchList(
  387. code: string,
  388. shopName: string,
  389. status: string,
  390. orderStartDate: string,
  391. orderEndDate: string,
  392. etaFrom: string,
  393. etaTo: string,
  394. page = 0,
  395. size = 500
  396. ): Promise<DoSearchAll[]> {
  397. const params = new URLSearchParams();
  398. if (code) params.append("code", code);
  399. if (shopName) params.append("shopName", shopName);
  400. if (status) params.append("status", status);
  401. if (orderStartDate) params.append("orderFrom", orderStartDate);
  402. if (orderEndDate) params.append("orderTo", orderEndDate);
  403. if (etaFrom) params.append("etaFrom", etaFrom);
  404. if (etaTo) params.append("etaTo", etaTo);
  405. params.append("page", String(page));
  406. params.append("size", String(size));
  407. const res = await fetch(`/api/delivery-order/search-do-list?${params.toString()}`);
  408. const pageData = await res.json(); // Spring Page 结构
  409. return pageData.content; // 前端继续沿用你原来的 client-side 分页逻辑
  410. }
  411. export async function printDN(request: PrintDeliveryNoteRequest){
  412. const params = new URLSearchParams();
  413. params.append('doPickOrderId', request.doPickOrderId.toString());
  414. params.append('printerId', request.printerId.toString());
  415. if (request.printQty !== null && request.printQty !== undefined) {
  416. params.append('printQty', request.printQty.toString());
  417. }
  418. params.append('numOfCarton', request.numOfCarton.toString());
  419. params.append('isDraft', request.isDraft.toString());
  420. try {
  421. const response = await serverFetch(`${BASE_API_URL}/do/print-DN?${params.toString()}`, {
  422. method: "GET",
  423. });
  424. if (response.ok) {
  425. return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse;
  426. }
  427. const errorText = await response.text();
  428. console.error("Print DN error:", errorText);
  429. return {
  430. success: false,
  431. message: "No data found for this pick order."
  432. } as PrintDeliveryNoteResponse;
  433. } catch (error) {
  434. console.error("Error in printDN:", error);
  435. return {
  436. success: false,
  437. message: "No data found for this pick order."
  438. } as PrintDeliveryNoteResponse;
  439. }
  440. }
  441. export async function printDNLabels(request: PrintDNLabelsRequest){
  442. const params = new URLSearchParams();
  443. params.append('doPickOrderId', request.doPickOrderId.toString());
  444. params.append('printerId', request.printerId.toString());
  445. if (request.printQty !== null && request.printQty !== undefined) {
  446. params.append('printQty', request.printQty.toString());
  447. }
  448. params.append('numOfCarton', request.numOfCarton.toString());
  449. const response = await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DNLabels?${params.toString()}`,{
  450. method: "GET"
  451. });
  452. return { success: true, message: "Print job sent successfully (labels)"} as PrintDeliveryNoteResponse
  453. }
  454. export async function printDNLabelsReprint(request: PrintDNLabelsReprintRequest){
  455. const params = new URLSearchParams();
  456. params.append('doPickOrderId', request.doPickOrderId.toString());
  457. params.append('printerId', request.printerId.toString());
  458. if (request.printQty !== null && request.printQty !== undefined) {
  459. params.append('printQty', request.printQty.toString());
  460. }
  461. params.append('fromCarton', request.fromCarton.toString());
  462. params.append('toCarton', request.toCarton.toString());
  463. params.append('totalCartonsOnShipment', request.totalCartonsOnShipment.toString());
  464. await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DNLabels-reprint?${params.toString()}`,{
  465. method: "GET"
  466. });
  467. return { success: true, message: "Print job sent successfully (reprint labels)"} as PrintDeliveryNoteResponse
  468. }
  469. export interface PrintWorkbenchDeliveryNoteRequest{
  470. deliveryOrderPickOrderId: number;
  471. printerId: number;
  472. printQty: number;
  473. numOfCarton: number;
  474. isDraft: boolean;
  475. }
  476. export interface PrintWorkbenchDNLabelsRequest{
  477. deliveryOrderPickOrderId: number;
  478. printerId: number;
  479. printQty: number;
  480. numOfCarton: number;
  481. }
  482. export interface PrintWorkbenchDNLabelsReprintRequest{
  483. deliveryOrderPickOrderId: number;
  484. printerId: number;
  485. printQty: number;
  486. fromCarton: number;
  487. toCarton: number;
  488. totalCartonsOnShipment: number;
  489. }
  490. export async function printDNWorkbench(request: PrintWorkbenchDeliveryNoteRequest){
  491. const params = new URLSearchParams();
  492. params.append("doPickOrderId", request.deliveryOrderPickOrderId.toString());
  493. params.append("printerId", request.printerId.toString());
  494. if (request.printQty !== null && request.printQty !== undefined) {
  495. params.append("printQty", request.printQty.toString());
  496. }
  497. params.append("numOfCarton", request.numOfCarton.toString());
  498. params.append("isDraft", request.isDraft.toString());
  499. try {
  500. const response = await serverFetch(`${BASE_API_URL}/doPickOrder/workbench/print-DN?${params.toString()}`, {
  501. method: "GET",
  502. });
  503. if (response.ok) {
  504. return { success: true, message: "Print job sent successfully (workbench DN)" } as PrintDeliveryNoteResponse;
  505. }
  506. const errorText = await response.text();
  507. console.error("Workbench print DN error:", errorText);
  508. return {
  509. success: false,
  510. message: "No workbench data found for this ticket.",
  511. } as PrintDeliveryNoteResponse;
  512. } catch (error) {
  513. console.error("Error in printDNWorkbench:", error);
  514. return {
  515. success: false,
  516. message: "No workbench data found for this ticket.",
  517. } as PrintDeliveryNoteResponse;
  518. }
  519. }
  520. export async function printDNLabelsWorkbench(request: PrintWorkbenchDNLabelsRequest){
  521. const params = new URLSearchParams();
  522. params.append("doPickOrderId", request.deliveryOrderPickOrderId.toString());
  523. params.append("printerId", request.printerId.toString());
  524. if (request.printQty !== null && request.printQty !== undefined) {
  525. params.append("printQty", request.printQty.toString());
  526. }
  527. params.append("numOfCarton", request.numOfCarton.toString());
  528. await serverFetchWithNoContent(`${BASE_API_URL}/doPickOrder/workbench/print-DNLabels?${params.toString()}`,{
  529. method: "GET"
  530. });
  531. return { success: true, message: "Print job sent successfully (workbench labels)"} as PrintDeliveryNoteResponse
  532. }
  533. export async function printDNLabelsReprintWorkbench(request: PrintWorkbenchDNLabelsReprintRequest){
  534. const params = new URLSearchParams();
  535. params.append("doPickOrderId", request.deliveryOrderPickOrderId.toString());
  536. params.append("printerId", request.printerId.toString());
  537. if (request.printQty !== null && request.printQty !== undefined) {
  538. params.append("printQty", request.printQty.toString());
  539. }
  540. params.append("fromCarton", request.fromCarton.toString());
  541. params.append("toCarton", request.toCarton.toString());
  542. params.append("totalCartonsOnShipment", request.totalCartonsOnShipment.toString());
  543. await serverFetchWithNoContent(`${BASE_API_URL}/doPickOrder/workbench/print-DNLabels-reprint?${params.toString()}`,{
  544. method: "GET"
  545. });
  546. return { success: true, message: "Print job sent successfully (workbench reprint labels)"} as PrintDeliveryNoteResponse
  547. }
  548. export interface Check4FTruckBatchResponse {
  549. hasProblem: boolean;
  550. problems: ProblemDoDto[];
  551. }
  552. export interface ProblemDoDto {
  553. deliveryOrderId: number;
  554. deliveryOrderCode: string;
  555. targetDate: string;
  556. availableTrucks: TruckInfoDto[];
  557. }
  558. export interface TruckInfoDto {
  559. id: number;
  560. truckLanceCode: string;
  561. departureTime: string;
  562. storeId: string;
  563. shopCode: string;
  564. shopName: string;
  565. }
  566. export const check4FTrucksBatch = cache(async (doIds: number[]) => {
  567. return await serverFetchJson<Check4FTruckBatchResponse>(`${BASE_API_URL}/do/check-4f-trucks-batch`, {
  568. method: "POST",
  569. body: JSON.stringify(doIds),
  570. headers: { "Content-Type": "application/json" },
  571. });
  572. });
  573. export async function fetchAllDoSearch(
  574. code: string,
  575. shopName: string,
  576. status: string,
  577. estArrStartDate: string,
  578. truckLanceCode?: string,
  579. floor?: string | null,
  580. isEtra?: boolean | null,
  581. ): Promise<DoSearchAll[]> {
  582. // 使用一个很大的 pageSize 来获取所有匹配的记录
  583. const requestBody: any = {
  584. code: code || null,
  585. shopName: shopName || null,
  586. status: status || null,
  587. estimatedArrivalDate: estArrStartDate || null,
  588. truckLanceCode: truckLanceCode || null,
  589. pageNum: 1,
  590. pageSize: 10000, // 使用一个很大的值来获取所有记录
  591. floor: floor && floor !== "All" ? floor : null,
  592. ...(isEtra !== undefined && isEtra !== null ? { isEtra } : {}),
  593. };
  594. if (estArrStartDate) {
  595. requestBody.estimatedArrivalDate = estArrStartDate;
  596. } else {
  597. requestBody.estimatedArrivalDate = null;
  598. }
  599. const data = await serverFetchJson<DoSearchLiteResponse>(
  600. `${BASE_API_URL}/do/search-do-lite-v2`,
  601. {
  602. method: "POST",
  603. headers: { "Content-Type": "application/json" },
  604. body: JSON.stringify(requestBody),
  605. },
  606. );
  607. return data.records;
  608. }