CANCERYS\kw093 пре 1 недеља
родитељ
комит
5ca2461c07
1 измењених фајлова са 261 додато и 55 уклоњено
  1. +261
    -55
      src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx

+ 261
- 55
src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx Прегледај датотеку

@@ -22,6 +22,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { normalizeTargetDateInput } from "@/utils/workbenchTargetDate"; import { normalizeTargetDateInput } from "@/utils/workbenchTargetDate";
import { isWorkbenchExtraTicket } from "@/utils/workbenchReleaseType";
import TestQrCodeProvider from "@/components/QrCodeScannerProvider/TestQrCodeProvider"; import TestQrCodeProvider from "@/components/QrCodeScannerProvider/TestQrCodeProvider";
import { fetchLotDetail } from "@/app/api/inventory/actions"; import { fetchLotDetail } from "@/app/api/inventory/actions";
import { formatDepartureTime } from "@/app/utils/formatUtil"; import { formatDepartureTime } from "@/app/utils/formatUtil";
@@ -131,37 +132,49 @@ function parseWorkbenchQrPayload(
} }
} }


function hasPendingActiveRowForStockInLine(
indexes: {
byStockInLineId: Map<number, any[]>;
activeLotsByItemId: Map<number, any[]>;
},
/**
* QR entry gate: pending / partial SOL not yet processed this session.
* Includes expired & unavailable rows (still need modal /换批); excludes completed/rejected/checked.
* `processedQrCombinations` still prevents re-picking the same SOL (e.g. two lines, same lot).
*/
function isLotRowEligibleForQrEntryGate(
lot: any,
itemId: number,
processedByItemId: ProcessedStockOutLinesByItemId,
): boolean {
if (Number(lot?.itemId) !== itemId) return false;
if (!isLotRowPending(lot)) return false;
const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
if (st === "rejected" || st === "completed" || st === "checked") return false;
if (String(lot?.lotAvailability ?? "").toLowerCase() === "rejected") return false;
return !isStockOutLineAlreadyProcessed(
processedByItemId,
itemId,
lot.stockOutLineId,
);
}

function hasPendingUnprocessedRowForStockInLine(
indexes: { byStockInLineId: Map<number, any[]> },
itemId: number, itemId: number,
stockInLineId: number, stockInLineId: number,
processedByItemId: ProcessedStockOutLinesByItemId, processedByItemId: ProcessedStockOutLinesByItemId,
): boolean { ): boolean {
const rows = indexes.byStockInLineId.get(stockInLineId) ?? []; const rows = indexes.byStockInLineId.get(stockInLineId) ?? [];
const activeSet = new Set(indexes.activeLotsByItemId.get(itemId) ?? []);
return rows.some(
(lot) =>
lot.itemId === itemId &&
activeSet.has(lot) &&
isLotRowPending(lot) &&
!isStockOutLineAlreadyProcessed(processedByItemId, itemId, lot.stockOutLineId),
return rows.some((lot) =>
isLotRowEligibleForQrEntryGate(lot, itemId, processedByItemId),
); );
} }


/** Any pending active SOL for this item (e.g. scan different location, same lot no → auto-switch). */
function hasPendingActiveRowForItem(
indexes: { activeLotsByItemId: Map<number, any[]> },
/** Any pending unprocessed SOL for this item (e.g. scan different stockInLineId → auto-switch). */
function hasPendingUnprocessedRowForItem(
indexes: { byItemId: Map<number, any[]> },
itemId: number, itemId: number,
processedByItemId: ProcessedStockOutLinesByItemId, processedByItemId: ProcessedStockOutLinesByItemId,
): boolean { ): boolean {
const activeLots = indexes.activeLotsByItemId.get(itemId) ?? [];
return activeLots.some(
(lot) =>
isLotRowPending(lot) &&
!isStockOutLineAlreadyProcessed(processedByItemId, itemId, lot.stockOutLineId),
const rows = indexes.byItemId.get(itemId) ?? [];
return rows.some((lot) =>
isLotRowEligibleForQrEntryGate(lot, itemId, processedByItemId),
); );
} }


