FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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