Переглянути джерело

update do is extra same order

production
CANCERYS\kw093 1 тиждень тому
джерело
коміт
ba510b8808
5 змінених файлів з 191 додано та 2 видалено
  1. +3
    -0
      src/app/api/pickOrder/actions.ts
  2. +5
    -1
      src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx
  3. +2
    -1
      src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx
  4. +167
    -0
      src/utils/workbenchPickLotUtils.ts
  5. +14
    -0
      src/utils/workbenchReleaseType.ts

+ 3
- 0
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;
}

// 新增:搜索参数接口


+ 5
- 1
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<Props> = ({
<Typography variant="h6">
{t("Pick Order Details")}: {selectedRecord.ticketNo}
</Typography>
{isWorkbenchExtraTicket(selectedRecord.releaseType, selectedRecord.ticketNo) && (
<Chip label={t("Etra")} color="secondary" size="small" />
)}
</Box>

<Paper sx={{ mb: 2, p: 2 }}>
@@ -674,7 +678,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({
<Box>
<Stack direction="row" alignItems="center" spacing={1} flexWrap="wrap">
<Typography variant="h6">{row.deliveryNoteCode || "-"}</Typography>
{(row.ticketNo ?? "").trim().toUpperCase().startsWith("TI-E-") && (
{isWorkbenchExtraTicket(row.releaseType, row.ticketNo) && (
<Chip
label={t("Etra")}
color="secondary"


+ 2
- 1
src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx Переглянути файл

@@ -295,6 +295,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
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<Props> = ({
setIsAssigning(false);
}
},
[currentUserId, inEtraUi, loadEtraSummaries, loadSummaries, onPickOrderAssigned, onSwitchToDetailTab, t],
[currentUserId, inEtraUi, loadEtraSummaries, loadSummaries, onPickOrderAssigned, onSwitchToDetailTab, releaseType, t],
);

const handleLaneButtonClick = useCallback(


+ 167
- 0
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, unknown>) => 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<string, unknown> {
const patch: Record<string, unknown> = { 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: "此批號狀態:可提貨" };
}

+ 14
- 0
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-")
);
}

Завантаження…
Відмінити
Зберегти