"use client"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; export interface JobOrderListItem { id: number; code: string | null; planStart: string | null; itemCode: string | null; itemName: string | null; reqQty: number | null; stockInLineId: number | null; itemId: number | null; lotNo: string | null; /** 打袋機 DataFlex cumulative printed qty */ bagPrintedQty?: number; /** 標簽機 cumulative printed qty */ labelPrintedQty?: number; /** 激光機 cumulative printed qty */ laserPrintedQty?: number; } export interface PrinterStatusRequest { printerType: "dataflex" | "laser"; printerIp?: string; printerPort?: number; } export interface PrinterStatusResponse { connected: boolean; message: string; } export interface OnPackQrDownloadRequest { jobOrders: { jobOrderId: number; itemCode: string; }[]; } /** Same mapping as Bag Print download buttons: one entry per row with a non-empty item code. */ export function buildOnPackJobOrdersPayload(jobOrders: JobOrderListItem[]): { jobOrderId: number; itemCode: string; }[] { return jobOrders .map((jobOrder) => ({ jobOrderId: jobOrder.id, itemCode: jobOrder.itemCode?.trim() || "", })) .filter((jobOrder) => jobOrder.itemCode.length > 0); } export interface NgpclPushResponse { pushed: boolean; message: string; } /** * POST the same lemon OnPack ZIP bytes as download-onpack-qr-text to the server-configured NGPCL HTTP endpoint (ngpcl.push-url). * When the URL is not configured, response has pushed=false — use download ZIP instead. */ export async function pushOnPackTextQrZipToNgpcl(request: OnPackQrDownloadRequest): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/ngpcl/push-onpack-qr-text`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); if (res.status === 401 || res.status === 403) { return { pushed: false, message: "Session expired or unauthorized." }; } const data = (await res.json()) as NgpclPushResponse; if (!res.ok) { throw new Error(data.message || `HTTP ${res.status}`); } return data; } /** Readable message when ZIP download returns non-OK (plain text, JSON error body, or generic). */ async function zipDownloadError(res: Response): Promise { const text = await res.text(); const ct = res.headers.get("content-type") ?? ""; if (ct.includes("application/json")) { try { const j = JSON.parse(text) as { message?: string; error?: string }; if (typeof j.message === "string" && j.message.length > 0) { return new Error(j.message); } if (typeof j.error === "string" && j.error.length > 0) { return new Error(j.error); } } catch { /* ignore parse */ } } if (text && text.length > 0 && text.length < 800 && !text.trim().startsWith("{")) { return new Error(text); } return new Error(`下載失敗(HTTP ${res.status})。請查看後端日誌或確認資料庫已執行 Liquibase 更新。`); } /** * Fetch job orders by plan date from GET /py/job-orders. * Client-side only; uses auth token from localStorage. */ export async function fetchJobOrders(planStart: string): Promise { const url = `${NEXT_PUBLIC_API_URL}/py/job-orders?planStart=${encodeURIComponent(planStart)}`; const res = await clientAuthFetch(url, { method: "GET" }); if (!res.ok) { throw new Error(`Failed to fetch job orders: ${res.status}`); } return res.json(); } export async function checkPrinterStatus( request: PrinterStatusRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/check-printer`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); const data = (await res.json()) as PrinterStatusResponse; if (!res.ok) { return data; } return data; } export async function downloadOnPackQrZip( request: OnPackQrDownloadRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); if (!res.ok) { throw await zipDownloadError(res); } return res.blob(); } /** OnPack2023 檸檬機 — text QR template (`onpack2030_2`), no separate .bmp */ export async function downloadOnPackTextQrZip( request: OnPackQrDownloadRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr-text`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); if (!res.ok) { throw await zipDownloadError(res); } return res.blob(); }