From 02c4f1e5bfe797ffd5823aeb02b00cd272de9198 Mon Sep 17 00:00:00 2001 From: "PC-20260115JRSN\\Administrator" Date: Sun, 23 Aug 2026 13:20:37 +0800 Subject: [PATCH] fix for exp date --- src/app/api/laserPrint/actions.ts | 62 +++++++++++++++++-- .../LaserPrint/LaserPrintSearch.tsx | 38 +++++++++--- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/app/api/laserPrint/actions.ts b/src/app/api/laserPrint/actions.ts index 749385cd..ffde3afb 100644 --- a/src/app/api/laserPrint/actions.ts +++ b/src/app/api/laserPrint/actions.ts @@ -15,7 +15,8 @@ export interface JobOrderListItem { lotNo: string | null; defaultShelfLifeDays?: number | null; useMinus18?: boolean | null; - expiryDate?: string | null; + /** ISO `yyyy-MM-dd`, or Jackson date array `[yyyy,M,d]`. */ + expiryDate?: string | number[] | null; bagPrintedQty?: number; labelPrintedQty?: number; laserPrintedQty?: number; @@ -114,6 +115,39 @@ export async function fetchLaserBag2Settings(): Promise { return res.json() as Promise; } +/** List API may return LocalDate as `"2026-08-27"` or `[2026,8,27]` (@EnableWebMvc raw Jackson). */ +export function expiryDateForLaserSend(value: unknown): string | null { + if (value == null || value === "") return null; + if (typeof value === "string") { + const s = value.trim(); + if (!s) return null; + if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10); + return s; + } + if (Array.isArray(value) && value.length >= 3) { + const y = Number(value[0]); + const m = Number(value[1]); + const d = Number(value[2]); + if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d) || y < 1 || m < 1 || d < 1) { + return null; + } + return `${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`; + } + return null; +} + +function messageFromLaserSendBody(data: Record, status: number): string { + const message = typeof data.message === "string" ? data.message.trim() : ""; + if (message) return message; + const detail = typeof data.detail === "string" ? data.detail.trim() : ""; + if (detail) return detail; + const error = typeof data.error === "string" ? data.error.trim() : ""; + if (error) return error; + const traceId = typeof data.traceId === "string" ? data.traceId.trim() : ""; + if (traceId) return `送出失敗(traceId ${traceId})`; + return `送出失敗(HTTP ${status})`; +} + export async function sendLaserBag2Job(body: LaserBag2SendRequest): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/print-laser-bag2`; const res = await clientAuthFetch(url, { @@ -121,11 +155,29 @@ export async function sendLaserBag2Job(body: LaserBag2SendRequest): Promise = {}; + try { + const text = await res.text(); + data = text ? (JSON.parse(text) as Record) : {}; + } catch { + return { success: false, message: `送出失敗(HTTP ${res.status},無法解析回應)` }; } - return data; + if (!res.ok || data.success === false) { + return { + success: false, + message: messageFromLaserSendBody(data, res.status), + payloadSent: typeof data.payloadSent === "string" ? data.payloadSent : null, + printerAck: typeof data.printerAck === "string" ? data.printerAck : null, + receiveAcknowledged: Boolean(data.receiveAcknowledged), + }; + } + return { + success: true, + message: typeof data.message === "string" && data.message.trim() ? data.message : "已送出", + payloadSent: typeof data.payloadSent === "string" ? data.payloadSent : null, + printerAck: typeof data.printerAck === "string" ? data.printerAck : null, + receiveAcknowledged: Boolean(data.receiveAcknowledged), + }; } export interface PrinterStatusRequest { diff --git a/src/components/LaserPrint/LaserPrintSearch.tsx b/src/components/LaserPrint/LaserPrintSearch.tsx index 60b513c8..8aec53df 100644 --- a/src/components/LaserPrint/LaserPrintSearch.tsx +++ b/src/components/LaserPrint/LaserPrintSearch.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { Alert, Box, @@ -26,6 +26,7 @@ import { type LaserLastReceiveSuccess, JobOrderListItem, patchSetting, + expiryDateForLaserSend, sendLaserBag2Job, } from "@/app/api/laserPrint/actions"; import dayjs from "dayjs"; @@ -89,6 +90,7 @@ const LaserPrintSearch: React.FC = () => { const [settingsLoaded, setSettingsLoaded] = useState(false); const [printerConnected, setPrinterConnected] = useState(false); const [printerMessage, setPrinterMessage] = useState("檸檬機(激光機)未連接"); + const sendInFlightRef = useRef(false); const loadSystemSettings = useCallback(async () => { try { @@ -191,32 +193,38 @@ const LaserPrintSearch: React.FC = () => { jobOrderId: jo.id, jobOrderNo: jo.code, lotNo: jo.lotNo, - expiryDate: jo.expiryDate ?? null, + expiryDate: expiryDateForLaserSend(jo.expiryDate), source: "MANUAL", }); const handleRowClick = async (jo: JobOrderListItem) => { - if (sendingJobId !== null) return; + if (sendInFlightRef.current || sendingJobId !== null) return; if (!laserHost.trim()) { setErrorSnackbar({ open: true, message: "請在系統設定中填寫檸檬機(激光機) IP。" }); return; } + sendInFlightRef.current = true; setSelectedId(jo.id); setSendingJobId(jo.id); try { let lastAck: string | undefined; let anyReceiveAck = false; + let sentOk = 0; + let laterFail: string | null = null; for (let i = 0; i < LASER_SEND_COUNT; i++) { const r = await sendOne(jo); if (!r.success) { - setErrorSnackbar({ - open: true, - message: r.message || "檸檬機(激光機)未收到指令", - }); - return; + const failMsg = r.message?.trim() || `第 ${i + 1} 次送出失敗`; + if (sentOk === 0) { + setErrorSnackbar({ open: true, message: failMsg }); + return; + } + laterFail = failMsg; + break; } + sentOk += 1; if (r.printerAck) lastAck = r.printerAck; if (r.receiveAcknowledged) anyReceiveAck = true; if (i < LASER_SEND_COUNT - 1) { @@ -229,7 +237,11 @@ const LaserPrintSearch: React.FC = () => { : lastAck ? `(最後回覆:${lastAck})` : ""; - setSuccessSignal(`已送出 ${LASER_SEND_COUNT} 次至檸檬機(激光機)${ackHint}`); + setSuccessSignal( + laterFail + ? `已送出 ${sentOk} 次至檸檬機(激光機)${ackHint}(後續重送失敗:${laterFail})` + : `已送出 ${sentOk} 次至檸檬機(激光機)${ackHint}`, + ); await loadSystemSettings(); } catch (e) { setErrorSnackbar({ @@ -238,6 +250,7 @@ const LaserPrintSearch: React.FC = () => { }); } finally { setSendingJobId(null); + sendInFlightRef.current = false; } }; @@ -269,7 +282,12 @@ const LaserPrintSearch: React.FC = () => { {settingsLoaded && lastLaserReceive && ( - 上次印表機已確認工單:{lastLaserReceive.jobOrderNo ?? "—"} {formatHongKongDateTime(lastLaserReceive.sentAt)} + 上次印表機已確認工單:{lastLaserReceive.jobOrderNo ?? "—"}  + {formatHongKongDateTime(lastLaserReceive.sentAt)} + {lastLaserReceive.source ? ` (${lastLaserReceive.source === "AUTO" ? "自動送出" : "手動點選"})` : ""} + + + 此時間只會在檸檬機回覆 receive 時更新。之後送出失敗不會改這裡。 )}