@@ -343,6 +356,11 @@ function isInventoryLotLineUnavailable(lot: any): boolean {
return String(lot.lotStatus || "").toLowerCase() === "unavailable"; return String(lot.lotStatus || "").toLowerCase() === "unavailable";
} }


/** 過期或不可用:單筆 Just Complete / 顯示數量與批量提交一致,固定 qty=0 */
function isWorkbenchZeroCompleteLot(lot: any): boolean {
return isLotAvailabilityExpired(lot) || isInventoryLotLineUnavailable(lot);
}

/** 提貨台「列印標籤」彈窗頂部:依目前表格列判斷可提貨/已用畢/已過期等 */ /** 提貨台「列印標籤」彈窗頂部:依目前表格列判斷可提貨/已用畢/已過期等 */
function isWorkbenchSourceLotExpired(lot: any): boolean { function isWorkbenchSourceLotExpired(lot: any): boolean {
if (!lot) return false; if (!lot) return false;
@@ -419,6 +437,55 @@ function translateWorkbenchRejectMessage(raw: string, t: PickOrderT): string {
return t(msg); return t(msg);
} }


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);
}

type UnpickableScanAvailability = "expired" | "status_unavailable";

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;
}

function buildUnpickableScanRowPatch(
scannedLot: any | null | undefined,
availability: UnpickableScanAvailability,
): Record<string, unknown> {
const patch: Record<string, unknown> = { lotAvailability: availability };
if (availability === "status_unavailable") {
patch.lotStatus = "unavailable";
}
if (scannedLot?.lotNo) patch.lotNo = scannedLot.lotNo;
if (scannedLot?.stockInLineId) patch.stockInLineId = scannedLot.stockInLineId;
if (scannedLot?.expiryDate) patch.expiryDate = scannedLot.expiryDate;
if (scannedLot?.lotId) patch.lotId = scannedLot.lotId;
return patch;
}

/** /**
* 顯示後端拒絕原因:優先 workbench scan API 的 message(暫存於 scanRejectBySolId), * 顯示後端拒絕原因:優先 workbench scan API 的 message(暫存於 scanRejectBySolId),
* 其次階層 API 若帶 stockOutLineRejectMessage,最後依 rejected + lotAvailability 推斷(與後端語意對齊)。 * 其次階層 API 若帶 stockOutLineRejectMessage,最後依 rejected + lotAvailability 推斷(與後端語意對齊)。
@@ -646,10 +713,10 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);


const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
const isExtraTicket = useMemo(() => {
const ticketNo = String(fgPickOrders?.[0]?.ticketNo ?? "").trim().toUpperCase();
return ticketNo.startsWith("TI-E-");
}, [fgPickOrders]);
const isExtraTicket = useMemo(
() => isWorkbenchExtraTicket(fgPickOrders?.[0]?.releaseType, fgPickOrders?.[0]?.ticketNo),
[fgPickOrders],
);


const lotFloorPrefixFilter = useMemo(() => { const lotFloorPrefixFilter = useMemo(() => {
const storeId = String(fgPickOrders?.[0]?.storeId ?? "") const storeId = String(fgPickOrders?.[0]?.storeId ?? "")
@@ -894,6 +961,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shopName: hierarchicalData.fgInfo.shopName, shopName: hierarchicalData.fgInfo.shopName,
truckLanceCode: hierarchicalData.fgInfo.truckLanceCode, truckLanceCode: hierarchicalData.fgInfo.truckLanceCode,
DepartureTime: hierarchicalData.fgInfo.departureTime, DepartureTime: hierarchicalData.fgInfo.departureTime,
releaseType: hierarchicalData.fgInfo.releaseType,
shopAddress: "", shopAddress: "",
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
@@ -1163,12 +1231,64 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
} else { } else {
setWorkbenchLotLabelInitialPayload(null); setWorkbenchLotLabelInitialPayload(null);
} }
setWorkbenchLotLabelReminderText(reminderText ?? null);
setWorkbenchLotLabelReminderText(
reminderText ? translateWorkbenchRejectMessage(reminderText, t) : null,
);
// Clear latched success so the lot-label modal effect cannot instantly re-close on open. // Clear latched success so the lot-label modal effect cannot instantly re-close on open.
setQrScanSuccess(false); setQrScanSuccess(false);
setWorkbenchLotLabelModalOpen(true); setWorkbenchLotLabelModalOpen(true);
}, },
[],
[t],
);

/** Patch pick row locally so table shows 已過期/不可用 without full refresh. */
const patchWorkbenchRowForUnpickableScan = useCallback(
(
pickRow: any,
scannedLot: any | null | undefined,
availability: UnpickableScanAvailability,
) => {
const solId = Number(pickRow?.stockOutLineId);
if (!solId) return;
const rowPatch = buildUnpickableScanRowPatch(scannedLot, availability);
const mapRows = (prev: any[]) =>
prev.map((lot) =>
Number(lot.stockOutLineId) === solId ? { ...lot, ...rowPatch } : lot,
);
setCombinedLotData(mapRows);
setOriginalCombinedData(mapRows);
clearWorkbenchScanReject(solId);
},
[clearWorkbenchScanReject],
);

