FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

124 行
3.4 KiB

  1. "use client";
  2. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  3. import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
  4. export interface JobOrderListItem {
  5. id: number;
  6. code: string | null;
  7. planStart: string | null;
  8. itemCode: string | null;
  9. itemName: string | null;
  10. reqQty: number | null;
  11. stockInLineId: number | null;
  12. itemId: number | null;
  13. lotNo: string | null;
  14. }
  15. export interface PrinterStatusRequest {
  16. printerType: "dataflex" | "laser";
  17. printerIp?: string;
  18. printerPort?: number;
  19. }
  20. export interface PrinterStatusResponse {
  21. connected: boolean;
  22. message: string;
  23. }
  24. export interface OnPackQrDownloadRequest {
  25. jobOrders: {
  26. jobOrderId: number;
  27. itemCode: string;
  28. }[];
  29. }
  30. /** Readable message when ZIP download returns non-OK (plain text, JSON error body, or generic). */
  31. async function zipDownloadError(res: Response): Promise<Error> {
  32. const text = await res.text();
  33. const ct = res.headers.get("content-type") ?? "";
  34. if (ct.includes("application/json")) {
  35. try {
  36. const j = JSON.parse(text) as { message?: string; error?: string };
  37. if (typeof j.message === "string" && j.message.length > 0) {
  38. return new Error(j.message);
  39. }
  40. if (typeof j.error === "string" && j.error.length > 0) {
  41. return new Error(j.error);
  42. }
  43. } catch {
  44. /* ignore parse */
  45. }
  46. }
  47. if (text && text.length > 0 && text.length < 800 && !text.trim().startsWith("{")) {
  48. return new Error(text);
  49. }
  50. return new Error(`下載失敗(HTTP ${res.status})。請查看後端日誌或確認資料庫已執行 Liquibase 更新。`);
  51. }
  52. /**
  53. * Fetch job orders by plan date from GET /py/job-orders.
  54. * Client-side only; uses auth token from localStorage.
  55. */
  56. export async function fetchJobOrders(planStart: string): Promise<JobOrderListItem[]> {
  57. const url = `${NEXT_PUBLIC_API_URL}/py/job-orders?planStart=${encodeURIComponent(planStart)}`;
  58. const res = await clientAuthFetch(url, { method: "GET" });
  59. if (!res.ok) {
  60. throw new Error(`Failed to fetch job orders: ${res.status}`);
  61. }
  62. return res.json();
  63. }
  64. export async function checkPrinterStatus(
  65. request: PrinterStatusRequest,
  66. ): Promise<PrinterStatusResponse> {
  67. const url = `${NEXT_PUBLIC_API_URL}/plastic/check-printer`;
  68. const res = await clientAuthFetch(url, {
  69. method: "POST",
  70. headers: { "Content-Type": "application/json" },
  71. body: JSON.stringify(request),
  72. });
  73. const data = (await res.json()) as PrinterStatusResponse;
  74. if (!res.ok) {
  75. return data;
  76. }
  77. return data;
  78. }
  79. export async function downloadOnPackQrZip(
  80. request: OnPackQrDownloadRequest,
  81. ): Promise<Blob> {
  82. const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr`;
  83. const res = await clientAuthFetch(url, {
  84. method: "POST",
  85. headers: { "Content-Type": "application/json" },
  86. body: JSON.stringify(request),
  87. });
  88. if (!res.ok) {
  89. throw await zipDownloadError(res);
  90. }
  91. return res.blob();
  92. }
  93. /** OnPack2023 檸檬機 — text QR template (`onpack2030_2`), no separate .bmp */
  94. export async function downloadOnPackTextQrZip(
  95. request: OnPackQrDownloadRequest,
  96. ): Promise<Blob> {
  97. const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr-text`;
  98. const res = await clientAuthFetch(url, {
  99. method: "POST",
  100. headers: { "Content-Type": "application/json" },
  101. body: JSON.stringify(request),
  102. });
  103. if (!res.ok) {
  104. throw await zipDownloadError(res);
  105. }
  106. return res.blob();
  107. }