FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 

1793 righe
50 KiB

  1. "use server";
  2. import { cache } from 'react';
  3. import { Pageable, ServerFetchError, serverFetchBlob, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
  4. import { JobOrder, JoStatus, Machine, Operator } from ".";
  5. import { BASE_API_URL } from "@/config/api";
  6. import { revalidateTag } from "next/cache";
  7. import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
  8. import { FileResponse } from "@/app/api/pdf/actions";
  9. export interface SaveJo {
  10. bomId: number;
  11. planStart: string;
  12. planEnd: string;
  13. reqQty: number;
  14. type: string;
  15. //jobType?: string;
  16. jobTypeId?: number;
  17. productionPriority?: number;
  18. }
  19. export interface SaveJoResponse {
  20. id: number;
  21. }
  22. export interface SearchJoResultRequest extends Pageable {
  23. code: string;
  24. itemName?: string;
  25. planStart?: string;
  26. planStartTo?: string;
  27. jobTypeName?: string;
  28. joSearchStatus?: string;
  29. }
  30. export interface productProcessLineQtyRequest {
  31. productProcessLineId: number;
  32. outputFromProcessQty: number;
  33. outputFromProcessUom: string;
  34. defectQty: number;
  35. defectUom: string;
  36. scrapQty: number;
  37. scrapUom: string;
  38. }
  39. export interface SearchJoResultResponse {
  40. records: JobOrder[];
  41. total: number;
  42. }
  43. // DEPRECIATED
  44. export interface SearchJoResult {
  45. id: number;
  46. code: string;
  47. itemCode: string;
  48. name: string;
  49. reqQty: number;
  50. uom: string;
  51. status: JoStatus;
  52. }
  53. export interface UpdateJoRequest {
  54. id: number;
  55. status: string;
  56. }
  57. // For Jo Button Actions
  58. export interface CommonActionJoRequest {
  59. id: number;
  60. }
  61. export interface CommonActionJoResponse {
  62. id: number;
  63. entity: { status: JoStatus }
  64. }
  65. // For Jo Process
  66. export interface IsOperatorExistResponse<T> {
  67. id: number | null;
  68. name: string;
  69. code: string;
  70. type?: string;
  71. message: string | null;
  72. errorPosition: string | keyof T;
  73. entity: T;
  74. }
  75. export interface isCorrectMachineUsedResponse<T> {
  76. id: number | null;
  77. name: string;
  78. code: string;
  79. type?: string;
  80. message: string | null;
  81. errorPosition: string | keyof T;
  82. entity: T;
  83. }
  84. export interface JobOrderDetail {
  85. id: number;
  86. code: string;
  87. name: string;
  88. reqQty: number;
  89. uom: string;
  90. pickLines: any[];
  91. jobTypeName: string;
  92. status: string;
  93. }
  94. export interface UnassignedJobOrderPickOrder {
  95. pickOrderId: number;
  96. pickOrderCode: string;
  97. pickOrderConsoCode: string;
  98. pickOrderTargetDate: string;
  99. pickOrderStatus: string;
  100. jobOrderId: number;
  101. jobOrderCode: string;
  102. jobOrderName: string;
  103. reqQty: number;
  104. uom: string;
  105. planStart: string;
  106. planEnd: string;
  107. }
  108. export interface AssignJobOrderResponse {
  109. id: number | null;
  110. code: string | null;
  111. name: string | null;
  112. type: string | null;
  113. message: string | null;
  114. errorPosition: string | null;
  115. }
  116. export interface PrintPickRecordRequest{
  117. pickOrderId: number;
  118. printerId: number;
  119. printQty: number;
  120. floor?: "2F" | "3F" | "4F" | "ALL";
  121. plasticBoxCartonQty?: number;
  122. plasticBoxCartonQty2f?: number;
  123. plasticBoxCartonQty3f?: number;
  124. plasticBoxCartonQty4f?: number;
  125. }
  126. export interface PrintPickRecordResponse{
  127. success: boolean;
  128. message?: string
  129. }
  130. export interface PickRecordPlasticBoxCartonQtyResponse {
  131. plasticBoxCartonQty2f: number | null;
  132. plasticBoxCartonQty3f: number | null;
  133. plasticBoxCartonQty4f: number | null;
  134. }
  135. export const fetchPickRecordPlasticBoxCartonQty = async (
  136. pickOrderId: number,
  137. ): Promise<PickRecordPlasticBoxCartonQtyResponse> => {
  138. return serverFetchJson<PickRecordPlasticBoxCartonQtyResponse>(
  139. `${BASE_API_URL}/jo/pick-record-plastic-box-carton-qty/${pickOrderId}`,
  140. {
  141. method: "GET",
  142. headers: { "Content-Type": "application/json" },
  143. },
  144. );
  145. };
  146. export interface PrintFGStockInLabelRequest {
  147. stockInLineId: number;
  148. printerId: number;
  149. printQty?: number;
  150. }
  151. export const printFGStockInLabel = cache(async(data: PrintFGStockInLabelRequest) => {
  152. const params = new URLSearchParams();
  153. if (data.stockInLineId) {
  154. params.append('stockInLineId', data.stockInLineId.toString());
  155. }
  156. params.append('printerId', data.printerId.toString());
  157. if (data.printQty !== undefined && data.printQty !== null) {
  158. params.append('printQty', data.printQty.toString());
  159. }
  160. return serverFetchWithNoContent(
  161. `${BASE_API_URL}/jo/print-FGStockInLabel?${params.toString()}`,
  162. {
  163. method: "GET",
  164. next: {
  165. tags: ["printFGStockInLabel"],
  166. },
  167. }
  168. );
  169. });
  170. export interface UpdateJoReqQtyRequest {
  171. id: number;
  172. reqQty: number;
  173. }
  174. // 添加更新 reqQty 的函数
  175. export const updateJoReqQty = cache(async (data: UpdateJoReqQtyRequest) => {
  176. return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/updateReqQty`, {
  177. method: "POST",
  178. body: JSON.stringify(data),
  179. headers: { "Content-Type": "application/json" },
  180. })
  181. })
  182. export const recordSecondScanIssue = cache(async (
  183. pickOrderId: number,
  184. itemId: number,
  185. data: {
  186. qty: number; // verified qty (actual pick qty)
  187. missQty?: number; // 添加:miss qty
  188. badItemQty?: number; // 添加:bad item qty
  189. isMissing: boolean;
  190. isBad: boolean;
  191. reason: string;
  192. createdBy: number;
  193. type?: string; // type 也应该是可选的
  194. }
  195. ) => {
  196. return serverFetchJson<any>(
  197. `${BASE_API_URL}/jo/second-scan-issue/${pickOrderId}/${itemId}`,
  198. {
  199. method: "POST",
  200. headers: { "Content-Type": "application/json" },
  201. body: JSON.stringify(data),
  202. next: { tags: ["jo-second-scan"] },
  203. },
  204. );
  205. });
  206. export interface ProductProcessResponse {
  207. id: number;
  208. productProcessCode: string;
  209. status: string;
  210. startTime?: string;
  211. endTime?: string;
  212. date: string;
  213. bomId?: number;
  214. jobOrderId?: number;
  215. }
  216. export interface ProductProcessLineResponse {
  217. id: number,
  218. bomprocessId: number,
  219. operatorId: number,
  220. operatorName: string,
  221. equipmentId: number,
  222. handlerId: number,
  223. seqNo: number,
  224. name: string,
  225. description: string,
  226. equipmentDetailId: number,
  227. equipment_name: string,
  228. equipmentDetailCode: string,
  229. status: string,
  230. byproductId: number,
  231. byproductName: string,
  232. byproductQty: number,
  233. byproductUom: string,
  234. scrapQty: number,
  235. defectQty: number,
  236. defectUom: string,
  237. outputFromProcessQty: number,
  238. outputFromProcessUom: string,
  239. durationInMinutes: number,
  240. prepTimeInMinutes: number,
  241. postProdTimeInMinutes: number,
  242. startTime: string,
  243. endTime: string,
  244. isOringinal: boolean,
  245. }
  246. export interface ProductProcessWithLinesResponse {
  247. id: number;
  248. productProcessCode: string;
  249. status: string;
  250. startTime?: string;
  251. endTime?: string;
  252. date: string;
  253. bomId?: number;
  254. jobOrderId?: number;
  255. jobOrderCode: string;
  256. jobOrderStatus: string;
  257. bomDescription: string;
  258. jobType: string;
  259. isDark: number | null;
  260. bomBaseQty: number;
  261. isDense: number | null;
  262. isFloat: number | null;
  263. timeSequence: number | null;
  264. complexity: number | null;
  265. scrapRate: number;
  266. allergicSubstance: number | null;
  267. itemId: number;
  268. itemCode: string;
  269. itemName: string;
  270. outputQty: number;
  271. outputQtyUom: string;
  272. productionPriority: number;
  273. submitedBagRecord?: boolean;
  274. jobOrderLines: JobOrderLineInfo[];
  275. productProcessLines: ProductProcessLineResponse[];
  276. }
  277. export interface UpdateProductProcessLineQtyRequest {
  278. productProcessLineId: number;
  279. outputFromProcessQty: number;
  280. outputFromProcessUom: string;
  281. byproductName: string;
  282. byproductQty: number;
  283. byproductUom: string;
  284. defectQty: number;
  285. defectUom: string;
  286. defect2Qty: number;
  287. defect2Uom: string;
  288. defect3Qty: number;
  289. defect3Uom: string;
  290. defectDescription: string;
  291. defectDescription2: string;
  292. defectDescription3: string;
  293. scrapQty: number;
  294. scrapUom: string;
  295. }
  296. export interface UpdateProductProcessLineQtyResponse {
  297. id: number;
  298. outputFromProcessQty: number;
  299. outputFromProcessUom: string;
  300. defectQty: number;
  301. defectUom: string;
  302. defect2Qty: number;
  303. defect2Uom: string;
  304. defect3Qty: number;
  305. defect3Uom: string;
  306. defectDescription: string;
  307. defectDescription2: string;
  308. defectDescription3: string;
  309. scrapQty: number;
  310. scrapUom: string;
  311. byproductName: string;
  312. byproductQty: number;
  313. byproductUom: string;
  314. }
  315. export interface AllProductProcessResponse {
  316. id: number;
  317. productProcessCode: string;
  318. status: string;
  319. startTime?: string;
  320. endTime?: string;
  321. date: string;
  322. bomId?: number;
  323. }
  324. export interface AllJoborderProductProcessInfoResponse {
  325. id: number;
  326. productProcessCode: string;
  327. status: string;
  328. startTime?: string;
  329. endTime?: string;
  330. date: string;
  331. matchStatus: string;
  332. bomId?: number;
  333. productionPriority: number;
  334. assignedTo: number;
  335. pickOrderId: number;
  336. pickOrderStatus: string;
  337. itemCode: string;
  338. itemName: string;
  339. bomDescription?: string | null;
  340. /** BOM.type (e.g. drink / Powder_Mixture / other). */
  341. bomType?: string | null;
  342. lotNo: string;
  343. requiredQty: number;
  344. jobOrderId: number;
  345. timeNeedToComplete: number;
  346. uom: string;
  347. isDrink?: boolean | null;
  348. stockInLineId: number;
  349. /** Stock-in-line current status (e.g. receiving/received/partially_completed/completed/rejected). */
  350. stockInLineStatus?: string | null;
  351. jobOrderCode: string;
  352. productProcessLineCount: number;
  353. FinishedProductProcessLineCount: number;
  354. lines: ProductProcessInfoResponse[];
  355. isPicked?: boolean | null;
  356. /** Fine-grained pick/process bucket from backend. */
  357. pickProcessBucket?: ProductionProcessFinePickBucket | null;
  358. }
  359. /** Fine-grained buckets returned per row. */
  360. export type ProductionProcessFinePickBucket =
  361. | "not_picked_not_started"
  362. | "picked_not_started"
  363. | "picked_started"
  364. | "not_picked_started";
  365. /** Merged tab filter: pending = not started; processing = started. */
  366. export type ProductionProcessPickBucket = "pending" | "processing" | ProductionProcessFinePickBucket;
  367. export interface JobOrderProductProcessBucketCounts {
  368. notPickedNotStarted: number;
  369. pickedNotStarted: number;
  370. pickedStarted: number;
  371. notPickedStarted: number;
  372. }
  373. export interface JobOrderProductProcessPageResponse {
  374. content: AllJoborderProductProcessInfoResponse[];
  375. totalJobOrders: number;
  376. page: number;
  377. size: number;
  378. bucketCounts?: JobOrderProductProcessBucketCounts | null;
  379. searchDate?: string | null;
  380. carriedOverCount?: number | null;
  381. }
  382. export interface ProductProcessInfoResponse {
  383. id: number;
  384. operatorId?: number;
  385. operatorName?: string;
  386. equipmentId?: number;
  387. equipmentName?: string;
  388. startTime?: string;
  389. endTime?: string;
  390. status: string;
  391. }
  392. export interface ProductProcessLineQrscanUpadteRequest {
  393. productProcessLineId: number;
  394. //operatorId?: number;
  395. //equipmentId?: number;
  396. equipmentTypeSubTypeEquipmentNo?: string;
  397. staffNo?: string;
  398. }
  399. export interface NewProductProcessLineQrscanUpadteRequest{
  400. productProcessLineId: number;
  401. equipmentCode?: string;
  402. staffNo?: string;
  403. }
  404. export interface ProductProcessLineDetailResponse {
  405. id: number,
  406. productProcessId: number,
  407. bomProcessId: number,
  408. operatorId: number,
  409. equipmentType: string,
  410. operatorName: string,
  411. handlerId: number,
  412. seqNo: number,
  413. isDark: string,
  414. isDense: number,
  415. isFloat: string,
  416. outputQtyUom: string,
  417. outputQty: number,
  418. pickOrderId: number,
  419. jobOrderCode: string,
  420. jobOrderId: number,
  421. name: string,
  422. description: string,
  423. equipment: string,
  424. startTime: string,
  425. endTime: string,
  426. defectQty: number,
  427. defectUom: string,
  428. scrapQty: number,
  429. scrapUom: string,
  430. byproductId: number,
  431. byproductName: string,
  432. byproductQty: number,
  433. byproductUom: string | undefined,
  434. totalStockQty: number,
  435. insufficientStockQty: number,
  436. sufficientStockQty: number,
  437. productionPriority: number,
  438. productProcessLines: ProductProcessLineInfoResponse[],
  439. jobOrderLineInfo: JobOrderLineInfo[],
  440. }
  441. export interface JobOrderProcessLineDetailResponse {
  442. id: number;
  443. productProcessId: number;
  444. bomProcessId: number;
  445. operatorId: number;
  446. equipmentType: string | null;
  447. operatorName: string;
  448. handlerId: number;
  449. seqNo: number;
  450. durationInMinutes: number;
  451. name: string;
  452. description: string;
  453. equipmentId: number;
  454. startTime: string | number[]; // API 返回的是数组格式
  455. endTime: string | number[];
  456. stopTime: string | number[];
  457. totalPausedTimeMs?: number; // API 返回的是数组格式
  458. status: string;
  459. submitedBagRecord: boolean;
  460. outputFromProcessQty: number;
  461. outputFromProcessUom: string;
  462. defectQty: number;
  463. defectUom: string;
  464. defectDescription: string;
  465. defectQty2: number;
  466. defectUom2: string;
  467. defectDescription2: string;
  468. defectQty3: number;
  469. defectUom3: string;
  470. defectDescription3: string;
  471. scrapQty: number;
  472. scrapUom: string;
  473. byproductId: number;
  474. byproductName: string;
  475. byproductQty: number;
  476. byproductUom: string;
  477. productProcessIssueId: number;
  478. productProcessIssueStatus: string;
  479. }
  480. export interface JobOrderLineInfo {
  481. id: number,
  482. itemId: number,
  483. itemCode: string,
  484. itemName: string,
  485. type: string,
  486. reqQty: number,
  487. baseReqQty: number,
  488. stockReqQty: number,
  489. stockQty: number,
  490. baseStockQty: number,
  491. reqUom: string,
  492. reqBaseUom: string,
  493. stockUom: string,
  494. stockBaseUom: string,
  495. availableStatus: string,
  496. bomProcessId: number,
  497. bomProcessSeqNo: number,
  498. isOringinal: boolean
  499. }
  500. export interface ProductProcessLineInfoResponse {
  501. id: number,
  502. bomprocessId: number,
  503. operatorId: number,
  504. operatorName: string,
  505. equipmentId: number,
  506. handlerId: number,
  507. seqNo: number,
  508. name: string,
  509. description: string,
  510. equipment_name: string,
  511. equipmentDetailCode: string,
  512. status: string,
  513. byproductId: number,
  514. byproductName: string,
  515. byproductQty: number,
  516. byproductUom: string,
  517. scrapQty: number,
  518. defectQty: number,
  519. defectUom: string,
  520. durationInMinutes: number,
  521. prepTimeInMinutes: number,
  522. postProdTimeInMinutes: number,
  523. outputFromProcessQty: number,
  524. outputFromProcessUom: string,
  525. startTime: string,
  526. endTime: string
  527. }
  528. export interface FloorPickCount {
  529. floor: string;
  530. finishedCount: number;
  531. totalCount: number;
  532. }
  533. export interface AllJoPickOrderResponse {
  534. id: number;
  535. pickOrderId: number | null;
  536. pickOrderCode: string | null;
  537. jobOrderId: number | null;
  538. jobOrderCode: string | null;
  539. jobOrderTypeId: number | null;
  540. jobOrderType: string | null;
  541. itemId: number;
  542. itemName: string;
  543. itemCode?: string | null;
  544. bomDescription?: string | null;
  545. /** BOM.type (e.g. drink / Powder_Mixture / other). */
  546. bomType?: string | null;
  547. lotNo: string | null;
  548. planStart?: string | number[] | null;
  549. reqQty: number;
  550. uomId: number;
  551. uomName: string;
  552. jobOrderStatus: string;
  553. finishedPickOLineCount: number;
  554. floorPickCounts: FloorPickCount[];
  555. noLotPickCount?: FloorPickCount | null;
  556. suggestedFailCount?: number;
  557. }
  558. export interface UpdateJoPickOrderHandledByRequest {
  559. pickOrderId: number;
  560. itemId: number;
  561. userId: number;
  562. }
  563. export interface JobTypeResponse {
  564. id: number;
  565. name: string;
  566. }
  567. export interface SaveProductProcessIssueTimeRequest {
  568. productProcessLineId: number;
  569. reason: string;
  570. }
  571. export interface JobOrderLotsHierarchicalResponse {
  572. pickOrder: PickOrderInfoResponse;
  573. pickOrderLines: PickOrderLineWithLotsResponse[];
  574. }
  575. /** JO Workbench: same shape as [JobOrderLotsHierarchicalResponse] but `pickOrder.jobOrder` includes BOM code/name. */
  576. export interface JobOrderLotsHierarchicalWorkbenchResponse {
  577. pickOrder: PickOrderInfoWorkbenchResponse;
  578. pickOrderLines: PickOrderLineWithLotsResponse[];
  579. }
  580. export interface PickOrderInfoResponse {
  581. id: number | null;
  582. code: string | null;
  583. consoCode: string | null;
  584. targetDate: string | null;
  585. type: string | null;
  586. status: string | null;
  587. assignTo: number | null;
  588. jobOrder: JobOrderBasicInfoResponse;
  589. }
  590. export interface PickOrderInfoWorkbenchResponse {
  591. id: number | null;
  592. code: string | null;
  593. consoCode: string | null;
  594. targetDate: string | null;
  595. type: string | null;
  596. status: string | null;
  597. assignTo: number | null;
  598. jobOrder: JobOrderBasicInfoWorkbenchResponse;
  599. }
  600. export interface JobOrderBasicInfoResponse {
  601. id: number;
  602. code: string;
  603. name: string;
  604. }
  605. /** BOM header code/name from job order's BOM (workbench hierarchical API only). */
  606. export interface JobOrderBasicInfoWorkbenchResponse {
  607. id: number;
  608. code: string;
  609. name: string;
  610. itemCode: string | null;
  611. itemName: string | null;
  612. }
  613. export interface PickOrderLineWithLotsResponse {
  614. id: number;
  615. itemId: number | null;
  616. itemCode: string | null;
  617. itemName: string | null;
  618. requiredQty: number | null;
  619. totalAvailableQty?: number | null;
  620. uomCode: string | null;
  621. uomDesc: string | null;
  622. status: string | null;
  623. handler: string | null;
  624. lots: LotDetailResponse[];
  625. stockouts?: StockOutLineDetailResponse[];
  626. }
  627. export interface StockOutLineDetailResponse {
  628. id: number | null;
  629. status: string | null;
  630. qty: number | null;
  631. lotId: number | null;
  632. lotNo: string | null;
  633. location: string | null;
  634. availableQty: number | null;
  635. noLot: boolean;
  636. /** Workbench API: matched suggest_pick_lot qty for this SOL lot line */
  637. suggestedPickQty?: number | null;
  638. suggestedPickLotId?: number | null;
  639. }
  640. export interface LotDetailResponse {
  641. lotId: number | null;
  642. lotNo: string | null;
  643. expiryDate: string | null;
  644. location: string | null;
  645. availableQty: number | null;
  646. requiredQty: number | null;
  647. actualPickQty: number | null;
  648. processingStatus: string | null;
  649. lotAvailability: string | null;
  650. pickOrderId: number | null;
  651. pickOrderCode: string | null;
  652. pickOrderConsoCode: string | null;
  653. pickOrderLineId: number | null;
  654. stockOutLineId: number | null;
  655. stockInLineId: number | null;
  656. suggestedPickLotId: number | null;
  657. stockOutLineQty: number | null;
  658. stockOutLineStatus: string | null;
  659. routerIndex: number | null;
  660. routerArea: string | null;
  661. routerRoute: string | null;
  662. uomShortDesc: string | null;
  663. matchStatus?: string | null;
  664. matchBy?: number | null;
  665. matchQty?: number | null;
  666. }
  667. export interface JobOrderListForPrintQrCodeResponse {
  668. id: number;
  669. code: string;
  670. name: string;
  671. reqQty: number;
  672. stockOutLineId: number;
  673. stockOutLineQty: number;
  674. stockOutLineStatus: string;
  675. finihedTime: string;
  676. }
  677. export interface UpdateJoPlanStartRequest {
  678. id: number;
  679. planStart: string; // Format: YYYY-MM-DDTHH:mm:ss or YYYY-MM-DD
  680. }
  681. export const saveProductProcessIssueTime = cache(async (request: SaveProductProcessIssueTimeRequest) => {
  682. return serverFetchJson<any>(
  683. `${BASE_API_URL}/product-process/Demo/ProcessLine/issue`,
  684. {
  685. method: "POST",
  686. headers: { "Content-Type": "application/json" },
  687. body: JSON.stringify(request),
  688. }
  689. );
  690. });
  691. export const saveProductProcessResumeTime = cache(async (productProcessIssueId: number) => {
  692. return serverFetchJson<any>(
  693. `${BASE_API_URL}/product-process/Demo/ProcessLine/resume/${productProcessIssueId}`,
  694. {
  695. method: "POST",
  696. }
  697. );
  698. });
  699. export const deleteJobOrder=cache(async (jobOrderId: number) => {
  700. return serverFetchJson<any>(
  701. `${BASE_API_URL}/jo/demo/deleteJobOrder/${jobOrderId}`,
  702. {
  703. method: "POST",
  704. }
  705. );
  706. });
  707. export const setJobOrderHidden = cache(async (jobOrderId: number, hidden: boolean) => {
  708. const response = await serverFetchJson<any>(`${BASE_API_URL}/jo/set-hidden`, {
  709. method: "POST",
  710. headers: { "Content-Type": "application/json" },
  711. body: JSON.stringify({ id: jobOrderId, hidden }),
  712. });
  713. revalidateTag("jos");
  714. return response;
  715. });
  716. export const fetchAllJobTypes = cache(async () => {
  717. return serverFetchJson<JobTypeResponse[]>(
  718. `${BASE_API_URL}/jo/jobTypes`,
  719. {
  720. method: "GET",
  721. }
  722. );
  723. });
  724. export const updateJoPickOrderHandledBy = cache(async (request: UpdateJoPickOrderHandledByRequest) => {
  725. return serverFetchJson<any>(
  726. `${BASE_API_URL}/jo/update-jo-pick-order-handled-by`,
  727. {
  728. method: "POST",
  729. body: JSON.stringify(request),
  730. headers: { "Content-Type": "application/json" },
  731. },
  732. );
  733. });
  734. export const fetchJobOrderLotsHierarchicalByPickOrderId = cache(async (pickOrderId: number) => {
  735. return serverFetchJson<JobOrderLotsHierarchicalResponse>(
  736. `${BASE_API_URL}/jo/all-lots-hierarchical-by-pick-order/${pickOrderId}`,
  737. {
  738. method: "GET",
  739. next: { tags: ["jo-hierarchical"] },
  740. },
  741. );
  742. });
  743. /** JO Workbench: in−out available (matches scan-pick); stockouts include suggestedPickQty / suggestedPickLotId when SPL matches SOL lot line */
  744. export const fetchJobOrderLotsHierarchicalByPickOrderIdWorkbench = cache(
  745. async (pickOrderId: number) => {
  746. return serverFetchJson<JobOrderLotsHierarchicalWorkbenchResponse>(
  747. `${BASE_API_URL}/jo/all-lots-hierarchical-by-pick-order-workbench/${pickOrderId}`,
  748. {
  749. method: "GET",
  750. next: { tags: ["jo-hierarchical-workbench"] },
  751. },
  752. );
  753. },
  754. );
  755. // NOTE: Do NOT wrap in `cache()` because the list needs to reflect just-completed lines
  756. // immediately when navigating back from JobPickExecution.
  757. export interface FetchAllJoPickOrdersFilters {
  758. jobOrderCode?: string | null;
  759. pickOrderCode?: string | null;
  760. itemName?: string | null;
  761. bomDescription?: string | null;
  762. planStart?: string | null;
  763. }
  764. export const fetchAllJoPickOrders = async (
  765. type?: string | null,
  766. floor?: string | null,
  767. filters?: FetchAllJoPickOrdersFilters,
  768. ) => {
  769. const params = new URLSearchParams();
  770. if (type) params.set("type", type);
  771. if (floor) params.set("floor", floor);
  772. if (filters?.jobOrderCode) params.set("jobOrderCode", filters.jobOrderCode);
  773. if (filters?.pickOrderCode) params.set("pickOrderCode", filters.pickOrderCode);
  774. if (filters?.itemName) params.set("itemName", filters.itemName);
  775. if (filters?.bomDescription) params.set("bomDescription", filters.bomDescription);
  776. if (filters?.planStart) params.set("planStart", filters.planStart);
  777. const query = params.toString() ? `?${params.toString()}` : "";
  778. return serverFetchJson<AllJoPickOrderResponse[]>(
  779. `${BASE_API_URL}/jo/AllJoPickOrder${query}`,
  780. // Force re-fetch. This page reflects real-time pick completion state.
  781. { method: "GET", cache: "no-store" }
  782. );
  783. };
  784. export const fetchProductProcessLineDetail = cache(async (lineId: number) => {
  785. return serverFetchJson<JobOrderProcessLineDetailResponse>(
  786. `${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`,
  787. {
  788. method: "GET",
  789. }
  790. );
  791. });
  792. export const updateProductProcessLineQty = cache(async (request: UpdateProductProcessLineQtyRequest) => {
  793. return serverFetchJson<UpdateProductProcessLineQtyResponse>(
  794. `${BASE_API_URL}/product-process/Demo/ProcessLine/update/qty/${request.productProcessLineId}`,
  795. {
  796. method: "POST",
  797. headers: { "Content-Type": "application/json" },
  798. body: JSON.stringify(request),
  799. }
  800. );
  801. });
  802. export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => {
  803. const requestBody: any = {
  804. productProcessLineId: request.productProcessLineId,
  805. //operatorId: request.operatorId,
  806. //equipmentId: request.equipmentId,
  807. equipmentTypeSubTypeEquipmentNo: request.equipmentTypeSubTypeEquipmentNo,
  808. staffNo: request.staffNo,
  809. };
  810. if (request.equipmentTypeSubTypeEquipmentNo !== undefined) {
  811. requestBody["EquipmentType-SubType-EquipmentNo"] = request.equipmentTypeSubTypeEquipmentNo;
  812. }
  813. return serverFetchJson<any>(
  814. `${BASE_API_URL}/product-process/Demo/update`,
  815. {
  816. method: "POST",
  817. headers: { "Content-Type": "application/json" },
  818. body: JSON.stringify(requestBody),
  819. }
  820. );
  821. });
  822. export const newUpdateProductProcessLineQrscan = cache(async (request: NewProductProcessLineQrscanUpadteRequest) => {
  823. return serverFetchJson<any>(
  824. `${BASE_API_URL}/product-process/Demo/NewUpdate`,
  825. {
  826. method: "POST",
  827. headers: { "Content-Type": "application/json" },
  828. body: JSON.stringify(request),
  829. }
  830. );
  831. });
  832. export const fetchAllJoborderProductProcessInfo = cache(async (type?: string | null) => {
  833. const query = type
  834. ? `?type=${encodeURIComponent(type)}`
  835. : "";
  836. return serverFetchJson<AllJoborderProductProcessInfoResponse[]>(
  837. `${BASE_API_URL}/product-process/Demo/Process/all${query}`,
  838. {
  839. method: "GET",
  840. next: { tags: ["productProcess"] },
  841. }
  842. );
  843. });
  844. /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */
  845. export const fetchJoborderProductProcessesPage = cache(async (params: {
  846. /** Job order / process date(YYYY-MM-DD) */
  847. date?: string | null;
  848. itemCode?: string | null;
  849. jobOrderCode?: string | null;
  850. bomIds?: number[] | null;
  851. qcReady?: boolean | null;
  852. type?: string | null;
  853. includePutaway?: boolean | null;
  854. /** all | completed | notCompleted */
  855. putawayStatus?: string | null;
  856. /** Production list carry-over window (days before date). */
  857. lookbackDays?: number | null;
  858. /** Pick/process tab filter when lookbackDays is set. */
  859. bucket?: ProductionProcessPickBucket | "all" | null;
  860. page?: number;
  861. size?: number;
  862. }) => {
  863. const {
  864. date,
  865. itemCode,
  866. jobOrderCode,
  867. bomIds,
  868. qcReady,
  869. includePutaway,
  870. putawayStatus,
  871. type,
  872. lookbackDays,
  873. bucket,
  874. page = 0,
  875. size = 50,
  876. } = params;
  877. const queryParts: string[] = [];
  878. if (date) {
  879. queryParts.push(`date=${encodeURIComponent(date)}`);
  880. }
  881. if (itemCode) queryParts.push(`itemCode=${encodeURIComponent(itemCode)}`);
  882. if (jobOrderCode) queryParts.push(`jobOrderCode=${encodeURIComponent(jobOrderCode)}`);
  883. if (bomIds && bomIds.length > 0) queryParts.push(`bomIds=${bomIds.join(",")}`);
  884. if (qcReady !== undefined && qcReady !== null) queryParts.push(`qcReady=${qcReady}`);
  885. if (type) queryParts.push(`type=${encodeURIComponent(type)}`);
  886. if (includePutaway !== undefined && includePutaway !== null) {
  887. queryParts.push(`includePutaway=${includePutaway}`);
  888. }
  889. if (putawayStatus) queryParts.push(`putawayStatus=${encodeURIComponent(putawayStatus)}`);
  890. if (lookbackDays !== undefined && lookbackDays !== null) {
  891. queryParts.push(`lookbackDays=${lookbackDays}`);
  892. }
  893. if (bucket) queryParts.push(`bucket=${encodeURIComponent(bucket)}`);
  894. queryParts.push(`page=${page}`);
  895. queryParts.push(`size=${size}`);
  896. const query = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
  897. return serverFetchJson<JobOrderProductProcessPageResponse>(
  898. `${BASE_API_URL}/product-process/Demo/Process/search${query}`,
  899. {
  900. method: "GET",
  901. next: { tags: ["productProcessSearch"] },
  902. }
  903. );
  904. });
  905. /*
  906. export const updateProductProcessLineQty = async (request: UpdateProductProcessLineQtyRequest) => {
  907. return serverFetchJson<UpdateProductProcessLineQtyResponse>(
  908. `${BASE_API_URL}/product-process/lines/${request.productProcessLineId}/update/qty`,
  909. {
  910. method: "POST",
  911. headers: { "Content-Type": "application/json" },
  912. body: JSON.stringify(request),
  913. }
  914. );
  915. };
  916. */
  917. export const startProductProcessLine = async (lineId: number) => {
  918. return serverFetchJson<any>(
  919. `${BASE_API_URL}/product-process/Demo/ProcessLine/start/${lineId}`,
  920. {
  921. method: "POST",
  922. headers: { "Content-Type": "application/json" },
  923. }
  924. );
  925. };
  926. export const completeProductProcessLine = async (lineId: number) => {
  927. return serverFetchJson<any>(
  928. `${BASE_API_URL}/product-process/Demo/ProcessLine/complete/${lineId}`,
  929. {
  930. method: "POST",
  931. headers: { "Content-Type": "application/json" },
  932. }
  933. );
  934. };
  935. // 查询所有 production processes
  936. export const fetchProductProcesses = cache(async () => {
  937. return serverFetchJson<{ content: ProductProcessResponse[] }>(
  938. `${BASE_API_URL}/product-process`,
  939. {
  940. method: "GET",
  941. next: { tags: ["productProcess"] },
  942. }
  943. );
  944. });
  945. // 根据 ID 查询
  946. export const fetchProductProcessById = cache(async (id: number) => {
  947. return serverFetchJson<ProductProcessResponse>(
  948. `${BASE_API_URL}/product-process/${id}`,
  949. {
  950. method: "GET",
  951. next: { tags: ["productProcess"] },
  952. }
  953. );
  954. });
  955. export const updateProductProcessPriority = cache(async (productProcessId: number, productionPriority: number) => {
  956. return serverFetchJson<any>(
  957. `${BASE_API_URL}/product-process/Demo/Process/update/priority/${productProcessId}/${productionPriority}`,
  958. {
  959. method: "POST",
  960. }
  961. );
  962. });
  963. // 根据 Job Order ID 查询
  964. export const fetchProductProcessesByJobOrderId = cache(async (jobOrderId: number) => {
  965. return serverFetchJson<ProductProcessWithLinesResponse[]>(
  966. `${BASE_API_URL}/product-process/demo/joid/${jobOrderId}`,
  967. {
  968. method: "GET",
  969. next: { tags: ["productProcess"] },
  970. }
  971. );
  972. });
  973. export const newProductProcessLine = cache(async (lineId: number) => {
  974. return serverFetchJson<any>(
  975. `${BASE_API_URL}/product-process/Demo/ProcessLine/new/${lineId}`,
  976. {
  977. method: "POST",
  978. }
  979. );
  980. });
  981. // 获取 process 的所有 lines
  982. export const fetchProductProcessLines = cache(async (processId: number) => {
  983. return serverFetchJson<ProductProcessLineResponse[]>(
  984. `${BASE_API_URL}/product-process/${processId}/lines`,
  985. {
  986. method: "GET",
  987. next: { tags: ["productProcessLines"] },
  988. }
  989. );
  990. });
  991. // 创建 production process
  992. export const createProductProcess = async (data: {
  993. bomId: number;
  994. jobOrderId?: number;
  995. date?: string;
  996. }) => {
  997. return serverFetchJson<{ id: number; productProcessCode: string; linesCreated: number }>(
  998. `${BASE_API_URL}/product-process`,
  999. {
  1000. method: "POST",
  1001. headers: { "Content-Type": "application/json" },
  1002. body: JSON.stringify(data),
  1003. }
  1004. );
  1005. };
  1006. // 更新 line 产出数据
  1007. export const updateLineOutput = async (lineId: number, data: {
  1008. outputQty?: number;
  1009. outputUom?: string;
  1010. defectQty?: number;
  1011. defectUom?: string;
  1012. scrapQty?: number;
  1013. scrapUom?: string;
  1014. byproductName?: string;
  1015. byproductQty?: number;
  1016. byproductUom?: string;
  1017. }) => {
  1018. return serverFetchJson<ProductProcessLineResponse>(
  1019. `${BASE_API_URL}/product-process/lines/${lineId}/output`,
  1020. {
  1021. method: "PUT",
  1022. headers: { "Content-Type": "application/json" },
  1023. body: JSON.stringify(data),
  1024. }
  1025. );
  1026. };
  1027. export const updateSecondQrScanStatus = cache(async (pickOrderId: number, itemId: number, userId: number, qty: number) => {
  1028. return serverFetchJson<any>(
  1029. `${BASE_API_URL}/jo/update-match-status`,
  1030. {
  1031. method: "POST",
  1032. body: JSON.stringify({
  1033. pickOrderId,
  1034. itemId,
  1035. userId,
  1036. qty
  1037. }),
  1038. headers: {
  1039. 'Content-Type': 'application/json',
  1040. },
  1041. next: { tags: ["update-match-status"] },
  1042. },
  1043. );
  1044. });
  1045. export const submitSecondScanQuantity = cache(async (
  1046. pickOrderId: number,
  1047. itemId: number,
  1048. data: { qty: number; isMissing?: boolean; isBad?: boolean; reason?: string; userId?: number }
  1049. ) => {
  1050. return serverFetchJson<any>(
  1051. `${BASE_API_URL}/jo/second-scan-submit/${pickOrderId}/${itemId}`,
  1052. {
  1053. method: "POST",
  1054. headers: { "Content-Type": "application/json" },
  1055. body: JSON.stringify(data),
  1056. next: { tags: ["jo-second-scan"] },
  1057. },
  1058. );
  1059. });
  1060. // 获取未分配的 Job Order pick orders
  1061. export const fetchUnassignedJobOrderPickOrders = cache(async () => {
  1062. return serverFetchJson<UnassignedJobOrderPickOrder[]>(
  1063. `${BASE_API_URL}/jo/unassigned-job-order-pick-orders`,
  1064. {
  1065. method: "GET",
  1066. next: { tags: ["jo-unassigned"] },
  1067. },
  1068. );
  1069. });
  1070. // 分配 Job Order pick order 给用户
  1071. export const assignJobOrderPickOrder = async (pickOrderId: number, userId: number) => {
  1072. return serverFetchJson<AssignJobOrderResponse>(
  1073. `${BASE_API_URL}/jo/assign-job-order-pick-order/${pickOrderId}/${userId}`,
  1074. {
  1075. method: "POST",
  1076. headers: { "Content-Type": "application/json" },
  1077. }
  1078. );
  1079. };
  1080. export const unAssignJobOrderPickOrder = async (pickOrderId: number) => {
  1081. return serverFetchJson<AssignJobOrderResponse>(
  1082. `${BASE_API_URL}/jo/unassign-job-order-pick-order/${pickOrderId}`,
  1083. {
  1084. method: "POST",
  1085. headers: { "Content-Type": "application/json" },
  1086. }
  1087. );
  1088. };
  1089. // 获取 Job Order 分层数据
  1090. export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => {
  1091. return serverFetchJson<JobOrderLotsHierarchicalResponse>(
  1092. `${BASE_API_URL}/jo/all-lots-hierarchical/${userId}`,
  1093. {
  1094. method: "GET",
  1095. next: { tags: ["jo-hierarchical"] },
  1096. },
  1097. );
  1098. });
  1099. export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) => {
  1100. return serverFetchJson<any>(
  1101. `${BASE_API_URL}/jo/completed-job-order-pick-orders/${userId}`,
  1102. {
  1103. method: "GET",
  1104. next: { tags: ["jo-completed"] },
  1105. },
  1106. );
  1107. });
  1108. /*
  1109. // 获取已完成的 Job Order pick orders
  1110. export const fetchCompletedJobOrderPickOrdersrecords = cache(async () => {
  1111. return serverFetchJson<any>(
  1112. `${BASE_API_URL}/jo/completed-job-order-pick-orders-only`,
  1113. {
  1114. method: "GET",
  1115. next: { tags: ["jo-completed"] },
  1116. },
  1117. );
  1118. });
  1119. */
  1120. export interface CompletedJobOrderPickOrderDashboardRecord {
  1121. id?: number;
  1122. pickOrderId?: number;
  1123. pickOrderCode?: string;
  1124. /** YYYY-MM-DD from backend (preferred for grouping) */
  1125. statDate?: string | null;
  1126. completedDate?: string | null;
  1127. planStart?: string | null;
  1128. plasticBoxCartonQty2f?: number | null;
  1129. plasticBoxCartonQty3f?: number | null;
  1130. plasticBoxCartonQty4f?: number | null;
  1131. }
  1132. export const fetchPlasticBoxCartonQtyDashboard = async (
  1133. from: string,
  1134. to: string,
  1135. ): Promise<CompletedJobOrderPickOrderDashboardRecord[]> => {
  1136. const params = new URLSearchParams({
  1137. from: from.trim(),
  1138. to: to.trim(),
  1139. });
  1140. return serverFetchJson<CompletedJobOrderPickOrderDashboardRecord[]>(
  1141. `${BASE_API_URL}/jo/plastic-box-carton-qty-dashboard?${params.toString()}`,
  1142. {
  1143. method: "GET",
  1144. cache: "no-store",
  1145. },
  1146. );
  1147. };
  1148. export const fetchCompletedJobOrderPickOrdersrecords = async (
  1149. completedDate?: string | null,
  1150. ): Promise<CompletedJobOrderPickOrderDashboardRecord[]> => {
  1151. const q =
  1152. completedDate && String(completedDate).trim() !== ""
  1153. ? `?date=${encodeURIComponent(String(completedDate).trim())}`
  1154. : "";
  1155. return serverFetchJson<CompletedJobOrderPickOrderDashboardRecord[]>(
  1156. `${BASE_API_URL}/jo/completed-job-order-pick-orders-only${q}`,
  1157. {
  1158. method: "GET",
  1159. cache: "no-store",
  1160. },
  1161. );
  1162. };
  1163. export const fetchJobOrderPickOrdersrecords = async (
  1164. date?: string | null,
  1165. status?: string | null,
  1166. ) => {
  1167. const params = new URLSearchParams();
  1168. // Backend expects LocalDate (YYYY-MM-DD); strip any time component.
  1169. const dateOnly = (() => {
  1170. if (!date || String(date).trim() === "") return null;
  1171. const match = String(date).trim().match(/(\d{4}-\d{2}-\d{2})/);
  1172. return match ? match[1] : String(date).trim().slice(0, 10);
  1173. })();
  1174. if (dateOnly) {
  1175. params.set("date", dateOnly);
  1176. }
  1177. if (status && String(status).trim() !== "" && String(status) !== "All") {
  1178. params.set("status", String(status).trim());
  1179. }
  1180. const q = params.toString() ? `?${params.toString()}` : "";
  1181. return serverFetchJson<any>(`${BASE_API_URL}/jo/job-order-pick-orders${q}`, {
  1182. method: "GET",
  1183. cache: "no-store",
  1184. });
  1185. };
  1186. export const fetchJobOrderPickOrderLotDetailsForPick = cache(async (pickOrderId: number) => {
  1187. return serverFetchJson<any[]>(`${BASE_API_URL}/jo/job-order-pick-order-lot-details/${pickOrderId}`, {
  1188. method: "GET",
  1189. headers: { "Content-Type": "application/json" }
  1190. })
  1191. })
  1192. export const fetchJoForPrintQrCode = cache(async (date: string) => {
  1193. return serverFetchJson<JobOrderListForPrintQrCodeResponse[]>(
  1194. `${BASE_API_URL}/jo/joForPrintQrCode/${date}`,
  1195. {
  1196. method: "GET",
  1197. next: { tags: ["jo-print-qr-code"] },
  1198. },
  1199. );
  1200. });
  1201. // 获取已完成的 Job Order pick order records
  1202. export const fetchCompletedJobOrderPickOrderRecords = cache(async (userId: number) => {
  1203. return serverFetchJson<any[]>(
  1204. `${BASE_API_URL}/jo/completed-job-order-pick-order-records/${userId}`,
  1205. {
  1206. method: "GET",
  1207. next: { tags: ["jo-records"] },
  1208. },
  1209. );
  1210. });
  1211. export const fetchJobOrderDetailByCode = cache(async (code: string) => {
  1212. return serverFetchJson<JobOrderDetail>(
  1213. `${BASE_API_URL}/jo/detailByCode/${code}`,
  1214. {
  1215. method: "GET",
  1216. next: { tags: ["jo"] },
  1217. },
  1218. );
  1219. });
  1220. export const isOperatorExist = async (username: string) => {
  1221. const isExist = await serverFetchJson<IsOperatorExistResponse<Operator>>(
  1222. `${BASE_API_URL}/jop/isOperatorExist`,
  1223. {
  1224. method: "POST",
  1225. body: JSON.stringify({ username }),
  1226. headers: { "Content-Type": "application/json" },
  1227. },
  1228. );
  1229. revalidateTag("po");
  1230. return isExist;
  1231. };
  1232. export const isCorrectMachineUsed = async (machineCode: string) => {
  1233. const isExist = await serverFetchJson<isCorrectMachineUsedResponse<Machine>>(
  1234. `${BASE_API_URL}/jop/isCorrectMachineUsed`,
  1235. {
  1236. method: "POST",
  1237. body: JSON.stringify({ machineCode }),
  1238. headers: { "Content-Type": "application/json" },
  1239. },
  1240. );
  1241. revalidateTag("po");
  1242. return isExist;
  1243. };
  1244. export const fetchJos = cache(async (data?: SearchJoResultRequest) => {
  1245. const queryStr = convertObjToURLSearchParams(data)
  1246. console.log("queryStr", queryStr)
  1247. const fullUrl = `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`;
  1248. console.log("fetchJos full URL:", fullUrl);
  1249. console.log("fetchJos BASE_API_URL:", BASE_API_URL);
  1250. const response = await serverFetchJson<SearchJoResultResponse>(
  1251. `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
  1252. {
  1253. method: "GET",
  1254. headers: { "Content-Type": "application/json" },
  1255. next: {
  1256. tags: ["jos"]
  1257. }
  1258. }
  1259. )
  1260. // console.log("fetchJos response:", response)
  1261. return response
  1262. })
  1263. export interface PostPickOrderResponse<T = null> {
  1264. id: number | null;
  1265. name: string;
  1266. code: string;
  1267. type?: string;
  1268. message: string | null;
  1269. errorPosition: string
  1270. entity?: T | T[];
  1271. consoCode?: string;
  1272. }
  1273. export interface PickExecutionIssueData {
  1274. type: string;
  1275. pickOrderId: number;
  1276. pickOrderCode: string;
  1277. pickOrderCreateDate: string;
  1278. pickExecutionDate: string;
  1279. pickOrderLineId: number;
  1280. itemId: number;
  1281. itemCode: string;
  1282. itemDescription: string;
  1283. lotId: number|null;
  1284. lotNo: string|null;
  1285. storeLocation: string;
  1286. requiredQty: number;
  1287. actualPickQty: number;
  1288. missQty: number;
  1289. badItemQty: number;
  1290. badPackageQty?: number;
  1291. /** Optional: frontend-only reference to stock_out_line.id for the picked lot. */
  1292. stockOutLineId?: number;
  1293. issueRemark: string;
  1294. pickerName: string;
  1295. handledBy?: number;
  1296. badReason?: string;
  1297. reason?: string;
  1298. }
  1299. /** 无 miss/bad/bad package:仅后端 hold + SOL checked,不写 pick_execution_issue(避免 DUPLICATE)。 */
  1300. export const applyPickExecutionHoldAndChecked = async (data: PickExecutionIssueData) => {
  1301. const result = await serverFetchJson<PostPickOrderResponse>(
  1302. `${BASE_API_URL}/pickExecution/applyHoldAndChecked`,
  1303. {
  1304. method: "POST",
  1305. body: JSON.stringify(data),
  1306. headers: { "Content-Type": "application/json" },
  1307. },
  1308. );
  1309. revalidateTag("pickorder");
  1310. return result;
  1311. };
  1312. export const updateJo = cache(async (data: UpdateJoRequest) => {
  1313. return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/update`,
  1314. {
  1315. method: "POST",
  1316. body: JSON.stringify(data),
  1317. headers: { "Content-Type": "application/json" },
  1318. })
  1319. })
  1320. export const releaseJo = cache(async (data: CommonActionJoRequest) => {
  1321. const response = serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/release`,
  1322. {
  1323. method: "POST",
  1324. body: JSON.stringify(data),
  1325. headers: { "Content-Type": "application/json" },
  1326. })
  1327. // Invalidate the cache after releasing
  1328. revalidateTag("jo");
  1329. return response;
  1330. })
  1331. export const startJo = cache(async (data: CommonActionJoRequest) => {
  1332. const response = serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/start`,
  1333. {
  1334. method: "POST",
  1335. body: JSON.stringify(data),
  1336. headers: { "Content-Type": "application/json" },
  1337. })
  1338. // Invalidate the cache after starting
  1339. revalidateTag("jo");
  1340. return response;
  1341. })
  1342. export const manualCreateJo = cache(async (data: SaveJo) => {
  1343. return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/manualCreate`, {
  1344. method: "POST",
  1345. body: JSON.stringify(data),
  1346. headers: { "Content-Type": "application/json" }
  1347. })
  1348. })
  1349. export const fetchCompletedJobOrderPickOrdersWithCompletedSecondScan = cache(async (userId: number) => {
  1350. return serverFetchJson<any[]>(`${BASE_API_URL}/jo/completed-job-order-pick-orders-with-completed-second-scan/${userId}`, {
  1351. method: "GET",
  1352. headers: { "Content-Type": "application/json" }
  1353. })
  1354. })
  1355. export const fetchCompletedJobOrderPickOrderLotDetails = cache(async (pickOrderId: number) => {
  1356. return serverFetchJson<any[]>(`${BASE_API_URL}/jo/completed-job-order-pick-order-lot-details/${pickOrderId}`, {
  1357. method: "GET",
  1358. headers: { "Content-Type": "application/json" }
  1359. })
  1360. })
  1361. export const fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick = cache(async (pickOrderId: number) => {
  1362. return serverFetchJson<any[]>(`${BASE_API_URL}/jo/completed-job-order-pick-order-lot-details-completed-pick/${pickOrderId}`, {
  1363. method: "GET",
  1364. headers: { "Content-Type": "application/json" }
  1365. })
  1366. })
  1367. export async function PrintPickRecord(request: PrintPickRecordRequest){
  1368. const params = new URLSearchParams();
  1369. params.append('pickOrderId', request.pickOrderId.toString())
  1370. params.append('printerId', request.printerId.toString())
  1371. if (request.printQty !== null && request.printQty !== undefined) {
  1372. params.append('printQty', request.printQty.toString());
  1373. }
  1374. if (request.floor) {
  1375. params.append('floor', request.floor);
  1376. }
  1377. if (request.plasticBoxCartonQty !== null && request.plasticBoxCartonQty !== undefined) {
  1378. params.append('plasticBoxCartonQty', request.plasticBoxCartonQty.toString());
  1379. }
  1380. if (request.plasticBoxCartonQty2f !== null && request.plasticBoxCartonQty2f !== undefined) {
  1381. params.append('plasticBoxCartonQty2f', request.plasticBoxCartonQty2f.toString());
  1382. }
  1383. if (request.plasticBoxCartonQty3f !== null && request.plasticBoxCartonQty3f !== undefined) {
  1384. params.append('plasticBoxCartonQty3f', request.plasticBoxCartonQty3f.toString());
  1385. }
  1386. if (request.plasticBoxCartonQty4f !== null && request.plasticBoxCartonQty4f !== undefined) {
  1387. params.append('plasticBoxCartonQty4f', request.plasticBoxCartonQty4f.toString());
  1388. }
  1389. try {
  1390. await serverFetchWithNoContent(
  1391. `${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,
  1392. { method: "GET" },
  1393. );
  1394. return {
  1395. success: true,
  1396. message: "Print job sent successfully (Pick Record)",
  1397. } as PrintPickRecordResponse;
  1398. } catch (error) {
  1399. const message =
  1400. error instanceof ServerFetchError
  1401. ? error.message
  1402. : error instanceof Error
  1403. ? error.message
  1404. : "Print failed";
  1405. return { success: false, message } as PrintPickRecordResponse;
  1406. }
  1407. }
  1408. export interface ExportFGStockInLabelRequest {
  1409. stockInLineId: number;
  1410. }
  1411. export const fetchFGStockInLabel = async (data: ExportFGStockInLabelRequest): Promise<FileResponse> => {
  1412. const reportBlob = await serverFetchBlob<FileResponse>(
  1413. `${BASE_API_URL}/jo/FGStockInLabel`,
  1414. {
  1415. method: "POST",
  1416. body: JSON.stringify(data),
  1417. headers: { "Content-Type": "application/json" },
  1418. },
  1419. );
  1420. return reportBlob;
  1421. };
  1422. export const updateJoPlanStart = cache(async (data: UpdateJoPlanStartRequest) => {
  1423. return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/update-jo-plan-start`,
  1424. {
  1425. method: "POST",
  1426. body: JSON.stringify(data),
  1427. headers: { "Content-Type": "application/json" },
  1428. })
  1429. })
  1430. export interface UpdateProductProcessLineStatusRequest {
  1431. productProcessLineId: number;
  1432. status: string;
  1433. }
  1434. export const updateProductProcessLineStatus = async (request: UpdateProductProcessLineStatusRequest) => {
  1435. return serverFetchJson<any>(
  1436. `${BASE_API_URL}/product-process/Demo/ProcessLine/update/status`,
  1437. {
  1438. method: "POST",
  1439. body: JSON.stringify(request),
  1440. headers: { "Content-Type": "application/json" },
  1441. }
  1442. );
  1443. };
  1444. export const passProductProcessLine = async (lineId: number) => {
  1445. return serverFetchJson<any>(
  1446. `${BASE_API_URL}/product-process/Demo/ProcessLine/pass/${lineId}`,
  1447. {
  1448. method: "POST",
  1449. headers: { "Content-Type": "application/json" },
  1450. }
  1451. );
  1452. };
  1453. export interface UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest {
  1454. productProcessLineId: number;
  1455. processingTime: number;
  1456. setupTime: number;
  1457. changeoverTime: number;
  1458. }
  1459. export const updateProductProcessLineProcessingTimeSetupTimeChangeoverTime = async (lineId: number, request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest) => {
  1460. return serverFetchJson<any>(
  1461. `${BASE_API_URL}/product-process/Demo/ProcessLine/update/processingTimeSetupTimeChangeoverTime/${lineId}`,
  1462. {
  1463. method: "POST",
  1464. body: JSON.stringify(request),
  1465. headers: { "Content-Type": "application/json" },
  1466. }
  1467. );
  1468. };
  1469. export interface MaterialPickStatusItem {
  1470. id: number;
  1471. pickOrderId: number | null;
  1472. pickOrderCode: string | null;
  1473. jobOrderId: number | null;
  1474. jobOrderCode: string | null;
  1475. itemId: number | null;
  1476. itemCode: string | null;
  1477. itemName: string | null;
  1478. jobOrderQty: number | null;
  1479. uom: string | null;
  1480. pickStartTime: string | null; // ISO datetime string
  1481. pickEndTime: string | null; // ISO datetime string
  1482. numberOfItemsToPick: number;
  1483. numberOfItemsWithIssue: number;
  1484. pickStatus: string | null;
  1485. }
  1486. export const fetchMaterialPickStatus = cache(async (date?: string): Promise<MaterialPickStatusItem[]> => {
  1487. const params = new URLSearchParams();
  1488. if (date) params.set("date", date); // yyyy-MM-dd
  1489. const qs = params.toString();
  1490. const url = `${BASE_API_URL}/jo/material-pick-status${qs ? `?${qs}` : ""}`;
  1491. return await serverFetchJson<MaterialPickStatusItem[]>(
  1492. url,
  1493. {
  1494. method: "GET",
  1495. }
  1496. );
  1497. })
  1498. export interface ProcessStatusInfo {
  1499. processName?: string | null;
  1500. equipmentName?: string | null;
  1501. equipmentDetailName?: string | null;
  1502. /** 經手人姓名(對應 product process line.handler) */
  1503. handlerName?: string | null;
  1504. startTime?: string | null;
  1505. endTime?: string | null;
  1506. isRequired: boolean;
  1507. }
  1508. export interface JobProcessStatusResponse {
  1509. jobOrderId: number;
  1510. jobOrderCode: string;
  1511. itemCode: string;
  1512. itemName: string;
  1513. status: string;
  1514. processingTime: number | null;
  1515. setupTime: number | null;
  1516. changeoverTime: number | null;
  1517. planEndTime?: string | null;
  1518. processes: ProcessStatusInfo[];
  1519. }
  1520. export const fetchJobProcessStatus = cache(
  1521. async (date?: string, productProcessStatus?: string | null) => {
  1522. const params = new URLSearchParams();
  1523. if (date) params.set("date", date); // yyyy-MM-dd
  1524. if (productProcessStatus && productProcessStatus.length > 0) {
  1525. params.set("productProcessStatus", productProcessStatus);
  1526. }
  1527. const qs = params.toString();
  1528. const url = `${BASE_API_URL}/product-process/Demo/JobProcessStatus${qs ? `?${qs}` : ""}`;
  1529. return serverFetchJson<JobProcessStatusResponse[]>(url, {
  1530. method: "GET",
  1531. next: { tags: ["jobProcessStatus"] },
  1532. });
  1533. },
  1534. );
  1535. // ===== Operator KPI Dashboard =====
  1536. export interface OperatorKpiProcessInfo {
  1537. jobOrderId?: number | null;
  1538. jobOrderCode?: string | null;
  1539. productProcessId?: number | null;
  1540. productProcessLineId?: number | null;
  1541. processName?: string | null;
  1542. equipmentName?: string | null;
  1543. equipmentDetailName?: string | null;
  1544. startTime?: string | number[] | null;
  1545. endTime?: string | number[] | null;
  1546. processingTime?: number | null;
  1547. itemCode?: string | null;
  1548. itemName?: string | null;
  1549. }
  1550. export interface OperatorKpiResponse {
  1551. operatorId: number;
  1552. operatorName?: string | null;
  1553. staffNo?: string | null;
  1554. totalProcessingMinutes: number;
  1555. totalJobOrderCount: number;
  1556. currentProcesses: OperatorKpiProcessInfo[];
  1557. }
  1558. export const fetchOperatorKpi = cache(async (date?: string) => {
  1559. const params = new URLSearchParams();
  1560. if (date) params.set("date", date);
  1561. const qs = params.toString();
  1562. const url = `${BASE_API_URL}/product-process/Demo/OperatorKpi${qs ? `?${qs}` : ""}`;
  1563. return serverFetchJson<OperatorKpiResponse[]>(url, {
  1564. method: "GET",
  1565. next: { tags: ["operatorKpi"] },
  1566. });
  1567. });
  1568. // ===== Drink Production Qty Dashboard =====
  1569. export interface DrinkProductionQtyProcessStep {
  1570. jobOrderId: number;
  1571. jobOrderCode?: string | null;
  1572. itemCode?: string | null;
  1573. itemName?: string | null;
  1574. seqNo?: number | null;
  1575. processName?: string | null;
  1576. operatorName?: string | null;
  1577. handlerName?: string | null;
  1578. startTime?: string | null;
  1579. endTime?: string | null;
  1580. status?: string | null;
  1581. }
  1582. export interface DrinkProductionQtyJobOrderDetail {
  1583. jobOrderId: number;
  1584. jobOrderCode?: string | null;
  1585. productionDate?: string | null;
  1586. reqQty: number;
  1587. productionQty: number;
  1588. jobOrderStatus?: string | null;
  1589. startTime?: string | null;
  1590. assumeTimeNeedMins?: number;
  1591. assumeEndTime?: string | null;
  1592. actualEndTime?: string | null;
  1593. latestStartBy?: string | null;
  1594. processOperators?: string | null;
  1595. processHandlers?: string | null;
  1596. qcUsers?: string | null;
  1597. putAwayUsers?: string | null;
  1598. processSteps?: DrinkProductionQtyProcessStep[];
  1599. }
  1600. export interface DrinkProductionQtyResponse {
  1601. itemCode?: string | null;
  1602. itemName?: string | null;
  1603. uom?: string | null;
  1604. totalReqQty: number;
  1605. totalQty: number;
  1606. jobOrders?: DrinkProductionQtyJobOrderDetail[];
  1607. }
  1608. /** FP-MTMS Version Checklist | Functions Ref. No. 26 | v1.0.0 | 2026-07-20 */
  1609. export const fetchDrinkProductionQty = cache(
  1610. async (date?: string, view: "actual" | "planned" = "actual") => {
  1611. const params = new URLSearchParams();
  1612. if (date) params.set("date", date);
  1613. params.set("view", view);
  1614. const qs = params.toString();
  1615. const url = `${BASE_API_URL}/product-process/Demo/DrinkProductionQty${qs ? `?${qs}` : ""}`;
  1616. return serverFetchJson<DrinkProductionQtyResponse[]>(url, {
  1617. method: "GET",
  1618. next: { tags: ["drinkProductionQty"] },
  1619. });
  1620. },
  1621. );
  1622. // ===== Equipment Status Dashboard =====
  1623. export interface EquipmentStatusProcessInfo {
  1624. jobOrderId?: number | null;
  1625. jobOrderCode?: string | null;
  1626. productProcessId?: number | null;
  1627. productProcessLineId?: number | null;
  1628. processName?: string | null;
  1629. operatorName?: string | null;
  1630. startTime?: string | number[] | null;
  1631. processingTime?: number | null;
  1632. }
  1633. export interface EquipmentStatusPerDetail {
  1634. equipmentDetailId: number;
  1635. equipmentDetailCode?: string | null;
  1636. equipmentDetailName?: string | null;
  1637. equipmentId?: number | null;
  1638. equipmentTypeName?: string | null;
  1639. status: string;
  1640. repairAndMaintenanceStatus?: boolean | null;
  1641. latestRepairAndMaintenanceDate?: string | null;
  1642. lastRepairAndMaintenanceDate?: string | null;
  1643. repairAndMaintenanceRemarks?: string | null;
  1644. currentProcess?: EquipmentStatusProcessInfo | null;
  1645. }
  1646. export interface EquipmentStatusByTypeResponse {
  1647. equipmentTypeId: number;
  1648. equipmentTypeName?: string | null;
  1649. details: EquipmentStatusPerDetail[];
  1650. }
  1651. export const fetchEquipmentStatus = cache(async () => {
  1652. const url = `${BASE_API_URL}/product-process/Demo/EquipmentStatus`;
  1653. return serverFetchJson<EquipmentStatusByTypeResponse[]>(url, {
  1654. method: "GET",
  1655. next: { tags: ["equipmentStatus"] },
  1656. });
  1657. });
  1658. export const deleteProductProcessLine = async (lineId: number) => {
  1659. return serverFetchJson<any>(
  1660. `${BASE_API_URL}/product-process/Demo/ProcessLine/delete/${lineId}`,
  1661. {
  1662. method: "POST",
  1663. headers: { "Content-Type": "application/json" },
  1664. }
  1665. );
  1666. };
  1667. ;