const openUnpickableScanLotLabelModal = useCallback(
(
pickRow: any,
scannedLot: any | null | undefined,
reminderText: string,
) => {
const fromMsg = inferUnpickableScanAvailability(reminderText);
const availability =
fromMsg ??
(isWorkbenchSourceLotExpired(scannedLot ?? pickRow)
? "expired"
: isInventoryLotLineUnavailable(scannedLot ?? pickRow)
? "status_unavailable"
: null);
const mergedPickRow =
availability != null
? { ...pickRow, ...buildUnpickableScanRowPatch(scannedLot, availability) }
: pickRow;
if (availability != null) {
patchWorkbenchRowForUnpickableScan(pickRow, scannedLot, availability);
}
openWorkbenchLotLabelModalForLot(mergedPickRow, reminderText);
},
[
patchWorkbenchRowForUnpickableScan,
openWorkbenchLotLabelModalForLot,
],
); );


const shouldOpenWorkbenchLotLabelModalForFailure = useCallback( const shouldOpenWorkbenchLotLabelModalForFailure = useCallback(
@@ -1253,9 +1373,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
severity: undefined as "success" | "warning" | "error" | undefined, severity: undefined as "success" | "warning" | "error" | undefined,
}; };
} }
const reminder = workbenchLotLabelReminderText?.trim() ?? "";
if (reminder && isExpiredWorkbenchReminderMessage(reminder)) {
return { text: "此批號狀態:已過期", severity: "error" as const };
}
const s = getWorkbenchSourceLotStatusSummary(workbenchLotLabelContextLot); const s = getWorkbenchSourceLotStatusSummary(workbenchLotLabelContextLot);
return { text: s.text, severity: s.severity }; return { text: s.text, severity: s.severity };
}, [workbenchLotLabelModalOpen, workbenchLotLabelContextLot]);
}, [
workbenchLotLabelModalOpen,
workbenchLotLabelContextLot,
workbenchLotLabelReminderText,
]);


const workbenchLotLabelSubmitQty = useMemo(() => { const workbenchLotLabelSubmitQty = useMemo(() => {
if (!workbenchLotLabelContextLot) return 0; if (!workbenchLotLabelContextLot) return 0;
@@ -1541,6 +1669,19 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
} }
}; };


