|
- "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<NgpclPushResponse> {
- 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<Error> {
- 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<JobOrderListItem[]> {
- 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<PrinterStatusResponse> {
- 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<Blob> {
- 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<Blob> {
- 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();
- }
|