FPSMS-frontend
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 

1759 rader
49 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 { serverFetchJson } from "@/app/utils/fetchUtil";
  7. import { QcItemResult } from "../settings/qcItem";
  8. import { RecordsRes } from "../utils";
  9. import {
  10. ConsoPickOrderResult,
  11. PickOrderLineWithSuggestedLot,
  12. PickOrderResult,
  13. PreReleasePickOrderSummary,
  14. StockOutLine,
  15. } from ".";
  16. import { PurchaseQcResult } from "../po/actions";
  17. import { StringNullableChain } from "lodash";
  18. // import { BASE_API_URL } from "@/config/api";
  19. import dayjs from "dayjs";
  20. export interface SavePickOrderLineRequest {
  21. itemId: number
  22. qty: number
  23. uomId: number
  24. }
  25. export interface SavePickOrderRequest {
  26. type: string
  27. targetDate: string
  28. pickOrderLine: SavePickOrderLineRequest[]
  29. }
  30. export interface PostPickOrderResponse<T = null> {
  31. id: number | null;
  32. name: string;
  33. code: string;
  34. type?: string;
  35. message: string | null;
  36. errorPosition: string
  37. entity?: T | T[];
  38. consoCode?: string;
  39. }
  40. export interface PostStockOutLiineResponse<T> {
  41. id: number | null;
  42. name: string;
  43. code: string;
  44. type?: string;
  45. message: string | null;
  46. errorPosition: string | keyof T;
  47. entity: T | T[] | null;
  48. }
  49. export interface ReleasePickOrderInputs {
  50. consoCode: string;
  51. assignTo: number;
  52. }
  53. export interface CreateStockOutLine {
  54. consoCode: string;
  55. pickOrderLineId: number;
  56. inventoryLotLineId: number;
  57. qty: number;
  58. }
  59. export interface UpdateStockOutLine {
  60. id: number;
  61. // consoCode: String,
  62. itemId: number;
  63. qty: number;
  64. pickOrderLineId: number;
  65. inventoryLotLineId?: number;
  66. status: string;
  67. pickTime?: string;
  68. // pickerId: number?
  69. }
  70. export interface PickOrderQcInput {
  71. qty: number;
  72. status: string;
  73. qcResult: PurchaseQcResult[];
  74. }
  75. export interface PickOrderApprovalInput {
  76. allowQty: number;
  77. rejectQty: number;
  78. status: string;
  79. }
  80. export interface GetPickOrderInfoResponse {
  81. consoCode: string | null;
  82. pickOrders: GetPickOrderInfo[];
  83. items: CurrentInventoryItemInfo[];
  84. }
  85. export interface GetPickOrderInfo {
  86. id: number;
  87. code: string;
  88. consoCode: string | null; // 添加 consoCode 属性
  89. targetDate: string | number[]; // Support both formats
  90. type: string;
  91. status: string;
  92. assignTo: number;
  93. groupName: string; // Add this field
  94. pickOrderLines: GetPickOrderLineInfo[];
  95. }
  96. export interface GetPickOrderLineInfo {
  97. id: number;
  98. itemId: number;
  99. itemCode: string;
  100. itemName: string;
  101. availableQty: number| null;
  102. requiredQty: number;
  103. uomShortDesc: string;
  104. uomDesc: string;
  105. suggestedList: any[];
  106. pickedQty: number;
  107. noLotLines: NoLotLineDto[];
  108. }
  109. export interface NoLotLineDto {
  110. stockOutLineId: number;
  111. status: string;
  112. qty: number;
  113. created: string;
  114. modified: string;
  115. }
  116. export interface CurrentInventoryItemInfo {
  117. id: number;
  118. code: string;
  119. name: string;
  120. uomDesc: string;
  121. availableQty: number;
  122. requiredQty: number;
  123. }
  124. export interface SavePickOrderGroupRequest {
  125. groupIds?: number[];
  126. names?: string[];
  127. targetDate?: string;
  128. pickOrderId?: number | null;
  129. }
  130. export interface PickOrderGroupInfo {
  131. id: number;
  132. name: string;
  133. targetDate: string | null;
  134. pickOrderId: number | null;
  135. }
  136. export interface AssignPickOrderInputs {
  137. pickOrderIds: number[];
  138. assignTo: number;
  139. }
  140. export interface LotDetailWithStockOutLine {
  141. lotId: number;
  142. lotNo: string;
  143. expiryDate: string;
  144. location: string;
  145. stockUnit: string;
  146. availableQty: number;
  147. requiredQty: number;
  148. actualPickQty: number;
  149. suggestedPickLotId: number;
  150. lotStatus: string;
  151. lotAvailability: string;
  152. stockOutLineId?: number;
  153. stockOutLineStatus?: string;
  154. stockOutLineQty?: number;
  155. }
  156. export interface PickAnotherLotFormData {
  157. pickOrderLineId: number;
  158. lotId: number;
  159. qty: number;
  160. type: string;
  161. handlerId?: number;
  162. category?: string;
  163. releasedBy?: number;
  164. recordDate?: string;
  165. }
  166. export const recordFailLot = async (data: PickAnotherLotFormData) => {
  167. const result = await serverFetchJson<PostPickOrderResponse>(
  168. `${BASE_API_URL}/suggestedPickLot/recordFailLot`,
  169. {
  170. method: "POST",
  171. body: JSON.stringify(data),
  172. headers: { "Content-Type": "application/json" },
  173. },
  174. );
  175. revalidateTag("pickorder");
  176. return result;
  177. };
  178. export interface PickExecutionIssueData {
  179. type: string;
  180. pickOrderId: number;
  181. pickOrderCode: string;
  182. pickOrderCreateDate: string;
  183. pickExecutionDate: string;
  184. pickOrderLineId: number;
  185. itemId: number;
  186. itemCode: string;
  187. itemDescription: string;
  188. lotId: number|null;
  189. lotNo: string|null;
  190. storeLocation: string;
  191. requiredQty: number;
  192. actualPickQty: number;
  193. missQty: number;
  194. badItemQty: number;
  195. badPackageQty?: number;
  196. /** Optional: frontend-only reference to stock_out_line.id for the picked lot. */
  197. stockOutLineId?: number;
  198. issueRemark: string;
  199. pickerName: string;
  200. handledBy?: number;
  201. badReason?: string;
  202. reason?: string;
  203. }
  204. export type AutoAssignReleaseResponse = {
  205. id: number | null;
  206. name?: string | null;
  207. code?: string | null;
  208. type?: string | null;
  209. message?: string | null;
  210. errorPosition?: string | null;
  211. entity?: any;
  212. };
  213. export interface PickOrderCompletionResponse {
  214. id: number | null;
  215. name: string;
  216. code: string;
  217. type?: string;
  218. message: string | null;
  219. errorPosition: string;
  220. entity?: {
  221. hasCompletedOrders: boolean;
  222. completedOrders: Array<{
  223. pickOrderId: number;
  224. pickOrderCode: string;
  225. consoCode: string;
  226. isCompleted: boolean;
  227. stockOutStatus: string;
  228. totalLines: number;
  229. unfinishedLines: number;
  230. }>;
  231. allOrders: Array<{
  232. pickOrderId: number;
  233. pickOrderCode: string;
  234. consoCode: string;
  235. isCompleted: boolean;
  236. stockOutStatus: string;
  237. totalLines: number;
  238. unfinishedLines: number;
  239. }>;
  240. };
  241. }
  242. export interface UpdateSuggestedLotLineIdRequest {
  243. newLotLineId: number;
  244. }
  245. export interface stockReponse{
  246. id: number;
  247. status: string;
  248. qty: number;
  249. lotId: number;
  250. lotNo: string;
  251. location: string;
  252. availableQty: number;
  253. noLot: boolean;
  254. }
  255. export interface FGPickOrderResponse {
  256. // 新增:支持多个 pick orders
  257. doPickOrderId: number;
  258. pickOrderIds?: number[];
  259. pickOrderCodes?: string[]; // 改为数组
  260. deliveryOrderIds?: number[];
  261. deliveryNos?: string[]; // 改为数组
  262. numberOfPickOrders?: number;
  263. lineCountsPerPickOrder?: number[];// 新增:pick order 数量
  264. // 保留原有字段用于向后兼容(显示第一个 pick order)
  265. pickOrderId: number;
  266. pickOrderCode: string;
  267. pickOrderConsoCode: string;
  268. pickOrderTargetDate: string;
  269. pickOrderStatus: string;
  270. deliveryOrderId: number;
  271. deliveryNo: string;
  272. deliveryDate: string;
  273. shopId: number;
  274. shopCode: string;
  275. shopName: string;
  276. shopAddress: string;
  277. ticketNo: string;
  278. shopPoNo: string;
  279. numberOfCartons: number;
  280. DepartureTime: string;
  281. truckLanceCode: string;
  282. storeId: string;
  283. qrCodeData: number;
  284. releaseType?: string;
  285. }
  286. export interface DoPickOrderDetail {
  287. doPickOrder: {
  288. id: number;
  289. store_id: string;
  290. ticket_no: string;
  291. ticket_status: string;
  292. truck_id: number;
  293. truck_departure_time: string;
  294. shop_id: number;
  295. handled_by: number | null;
  296. loading_sequence: number;
  297. ticket_release_time: string | null;
  298. TruckLanceCode: string;
  299. ShopCode: string;
  300. ShopName: string;
  301. RequiredDeliveryDate: string;
  302. };
  303. pickOrders: Array<{
  304. pick_order_id: number;
  305. pick_order_code: string;
  306. do_order_id: number;
  307. delivery_order_code: string;
  308. consoCode: string;
  309. status: string;
  310. targetDate: string;
  311. }>;
  312. selectedPickOrderId: number;
  313. lotDetails: any[]; // 使用现有的 lot detail 结构
  314. pickOrderCodes?: string;
  315. deliveryNos?: string;
  316. }
  317. export interface AutoAssignReleaseByStoreRequest {
  318. userId: number;
  319. storeId: string; // "2/F" | "4/F"
  320. }
  321. export interface UpdateDoPickOrderHideStatusRequest {
  322. id: number;
  323. name: string;
  324. code: string;
  325. type: string;
  326. message: string;
  327. errorPosition: string;
  328. }
  329. export interface CompletedDoPickOrderResponse {
  330. id: number;
  331. doPickOrderRecordId: number; // ✅ 新增
  332. recordId: number | null;
  333. pickOrderId: number;
  334. pickOrderIds: number[]; // 新增:所有 pick order IDs
  335. pickOrderCode: string;
  336. pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔)
  337. pickOrderConsoCode: string;
  338. pickOrderStatus: string;
  339. deliveryOrderId: number;
  340. deliveryOrderIds: number[]; // 新增:所有 delivery order IDs
  341. deliveryNo: string;
  342. deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔)
  343. deliveryDate: string;
  344. shopId: number;
  345. shopCode: string;
  346. shopName: string;
  347. shopAddress: string;
  348. ticketNo: string;
  349. shopPoNo: string;
  350. numberOfCartons: number;
  351. truckLanceCode: string;
  352. DepartureTime: string; // 新增
  353. storeId: string;
  354. completedDate: string;
  355. fgPickOrders: FGPickOrderResponse[];
  356. deliveryNoteCode: number;
  357. /** Legacy: do_pick_order_record.handler_name; workbench: delivery_order_pick_order.handlerName */
  358. handlerName?: string | null;
  359. /** Workbench: delivery_order_pick_order.releaseType (e.g. isExtrabatch). */
  360. releaseType?: string | null;
  361. }
  362. // 新增:搜索参数接口
  363. export interface CompletedDoPickOrderSearchParams {
  364. targetDate?: string;
  365. shopName?: string;
  366. deliveryNoteCode?: string;
  367. /** 卡車/車道(後端 truckLanceCode 模糊匹配) */
  368. truckLanceCode?: string;
  369. ticketNo?: string;
  370. }
  371. export interface PickExecutionIssue {
  372. id: number;
  373. pickOrderId: number;
  374. pickOrderCode: string;
  375. pickOrderCreateDate: string;
  376. pickExecutionDate: string;
  377. pickOrderLineId: number;
  378. issueNo: string;
  379. joPickOrderId?: number;
  380. doPickOrderId?: number;
  381. issueCategory: string;
  382. itemId: number;
  383. itemCode: string;
  384. itemDescription: string;
  385. lotId?: number;
  386. lotNo?: string;
  387. storeLocation?: string;
  388. requiredQty: number;
  389. actualPickQty?: number;
  390. missQty?: number;
  391. badItemQty?: number;
  392. issueRemark?: string;
  393. pickerName?: string;
  394. handleStatus: string;
  395. handleDate?: string;
  396. handledBy?: string;
  397. created: string;
  398. createdBy: string;
  399. modified: string;
  400. modifiedBy: string;
  401. }
  402. export interface FetchPickExecutionIssuesParams {
  403. type?: "jo" | "do" | "material";
  404. }
  405. export const fetchAllPickExecutionIssues = cache(
  406. async (params?: FetchPickExecutionIssuesParams): Promise<PickExecutionIssue[]> => {
  407. const queryParams = new URLSearchParams();
  408. if (params?.type) {
  409. queryParams.append("type", params.type);
  410. }
  411. const url = `${BASE_API_URL}/pickExecution/issues/all${
  412. queryParams.toString() ? `?${queryParams.toString()}` : ""
  413. }`;
  414. const result = await serverFetchJson<PickExecutionIssue[]>(url, {
  415. method: "GET",
  416. headers: { "Content-Type": "application/json" },
  417. });
  418. return result ?? [];
  419. }
  420. );
  421. export interface UpdatePickExecutionIssueRequest {
  422. issueId: number;
  423. handleStatus: string;
  424. handleDate?: string;
  425. handledBy?: string;
  426. handleRemark?: string;
  427. }
  428. export interface StoreLaneSummary {
  429. storeId: string;
  430. rows: LaneRow[];
  431. defaultTruckCount: number | null;
  432. }
  433. export interface LaneRow {
  434. truckDepartureTime: string;
  435. lanes: LaneBtn[];
  436. }
  437. export interface LaneBtn {
  438. truckLanceCode: string;
  439. loadingSequence?: number | null;
  440. unassigned: number;
  441. total: number;
  442. handlerName?: string | null;
  443. /** Workbench Etra: dop storeId for assign scope */
  444. storeId?: string | null;
  445. /** ISO local time string for workbench assign-by-lane (Etra summary) */
  446. truckDepartureTime?: string | null;
  447. }
  448. export interface QrPickBatchSubmitRequest {
  449. userId: number;
  450. lines: QrPickSubmitLineRequest[];
  451. }
  452. export interface QrPickSubmitLineRequest {
  453. stockOutLineId: number;
  454. pickOrderLineId: number;
  455. inventoryLotLineId: number | null; // ✅ 修复:应该是 nullable
  456. requiredQty: number | null; // ✅ 修复:添加 requiredQty
  457. actualPickQty: number | null; // ✅ 修复:添加 actualPickQty
  458. stockOutLineStatus: string | null; // ✅ 修复:添加 stockOutLineStatus
  459. pickOrderConsoCode: string | null; // ✅ 修复:添加 pickOrderConsoCode
  460. noLot: boolean; // ✅ 修复:添加 noLot
  461. }
  462. export interface UpdateStockOutLineStatusByQRCodeAndLotNoRequest {
  463. pickOrderLineId: number,
  464. inventoryLotNo: string,
  465. stockInLineId?: number | null,
  466. stockOutLineId: number,
  467. itemId: number,
  468. status: string
  469. }
  470. export interface batchSubmitListRequest {
  471. userId: number;
  472. lines: batchSubmitListLineRequest[];
  473. }
  474. export interface batchSubmitListLineRequest {
  475. stockOutLineId: number; // 修复:改为 stockOutLineId(不是 stockInLineId)
  476. pickOrderLineId: number;
  477. inventoryLotLineId: number | null; // 添加:后端需要的字段
  478. requiredQty: number;
  479. actualPickQty: number;
  480. stockOutLineStatus: string;
  481. pickOrderConsoCode: string;
  482. noLot: boolean;
  483. // 移除:lotNo 和 stockInLineId(后端不需要)
  484. }
  485. export const batchSubmitList = async (data: batchSubmitListRequest) => {
  486. // ✅ 确保发送的是对象,不是数组
  487. const requestBody = Array.isArray(data) ? data[0] : data;
  488. console.log("📤 batchSubmitList - Request body type:", Array.isArray(requestBody) ? "array" : "object");
  489. console.log("📤 batchSubmitList - Request body:", JSON.stringify(requestBody, null, 2));
  490. const response = await serverFetchJson<PostPickOrderResponse<batchSubmitListRequest>>(
  491. `${BASE_API_URL}/stockOutLine/batchSubmitList`,
  492. {
  493. method: "POST",
  494. body: JSON.stringify(requestBody), // ✅ 确保是对象
  495. headers: {
  496. "Content-Type": "application/json", // ✅ 明确指定 Content-Type
  497. },
  498. },
  499. );
  500. return response;
  501. };
  502. export const updateStockOutLineStatusByQRCodeAndLotNo = async (data: UpdateStockOutLineStatusByQRCodeAndLotNoRequest) => {
  503. console.log(" Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data);
  504. try {
  505. const response = await serverFetchJson<PostPickOrderResponse<UpdateStockOutLineStatusByQRCodeAndLotNoRequest>>(
  506. `${BASE_API_URL}/stockOutLine/updateStatusByQRCodeAndLotNo`,
  507. {
  508. method: "POST",
  509. headers: {
  510. "Content-Type": "application/json",
  511. },
  512. body: JSON.stringify(data),
  513. },
  514. );
  515. console.log("✅ Frontend: API call successful, response:", response);
  516. return response;
  517. } catch (error) {
  518. console.error("❌ Frontend: API call failed:", error);
  519. throw error;
  520. }
  521. };
  522. export const batchQrSubmit = async (data: QrPickBatchSubmitRequest) => {
  523. const response = await serverFetchJson<PostPickOrderResponse<QrPickBatchSubmitRequest>>(
  524. `${BASE_API_URL}/stockOutLine/batchQrSubmit`,
  525. {
  526. method: "POST",
  527. body: JSON.stringify(data),
  528. },
  529. );
  530. return response;
  531. };
  532. export interface BatchScanRequest {
  533. userId: number;
  534. lines: BatchScanLineRequest[];
  535. }
  536. export interface BatchScanLineRequest {
  537. pickOrderLineId: number;
  538. inventoryLotLineId: number | null; // 如果有 lot,提供 lotId;如果没有则为 null
  539. pickOrderConsoCode: string;
  540. lotNo: string | null; // 用于日志和验证
  541. itemId: number;
  542. itemCode: string;
  543. stockOutLineId: number | null; // ✅ 新增:如果已有 stockOutLineId,直接使用
  544. }
  545. export const batchScan = async (data: BatchScanRequest) => {
  546. console.log("📤 batchScan - Request body:", JSON.stringify(data, null, 2));
  547. const response = await serverFetchJson<PostPickOrderResponse<BatchScanRequest>>(
  548. `${BASE_API_URL}/stockOutLine/batchScan`,
  549. {
  550. method: "POST",
  551. body: JSON.stringify(data),
  552. headers: {
  553. "Content-Type": "application/json",
  554. },
  555. },
  556. );
  557. //console.log("📥 batchScan - Response:", response);
  558. return response;
  559. };
  560. export const fetchDoPickOrderDetail = async (
  561. doPickOrderId: number,
  562. selectedPickOrderId?: number
  563. ): Promise<DoPickOrderDetail> => {
  564. const url = selectedPickOrderId
  565. ? `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}?selectedPickOrderId=${selectedPickOrderId}`
  566. : `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}`;
  567. const response = await serverFetchJson<DoPickOrderDetail>(url, {
  568. method: "GET",
  569. });
  570. return response;
  571. };
  572. export const updatePickExecutionIssueStatus = async (
  573. data: UpdatePickExecutionIssueRequest
  574. ): Promise<PostPickOrderResponse> => {
  575. const result = await serverFetchJson<PostPickOrderResponse>(
  576. `${BASE_API_URL}/pickExecution/updateIssueStatus`,
  577. {
  578. method: "POST",
  579. body: JSON.stringify(data),
  580. headers: { "Content-Type": "application/json" },
  581. }
  582. );
  583. revalidateTag("pickExecutionIssues");
  584. return result;
  585. };
  586. export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise<StoreLaneSummary> {
  587. const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD');
  588. const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`;
  589. const label = `[API] fetchStoreLaneSummary ${storeId}`;
  590. console.time(label);
  591. try {
  592. const response = await serverFetchJson<StoreLaneSummary>(url, {
  593. method: "GET",
  594. cache: "no-store",
  595. });
  596. console.timeEnd(label);
  597. return response;
  598. } catch (error) {
  599. console.error(`[API] Error in fetchStoreLaneSummary ${storeId}:`, error);
  600. throw error;
  601. }
  602. }
  603. // 按车道分配订单
  604. export async function assignByLane(
  605. userId: number,
  606. storeId: string,
  607. truckLanceCode: string,
  608. truckDepartureTime?: string,
  609. loadingSequence?: number | null,
  610. requiredDate?: string
  611. ): Promise<any> {
  612. const response = await serverFetchJson(
  613. `${BASE_API_URL}/doPickOrder/assign-by-lane`,
  614. {
  615. method: "POST",
  616. headers: {
  617. "Content-Type": "application/json",
  618. },
  619. body: JSON.stringify({
  620. userId,
  621. storeId,
  622. truckLanceCode,
  623. truckDepartureTime,
  624. loadingSequence,
  625. requiredDate,
  626. }),
  627. }
  628. );
  629. return response;
  630. }
  631. // 新增:获取已完成的 DO Pick Orders API
  632. export const fetchCompletedDoPickOrders = async (
  633. userId: number,
  634. searchParams?: CompletedDoPickOrderSearchParams
  635. ): Promise<CompletedDoPickOrderResponse[]> => {
  636. const params = new URLSearchParams();
  637. if (searchParams?.deliveryNoteCode) {
  638. params.append('deliveryNoteCode', searchParams.deliveryNoteCode);
  639. }
  640. if (searchParams?.shopName) {
  641. params.append('shopName', searchParams.shopName);
  642. }
  643. if (searchParams?.targetDate) {
  644. params.append('targetDate', searchParams.targetDate);
  645. }
  646. if (searchParams?.truckLanceCode) {
  647. params.append("truckLanceCode", searchParams.truckLanceCode);
  648. }
  649. const queryString = params.toString();
  650. const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders/${userId}${queryString ? `?${queryString}` : ''}`;
  651. const response = await serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
  652. method: "GET",
  653. });
  654. return response;
  655. };
  656. /** DO workbench: completed tickets from `delivery_order_pick_order.ticketStatus = completed`. **/
  657. export const fetchCompletedDoPickOrdersWorkbench = async (
  658. userId: number,
  659. searchParams?: CompletedDoPickOrderSearchParams,
  660. ): Promise<CompletedDoPickOrderResponse[]> => {
  661. const params = new URLSearchParams();
  662. if (searchParams?.deliveryNoteCode) {
  663. params.append("deliveryNoteCode", searchParams.deliveryNoteCode);
  664. }
  665. if (searchParams?.shopName) {
  666. params.append("shopName", searchParams.shopName);
  667. }
  668. if (searchParams?.targetDate) {
  669. params.append("targetDate", searchParams.targetDate);
  670. }
  671. if (searchParams?.truckLanceCode) {
  672. params.append("truckLanceCode", searchParams.truckLanceCode);
  673. }
  674. if (searchParams?.ticketNo) {
  675. params.append("ticketNo", searchParams.ticketNo);
  676. }
  677. const queryString = params.toString();
  678. const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-workbench/${userId}${
  679. queryString ? `?${queryString}` : ""
  680. }`;
  681. return serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
  682. method: "GET",
  683. });
  684. };
  685. /** DO workbench: completed tickets from `delivery_order_pick_order.ticketStatus = completed` (all users). */
  686. export const fetchCompletedDoPickOrdersWorkbenchAll = async (
  687. searchParams?: CompletedDoPickOrderSearchParams,
  688. ): Promise<CompletedDoPickOrderResponse[]> => {
  689. const params = new URLSearchParams();
  690. if (searchParams?.deliveryNoteCode) {
  691. params.append("deliveryNoteCode", searchParams.deliveryNoteCode);
  692. }
  693. if (searchParams?.shopName) {
  694. params.append("shopName", searchParams.shopName);
  695. }
  696. if (searchParams?.targetDate) {
  697. params.append("targetDate", searchParams.targetDate);
  698. }
  699. if (searchParams?.truckLanceCode) {
  700. params.append("truckLanceCode", searchParams.truckLanceCode);
  701. }
  702. if (searchParams?.ticketNo) {
  703. params.append("ticketNo", searchParams.ticketNo);
  704. }
  705. const queryString = params.toString();
  706. const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-workbench-all${
  707. queryString ? `?${queryString}` : ""
  708. }`;
  709. return serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
  710. method: "GET",
  711. });
  712. };
  713. /** 全部已完成 DO 提貨記錄(不限經手人),需後端 `/completed-do-pick-orders-all` */
  714. export const fetchCompletedDoPickOrdersAll = async (
  715. searchParams?: CompletedDoPickOrderSearchParams
  716. ): Promise<CompletedDoPickOrderResponse[]> => {
  717. const params = new URLSearchParams();
  718. if (searchParams?.deliveryNoteCode) {
  719. params.append("deliveryNoteCode", searchParams.deliveryNoteCode);
  720. }
  721. if (searchParams?.shopName) {
  722. params.append("shopName", searchParams.shopName);
  723. }
  724. if (searchParams?.targetDate) {
  725. params.append("targetDate", searchParams.targetDate);
  726. }
  727. if (searchParams?.truckLanceCode) {
  728. params.append("truckLanceCode", searchParams.truckLanceCode);
  729. }
  730. const queryString = params.toString();
  731. const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders-all${queryString ? `?${queryString}` : ""}`;
  732. const response = await serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
  733. method: "GET",
  734. });
  735. return response;
  736. };
  737. /** 強制完成進行中的 do_pick_order(僅改狀態並歸檔,不調整揀貨數量) */
  738. export const forceCompleteDoPickOrder = async (
  739. doPickOrderId: number,
  740. ): Promise<PostPickOrderResponse> => {
  741. return serverFetchJson<PostPickOrderResponse>(
  742. `${BASE_API_URL}/doPickOrder/force-complete/${doPickOrderId}`,
  743. { method: "POST", headers: { "Content-Type": "application/json" } },
  744. );
  745. };
  746. /** 撤銷使用者領取,可再次分配 */
  747. export const revertDoPickOrderAssignment = async (
  748. doPickOrderId: number,
  749. ): Promise<PostPickOrderResponse> => {
  750. return serverFetchJson<PostPickOrderResponse>(
  751. `${BASE_API_URL}/doPickOrder/revert-assignment/${doPickOrderId}`,
  752. { method: "POST", headers: { "Content-Type": "application/json" } },
  753. );
  754. };
  755. export const updatePickOrderHideStatus = async (pickOrderId: number, hide: boolean) => {
  756. const response = await serverFetchJson<UpdateDoPickOrderHideStatusRequest>(
  757. `${BASE_API_URL}/pickOrder/update-hide-status/${pickOrderId}?hide=${hide}`,
  758. {
  759. method: "POST",
  760. headers: { "Content-Type": "application/json" },
  761. },
  762. );
  763. revalidateTag("pickorder");
  764. return response;
  765. };
  766. export const fetchFGPickOrders = async (pickOrderId: number) => {
  767. const response = await serverFetchJson<FGPickOrderResponse>(
  768. `${BASE_API_URL}/pickOrder/fg-pick-orders/${pickOrderId}`,
  769. {
  770. method: "GET",
  771. },
  772. );
  773. return response;
  774. };
  775. export const fetchFGPickOrdersByUserId = async (userId: number) => {
  776. const response = await serverFetchJson<FGPickOrderResponse[]>(
  777. `${BASE_API_URL}/pickOrder/fg-pick-orders/${userId}`,
  778. {
  779. method: "GET",
  780. },
  781. );
  782. return response;
  783. };
  784. /** DO workbench: FG headers from `delivery_order_pick_order`, not `do_pick_order_line`. */
  785. export const fetchFGPickOrdersByUserIdWorkbench = async (userId: number) => {
  786. return serverFetchJson<FGPickOrderResponse[]>(
  787. `${BASE_API_URL}/pickOrder/fg-pick-orders-workbench/${userId}`,
  788. {
  789. method: "GET",
  790. // Must be fresh: determines whether shell shows Floor/Lane panel or Detail.
  791. cache: "no-store",
  792. },
  793. );
  794. };
  795. export const updateSuggestedLotLineId = async (suggestedPickLotId: number, newLotLineId: number) => {
  796. const response = await serverFetchJson<PostPickOrderResponse<UpdateSuggestedLotLineIdRequest>>(
  797. `${BASE_API_URL}/suggestedPickLot/update-suggested-lot/${suggestedPickLotId}`,
  798. {
  799. method: "POST",
  800. body: JSON.stringify({ newLotLineId }),
  801. headers: { "Content-Type": "application/json" },
  802. },
  803. );
  804. revalidateTag("pickorder");
  805. return response;
  806. };
  807. export const autoAssignAndReleasePickOrder = async (userId: number): Promise<AutoAssignReleaseResponse> => {
  808. const response = await serverFetchJson<AutoAssignReleaseResponse>(
  809. `${BASE_API_URL}/pickOrder/auto-assign-release/${userId}`,
  810. {
  811. method: "POST",
  812. headers: { "Content-Type": "application/json" },
  813. },
  814. );
  815. revalidateTag("pickorder");
  816. return response;
  817. };
  818. export const autoAssignAndReleasePickOrderByStore = async (
  819. userId: number,
  820. storeId: string
  821. ): Promise<AutoAssignReleaseResponse> => {
  822. const url = `${BASE_API_URL}/pickOrder/auto-assign-release-by-store?userId=${userId}&storeId=${encodeURIComponent(storeId)}`;
  823. const response = await serverFetchJson<AutoAssignReleaseResponse>(url, {
  824. method: "POST",
  825. headers: { "Content-Type": "application/json" },
  826. // no body
  827. next: { tags: ["pickorder"] },
  828. });
  829. revalidateTag("pickorder");
  830. return response;
  831. };
  832. export const checkPickOrderCompletion = async (userId: number): Promise<PickOrderCompletionResponse> => {
  833. const response = await serverFetchJson<PickOrderCompletionResponse>(
  834. `${BASE_API_URL}/pickOrder/check-pick-completion/${userId}`,
  835. {
  836. method: "GET",
  837. headers: { "Content-Type": "application/json" },
  838. },
  839. );
  840. return response;
  841. };
  842. export const recordPickExecutionIssue = async (data: PickExecutionIssueData) => {
  843. const result = await serverFetchJson<PostPickOrderResponse>(
  844. `${BASE_API_URL}/pickExecution/recordIssue`,
  845. {
  846. method: "POST",
  847. body: JSON.stringify(data),
  848. headers: { "Content-Type": "application/json" },
  849. },
  850. );
  851. revalidateTag("pickorder");
  852. return result;
  853. };
  854. export const resuggestPickOrder = async (pickOrderId: number) => {
  855. console.log("Resuggesting pick order:", pickOrderId);
  856. const result = await serverFetchJson<PostPickOrderResponse>(
  857. `${BASE_API_URL}/suggestedPickLot/resuggest/${pickOrderId}`,
  858. {
  859. method: "POST",
  860. headers: { "Content-Type": "application/json" },
  861. },
  862. );
  863. revalidateTag("pickorder");
  864. return result;
  865. };
  866. /**
  867. * Workbench suggest (no-hold path target).
  868. * Current backend route is shared with legacy resuggest, but we expose a dedicated
  869. * API name so PickOrder workbench pages can migrate independently.
  870. */
  871. export const suggestPickOrderWorkbenchV2 = async (pickOrderId: number, userId: number) => {
  872. const result = await serverFetchJson<PostPickOrderResponse>(
  873. `${BASE_API_URL}/pickOrder/workbench/suggest-v2/${pickOrderId}`,
  874. {
  875. method: "POST",
  876. body: JSON.stringify({ userId }),
  877. headers: { "Content-Type": "application/json" },
  878. },
  879. );
  880. revalidateTag("pickorder");
  881. return result;
  882. };
  883. /**
  884. * Workbench release V2 (no-hold): do not create stock_out at release time.
  885. * Downstream suggestion/stock_out_line are created when assigning workbench ticket.
  886. */
  887. export const releasePickOrderWorkbenchV2 = async (data: {
  888. pickOrderIds: number[];
  889. assignTo: number;
  890. }) => {
  891. const response = await serverFetchJson<PostPickOrderResponse>(
  892. `${BASE_API_URL}/pickOrder/workbench/release-v2`,
  893. {
  894. method: "POST",
  895. body: JSON.stringify(data),
  896. headers: { "Content-Type": "application/json" },
  897. },
  898. );
  899. revalidateTag("pickorder");
  900. return response;
  901. };
  902. /** Consumable workbench hierarchical lots (not DO workbench). */
  903. export const fetchConsumableWorkbenchPickOrderLotsHierarchical = cache(async (userId: number): Promise<any> => {
  904. try {
  905. const data = await serverFetchJson<any>(
  906. `${BASE_API_URL}/pickOrder/workbench/all-lots-hierarchical/${userId}`,
  907. {
  908. method: "GET",
  909. next: { tags: ["pickorder"] },
  910. },
  911. );
  912. return data;
  913. } catch (error) {
  914. console.error("❌ Error fetching consumable workbench hierarchical lot details:", error);
  915. return {
  916. fgInfo: null,
  917. pickOrders: [],
  918. };
  919. }
  920. });
  921. /** Bust pickorder cache then reload consumable workbench hierarchy (after scan-pick). */
  922. export async function reloadConsumableWorkbenchPickOrderLotsHierarchical(userId: number): Promise<any> {
  923. revalidateTag("pickorder");
  924. try {
  925. return await serverFetchJson<any>(
  926. `${BASE_API_URL}/pickOrder/workbench/all-lots-hierarchical/${userId}`,
  927. {
  928. method: "GET",
  929. next: { tags: ["pickorder"] },
  930. },
  931. );
  932. } catch (error) {
  933. console.error("❌ Error reloading consumable workbench hierarchical lot details:", error);
  934. return {
  935. fgInfo: null,
  936. pickOrders: [],
  937. };
  938. }
  939. }
  940. /**
  941. * Workbench assign: assign by delivery_order_pick_order id.
  942. */
  943. export const assignPickOrderWorkbenchV2 = async (data: {
  944. pickOrderIds: number[];
  945. assignTo: number;
  946. }) => {
  947. const response = await serverFetchJson<PostPickOrderResponse>(
  948. `${BASE_API_URL}/pickOrder/workbench/assign-v2`,
  949. {
  950. method: "POST",
  951. body: JSON.stringify(data),
  952. headers: { "Content-Type": "application/json" },
  953. },
  954. );
  955. revalidateTag("pickorder");
  956. return response;
  957. };
  958. export const updateStockOutLineStatus = async (data: {
  959. id: number;
  960. status: string;
  961. qty?: number;
  962. remarks?: string;
  963. }) => {
  964. console.log("Updating stock out line status:", data);
  965. const result = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  966. `${BASE_API_URL}/stockOutLine/updateStatus`,
  967. {
  968. method: "POST",
  969. body: JSON.stringify(data),
  970. headers: { "Content-Type": "application/json" },
  971. },
  972. );
  973. revalidateTag("pickorder");
  974. return result;
  975. };
  976. // Missing function 1: newassignPickOrder
  977. export const newassignPickOrder = async (data: AssignPickOrderInputs) => {
  978. const response = await serverFetchJson<PostPickOrderResponse>(
  979. `${BASE_API_URL}/pickOrder/assign`,
  980. {
  981. method: "POST",
  982. body: JSON.stringify(data),
  983. headers: { "Content-Type": "application/json" },
  984. },
  985. );
  986. revalidateTag("pickorder");
  987. return response;
  988. };
  989. export const cancelPickOrders = async (pickOrderIds: number[]) => {
  990. const response = await serverFetchJson<PostPickOrderResponse>(
  991. `${BASE_API_URL}/pickOrder/cancel`,
  992. {
  993. method: "POST",
  994. body: JSON.stringify({ pickOrderIds }),
  995. headers: { "Content-Type": "application/json" },
  996. },
  997. );
  998. revalidateTag("pickorder");
  999. return response;
  1000. };
  1001. // Missing function 2: releaseAssignedPickOrders
  1002. export const releaseAssignedPickOrders = async (data: AssignPickOrderInputs) => {
  1003. const response = await serverFetchJson<PostPickOrderResponse>(
  1004. `${BASE_API_URL}/pickOrder/release-assigned`,
  1005. {
  1006. method: "POST",
  1007. body: JSON.stringify(data),
  1008. headers: { "Content-Type": "application/json" },
  1009. },
  1010. );
  1011. revalidateTag("pickorder");
  1012. return response;
  1013. };
  1014. // Get latest group name and create it automatically
  1015. export const getLatestGroupNameAndCreate = async () => {
  1016. return serverFetchJson<PostPickOrderResponse>(
  1017. `${BASE_API_URL}/pickOrder/groups/latest`,
  1018. {
  1019. method: "GET",
  1020. next: { tags: ["pickorder"] },
  1021. },
  1022. );
  1023. };
  1024. // Get all groups
  1025. export const fetchAllGroups = cache(async () => {
  1026. return serverFetchJson<PickOrderGroupInfo[]>(
  1027. `${BASE_API_URL}/pickOrder/groups/list`,
  1028. {
  1029. method: "GET",
  1030. next: { tags: ["pickorder"] },
  1031. },
  1032. );
  1033. });
  1034. // Create or update groups (flexible - can handle both cases)
  1035. export const createOrUpdateGroups = async (data: SavePickOrderGroupRequest) => {
  1036. const response = await serverFetchJson<PostPickOrderResponse>(
  1037. `${BASE_API_URL}/pickOrder/groups/create`,
  1038. {
  1039. method: "POST",
  1040. body: JSON.stringify(data),
  1041. headers: { "Content-Type": "application/json" },
  1042. },
  1043. );
  1044. revalidateTag("pickorder");
  1045. return response;
  1046. };
  1047. // Get groups by pick order ID
  1048. export const fetchGroupsByPickOrderId = cache(async (pickOrderId: number) => {
  1049. return serverFetchJson<PickOrderGroupInfo[]>(
  1050. `${BASE_API_URL}/pickOrder/groups/${pickOrderId}`,
  1051. {
  1052. method: "GET",
  1053. next: { tags: ["pickorder"] },
  1054. },
  1055. );
  1056. });
  1057. export const fetchPickOrderDetails = cache(async (ids: string) => {
  1058. return serverFetchJson<GetPickOrderInfoResponse>(
  1059. `${BASE_API_URL}/pickOrder/detail/${ids}`,
  1060. {
  1061. method: "GET",
  1062. next: { tags: ["pickorder"] },
  1063. },
  1064. );
  1065. });
  1066. export interface PickOrderLotDetailResponse {
  1067. lotId: number | null; // ✅ 改为可空
  1068. stockInLineId?: number | null;
  1069. lotNo: string | null; // ✅ 改为可空
  1070. expiryDate: string | null; // ✅ 改为可空
  1071. location: string | null; // ✅ 改为可空
  1072. itemId: number | null;
  1073. stockUnit: string | null;
  1074. inQty: number | null;
  1075. availableQty: number | null; // ✅ 改为可空
  1076. requiredQty: number;
  1077. actualPickQty: number;
  1078. suggestedPickLotId: number | null;
  1079. lotStatus: string | null;
  1080. lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected';
  1081. stockOutLineId: number | null; // ✅ 添加
  1082. stockOutLineStatus: string | null; // ✅ 添加
  1083. stockOutLineQty: number | null; // ✅ 添加
  1084. totalPickedByAllPickOrders: number | null; // ✅ 添加
  1085. remainingAfterAllPickOrders: number | null; // ✅ 添加
  1086. noLot: boolean; // ✅ 关键:添加 noLot 字段
  1087. outQty?: number; // ✅ 添加
  1088. holdQty?: number; // ✅ 添加
  1089. }
  1090. interface ALLPickOrderLotDetailResponse {
  1091. // Pick Order Information
  1092. pickOrderId: number;
  1093. pickOrderCode: string;
  1094. pickOrderTargetDate: string;
  1095. pickOrderType: string;
  1096. pickOrderStatus: string;
  1097. pickOrderAssignTo: number;
  1098. groupName: string;
  1099. // Pick Order Line Information
  1100. pickOrderLineId: number;
  1101. pickOrderLineRequiredQty: number;
  1102. pickOrderLineStatus: string;
  1103. // Item Information
  1104. itemId: number;
  1105. itemCode: string;
  1106. itemName: string;
  1107. uomCode: string;
  1108. uomDesc: string;
  1109. // Lot Information
  1110. lotId: number;
  1111. lotNo: string;
  1112. expiryDate: string;
  1113. location: string;
  1114. outQty: number;
  1115. holdQty: number;
  1116. stockUnit: string;
  1117. availableQty: number;
  1118. requiredQty: number;
  1119. actualPickQty: number;
  1120. totalPickedByAllPickOrders: number;
  1121. suggestedPickLotId: number;
  1122. lotStatus: string;
  1123. stockOutLineId?: number;
  1124. stockOutLineStatus?: string;
  1125. stockOutLineQty?: number;
  1126. lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
  1127. processingStatus: string;
  1128. }
  1129. interface SuggestionWithStatus {
  1130. suggestionId: number;
  1131. suggestionQty: number;
  1132. suggestionCreated: string;
  1133. lotLineId: number;
  1134. lotNo: string;
  1135. expiryDate: string;
  1136. location: string;
  1137. stockOutLineId?: number;
  1138. stockOutLineStatus?: string;
  1139. stockOutLineQty?: number;
  1140. suggestionStatus: 'active' | 'completed' | 'rejected' | 'in_progress' | 'unknown';
  1141. }
  1142. // 在 actions.ts 中修改接口定义
  1143. export interface FGPickOrderHierarchicalResponse {
  1144. fgInfo: {
  1145. doPickOrderId: number;
  1146. ticketNo: string;
  1147. storeId: string;
  1148. shopCode: string;
  1149. shopName: string;
  1150. truckLanceCode: string;
  1151. departureTime: string;
  1152. };
  1153. pickOrders: Array<{
  1154. pickOrderId: number;
  1155. pickOrderCode: string;
  1156. doOrderId: number;
  1157. deliveryOrderCode: string;
  1158. consoCode: string;
  1159. status: string;
  1160. targetDate: string;
  1161. pickOrderLines: Array<{
  1162. id: number;
  1163. requiredQty: number;
  1164. status: string;
  1165. item: {
  1166. id: number;
  1167. code: string;
  1168. name: string;
  1169. uomCode: string;
  1170. uomDesc: string;
  1171. };
  1172. lots: Array<any>; // 可以是空数组
  1173. }>;
  1174. }>;
  1175. }
  1176. export interface CheckCompleteResponse {
  1177. id: number | null;
  1178. name: string;
  1179. code: string;
  1180. type?: string;
  1181. message: string | null;
  1182. errorPosition: string;
  1183. }
  1184. export interface LotSubstitutionConfirmRequest {
  1185. pickOrderLineId: number;
  1186. stockOutLineId: number;
  1187. originalSuggestedPickLotId: number;
  1188. newInventoryLotNo: string;
  1189. newStockInLineId: number;
  1190. }
  1191. export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => {
  1192. const response = await serverFetchJson<PostPickOrderResponse>(
  1193. `${BASE_API_URL}/pickOrder/lot-substitution/confirm`,
  1194. {
  1195. method: "POST",
  1196. body: JSON.stringify(data),
  1197. headers: { "Content-Type": "application/json" },
  1198. },
  1199. );
  1200. revalidateTag("pickorder");
  1201. return response;
  1202. };
  1203. export const checkAndCompletePickOrderByConsoCode = async (consoCode: string): Promise<CheckCompleteResponse> => {
  1204. const response = await serverFetchJson<CheckCompleteResponse>(
  1205. `${BASE_API_URL}/pickOrder/check-complete/${consoCode}`,
  1206. {
  1207. method: "POST",
  1208. headers: {
  1209. "Content-Type": "application/json",
  1210. },
  1211. },
  1212. );
  1213. revalidateTag("pickorder");
  1214. return response;
  1215. };
  1216. export const fetchPickOrderDetailsOptimized = cache(async (userId?: number) => {
  1217. const url = userId
  1218. ? `${BASE_API_URL}/pickOrder/detail-optimized?userId=${userId}`
  1219. : `${BASE_API_URL}/pickOrder/detail-optimized`;
  1220. return serverFetchJson<any[]>(
  1221. url,
  1222. {
  1223. method: "GET",
  1224. next: { tags: ["pickorder"] },
  1225. },
  1226. );
  1227. });
  1228. const fetchSuggestionsWithStatus = async (pickOrderLineId: number) => {
  1229. try {
  1230. const response = await fetch(`/api/suggestedPickLot/suggestions-with-status/${pickOrderLineId}`);
  1231. const suggestions: SuggestionWithStatus[] = await response.json();
  1232. return suggestions;
  1233. } catch (error) {
  1234. console.error('Error fetching suggestions with status:', error);
  1235. return [];
  1236. }
  1237. };
  1238. export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): Promise<any> => {
  1239. try {
  1240. console.log("🔍 Fetching hierarchical pick order lots for userId:", userId);
  1241. const data = await serverFetchJson<any>(
  1242. `${BASE_API_URL}/pickOrder/all-lots-hierarchical/${userId}`,
  1243. {
  1244. method: 'GET',
  1245. next: { tags: ["pickorder"] },
  1246. }
  1247. );
  1248. console.log(" Fetched hierarchical lot details:", data);
  1249. return data;
  1250. } catch (error) {
  1251. console.error("❌ Error fetching hierarchical lot details:", error);
  1252. return {
  1253. pickOrder: null,
  1254. pickOrderLines: []
  1255. };
  1256. }
  1257. });
  1258. /** DO workbench: hierarchical lots where header is `delivery_order_pick_order`. */
  1259. export const fetchAllPickOrderLotsHierarchicalWorkbench = cache(async (userId: number): Promise<any> => {
  1260. try {
  1261. const data = await serverFetchJson<any>(
  1262. `${BASE_API_URL}/pickOrder/all-lots-hierarchical-workbench/${userId}`,
  1263. {
  1264. method: 'GET',
  1265. next: { tags: ["pickorder"] },
  1266. },
  1267. );
  1268. return data;
  1269. } catch (error) {
  1270. console.error("❌ Error fetching workbench hierarchical lot details:", error);
  1271. return {
  1272. pickOrder: null,
  1273. pickOrderLines: [],
  1274. };
  1275. }
  1276. });
  1277. export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId: number): Promise<{
  1278. fgInfo: any;
  1279. pickOrders: any[];
  1280. }> => {
  1281. try {
  1282. console.log("🔍 Fetching lot details for doPickOrderRecordId:", doPickOrderRecordId);
  1283. const data = await serverFetchJson<{
  1284. fgInfo: any;
  1285. pickOrders: any[];
  1286. }>(
  1287. `${BASE_API_URL}/pickOrder/lot-details-by-do-pick-order-record/${doPickOrderRecordId}`,
  1288. {
  1289. method: 'GET',
  1290. next: { tags: ["pickorder"] },
  1291. }
  1292. );
  1293. console.log(" Fetched hierarchical lot details:", data);
  1294. return data;
  1295. } catch (error) {
  1296. console.error("❌ Error fetching lot details:", error);
  1297. return {
  1298. fgInfo: null,
  1299. pickOrders: []
  1300. };
  1301. }
  1302. };
  1303. export const fetchAllPickOrderDetails = cache(async (userId?: number) => {
  1304. if (!userId) {
  1305. return {
  1306. consoCode: null,
  1307. pickOrders: [],
  1308. items: []
  1309. };
  1310. }
  1311. // Use the correct endpoint with userId in the path
  1312. const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`;
  1313. return serverFetchJson<GetPickOrderInfoResponse>(
  1314. url,
  1315. {
  1316. method: "GET",
  1317. next: { tags: ["pickorder"] },
  1318. },
  1319. );
  1320. });
  1321. export const fetchPickOrderLineLotDetails = cache(async (pickOrderLineId: number) => {
  1322. return serverFetchJson<PickOrderLotDetailResponse[]>(
  1323. `${BASE_API_URL}/pickOrder/lot-details/${pickOrderLineId}`,
  1324. {
  1325. method: "GET",
  1326. next: { tags: ["pickorder"] },
  1327. },
  1328. );
  1329. });
  1330. export const fetchWorkbenchPickOrderLineDetailV2 = cache(async (pickOrderLineId: number) => {
  1331. return serverFetchJson<PickOrderLotDetailResponse[]>(
  1332. `${BASE_API_URL}/pickOrder/workbench/line-detail-v2/${pickOrderLineId}`,
  1333. {
  1334. method: "GET",
  1335. next: { tags: ["pickorder"] },
  1336. },
  1337. );
  1338. });
  1339. export const createPickOrder = async (data: SavePickOrderRequest) => {
  1340. console.log(data);
  1341. const po = await serverFetchJson<PostPickOrderResponse>(
  1342. `${BASE_API_URL}/pickOrder/create`,
  1343. {
  1344. method: "POST",
  1345. body: JSON.stringify(data),
  1346. headers: { "Content-Type": "application/json" },
  1347. },
  1348. );
  1349. revalidateTag("pickorder");
  1350. return po;
  1351. }
  1352. export const assignPickOrder = async (ids: number[]) => {
  1353. const pickOrder = await serverFetchJson<any>(
  1354. `${BASE_API_URL}/pickOrder/conso`,
  1355. {
  1356. method: "POST",
  1357. body: JSON.stringify({ ids: ids }),
  1358. headers: { "Content-Type": "application/json" },
  1359. },
  1360. );
  1361. // revalidateTag("po");
  1362. return pickOrder;
  1363. };
  1364. export const consolidatePickOrder = async (ids: number[]) => {
  1365. const pickOrder = await serverFetchJson<any>(
  1366. `${BASE_API_URL}/pickOrder/conso`,
  1367. {
  1368. method: "POST",
  1369. body: JSON.stringify({ ids: ids }),
  1370. headers: { "Content-Type": "application/json" },
  1371. },
  1372. );
  1373. return pickOrder;
  1374. };
  1375. export const consolidatePickOrder_revert = async (ids: number[]) => {
  1376. const pickOrder = await serverFetchJson<any>(
  1377. `${BASE_API_URL}/pickOrder/deconso`,
  1378. {
  1379. method: "POST",
  1380. body: JSON.stringify({ ids: ids }),
  1381. headers: { "Content-Type": "application/json" },
  1382. },
  1383. );
  1384. // revalidateTag("po");
  1385. return pickOrder;
  1386. };
  1387. export const fetchPickOrderClient = cache(
  1388. async (queryParams?: Record<string, any>) => {
  1389. if (queryParams) {
  1390. const queryString = new URLSearchParams(queryParams).toString();
  1391. return serverFetchJson<RecordsRes<PickOrderResult[]>>(
  1392. `${BASE_API_URL}/pickOrder/getRecordByPage?${queryString}`,
  1393. {
  1394. method: "GET",
  1395. next: { tags: ["pickorder"] },
  1396. },
  1397. );
  1398. } else {
  1399. return serverFetchJson<RecordsRes<PickOrderResult[]>>(
  1400. `${BASE_API_URL}/pickOrder/getRecordByPage`,
  1401. {
  1402. method: "GET",
  1403. next: { tags: ["pickorder"] },
  1404. },
  1405. );
  1406. }
  1407. },
  1408. );
  1409. export const fetchPickOrderWithStockClient = cache(
  1410. async (queryParams?: Record<string, any>) => {
  1411. if (queryParams) {
  1412. const queryString = new URLSearchParams(queryParams).toString();
  1413. return serverFetchJson<RecordsRes<GetPickOrderInfo[]>>(
  1414. `${BASE_API_URL}/pickOrder/getRecordByPageWithStock?${queryString}`,
  1415. {
  1416. method: "GET",
  1417. next: { tags: ["pickorder"] },
  1418. },
  1419. );
  1420. } else {
  1421. return serverFetchJson<RecordsRes<GetPickOrderInfo[]>>(
  1422. `${BASE_API_URL}/pickOrder/getRecordByPageWithStock`,
  1423. {
  1424. method: "GET",
  1425. next: { tags: ["pickorder"] },
  1426. },
  1427. );
  1428. }
  1429. },
  1430. );
  1431. export const fetchConsoPickOrderClient = cache(
  1432. async (queryParams?: Record<string, any>) => {
  1433. if (queryParams) {
  1434. const queryString = new URLSearchParams(queryParams).toString();
  1435. return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(
  1436. `${BASE_API_URL}/pickOrder/getRecordByPage-conso?${queryString}`,
  1437. {
  1438. method: "GET",
  1439. next: { tags: ["pickorder"] },
  1440. },
  1441. );
  1442. } else {
  1443. return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(
  1444. `${BASE_API_URL}/pickOrder/getRecordByPage-conso`,
  1445. {
  1446. method: "GET",
  1447. next: { tags: ["pickorder"] },
  1448. },
  1449. );
  1450. }
  1451. },
  1452. );
  1453. export const fetchPickOrderLineClient = cache(
  1454. async (queryParams?: Record<string, any>) => {
  1455. if (queryParams) {
  1456. const queryString = new URLSearchParams(queryParams).toString();
  1457. return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(
  1458. `${BASE_API_URL}/pickOrder/get-pickorder-line-byPage?${queryString}`,
  1459. {
  1460. method: "GET",
  1461. next: { tags: ["pickorder"] },
  1462. },
  1463. );
  1464. } else {
  1465. return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(
  1466. `${BASE_API_URL}/pickOrder/get-pickorder-line-byPage`,
  1467. {
  1468. method: "GET",
  1469. next: { tags: ["pickorder"] },
  1470. },
  1471. );
  1472. }
  1473. },
  1474. );
  1475. export const fetchStockOutLineClient = cache(
  1476. async (pickOrderLineId: number) => {
  1477. return serverFetchJson<StockOutLine[]>(
  1478. `${BASE_API_URL}/stockOutLine/getByPickOrderLineId/${pickOrderLineId}`,
  1479. {
  1480. method: "GET",
  1481. next: { tags: ["pickorder"] },
  1482. },
  1483. );
  1484. },
  1485. );
  1486. export const fetchConsoDetail = cache(async (consoCode: string) => {
  1487. return serverFetchJson<PreReleasePickOrderSummary>(
  1488. `${BASE_API_URL}/pickOrder/pre-release-info/${consoCode}`,
  1489. {
  1490. method: "GET",
  1491. next: { tags: ["pickorder"] },
  1492. },
  1493. );
  1494. });
  1495. export const releasePickOrder = async (data: ReleasePickOrderInputs) => {
  1496. console.log(data);
  1497. console.log(JSON.stringify(data));
  1498. const po = await serverFetchJson<{ consoCode: string }>(
  1499. `${BASE_API_URL}/pickOrder/releaseConso`,
  1500. {
  1501. method: "POST",
  1502. body: JSON.stringify(data),
  1503. headers: { "Content-Type": "application/json" },
  1504. },
  1505. );
  1506. revalidateTag("pickorder");
  1507. return po;
  1508. };
  1509. export const createStockOutLine = async (data: CreateStockOutLine) => {
  1510. console.log("triggering");
  1511. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1512. `${BASE_API_URL}/stockOutLine/create`,
  1513. {
  1514. method: "POST",
  1515. body: JSON.stringify(data),
  1516. headers: { "Content-Type": "application/json" },
  1517. },
  1518. );
  1519. revalidateTag("pickorder");
  1520. return po;
  1521. };
  1522. export const updateStockOutLine = async (data: UpdateStockOutLine) => {
  1523. console.log(data);
  1524. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1525. `${BASE_API_URL}/stockOutLine/update`,
  1526. {
  1527. method: "POST",
  1528. body: JSON.stringify(data),
  1529. headers: { "Content-Type": "application/json" },
  1530. },
  1531. );
  1532. revalidateTag("pickorder");
  1533. return po;
  1534. };
  1535. export const completeConsoPickOrder = async (consoCode: string) => {
  1536. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1537. `${BASE_API_URL}/pickOrder/consoPickOrder/complete/${consoCode}`,
  1538. {
  1539. method: "POST",
  1540. headers: { "Content-Type": "application/json" },
  1541. },
  1542. );
  1543. revalidateTag("pickorder");
  1544. return po;
  1545. };
  1546. export const fetchConsoStatus = cache(async (consoCode: string) => {
  1547. return serverFetchJson<{ status: string }>(
  1548. `${BASE_API_URL}/stockOut/get-status/${consoCode}`,
  1549. {
  1550. method: "GET",
  1551. next: { tags: ["pickorder"] },
  1552. },
  1553. );
  1554. });
  1555. export interface ReleasedDoPickOrderResponse {
  1556. id: number;
  1557. storeId: string;
  1558. ticketNo: string;
  1559. pickOrderId: number;
  1560. ticketStatus: string;
  1561. doOrderId: number;
  1562. shopId: number;
  1563. handledBy: number;
  1564. ticketReleaseTime: string;
  1565. }
  1566. export const fetchReleasedDoPickOrders = async (): Promise<ReleasedDoPickOrderResponse[]> => {
  1567. const response = await serverFetchJson<ReleasedDoPickOrderResponse[]>(
  1568. `${BASE_API_URL}/doPickOrder/released`,
  1569. {
  1570. method: "GET",
  1571. },
  1572. );
  1573. return response;
  1574. };
  1575. // 新增:Released Do Pick Order 列表項目(對應後端 ReleasedDoPickOrderListItem)
  1576. export interface ReleasedDoPickOrderListItem {
  1577. id: number;
  1578. requiredDeliveryDate: string | null;
  1579. shopCode: string | null;
  1580. shopName: string | null;
  1581. storeId: string | null;
  1582. truckLanceCode: string | null;
  1583. truckDepartureTime: string | null;
  1584. deliveryOrderCodes: string[];
  1585. }
  1586. // 修改:fetchReleasedDoPickOrders 支援 shopName 篩選,並回傳新結構
  1587. export const fetchReleasedDoPickOrdersForSelection = async (
  1588. shopName?: string,
  1589. storeId?: string,
  1590. truck?: string
  1591. ): Promise<ReleasedDoPickOrderListItem[]> => {
  1592. const params = new URLSearchParams();
  1593. if (shopName?.trim()) params.append("shopName", shopName.trim());
  1594. if (storeId?.trim()) params.append("storeId", storeId.trim());
  1595. if (truck?.trim()) params.append("truck", truck.trim());
  1596. const query = params.toString();
  1597. const url = `${BASE_API_URL}/doPickOrder/released${query ? `?${query}` : ""}`;
  1598. const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, {
  1599. method: "GET",
  1600. });
  1601. return response ?? [];
  1602. };
  1603. export const fetchReleasedDoPickOrdersForSelectionToday = async (
  1604. shopName?: string,
  1605. storeId?: string,
  1606. truck?: string,
  1607. _requiredDeliveryDate?: string
  1608. ): Promise<ReleasedDoPickOrderListItem[]> => {
  1609. const params = new URLSearchParams();
  1610. if (shopName?.trim()) params.append("shopName", shopName.trim());
  1611. if (storeId?.trim()) params.append("storeId", storeId.trim());
  1612. if (truck?.trim()) params.append("truck", truck.trim());
  1613. const query = params.toString();
  1614. const url = `${BASE_API_URL}/doPickOrder/released-today${query ? `?${query}` : ""}`;
  1615. const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, {
  1616. method: "GET",
  1617. });
  1618. return response ?? [];
  1619. };
  1620. export const fetchReleasedDoPickOrderCountByStore = async (
  1621. storeId: string
  1622. ): Promise<number> => {
  1623. const list = await fetchReleasedDoPickOrdersForSelection(undefined, storeId);
  1624. return list.length;
  1625. };
  1626. // 新增:依 doPickOrderId 分配
  1627. export const assignByDoPickOrderId = async (
  1628. userId: number,
  1629. doPickOrderId: number
  1630. ): Promise<PostPickOrderResponse> => {
  1631. const response = await serverFetchJson<PostPickOrderResponse>(
  1632. `${BASE_API_URL}/doPickOrder/assign-by-id`,
  1633. {
  1634. method: "POST",
  1635. headers: { "Content-Type": "application/json" },
  1636. body: JSON.stringify({ userId, doPickOrderId }),
  1637. }
  1638. );
  1639. revalidateTag("pickorder");
  1640. return response;
  1641. };