/** Stop QR effect re-entry after unpickable modal (expired/unavailable API fail). */
const markUnpickableScanSessionHandled = (
itemId: number,
stockOutLineId: number | null | undefined,
) => {
if (stockOutLineId != null) {
setProcessedQrCombinations((prev) =>
markProcessedStockOutLine(prev, itemId, stockOutLineId),
);
}
recordHandledQrScanCount(qrScanCountAtInvoke);
};

try { try {
// 1) Parse JSON safely (parse once, reuse) // 1) Parse JSON safely (parse once, reuse)
const parseStartTime = performance.now(); const parseStartTime = performance.now();
@@ -1575,13 +1716,13 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const scannedItemId = qrData.itemId; const scannedItemId = qrData.itemId;
const scannedStockInLineId = qrData.stockInLineId; const scannedStockInLineId = qrData.stockInLineId;


const hasPendingOnScannedSil = hasPendingActiveRowForStockInLine(
const hasPendingOnScannedSil = hasPendingUnprocessedRowForStockInLine(
indexes, indexes,
scannedItemId, scannedItemId,
scannedStockInLineId, scannedStockInLineId,
processedQrCombinations, processedQrCombinations,
); );
const hasPendingOnItem = hasPendingActiveRowForItem(
const hasPendingOnItem = hasPendingUnprocessedRowForItem(
indexes, indexes,
scannedItemId, scannedItemId,
processedQrCombinations, processedQrCombinations,
@@ -1590,6 +1731,15 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
console.log( console.log(
` [SKIP] No pending stock-out line left for itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId}`, ` [SKIP] No pending stock-out line left for itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId}`,
); );
startTransition(() => {
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(
t(
"No pending pick line left for this item. It may already be completed or fully processed.",
),
);
});
return; return;
} }
@@ -1628,14 +1778,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
`此批次(${scannedLot.lotNo || scannedStockInLineId})已被拒绝,无法使用。请扫描其他批次。` `此批次(${scannedLot.lotNo || scannedStockInLineId})已被拒绝,无法使用。请扫描其他批次。`
); );
}); });
if (scannedLot?.stockOutLineId != null) {
const nextProcessed = markProcessedStockOutLine(
processedQrCombinations,
scannedItemId,
scannedLot.stockOutLineId,
);
setProcessedQrCombinations(nextProcessed);
}
markUnpickableScanSessionHandled(
scannedItemId,
scannedLot.stockOutLineId,
);
return; return;
} }


@@ -1645,25 +1791,33 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
setQrScanError(false); setQrScanError(false);
setQrScanSuccess(false); setQrScanSuccess(false);
}); });
openWorkbenchLotLabelModalForLot(
openUnpickableScanLotLabelModal(
scannedLot,
scannedLot, scannedLot,
t("This lot is not available, please scan another lot."), t("This lot is not available, please scan another lot."),
); );
markUnpickableScanSessionHandled(
scannedItemId,
scannedLot.stockOutLineId,
);
return; return;
} }


