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

1373 lines
37 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. issueRemark: string;
  196. pickerName: string;
  197. handledBy?: number;
  198. }
  199. export type AutoAssignReleaseResponse = {
  200. id: number | null;
  201. name?: string | null;
  202. code?: string | null;
  203. type?: string | null;
  204. message?: string | null;
  205. errorPosition?: string | null;
  206. entity?: any;
  207. };
  208. export interface PickOrderCompletionResponse {
  209. id: number | null;
  210. name: string;
  211. code: string;
  212. type?: string;
  213. message: string | null;
  214. errorPosition: string;
  215. entity?: {
  216. hasCompletedOrders: boolean;
  217. completedOrders: Array<{
  218. pickOrderId: number;
  219. pickOrderCode: string;
  220. consoCode: string;
  221. isCompleted: boolean;
  222. stockOutStatus: string;
  223. totalLines: number;
  224. unfinishedLines: number;
  225. }>;
  226. allOrders: Array<{
  227. pickOrderId: number;
  228. pickOrderCode: string;
  229. consoCode: string;
  230. isCompleted: boolean;
  231. stockOutStatus: string;
  232. totalLines: number;
  233. unfinishedLines: number;
  234. }>;
  235. };
  236. }
  237. export interface UpdateSuggestedLotLineIdRequest {
  238. newLotLineId: number;
  239. }
  240. export interface stockReponse{
  241. id: number;
  242. status: string;
  243. qty: number;
  244. lotId: number;
  245. lotNo: string;
  246. location: string;
  247. availableQty: number;
  248. noLot: boolean;
  249. }
  250. export interface FGPickOrderResponse {
  251. // 新增:支持多个 pick orders
  252. doPickOrderId: number;
  253. pickOrderIds?: number[];
  254. pickOrderCodes?: string[]; // 改为数组
  255. deliveryOrderIds?: number[];
  256. deliveryNos?: string[]; // 改为数组
  257. numberOfPickOrders?: number;
  258. lineCountsPerPickOrder?: number[];// 新增:pick order 数量
  259. // 保留原有字段用于向后兼容(显示第一个 pick order)
  260. pickOrderId: number;
  261. pickOrderCode: string;
  262. pickOrderConsoCode: string;
  263. pickOrderTargetDate: string;
  264. pickOrderStatus: string;
  265. deliveryOrderId: number;
  266. deliveryNo: string;
  267. deliveryDate: string;
  268. shopId: number;
  269. shopCode: string;
  270. shopName: string;
  271. shopAddress: string;
  272. ticketNo: string;
  273. shopPoNo: string;
  274. numberOfCartons: number;
  275. DepartureTime: string;
  276. truckLanceCode: string;
  277. storeId: string;
  278. qrCodeData: number;
  279. }
  280. export interface DoPickOrderDetail {
  281. doPickOrder: {
  282. id: number;
  283. store_id: string;
  284. ticket_no: string;
  285. ticket_status: string;
  286. truck_id: number;
  287. truck_departure_time: string;
  288. shop_id: number;
  289. handled_by: number | null;
  290. loading_sequence: number;
  291. ticket_release_time: string | null;
  292. TruckLanceCode: string;
  293. ShopCode: string;
  294. ShopName: string;
  295. RequiredDeliveryDate: string;
  296. };
  297. pickOrders: Array<{
  298. pick_order_id: number;
  299. pick_order_code: string;
  300. do_order_id: number;
  301. delivery_order_code: string;
  302. consoCode: string;
  303. status: string;
  304. targetDate: string;
  305. }>;
  306. selectedPickOrderId: number;
  307. lotDetails: any[]; // 使用现有的 lot detail 结构
  308. pickOrderCodes?: string;
  309. deliveryNos?: string;
  310. }
  311. export interface AutoAssignReleaseByStoreRequest {
  312. userId: number;
  313. storeId: string; // "2/F" | "4/F"
  314. }
  315. export interface UpdateDoPickOrderHideStatusRequest {
  316. id: number;
  317. name: string;
  318. code: string;
  319. type: string;
  320. message: string;
  321. errorPosition: string;
  322. }
  323. export interface CompletedDoPickOrderResponse {
  324. id: number;
  325. doPickOrderRecordId: number; // ✅ 新增
  326. recordId: number | null;
  327. pickOrderId: number;
  328. pickOrderIds: number[]; // 新增:所有 pick order IDs
  329. pickOrderCode: string;
  330. pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔)
  331. pickOrderConsoCode: string;
  332. pickOrderStatus: string;
  333. deliveryOrderId: number;
  334. deliveryOrderIds: number[]; // 新增:所有 delivery order IDs
  335. deliveryNo: string;
  336. deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔)
  337. deliveryDate: string;
  338. shopId: number;
  339. shopCode: string;
  340. shopName: string;
  341. shopAddress: string;
  342. ticketNo: string;
  343. shopPoNo: string;
  344. numberOfCartons: number;
  345. truckLanceCode: string;
  346. DepartureTime: string; // 新增
  347. storeId: string;
  348. completedDate: string;
  349. fgPickOrders: FGPickOrderResponse[];
  350. deliveryNoteCode: number;
  351. }
  352. // 新增:搜索参数接口
  353. export interface CompletedDoPickOrderSearchParams {
  354. targetDate?: string;
  355. shopName?: string;
  356. deliveryNoteCode?: string;
  357. }
  358. export interface PickExecutionIssue {
  359. id: number;
  360. pickOrderId: number;
  361. pickOrderCode: string;
  362. pickOrderCreateDate: string;
  363. pickExecutionDate: string;
  364. pickOrderLineId: number;
  365. issueNo: string;
  366. joPickOrderId?: number;
  367. doPickOrderId?: number;
  368. issueCategory: string;
  369. itemId: number;
  370. itemCode: string;
  371. itemDescription: string;
  372. lotId?: number;
  373. lotNo?: string;
  374. storeLocation?: string;
  375. requiredQty: number;
  376. actualPickQty?: number;
  377. missQty?: number;
  378. badItemQty?: number;
  379. issueRemark?: string;
  380. pickerName?: string;
  381. handleStatus: string;
  382. handleDate?: string;
  383. handledBy?: string;
  384. created: string;
  385. createdBy: string;
  386. modified: string;
  387. modifiedBy: string;
  388. }
  389. export interface FetchPickExecutionIssuesParams {
  390. type?: "jo" | "do" | "material";
  391. }
  392. export const fetchAllPickExecutionIssues = cache(
  393. async (params?: FetchPickExecutionIssuesParams): Promise<PickExecutionIssue[]> => {
  394. const queryParams = new URLSearchParams();
  395. if (params?.type) {
  396. queryParams.append("type", params.type);
  397. }
  398. const url = `${BASE_API_URL}/pickExecution/issues/all${
  399. queryParams.toString() ? `?${queryParams.toString()}` : ""
  400. }`;
  401. const result = await serverFetchJson<PickExecutionIssue[]>(url, {
  402. method: "GET",
  403. headers: { "Content-Type": "application/json" },
  404. });
  405. return result ?? [];
  406. }
  407. );
  408. export interface UpdatePickExecutionIssueRequest {
  409. issueId: number;
  410. handleStatus: string;
  411. handleDate?: string;
  412. handledBy?: string;
  413. handleRemark?: string;
  414. }
  415. export interface StoreLaneSummary {
  416. storeId: string;
  417. rows: LaneRow[];
  418. }
  419. export interface LaneRow {
  420. truckDepartureTime: string;
  421. lanes: LaneBtn[];
  422. }
  423. export interface LaneBtn {
  424. truckLanceCode: string;
  425. unassigned: number;
  426. total: number;
  427. }
  428. export interface QrPickBatchSubmitRequest {
  429. userId: number;
  430. lines: QrPickSubmitLineRequest[];
  431. }
  432. export interface QrPickSubmitLineRequest {
  433. stockOutLineId: number;
  434. pickOrderLineId: number;
  435. inventoryLotLineId: number | null; // ✅ 修复:应该是 nullable
  436. requiredQty: number | null; // ✅ 修复:添加 requiredQty
  437. actualPickQty: number | null; // ✅ 修复:添加 actualPickQty
  438. stockOutLineStatus: string | null; // ✅ 修复:添加 stockOutLineStatus
  439. pickOrderConsoCode: string | null; // ✅ 修复:添加 pickOrderConsoCode
  440. noLot: boolean; // ✅ 修复:添加 noLot
  441. }
  442. export interface UpdateStockOutLineStatusByQRCodeAndLotNoRequest {
  443. pickOrderLineId: number,
  444. inventoryLotNo: string,
  445. stockOutLineId: number,
  446. itemId: number,
  447. status: string
  448. }
  449. export interface batchSubmitListRequest {
  450. userId: number;
  451. lines: batchSubmitListLineRequest[];
  452. }
  453. export interface batchSubmitListLineRequest {
  454. stockOutLineId: number; // 修复:改为 stockOutLineId(不是 stockInLineId)
  455. pickOrderLineId: number;
  456. inventoryLotLineId: number | null; // 添加:后端需要的字段
  457. requiredQty: number;
  458. actualPickQty: number;
  459. stockOutLineStatus: string;
  460. pickOrderConsoCode: string;
  461. noLot: boolean;
  462. // 移除:lotNo 和 stockInLineId(后端不需要)
  463. }
  464. export const batchSubmitList = async (data: batchSubmitListRequest) => {
  465. // ✅ 确保发送的是对象,不是数组
  466. const requestBody = Array.isArray(data) ? data[0] : data;
  467. console.log("📤 batchSubmitList - Request body type:", Array.isArray(requestBody) ? "array" : "object");
  468. console.log("📤 batchSubmitList - Request body:", JSON.stringify(requestBody, null, 2));
  469. const response = await serverFetchJson<PostPickOrderResponse<batchSubmitListRequest>>(
  470. `${BASE_API_URL}/stockOutLine/batchSubmitList`,
  471. {
  472. method: "POST",
  473. body: JSON.stringify(requestBody), // ✅ 确保是对象
  474. headers: {
  475. "Content-Type": "application/json", // ✅ 明确指定 Content-Type
  476. },
  477. },
  478. );
  479. return response;
  480. };
  481. export const updateStockOutLineStatusByQRCodeAndLotNo = async (data: UpdateStockOutLineStatusByQRCodeAndLotNoRequest) => {
  482. console.log(" Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data);
  483. try {
  484. const response = await serverFetchJson<PostPickOrderResponse<UpdateStockOutLineStatusByQRCodeAndLotNoRequest>>(
  485. `${BASE_API_URL}/stockOutLine/updateStatusByQRCodeAndLotNo`,
  486. {
  487. method: "POST",
  488. headers: {
  489. "Content-Type": "application/json",
  490. },
  491. body: JSON.stringify(data),
  492. },
  493. );
  494. console.log("✅ Frontend: API call successful, response:", response);
  495. return response;
  496. } catch (error) {
  497. console.error("❌ Frontend: API call failed:", error);
  498. throw error;
  499. }
  500. };
  501. export const batchQrSubmit = async (data: QrPickBatchSubmitRequest) => {
  502. const response = await serverFetchJson<PostPickOrderResponse<QrPickBatchSubmitRequest>>(
  503. `${BASE_API_URL}/stockOutLine/batchQrSubmit`,
  504. {
  505. method: "POST",
  506. body: JSON.stringify(data),
  507. },
  508. );
  509. return response;
  510. };
  511. export const fetchDoPickOrderDetail = async (
  512. doPickOrderId: number,
  513. selectedPickOrderId?: number
  514. ): Promise<DoPickOrderDetail> => {
  515. const url = selectedPickOrderId
  516. ? `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}?selectedPickOrderId=${selectedPickOrderId}`
  517. : `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}`;
  518. const response = await serverFetchJson<DoPickOrderDetail>(url, {
  519. method: "GET",
  520. });
  521. return response;
  522. };
  523. export const updatePickExecutionIssueStatus = async (
  524. data: UpdatePickExecutionIssueRequest
  525. ): Promise<PostPickOrderResponse> => {
  526. const result = await serverFetchJson<PostPickOrderResponse>(
  527. `${BASE_API_URL}/pickExecution/updateIssueStatus`,
  528. {
  529. method: "POST",
  530. body: JSON.stringify(data),
  531. headers: { "Content-Type": "application/json" },
  532. }
  533. );
  534. revalidateTag("pickExecutionIssues");
  535. return result;
  536. };
  537. export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise<StoreLaneSummary> {
  538. const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD');
  539. const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`;
  540. const response = await serverFetchJson<StoreLaneSummary>(
  541. url,
  542. {
  543. method: "GET",
  544. cache: "no-store",
  545. next: { revalidate: 0 }
  546. }
  547. );
  548. return response;
  549. }
  550. // 按车道分配订单
  551. export async function assignByLane(
  552. userId: number,
  553. storeId: string,
  554. truckLanceCode: string,
  555. truckDepartureTime?: string,
  556. requiredDate?: string
  557. ): Promise<any> {
  558. const response = await serverFetchJson(
  559. `${BASE_API_URL}/doPickOrder/assign-by-lane`,
  560. {
  561. method: "POST",
  562. headers: {
  563. "Content-Type": "application/json",
  564. },
  565. body: JSON.stringify({
  566. userId,
  567. storeId,
  568. truckLanceCode,
  569. truckDepartureTime,
  570. requiredDate,
  571. }),
  572. }
  573. );
  574. return response;
  575. }
  576. // 新增:获取已完成的 DO Pick Orders API
  577. export const fetchCompletedDoPickOrders = async (
  578. userId: number,
  579. searchParams?: CompletedDoPickOrderSearchParams
  580. ): Promise<CompletedDoPickOrderResponse[]> => {
  581. const params = new URLSearchParams();
  582. if (searchParams?.deliveryNoteCode) {
  583. params.append('deliveryNoteCode', searchParams.deliveryNoteCode);
  584. }
  585. if (searchParams?.shopName) {
  586. params.append('shopName', searchParams.shopName);
  587. }
  588. if (searchParams?.targetDate) {
  589. params.append('targetDate', searchParams.targetDate);
  590. }
  591. const queryString = params.toString();
  592. const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders/${userId}${queryString ? `?${queryString}` : ''}`;
  593. const response = await serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
  594. method: "GET",
  595. });
  596. return response;
  597. };
  598. export const updatePickOrderHideStatus = async (pickOrderId: number, hide: boolean) => {
  599. const response = await serverFetchJson<UpdateDoPickOrderHideStatusRequest>(
  600. `${BASE_API_URL}/pickOrder/update-hide-status/${pickOrderId}?hide=${hide}`,
  601. {
  602. method: "POST",
  603. headers: { "Content-Type": "application/json" },
  604. },
  605. );
  606. revalidateTag("pickorder");
  607. return response;
  608. };
  609. export const fetchFGPickOrders = async (pickOrderId: number) => {
  610. const response = await serverFetchJson<FGPickOrderResponse>(
  611. `${BASE_API_URL}/pickOrder/fg-pick-orders/${pickOrderId}`,
  612. {
  613. method: "GET",
  614. },
  615. );
  616. return response;
  617. };
  618. export const fetchFGPickOrdersByUserId = async (userId: number) => {
  619. const response = await serverFetchJson<FGPickOrderResponse[]>(
  620. `${BASE_API_URL}/pickOrder/fg-pick-orders/${userId}`,
  621. {
  622. method: "GET",
  623. },
  624. );
  625. return response;
  626. };
  627. export const updateSuggestedLotLineId = async (suggestedPickLotId: number, newLotLineId: number) => {
  628. const response = await serverFetchJson<PostPickOrderResponse<UpdateSuggestedLotLineIdRequest>>(
  629. `${BASE_API_URL}/suggestedPickLot/update-suggested-lot/${suggestedPickLotId}`,
  630. {
  631. method: "POST",
  632. body: JSON.stringify({ newLotLineId }),
  633. headers: { "Content-Type": "application/json" },
  634. },
  635. );
  636. revalidateTag("pickorder");
  637. return response;
  638. };
  639. export const autoAssignAndReleasePickOrder = async (userId: number): Promise<AutoAssignReleaseResponse> => {
  640. const response = await serverFetchJson<AutoAssignReleaseResponse>(
  641. `${BASE_API_URL}/pickOrder/auto-assign-release/${userId}`,
  642. {
  643. method: "POST",
  644. headers: { "Content-Type": "application/json" },
  645. },
  646. );
  647. revalidateTag("pickorder");
  648. return response;
  649. };
  650. export const autoAssignAndReleasePickOrderByStore = async (
  651. userId: number,
  652. storeId: string
  653. ): Promise<AutoAssignReleaseResponse> => {
  654. const url = `${BASE_API_URL}/pickOrder/auto-assign-release-by-store?userId=${userId}&storeId=${encodeURIComponent(storeId)}`;
  655. const response = await serverFetchJson<AutoAssignReleaseResponse>(url, {
  656. method: "POST",
  657. headers: { "Content-Type": "application/json" },
  658. // no body
  659. next: { tags: ["pickorder"] },
  660. });
  661. revalidateTag("pickorder");
  662. return response;
  663. };
  664. export const checkPickOrderCompletion = async (userId: number): Promise<PickOrderCompletionResponse> => {
  665. const response = await serverFetchJson<PickOrderCompletionResponse>(
  666. `${BASE_API_URL}/pickOrder/check-pick-completion/${userId}`,
  667. {
  668. method: "GET",
  669. headers: { "Content-Type": "application/json" },
  670. },
  671. );
  672. return response;
  673. };
  674. export const recordPickExecutionIssue = async (data: PickExecutionIssueData) => {
  675. const result = await serverFetchJson<PostPickOrderResponse>(
  676. `${BASE_API_URL}/pickExecution/recordIssue`,
  677. {
  678. method: "POST",
  679. body: JSON.stringify(data),
  680. headers: { "Content-Type": "application/json" },
  681. },
  682. );
  683. revalidateTag("pickorder");
  684. return result;
  685. };
  686. export const resuggestPickOrder = async (pickOrderId: number) => {
  687. console.log("Resuggesting pick order:", pickOrderId);
  688. const result = await serverFetchJson<PostPickOrderResponse>(
  689. `${BASE_API_URL}/suggestedPickLot/resuggest/${pickOrderId}`,
  690. {
  691. method: "POST",
  692. headers: { "Content-Type": "application/json" },
  693. },
  694. );
  695. revalidateTag("pickorder");
  696. return result;
  697. };
  698. export const updateStockOutLineStatus = async (data: {
  699. id: number;
  700. status: string;
  701. qty?: number;
  702. remarks?: string;
  703. }) => {
  704. console.log("Updating stock out line status:", data);
  705. const result = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  706. `${BASE_API_URL}/stockOutLine/updateStatus`,
  707. {
  708. method: "POST",
  709. body: JSON.stringify(data),
  710. headers: { "Content-Type": "application/json" },
  711. },
  712. );
  713. revalidateTag("pickorder");
  714. return result;
  715. };
  716. // Missing function 1: newassignPickOrder
  717. export const newassignPickOrder = async (data: AssignPickOrderInputs) => {
  718. const response = await serverFetchJson<PostPickOrderResponse>(
  719. `${BASE_API_URL}/pickOrder/assign`,
  720. {
  721. method: "POST",
  722. body: JSON.stringify(data),
  723. headers: { "Content-Type": "application/json" },
  724. },
  725. );
  726. revalidateTag("pickorder");
  727. return response;
  728. };
  729. // Missing function 2: releaseAssignedPickOrders
  730. export const releaseAssignedPickOrders = async (data: AssignPickOrderInputs) => {
  731. const response = await serverFetchJson<PostPickOrderResponse>(
  732. `${BASE_API_URL}/pickOrder/release-assigned`,
  733. {
  734. method: "POST",
  735. body: JSON.stringify(data),
  736. headers: { "Content-Type": "application/json" },
  737. },
  738. );
  739. revalidateTag("pickorder");
  740. return response;
  741. };
  742. // Get latest group name and create it automatically
  743. export const getLatestGroupNameAndCreate = async () => {
  744. return serverFetchJson<PostPickOrderResponse>(
  745. `${BASE_API_URL}/pickOrder/groups/latest`,
  746. {
  747. method: "GET",
  748. next: { tags: ["pickorder"] },
  749. },
  750. );
  751. };
  752. // Get all groups
  753. export const fetchAllGroups = cache(async () => {
  754. return serverFetchJson<PickOrderGroupInfo[]>(
  755. `${BASE_API_URL}/pickOrder/groups/list`,
  756. {
  757. method: "GET",
  758. next: { tags: ["pickorder"] },
  759. },
  760. );
  761. });
  762. // Create or update groups (flexible - can handle both cases)
  763. export const createOrUpdateGroups = async (data: SavePickOrderGroupRequest) => {
  764. const response = await serverFetchJson<PostPickOrderResponse>(
  765. `${BASE_API_URL}/pickOrder/groups/create`,
  766. {
  767. method: "POST",
  768. body: JSON.stringify(data),
  769. headers: { "Content-Type": "application/json" },
  770. },
  771. );
  772. revalidateTag("pickorder");
  773. return response;
  774. };
  775. // Get groups by pick order ID
  776. export const fetchGroupsByPickOrderId = cache(async (pickOrderId: number) => {
  777. return serverFetchJson<PickOrderGroupInfo[]>(
  778. `${BASE_API_URL}/pickOrder/groups/${pickOrderId}`,
  779. {
  780. method: "GET",
  781. next: { tags: ["pickorder"] },
  782. },
  783. );
  784. });
  785. export const fetchPickOrderDetails = cache(async (ids: string) => {
  786. return serverFetchJson<GetPickOrderInfoResponse>(
  787. `${BASE_API_URL}/pickOrder/detail/${ids}`,
  788. {
  789. method: "GET",
  790. next: { tags: ["pickorder"] },
  791. },
  792. );
  793. });
  794. export interface PickOrderLotDetailResponse {
  795. lotId: number | null; // ✅ 改为可空
  796. lotNo: string | null; // ✅ 改为可空
  797. expiryDate: string | null; // ✅ 改为可空
  798. location: string | null; // ✅ 改为可空
  799. stockUnit: string | null;
  800. inQty: number | null;
  801. availableQty: number | null; // ✅ 改为可空
  802. requiredQty: number;
  803. actualPickQty: number;
  804. suggestedPickLotId: number | null;
  805. lotStatus: string | null;
  806. lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected';
  807. stockOutLineId: number | null; // ✅ 添加
  808. stockOutLineStatus: string | null; // ✅ 添加
  809. stockOutLineQty: number | null; // ✅ 添加
  810. totalPickedByAllPickOrders: number | null; // ✅ 添加
  811. remainingAfterAllPickOrders: number | null; // ✅ 添加
  812. noLot: boolean; // ✅ 关键:添加 noLot 字段
  813. outQty?: number; // ✅ 添加
  814. holdQty?: number; // ✅ 添加
  815. }
  816. interface ALLPickOrderLotDetailResponse {
  817. // Pick Order Information
  818. pickOrderId: number;
  819. pickOrderCode: string;
  820. pickOrderTargetDate: string;
  821. pickOrderType: string;
  822. pickOrderStatus: string;
  823. pickOrderAssignTo: number;
  824. groupName: string;
  825. // Pick Order Line Information
  826. pickOrderLineId: number;
  827. pickOrderLineRequiredQty: number;
  828. pickOrderLineStatus: string;
  829. // Item Information
  830. itemId: number;
  831. itemCode: string;
  832. itemName: string;
  833. uomCode: string;
  834. uomDesc: string;
  835. // Lot Information
  836. lotId: number;
  837. lotNo: string;
  838. expiryDate: string;
  839. location: string;
  840. outQty: number;
  841. holdQty: number;
  842. stockUnit: string;
  843. availableQty: number;
  844. requiredQty: number;
  845. actualPickQty: number;
  846. totalPickedByAllPickOrders: number;
  847. suggestedPickLotId: number;
  848. lotStatus: string;
  849. stockOutLineId?: number;
  850. stockOutLineStatus?: string;
  851. stockOutLineQty?: number;
  852. lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
  853. processingStatus: string;
  854. }
  855. interface SuggestionWithStatus {
  856. suggestionId: number;
  857. suggestionQty: number;
  858. suggestionCreated: string;
  859. lotLineId: number;
  860. lotNo: string;
  861. expiryDate: string;
  862. location: string;
  863. stockOutLineId?: number;
  864. stockOutLineStatus?: string;
  865. stockOutLineQty?: number;
  866. suggestionStatus: 'active' | 'completed' | 'rejected' | 'in_progress' | 'unknown';
  867. }
  868. // 在 actions.ts 中修改接口定义
  869. export interface FGPickOrderHierarchicalResponse {
  870. fgInfo: {
  871. doPickOrderId: number;
  872. ticketNo: string;
  873. storeId: string;
  874. shopCode: string;
  875. shopName: string;
  876. truckLanceCode: string;
  877. departureTime: string;
  878. };
  879. pickOrders: Array<{
  880. pickOrderId: number;
  881. pickOrderCode: string;
  882. doOrderId: number;
  883. deliveryOrderCode: string;
  884. consoCode: string;
  885. status: string;
  886. targetDate: string;
  887. pickOrderLines: Array<{
  888. id: number;
  889. requiredQty: number;
  890. status: string;
  891. item: {
  892. id: number;
  893. code: string;
  894. name: string;
  895. uomCode: string;
  896. uomDesc: string;
  897. };
  898. lots: Array<any>; // 可以是空数组
  899. }>;
  900. }>;
  901. }
  902. export interface CheckCompleteResponse {
  903. id: number | null;
  904. name: string;
  905. code: string;
  906. type?: string;
  907. message: string | null;
  908. errorPosition: string;
  909. }
  910. export interface LotSubstitutionConfirmRequest {
  911. pickOrderLineId: number;
  912. stockOutLineId: number;
  913. originalSuggestedPickLotId: number;
  914. newInventoryLotNo: string;
  915. }
  916. export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => {
  917. const response = await serverFetchJson<PostPickOrderResponse>(
  918. `${BASE_API_URL}/pickOrder/lot-substitution/confirm`,
  919. {
  920. method: "POST",
  921. body: JSON.stringify(data),
  922. headers: { "Content-Type": "application/json" },
  923. },
  924. );
  925. revalidateTag("pickorder");
  926. return response;
  927. };
  928. export const checkAndCompletePickOrderByConsoCode = async (consoCode: string): Promise<CheckCompleteResponse> => {
  929. const response = await serverFetchJson<CheckCompleteResponse>(
  930. `${BASE_API_URL}/pickOrder/check-complete/${consoCode}`,
  931. {
  932. method: "POST",
  933. headers: {
  934. "Content-Type": "application/json",
  935. },
  936. },
  937. );
  938. revalidateTag("pickorder");
  939. return response;
  940. };
  941. export const fetchPickOrderDetailsOptimized = cache(async (userId?: number) => {
  942. const url = userId
  943. ? `${BASE_API_URL}/pickOrder/detail-optimized?userId=${userId}`
  944. : `${BASE_API_URL}/pickOrder/detail-optimized`;
  945. return serverFetchJson<any[]>(
  946. url,
  947. {
  948. method: "GET",
  949. next: { tags: ["pickorder"] },
  950. },
  951. );
  952. });
  953. const fetchSuggestionsWithStatus = async (pickOrderLineId: number) => {
  954. try {
  955. const response = await fetch(`/api/suggestedPickLot/suggestions-with-status/${pickOrderLineId}`);
  956. const suggestions: SuggestionWithStatus[] = await response.json();
  957. return suggestions;
  958. } catch (error) {
  959. console.error('Error fetching suggestions with status:', error);
  960. return [];
  961. }
  962. };
  963. export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): Promise<any> => {
  964. try {
  965. console.log("🔍 Fetching hierarchical pick order lots for userId:", userId);
  966. const data = await serverFetchJson<any>(
  967. `${BASE_API_URL}/pickOrder/all-lots-hierarchical/${userId}`,
  968. {
  969. method: 'GET',
  970. next: { tags: ["pickorder"] },
  971. }
  972. );
  973. console.log(" Fetched hierarchical lot details:", data);
  974. return data;
  975. } catch (error) {
  976. console.error("❌ Error fetching hierarchical lot details:", error);
  977. return {
  978. pickOrder: null,
  979. pickOrderLines: []
  980. };
  981. }
  982. });
  983. export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId: number): Promise<{
  984. fgInfo: any;
  985. pickOrders: any[];
  986. }> => {
  987. try {
  988. console.log("🔍 Fetching lot details for doPickOrderRecordId:", doPickOrderRecordId);
  989. const data = await serverFetchJson<{
  990. fgInfo: any;
  991. pickOrders: any[];
  992. }>(
  993. `${BASE_API_URL}/pickOrder/lot-details-by-do-pick-order-record/${doPickOrderRecordId}`,
  994. {
  995. method: 'GET',
  996. next: { tags: ["pickorder"] },
  997. }
  998. );
  999. console.log(" Fetched hierarchical lot details:", data);
  1000. return data;
  1001. } catch (error) {
  1002. console.error("❌ Error fetching lot details:", error);
  1003. return {
  1004. fgInfo: null,
  1005. pickOrders: []
  1006. };
  1007. }
  1008. };
  1009. // Update the existing function to use the non-auto-assign endpoint
  1010. export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => {
  1011. try {
  1012. console.log("🔍 Fetching all pick order line lot details for userId:", userId);
  1013. // Use the non-auto-assign endpoint
  1014. const data = await serverFetchJson<any[]>(
  1015. `${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`,
  1016. {
  1017. method: 'GET',
  1018. next: { tags: ["pickorder"] },
  1019. }
  1020. );
  1021. console.log(" Fetched lot details:", data);
  1022. return data;
  1023. } catch (error) {
  1024. console.error("❌ Error fetching lot details:", error);
  1025. return [];
  1026. }
  1027. });
  1028. export const fetchAllPickOrderDetails = cache(async (userId?: number) => {
  1029. if (!userId) {
  1030. return {
  1031. consoCode: null,
  1032. pickOrders: [],
  1033. items: []
  1034. };
  1035. }
  1036. // Use the correct endpoint with userId in the path
  1037. const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`;
  1038. return serverFetchJson<GetPickOrderInfoResponse>(
  1039. url,
  1040. {
  1041. method: "GET",
  1042. next: { tags: ["pickorder"] },
  1043. },
  1044. );
  1045. });
  1046. export const fetchPickOrderLineLotDetails = cache(async (pickOrderLineId: number) => {
  1047. return serverFetchJson<PickOrderLotDetailResponse[]>(
  1048. `${BASE_API_URL}/pickOrder/lot-details/${pickOrderLineId}`,
  1049. {
  1050. method: "GET",
  1051. next: { tags: ["pickorder"] },
  1052. },
  1053. );
  1054. });
  1055. export const createPickOrder = async (data: SavePickOrderRequest) => {
  1056. console.log(data);
  1057. const po = await serverFetchJson<PostPickOrderResponse>(
  1058. `${BASE_API_URL}/pickOrder/create`,
  1059. {
  1060. method: "POST",
  1061. body: JSON.stringify(data),
  1062. headers: { "Content-Type": "application/json" },
  1063. },
  1064. );
  1065. revalidateTag("pickorder");
  1066. return po;
  1067. }
  1068. export const assignPickOrder = async (ids: number[]) => {
  1069. const pickOrder = await serverFetchJson<any>(
  1070. `${BASE_API_URL}/pickOrder/conso`,
  1071. {
  1072. method: "POST",
  1073. body: JSON.stringify({ ids: ids }),
  1074. headers: { "Content-Type": "application/json" },
  1075. },
  1076. );
  1077. // revalidateTag("po");
  1078. return pickOrder;
  1079. };
  1080. export const consolidatePickOrder = async (ids: number[]) => {
  1081. const pickOrder = await serverFetchJson<any>(
  1082. `${BASE_API_URL}/pickOrder/conso`,
  1083. {
  1084. method: "POST",
  1085. body: JSON.stringify({ ids: ids }),
  1086. headers: { "Content-Type": "application/json" },
  1087. },
  1088. );
  1089. return pickOrder;
  1090. };
  1091. export const consolidatePickOrder_revert = async (ids: number[]) => {
  1092. const pickOrder = await serverFetchJson<any>(
  1093. `${BASE_API_URL}/pickOrder/deconso`,
  1094. {
  1095. method: "POST",
  1096. body: JSON.stringify({ ids: ids }),
  1097. headers: { "Content-Type": "application/json" },
  1098. },
  1099. );
  1100. // revalidateTag("po");
  1101. return pickOrder;
  1102. };
  1103. export const fetchPickOrderClient = cache(
  1104. async (queryParams?: Record<string, any>) => {
  1105. if (queryParams) {
  1106. const queryString = new URLSearchParams(queryParams).toString();
  1107. return serverFetchJson<RecordsRes<PickOrderResult[]>>(
  1108. `${BASE_API_URL}/pickOrder/getRecordByPage?${queryString}`,
  1109. {
  1110. method: "GET",
  1111. next: { tags: ["pickorder"] },
  1112. },
  1113. );
  1114. } else {
  1115. return serverFetchJson<RecordsRes<PickOrderResult[]>>(
  1116. `${BASE_API_URL}/pickOrder/getRecordByPage`,
  1117. {
  1118. method: "GET",
  1119. next: { tags: ["pickorder"] },
  1120. },
  1121. );
  1122. }
  1123. },
  1124. );
  1125. export const fetchPickOrderWithStockClient = cache(
  1126. async (queryParams?: Record<string, any>) => {
  1127. if (queryParams) {
  1128. const queryString = new URLSearchParams(queryParams).toString();
  1129. return serverFetchJson<RecordsRes<GetPickOrderInfo[]>>(
  1130. `${BASE_API_URL}/pickOrder/getRecordByPageWithStock?${queryString}`,
  1131. {
  1132. method: "GET",
  1133. next: { tags: ["pickorder"] },
  1134. },
  1135. );
  1136. } else {
  1137. return serverFetchJson<RecordsRes<GetPickOrderInfo[]>>(
  1138. `${BASE_API_URL}/pickOrder/getRecordByPageWithStock`,
  1139. {
  1140. method: "GET",
  1141. next: { tags: ["pickorder"] },
  1142. },
  1143. );
  1144. }
  1145. },
  1146. );
  1147. export const fetchConsoPickOrderClient = cache(
  1148. async (queryParams?: Record<string, any>) => {
  1149. if (queryParams) {
  1150. const queryString = new URLSearchParams(queryParams).toString();
  1151. return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(
  1152. `${BASE_API_URL}/pickOrder/getRecordByPage-conso?${queryString}`,
  1153. {
  1154. method: "GET",
  1155. next: { tags: ["pickorder"] },
  1156. },
  1157. );
  1158. } else {
  1159. return serverFetchJson<RecordsRes<ConsoPickOrderResult[]>>(
  1160. `${BASE_API_URL}/pickOrder/getRecordByPage-conso`,
  1161. {
  1162. method: "GET",
  1163. next: { tags: ["pickorder"] },
  1164. },
  1165. );
  1166. }
  1167. },
  1168. );
  1169. export const fetchPickOrderLineClient = cache(
  1170. async (queryParams?: Record<string, any>) => {
  1171. if (queryParams) {
  1172. const queryString = new URLSearchParams(queryParams).toString();
  1173. return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(
  1174. `${BASE_API_URL}/pickOrder/get-pickorder-line-byPage?${queryString}`,
  1175. {
  1176. method: "GET",
  1177. next: { tags: ["pickorder"] },
  1178. },
  1179. );
  1180. } else {
  1181. return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(
  1182. `${BASE_API_URL}/pickOrder/get-pickorder-line-byPage`,
  1183. {
  1184. method: "GET",
  1185. next: { tags: ["pickorder"] },
  1186. },
  1187. );
  1188. }
  1189. },
  1190. );
  1191. export const fetchStockOutLineClient = cache(
  1192. async (pickOrderLineId: number) => {
  1193. return serverFetchJson<StockOutLine[]>(
  1194. `${BASE_API_URL}/stockOutLine/getByPickOrderLineId/${pickOrderLineId}`,
  1195. {
  1196. method: "GET",
  1197. next: { tags: ["pickorder"] },
  1198. },
  1199. );
  1200. },
  1201. );
  1202. export const fetchConsoDetail = cache(async (consoCode: string) => {
  1203. return serverFetchJson<PreReleasePickOrderSummary>(
  1204. `${BASE_API_URL}/pickOrder/pre-release-info/${consoCode}`,
  1205. {
  1206. method: "GET",
  1207. next: { tags: ["pickorder"] },
  1208. },
  1209. );
  1210. });
  1211. export const releasePickOrder = async (data: ReleasePickOrderInputs) => {
  1212. console.log(data);
  1213. console.log(JSON.stringify(data));
  1214. const po = await serverFetchJson<{ consoCode: string }>(
  1215. `${BASE_API_URL}/pickOrder/releaseConso`,
  1216. {
  1217. method: "POST",
  1218. body: JSON.stringify(data),
  1219. headers: { "Content-Type": "application/json" },
  1220. },
  1221. );
  1222. revalidateTag("pickorder");
  1223. return po;
  1224. };
  1225. export const createStockOutLine = async (data: CreateStockOutLine) => {
  1226. console.log("triggering");
  1227. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1228. `${BASE_API_URL}/stockOutLine/create`,
  1229. {
  1230. method: "POST",
  1231. body: JSON.stringify(data),
  1232. headers: { "Content-Type": "application/json" },
  1233. },
  1234. );
  1235. revalidateTag("pickorder");
  1236. return po;
  1237. };
  1238. export const updateStockOutLine = async (data: UpdateStockOutLine) => {
  1239. console.log(data);
  1240. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1241. `${BASE_API_URL}/stockOutLine/update`,
  1242. {
  1243. method: "POST",
  1244. body: JSON.stringify(data),
  1245. headers: { "Content-Type": "application/json" },
  1246. },
  1247. );
  1248. revalidateTag("pickorder");
  1249. return po;
  1250. };
  1251. export const completeConsoPickOrder = async (consoCode: string) => {
  1252. const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(
  1253. `${BASE_API_URL}/pickOrder/consoPickOrder/complete/${consoCode}`,
  1254. {
  1255. method: "POST",
  1256. headers: { "Content-Type": "application/json" },
  1257. },
  1258. );
  1259. revalidateTag("pickorder");
  1260. return po;
  1261. };
  1262. export const fetchConsoStatus = cache(async (consoCode: string) => {
  1263. return serverFetchJson<{ status: string }>(
  1264. `${BASE_API_URL}/stockOut/get-status/${consoCode}`,
  1265. {
  1266. method: "GET",
  1267. next: { tags: ["pickorder"] },
  1268. },
  1269. );
  1270. });
  1271. export interface ReleasedDoPickOrderResponse {
  1272. id: number;
  1273. storeId: string;
  1274. ticketNo: string;
  1275. pickOrderId: number;
  1276. ticketStatus: string;
  1277. doOrderId: number;
  1278. shopId: number;
  1279. handledBy: number;
  1280. ticketReleaseTime: string;
  1281. }
  1282. export const fetchReleasedDoPickOrders = async (): Promise<ReleasedDoPickOrderResponse[]> => {
  1283. const response = await serverFetchJson<ReleasedDoPickOrderResponse[]>(
  1284. `${BASE_API_URL}/doPickOrder/released`,
  1285. {
  1286. method: "GET",
  1287. },
  1288. );
  1289. return response;
  1290. };