From ba510b8808f90b2e8c3ede31768cde8a18efcba5 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 8 Jun 2026 13:15:04 +0800 Subject: [PATCH] update do is extra same order --- src/app/api/pickOrder/actions.ts | 3 + .../GoodPickExecutionWorkbenchRecord.tsx | 6 +- .../DoWorkbench/WorkbenchFloorLanePanel.tsx | 3 +- src/utils/workbenchPickLotUtils.ts | 167 ++++++++++++++++++ src/utils/workbenchReleaseType.ts | 14 ++ 5 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/utils/workbenchPickLotUtils.ts create mode 100644 src/utils/workbenchReleaseType.ts diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 4e8f6f9..2cc0bdc 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -297,6 +297,7 @@ export interface FGPickOrderResponse { truckLanceCode: string; storeId: string; qrCodeData: number; + releaseType?: string; } export interface DoPickOrderDetail { @@ -372,6 +373,8 @@ export interface CompletedDoPickOrderResponse { deliveryNoteCode: number; /** Legacy: do_pick_order_record.handler_name; workbench: delivery_order_pick_order.handlerName */ handlerName?: string | null; + /** Workbench: delivery_order_pick_order.releaseType (e.g. isExtrabatch). */ + releaseType?: string | null; } // 新增:搜索参数接口 diff --git a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx index d3419cb..3465d0e 100644 --- a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx +++ b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx @@ -39,6 +39,7 @@ import { printDNWorkbench, printDNLabelsWorkbench, printDNLabelsReprintWorkbench import { fetchWorkbenchCompletedLotDetails } from "@/app/api/doworkbench/actions"; import SearchBox, { Criterion } from "../SearchBox"; import { resolveWorkbenchRecordTargetDate } from "@/utils/workbenchTargetDate"; +import { isWorkbenchExtraTicket } from "@/utils/workbenchReleaseType"; type Props = { printerCombo: PrinterCombo[]; @@ -522,6 +523,9 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({ {t("Pick Order Details")}: {selectedRecord.ticketNo} + {isWorkbenchExtraTicket(selectedRecord.releaseType, selectedRecord.ticketNo) && ( + + )} @@ -674,7 +678,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({ {row.deliveryNoteCode || "-"} - {(row.ticketNo ?? "").trim().toUpperCase().startsWith("TI-E-") && ( + {isWorkbenchExtraTicket(row.releaseType, row.ticketNo) && ( = ({ truckDepartureTime, loadingSequence, requiredDate: dateParam, + releaseType: inEtraUi ? "isExtra" : releaseType, }); console.log("assignByLane result:", res); if (res.code === "SUCCESS") { @@ -309,7 +310,7 @@ const WorkbenchFloorLanePanel: React.FC = ({ setIsAssigning(false); } }, - [currentUserId, inEtraUi, loadEtraSummaries, loadSummaries, onPickOrderAssigned, onSwitchToDetailTab, t], + [currentUserId, inEtraUi, loadEtraSummaries, loadSummaries, onPickOrderAssigned, onSwitchToDetailTab, releaseType, t], ); const handleLaneButtonClick = useCallback( diff --git a/src/utils/workbenchPickLotUtils.ts b/src/utils/workbenchPickLotUtils.ts new file mode 100644 index 0000000..6718db0 --- /dev/null +++ b/src/utils/workbenchPickLotUtils.ts @@ -0,0 +1,167 @@ +import dayjs from "dayjs"; + +export type WorkbenchPickLotLike = { + status?: string; + stockOutLineStatus?: string; + lotAvailability?: string; + lotStatus?: string; + expiryDate?: string; + noLot?: boolean; + lotNo?: string; + availableQty?: number; +}; + +export type PickOrderT = (key: string, options?: Record) => string; + +function solStatusOf(lot: WorkbenchPickLotLike | null | undefined): string { + return String(lot?.stockOutLineStatus || lot?.status || "").toLowerCase(); +} + +/** lotAvailability === expired(後端標記) */ +export function isLotAvailabilityExpired( + lot: WorkbenchPickLotLike | null | undefined, +): boolean { + return String(lot?.lotAvailability || "").toLowerCase() === "expired"; +} + +/** inventory_lot_line.status = unavailable */ +export function isInventoryLotLineUnavailable( + lot: WorkbenchPickLotLike | null | undefined, +): boolean { + if (!lot) return false; + const solSt = solStatusOf(lot); + if (solSt === "completed" || solSt === "partially_completed" || solSt === "partially_complete") { + return false; + } + if (String(lot.lotAvailability || "").toLowerCase() === "status_unavailable") return true; + return String(lot.lotStatus || "").toLowerCase() === "unavailable"; +} + +/** 含 expiryDate 日期判斷 */ +export function isWorkbenchSourceLotExpired( + lot: WorkbenchPickLotLike | null | undefined, +): boolean { + if (!lot) return false; + if (isLotAvailabilityExpired(lot)) return true; + if (String(lot.lotAvailability || "").toLowerCase() === "expired") return true; + if (lot.expiryDate) { + const d = dayjs(lot.expiryDate).startOf("day"); + if (d.isValid() && d.isBefore(dayjs().startOf("day"))) return true; + } + return false; +} + +/** 過期或不可用:單筆 Just Complete / 顯示數量與批量提交一致,固定 qty=0 */ +export function isWorkbenchZeroCompleteLot( + lot: WorkbenchPickLotLike | null | undefined, +): boolean { + return isLotAvailabilityExpired(lot) || isInventoryLotLineUnavailable(lot); +} + +export function translateWorkbenchRejectMessage(raw: string, t: PickOrderT): string { + const msg = raw.trim(); + if (!msg) return msg; + + const expiredMatch = msg.match(/^Lot is expired \(expiry=([^)]+)\)\.?$/i); + if (expiredMatch) { + return t("Lot is expired (expiry={{expiry}})", { + expiry: expiredMatch[1], + }); + } + + return t(msg); +} + +export function isExpiredWorkbenchReminderMessage(msg: string): boolean { + const trimmed = msg.trim(); + if (!trimmed) return false; + if (/^lot is expired \(expiry=/i.test(trimmed)) return true; + return /已過期/.test(trimmed) || /掃描批號已過期/.test(trimmed); +} + +export type UnpickableScanAvailability = "expired" | "status_unavailable"; + +export function inferUnpickableScanAvailability( + failMsg: string | null | undefined, +): UnpickableScanAvailability | null { + const m = String(failMsg ?? "").trim().toLowerCase(); + if (!m) return null; + if ( + m.includes("expired") || + m.includes("过期") || + m.includes("已過期") || + /^lot is expired/.test(m) + ) { + return "expired"; + } + if ( + m.includes("unavailable") || + m.includes("not available") || + m.includes("not yet putaway") || + m.includes("不可用") || + m.includes("未上架") + ) { + return "status_unavailable"; + } + return null; +} + +export function buildUnpickableScanRowPatch( + scannedLot: WorkbenchPickLotLike | null | undefined, + availability: UnpickableScanAvailability, +): Record { + const patch: Record = { lotAvailability: availability }; + if (availability === "status_unavailable") { + patch.lotStatus = "unavailable"; + } + if (scannedLot && "lotNo" in scannedLot && scannedLot.lotNo) { + patch.lotNo = scannedLot.lotNo; + } + if (scannedLot && "stockInLineId" in scannedLot && scannedLot.stockInLineId) { + patch.stockInLineId = scannedLot.stockInLineId; + } + if (scannedLot?.expiryDate) patch.expiryDate = scannedLot.expiryDate; + return patch; +} + +export function getWorkbenchSourceLotStatusSummary(lot: WorkbenchPickLotLike | null | undefined): { + severity: "success" | "warning" | "error"; + text: string; +} { + if (!lot) { + return { severity: "warning", text: "無法判斷此批號狀態" }; + } + if (isWorkbenchSourceLotExpired(lot)) { + return { severity: "error", text: "此批號狀態:已過期" }; + } + const solSt = solStatusOf(lot); + if (solSt === "rejected") { + return { severity: "warning", text: "此出庫行:已拒絕,請改掃其他批號" }; + } + if (solSt === "completed" || solSt === "partially_completed" || solSt === "partially_complete") { + return { severity: "warning", text: "此出庫行:已完成,無需再提貨" }; + } + const isNoLotRow = + lot.noLot === true || !lot.lotNo || String(lot.lotNo || "").trim() === ""; + if (isNoLotRow) { + return { + severity: "warning", + text: "尚未綁定批號/無可用庫存列:請掃描週邊入庫或轉倉 QR", + }; + } + const av = String(lot.lotAvailability || "").toLowerCase(); + if (av === "insufficient_stock") { + return { severity: "warning", text: "此批號狀態:已用畢(無剩餘庫存)" }; + } + const avail = Number(lot.availableQty); + if (lot.lotNo && Number.isFinite(avail) && avail <= 0) { + return { severity: "warning", text: "此批號狀態:已用畢(可用量為 0)" }; + } + if (isInventoryLotLineUnavailable(lot)) { + return { + severity: "warning", + text: "此批號狀態:庫存不可用(未上架或行狀態不可用)", + }; + } + return { severity: "success", text: "此批號狀態:可提貨" }; +} diff --git a/src/utils/workbenchReleaseType.ts b/src/utils/workbenchReleaseType.ts new file mode 100644 index 0000000..0868803 --- /dev/null +++ b/src/utils/workbenchReleaseType.ts @@ -0,0 +1,14 @@ +/** Workbench ticket carries extra DOs when releaseType or legacy TI-E- ticket. */ +export function isWorkbenchExtraTicket( + releaseType?: string | null, + ticketNo?: string | null, +): boolean { + const rt = (releaseType ?? "").trim().toLowerCase(); + const tn = (ticketNo ?? "").trim().toUpperCase(); + return ( + rt === "isextrabatch" || + rt === "isextrasingle" || + rt === "isextra" || + tn.startsWith("TI-E-") + ); +}