const isExpired =
String(scannedLot.lotAvailability || '').toLowerCase() === 'expired';
if (isExpired) {
if (isWorkbenchSourceLotExpired(scannedLot)) {
console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is expired; opening lot-label modal`); console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is expired; opening lot-label modal`);
startTransition(() => { startTransition(() => {
setQrScanError(false); setQrScanError(false);
setQrScanSuccess(false); setQrScanSuccess(false);
}); });
openWorkbenchLotLabelModalForLot(
openUnpickableScanLotLabelModal(
scannedLot,
scannedLot, scannedLot,
`Lot is expired (expiry=${scannedLot.expiryDate || "-"})`, `Lot is expired (expiry=${scannedLot.expiryDate || "-"})`,
); );
markUnpickableScanSessionHandled(
scannedItemId,
scannedLot.stockOutLineId,
);
return; return;
} }
} }
@@ -1766,7 +1920,19 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openUnpickableScanLotLabelModal(
expectedLot,
scannedLot ??
allLotsForItem.find(
(lot: any) => lot.stockInLineId === scannedStockInLineId,
) ??
null,
failMsg,
);
markUnpickableScanSessionHandled(
scannedItemId,
expectedLot.stockOutLineId,
);
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -1932,7 +2098,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openUnpickableScanLotLabelModal(
expectedLot,
scannedLot ??
allLotsForItem.find(
(lot: any) => lot.stockInLineId === scannedStockInLineId,
) ?? {
stockInLineId: scannedStockInLineId,
lotNo: scannedLotNo,
},
failMsg,
);
markUnpickableScanSessionHandled(
scannedItemId,
expectedLot.stockOutLineId,
);
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -2119,7 +2299,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
exactMatch exactMatch
) { ) {
openWorkbenchLotLabelModalForLot(exactMatch, failMsg);
openUnpickableScanLotLabelModal(exactMatch, exactMatch, failMsg);
markUnpickableScanSessionHandled(
scannedItemId,
exactMatch.stockOutLineId,
);
return; return;
} }
if (workbenchMode && exactMatch.stockOutLineId != null) { if (workbenchMode && exactMatch.stockOutLineId != null) {
@@ -2233,7 +2417,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openUnpickableScanLotLabelModal(
expectedLot,
scannedLot ??
allLotsForItem.find(
(lot: any) => lot.stockInLineId === scannedStockInLineId,
) ?? {
stockInLineId: scannedStockInLineId,
lotNo: scannedLotNo,
},
failMsg,
);
markUnpickableScanSessionHandled(
scannedItemId,
expectedLot.stockOutLineId,
);
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -2340,6 +2538,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
refreshWorkbenchAfterScanPick, refreshWorkbenchAfterScanPick,
workbenchScanPickQtyFromLot, workbenchScanPickQtyFromLot,
openWorkbenchLotLabelModalForLot, openWorkbenchLotLabelModalForLot,
openUnpickableScanLotLabelModal,
shouldOpenWorkbenchLotLabelModalForFailure, shouldOpenWorkbenchLotLabelModalForFailure,
t, t,
]); ]);
@@ -2445,6 +2644,12 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// If it's a different QR, allow processing // If it's a different QR, allow processing
console.log(` [QR PROCESS] Different QR detected while manual modal open, allowing processing`); console.log(` [QR PROCESS] Different QR detected while manual modal open, allowing processing`);
} }

// Skip re-processing while lot-label modal is already open for this scan
if (workbenchLotLabelModalOpen && latestQr === lastProcessedQrRef.current) {
console.log(` [QR PROCESS] Skipping - lot-label modal open for same QR`);
return;
}
const qrDetectionStartTime = performance.now(); const qrDetectionStartTime = performance.now();
console.log(` [QR DETECTION] Latest QR detected: ${latestQr?.substring(0, 50)}...`); console.log(` [QR DETECTION] Latest QR detected: ${latestQr?.substring(0, 50)}...`);
@@ -2457,7 +2662,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const canRetrySamePhysicalLot = const canRetrySamePhysicalLot =
qrPayload != null && qrPayload != null &&
isNewScanEvent && isNewScanEvent &&
hasPendingActiveRowForStockInLine(
hasPendingUnprocessedRowForStockInLine(
lotDataIndexes, lotDataIndexes,
qrPayload.itemId, qrPayload.itemId,
qrPayload.stockInLineId, qrPayload.stockInLineId,
@@ -2578,6 +2783,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
isRefreshingData, isRefreshingData,
combinedLotData.length, combinedLotData.length,
manualLotConfirmationOpen, manualLotConfirmationOpen,
workbenchLotLabelModalOpen,
lotDataIndexes, lotDataIndexes,
processedQrCombinations, processedQrCombinations,
]); ]);
@@ -2925,8 +3131,8 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
try { try {
if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: true })); if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: true }));
const targetUnavailable = isInventoryLotLineUnavailable(lot);
const effectiveSubmitQty = targetUnavailable && submitQty > 0 ? 0 : submitQty;
const targetZeroComplete = isWorkbenchZeroCompleteLot(lot);
const effectiveSubmitQty = targetZeroComplete && submitQty > 0 ? 0 : submitQty;


const canonicalLotForSol = const canonicalLotForSol =
solId > 0 solId > 0
@@ -2951,10 +3157,10 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe


const qtyPayload = workbenchScanPickQtyFromLot(canonicalLotForSol); const qtyPayload = workbenchScanPickQtyFromLot(canonicalLotForSol);
const wbJustQty = qtyPayload.qty; const wbJustQty = qtyPayload.qty;
const isUnavailableForJustComplete = isInventoryLotLineUnavailable(canonicalLotForSol);
const isZeroCompleteForJustComplete = isWorkbenchZeroCompleteLot(canonicalLotForSol);
const canPostScanPick = const canPostScanPick =
// unavailable lot: Just Completed must always submit qty=0, even without lotNo
isUnavailableForJustComplete || (
// expired / unavailable: Just Completed always submits qty=0, even without lotNo
isZeroCompleteForJustComplete || (
canonicalLotForSol.lotNo && String(canonicalLotForSol.lotNo).trim() !== "" && ( canonicalLotForSol.lotNo && String(canonicalLotForSol.lotNo).trim() !== "" && (
// explicit short submit: user typed 0 (must send qty=0 to backend) // explicit short submit: user typed 0 (must send qty=0 to backend)
(hasExplicitSubmitOverride && (hasExplicitSubmitOverride &&
@@ -2966,7 +3172,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
); );


if (canPostScanPick) { if (canPostScanPick) {
const qtyToSend = isUnavailableForJustComplete
const qtyToSend = isZeroCompleteForJustComplete
? 0 ? 0
: hasExplicitSubmitOverride && explicitSubmitOverride === 0 : hasExplicitSubmitOverride && explicitSubmitOverride === 0
? 0 ? 0
@@ -3011,7 +3217,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return; return;
} }
const justCompleteErr = t( const justCompleteErr = t(
"Just Completed (workbench): requires a valid lot number and quantity; expired rows must not use this button.",
"Just Completed (workbench): requires a valid lot number and quantity.",
); );
if (solId > 0) { if (solId > 0) {
rememberWorkbenchScanReject(solId, justCompleteErr); rememberWorkbenchScanReject(solId, justCompleteErr);
@@ -3800,7 +4006,7 @@ paginatedData.map((row, index) => {
const solIdForKey = Number(lot.stockOutLineId) || 0; const solIdForKey = Number(lot.stockOutLineId) || 0;
const lotKeyForSubmitQty = const lotKeyForSubmitQty =
Number.isFinite(solIdForKey) && solIdForKey > 0 ? `sol:${solIdForKey}` : `${lot.pickOrderLineId}-${lot.lotId}`; Number.isFinite(solIdForKey) && solIdForKey > 0 ? `sol:${solIdForKey}` : `${lot.pickOrderLineId}-${lot.lotId}`;
const lockedSubmitQtyDisplay = isInventoryLotLineUnavailable(lot) ? 0 : resolveSingleSubmitQty(lot);
const lockedSubmitQtyDisplay = isWorkbenchZeroCompleteLot(lot) ? 0 : resolveSingleSubmitQty(lot);
const hasPickOverride = Object.prototype.hasOwnProperty.call(pickQtyData, lotKeyForSubmitQty); const hasPickOverride = Object.prototype.hasOwnProperty.call(pickQtyData, lotKeyForSubmitQty);
const fromPickRow = hasPickOverride ? pickQtyData[lotKeyForSubmitQty] : undefined; const fromPickRow = hasPickOverride ? pickQtyData[lotKeyForSubmitQty] : undefined;
const workbenchSubmitQtyDisplay = const workbenchSubmitQtyDisplay =


Loading…
Откажи
Сачувај