Преглед изворни кода

do switch lot update V1

MergeProblem1
CANCERYS\kw093 пре 18 часа
родитељ
комит
270763a2ae
3 измењених фајлова са 218 додато и 136 уклоњено
  1. +141
    -87
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  2. +65
    -49
      src/components/FinishedGoodSearch/LotConfirmationModal.tsx
  3. +12
    -0
      src/i18n/zh/pickOrder.json

+ 141
- 87
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx Прегледај датотеку

@@ -75,11 +75,27 @@ import GoodPickExecutionForm from "./GoodPickExecutionForm";
import FGPickOrderCard from "./FGPickOrderCard";
import LinearProgressWithLabel from "../common/LinearProgressWithLabel";
import ScanStatusAlert from "../common/ScanStatusAlert";
import { translateLotSubstitutionFailure } from "./lotSubstitutionMessage";
interface Props {
filterArgs: Record<string, any>;
onSwitchToRecordTab?: () => void;
onRefreshReleasedOrderCount?: () => void;
}
type LotConfirmRunContext = {
expectedLotData: {
lotNo: string | null;
itemCode?: string;
itemName?: string;
};
scannedLotData: {
lotNo: string | null;
itemCode?: string;
itemName?: string;
stockInLineId: number; // 必須有,API 用
inventoryLotLineId?: number | null;
};
selectedLotForQr: any; // 與現在一樣:含 pickOrderLineId, stockOutLineId, suggestedPickLotId, itemId…
};

/** 同物料多行时,优先对「有建议批次号」的行做替换,避免误选「无批次/不足」行 */
function pickExpectedLotForSubstitution(activeSuggestedLots: any[]): any | null {
@@ -115,12 +131,12 @@ const QrCodeModal: React.FC<{
const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [scannedQrResult, setScannedQrResult] = useState<string>('');
const [fgPickOrder, setFgPickOrder] = useState<FGPickOrderResponse | null>(null);
const fetchingRef = useRef<Set<number>>(new Set());
// Process scanned QR codes
useEffect(() => {
// ✅ Don't process if modal is not open
if (!open) {
@@ -619,6 +635,10 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);
const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false);
const [lotConfirmationError, setLotConfirmationError] = useState<string | null>(null);
/** QR 静默换批失败时显示在对应行的 Lot# 列,key = stockOutLineId */
const [lotSwitchFailByStockOutLineId, setLotSwitchFailByStockOutLineId] = useState<
Record<number, string>
>({});
const [expectedLotData, setExpectedLotData] = useState<any>(null);
const [scannedLotData, setScannedLotData] = useState<any>(null);
const [isConfirmingLot, setIsConfirmingLot] = useState(false);
@@ -626,7 +646,6 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);

const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
// Add these missing state variables after line 352
const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
@@ -656,9 +675,10 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const lotConfirmLastQrRef = useRef<string>('');
const lotConfirmSkipNextScanRef = useRef<boolean>(false);
const lotConfirmOpenedAtRef = useRef<number>(0);
const handleLotConfirmationRef = useRef<
((overrideScannedLot?: any, runContext?: LotConfirmRunContext) => Promise<void>) | null
>(null);

// Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
@@ -733,33 +753,69 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
}
}, []);

const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any, qrScanCountAtOpen?: number) => {
const handleLotMismatch = useCallback((fullExpectedLotRow: any, scannedLot: any, qrScanCountAtOpen?: number) => {
const mismatchStartTime = performance.now();
console.log(` [HANDLE LOT MISMATCH START]`);
console.log(` Start time: ${new Date().toISOString()}`);
console.log("Lot mismatch detected:", { expectedLot, scannedLot });
console.log("Lot mismatch detected:", { fullExpectedLotRow, scannedLot });

lotConfirmOpenedQrCountRef.current =
typeof qrScanCountAtOpen === "number" ? qrScanCountAtOpen : 1;
// ✅ Use setTimeout to avoid flushSync warning - schedule modal update in next tick
// ✅ Use setTimeout to avoid flushSync warning - schedule state + silent substitution in next tick
const setTimeoutStartTime = performance.now();
console.time('setLotConfirmationOpen');
console.time('setLotMismatchStateAndSubstitute');
setTimeout(() => {
const setStateStartTime = performance.now();
setExpectedLotData(expectedLot);
setScannedLotData({
const expectedForDisplay = {
lotNo: fullExpectedLotRow.lotNo,
itemCode: fullExpectedLotRow.itemCode,
itemName: fullExpectedLotRow.itemName,
};
const scannedMerged = {
...scannedLot,
lotNo: scannedLot.lotNo || null,
});
// The QR that opened modal must NOT be treated as confirmation rescan.
};
setExpectedLotData(expectedForDisplay);
setScannedLotData(scannedMerged);
setSelectedLotForQr(fullExpectedLotRow);
// The QR that triggered mismatch must NOT be treated as confirmation rescan.
lotConfirmSkipNextScanRef.current = true;
lotConfirmOpenedAtRef.current = Date.now();
setLotConfirmationOpen(true);

const sid = Number(scannedLot.stockInLineId);
if (!Number.isFinite(sid)) {
console.error(` [HANDLE LOT MISMATCH] Invalid stockInLineId for substitution: ${scannedLot.stockInLineId}`);
const errMsg = t("Lot switch failed; pick line was not marked as checked.");
const rowSol = Number(fullExpectedLotRow.stockOutLineId);
if (Number.isFinite(rowSol)) {
setLotSwitchFailByStockOutLineId((prev) => ({ ...prev, [rowSol]: errMsg }));
}
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(errMsg);
const setStateTime = performance.now() - setStateStartTime;
console.timeEnd('setLotMismatchStateAndSubstitute');
console.log(` [HANDLE LOT MISMATCH] Lot switch failed (invalid stockInLineId), setState time: ${setStateTime.toFixed(2)}ms`);
return;
}

const runContext: LotConfirmRunContext = {
expectedLotData: expectedForDisplay,
scannedLotData: {
...scannedMerged,
stockInLineId: sid,
itemCode: scannedMerged.itemCode ?? fullExpectedLotRow.itemCode,
itemName: scannedMerged.itemName ?? fullExpectedLotRow.itemName,
inventoryLotLineId: scannedLot.inventoryLotLineId ?? scannedLot.lotId ?? null,
},
selectedLotForQr: fullExpectedLotRow,
};
void handleLotConfirmationRef.current?.(undefined, runContext);

const setStateTime = performance.now() - setStateStartTime;
console.timeEnd('setLotConfirmationOpen');
console.log(` [HANDLE LOT MISMATCH] Modal state set to open (setState time: ${setStateTime.toFixed(2)}ms)`);
console.log(`✅ [HANDLE LOT MISMATCH] Modal state set to open`);
console.timeEnd('setLotMismatchStateAndSubstitute');
console.log(` [HANDLE LOT MISMATCH] Silent lot substitution scheduled (setState time: ${setStateTime.toFixed(2)}ms)`);
}, 0);
const setTimeoutTime = performance.now() - setTimeoutStartTime;
console.log(` [PERF] setTimeout scheduling time: ${setTimeoutTime.toFixed(2)}ms`);
@@ -802,7 +858,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const totalTime = performance.now() - mismatchStartTime;
console.log(` [HANDLE LOT MISMATCH END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
}
}, [fetchStockInLineInfoCached]);
}, [fetchStockInLineInfoCached, t]);
const checkAllLotsCompleted = useCallback((lotData: any[]) => {
if (lotData.length === 0) {
setAllLotsCompleted(false);
@@ -1314,74 +1370,75 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
}, []);

const handleLotConfirmation = useCallback(async (overrideScannedLot?: any) => {
const effectiveScannedLot = overrideScannedLot ?? scannedLotData;
if (!expectedLotData || !effectiveScannedLot || !selectedLotForQr) return;
const handleLotConfirmation = useCallback(async (overrideScannedLot?: any, runContext?: LotConfirmRunContext) => {
const exp = runContext?.expectedLotData ?? expectedLotData;
const scan = overrideScannedLot ?? runContext?.scannedLotData ?? scannedLotData;
const sel = runContext?.selectedLotForQr ?? selectedLotForQr;
if (!exp || !scan || !sel) return;

const newStockInLineId = scan?.stockInLineId;
if (newStockInLineId == null || Number.isNaN(Number(newStockInLineId))) return;

const rowSolKey = Number(sel.stockOutLineId);
if (Number.isFinite(rowSolKey)) {
setLotSwitchFailByStockOutLineId((prev) => {
const next = { ...prev };
delete next[rowSolKey];
return next;
});
}

setIsConfirmingLot(true);
setLotConfirmationError(null);
try {
const newLotNo = effectiveScannedLot?.lotNo;
const newStockInLineId = effectiveScannedLot?.stockInLineId;
const substitutionResult = await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId,
newInventoryLotNo: "",
newStockInLineId: newStockInLineId
pickOrderLineId: sel.pickOrderLineId,
stockOutLineId: sel.stockOutLineId,
originalSuggestedPickLotId: sel.suggestedPickLotId,
newInventoryLotNo: "",
newStockInLineId: newStockInLineId,
});

const substitutionCode = substitutionResult?.code;
const switchedToUnavailable =
substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE";
if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) {
const errMsg =
substitutionResult?.code === "LOT_UNAVAILABLE"
? t(
"The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated."
)
: substitutionResult?.message ||
t("Lot switch failed; pick line was not marked as checked.");
setLotConfirmationError(errMsg);
const errMsg = translateLotSubstitutionFailure(t, substitutionResult);
if (Number.isFinite(rowSolKey)) {
setLotSwitchFailByStockOutLineId((prev) => ({ ...prev, [rowSolKey]: errMsg }));
}
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(errMsg);
return;
}
setQrScanError(false);
setQrScanSuccess(false);
setQrScanInput('');
// ✅ 修复:在确认后重置扫描状态,避免重复处理
setQrScanInput("");

resetScan();
// ✅ 修复:不要清空 processedQrCodes,而是保留当前 QR code 的标记
// 或者如果确实需要清空,应该在重置扫描后再清空
// setProcessedQrCodes(new Set());
// setLastProcessedQr('');

setPickExecutionFormOpen(false);
if (selectedLotForQr?.stockOutLineId && !switchedToUnavailable) {
const stockOutLineUpdate = await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId,
status: 'checked',
qty: 0
if (sel?.stockOutLineId && !switchedToUnavailable) {
await updateStockOutLineStatus({
id: sel.stockOutLineId,
status: "checked",
qty: 0,
});
}
// ✅ 修复:先关闭 modal 和清空状态,再刷新数据

clearLotConfirmationState(false);
// ✅ 修复:刷新数据前设置刷新标志,避免在刷新期间处理新的 QR code

setIsRefreshingData(true);
await fetchAllCombinedLotData();
setIsRefreshingData(false);
} catch (error) {
console.error("Error confirming lot substitution:", error);
const errMsg = t("Lot confirmation failed. Please try again.");
setLotConfirmationError(errMsg);
if (Number.isFinite(rowSolKey)) {
setLotSwitchFailByStockOutLineId((prev) => ({ ...prev, [rowSolKey]: errMsg }));
}
setQrScanError(true);
setQrScanErrorMsg(errMsg);
} finally {
@@ -1389,6 +1446,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData, resetScan, clearLotConfirmationState, t]);

useEffect(() => {
handleLotConfirmationRef.current = handleLotConfirmation;
}, [handleLotConfirmation]);

const handleLotConfirmationByRescan = useCallback(async (rawQr: string): Promise<boolean> => {
if (!lotConfirmationOpen || !selectedLotForQr || !expectedLotData || !scannedLotData) {
return false;
@@ -1923,22 +1984,17 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
) ||
allLotsForItem[0];
// ✅ Always open confirmation modal when no active lots (user needs to confirm switching)
// handleLotMismatch will fetch lotNo from backend using stockInLineId if needed
console.log(`⚠️ [QR PROCESS] Opening confirmation modal for lot switch (no active lots)`);
// Silent lot substitution; modal only if switch fails
console.log(`⚠️ [QR PROCESS] Lot switch (no active lots), attempting substitution`);
setSelectedLotForQr(expectedLot);
handleLotMismatch(
expectedLot,
{
lotNo: expectedLot.lotNo,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName
},
{
lotNo: scannedLot?.lotNo || null, // Will be fetched by handleLotMismatch if null
lotNo: scannedLot?.lotNo || null,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName,
inventoryLotLineId: scannedLot?.lotId || null,
stockInLineId: scannedStockInLineId // handleLotMismatch will use this to fetch lotNo
stockInLineId: scannedStockInLineId,
},
qrScanCountAtInvoke
);
@@ -1971,20 +2027,16 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
// Check if scanned lot is different from expected, or if scannedLot is undefined (not in allLotsForItem)
const shouldOpenModal = !scannedLot || (scannedLot.stockInLineId !== expectedLot.stockInLineId);
if (shouldOpenModal) {
console.log(`⚠️ [QR PROCESS] Opening confirmation modal (scanned lot ${scannedLot?.lotNo || 'not in data'} is not in active suggested lots)`);
console.log(`⚠️ [QR PROCESS] Lot switch (scanned lot ${scannedLot?.lotNo || 'not in data'} not in active suggested lots)`);
setSelectedLotForQr(expectedLot);
handleLotMismatch(
expectedLot,
{
lotNo: expectedLot.lotNo,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName
},
{
lotNo: scannedLot?.lotNo || null, // Will be fetched by handleLotMismatch if null
lotNo: scannedLot?.lotNo || null,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName,
inventoryLotLineId: scannedLot?.lotId || null,
stockInLineId: scannedStockInLineId // handleLotMismatch will use this to fetch lotNo
stockInLineId: scannedStockInLineId,
},
qrScanCountAtInvoke
);
@@ -2130,21 +2182,15 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
const setSelectedLotTime = performance.now() - setSelectedLotStartTime;
console.log(` [PERF] Set selected lot time: ${setSelectedLotTime.toFixed(2)}ms`);
// ✅ 获取扫描的 lot 信息(从 QR 数据中提取,或使用默认值)
// Call handleLotMismatch immediately - it will open the modal
const handleMismatchStartTime = performance.now();
handleLotMismatch(
expectedLot,
{
lotNo: expectedLot.lotNo,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName
},
{
lotNo: null, // 扫描的 lotNo 未知,需要从后端获取或显示为未知
lotNo: null,
itemCode: expectedLot.itemCode,
itemName: expectedLot.itemName,
inventoryLotLineId: null,
stockInLineId: scannedStockInLineId // ✅ 传递 stockInLineId
stockInLineId: scannedStockInLineId,
},
qrScanCountAtInvoke
);
@@ -3608,7 +3654,10 @@ const handleSubmitAllScanned = useCallback(async () => {
paginatedData.map((lot, index) => {
// 检查是否是 issue lot
const isIssueLot = lot.stockOutLineStatus === 'rejected' || !lot.lotNo;
const rowSolId = Number(lot.stockOutLineId);
const lotSwitchErr =
Number.isFinite(rowSolId) ? lotSwitchFailByStockOutLineId[rowSolId] : undefined;

return (
<TableRow
key={`${lot.pickOrderLineId}-${lot.lotId || 'null'}`}
@@ -3664,6 +3713,11 @@ paginatedData.map((lot, index) => {
)
)}
</Typography>
{lotSwitchErr ? (
<Typography variant="body2" color="error" sx={{ mt: 0.5, display: 'block', fontWeight: 500 }}>
{lotSwitchErr}
</Typography>
) : null}
</Box>
</TableCell>
<TableCell align="right">


+ 65
- 49
src/components/FinishedGoodSearch/LotConfirmationModal.tsx Прегледај датотеку

@@ -19,14 +19,14 @@ interface LotConfirmationModalProps {
onClose: () => void;
onConfirm: () => void;
expectedLot: {
lotNo: string;
itemCode: string;
itemName: string;
lotNo: string | null;
itemCode?: string;
itemName?: string;
};
scannedLot: {
lotNo: string;
itemCode: string;
itemName: string;
lotNo: string | null;
itemCode?: string;
itemName?: string;
};
isLoading?: boolean;
/** Shown inside the dialog when confirm/switch API fails (e.g. LOT_UNAVAILABLE). */
@@ -43,24 +43,25 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
errorMessage = null,
}) => {
const { t } = useTranslation("pickOrder");
const isFailure = Boolean(errorMessage);

return (
<Dialog
open={open}
onClose={onClose}
maxWidth="md"
fullWidth
disableScrollLock
disableAutoFocus
disableEnforceFocus
disableRestoreFocus
>
open={open}
onClose={onClose}
maxWidth="md"
fullWidth
disableScrollLock
disableAutoFocus
disableEnforceFocus
disableRestoreFocus
>
<DialogTitle>
<Typography variant="h6" component="div" color="warning.main">
{t("Lot Number Mismatch")}
</Typography>
</DialogTitle>
<Typography variant="h6" component="div" color={isFailure ? "error" : "warning.main"}>
{isFailure ? t("Lot switch failed") : t("Lot Number Mismatch")}
</Typography>
</DialogTitle>
<DialogContent>
<Stack spacing={3}>
{errorMessage ? (
@@ -68,23 +69,33 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
{t(errorMessage)}
</Alert>
) : null}
<Alert severity="warning">
{t("The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch.")}
</Alert>
{isFailure ? (
<Alert severity="warning">
{t(
"The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry."
)}
</Alert>
) : (
<Alert severity="warning">
{t(
"The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch."
)}
</Alert>
)}

<Box>
<Typography variant="subtitle1" gutterBottom color="primary">
{t("Expected Lot:")}
</Typography>
<Box sx={{ pl: 2, py: 1, backgroundColor: 'grey.50', borderRadius: 1 }}>
<Box sx={{ pl: 2, py: 1, backgroundColor: "grey.50", borderRadius: 1 }}>
<Typography variant="body2">
<strong>{t("Item Code")}:</strong> {expectedLot.itemCode}
<strong>{t("Item Code")}:</strong> {expectedLot.itemCode ?? "—"}
</Typography>
<Typography variant="body2">
<strong>{t("Item Name")}:</strong> {expectedLot.itemName}
<strong>{t("Item Name")}:</strong> {expectedLot.itemName ?? "—"}
</Typography>
<Typography variant="body2">
<strong>{t("Lot No")}:</strong> {expectedLot.lotNo}
<strong>{t("Lot No")}:</strong> {expectedLot.lotNo ?? "—"}
</Typography>
</Box>
</Box>
@@ -95,42 +106,47 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
<Typography variant="subtitle1" gutterBottom color="warning.main">
{t("Scanned Lot:")}
</Typography>
<Box sx={{ pl: 2, py: 1, backgroundColor: 'warning.50', borderRadius: 1 }}>
<Box sx={{ pl: 2, py: 1, backgroundColor: "warning.50", borderRadius: 1 }}>
<Typography variant="body2">
<strong>{t("Item Code")}:</strong> {scannedLot.itemCode}
<strong>{t("Item Code")}:</strong> {scannedLot.itemCode ?? "—"}
</Typography>
<Typography variant="body2">
<strong>{t("Item Name")}:</strong> {scannedLot.itemName}
<strong>{t("Item Name")}:</strong> {scannedLot.itemName ?? "—"}
</Typography>
<Typography variant="body2">
<strong>{t("Lot No")}:</strong> {scannedLot.lotNo}
<strong>{t("Lot No")}:</strong> {scannedLot.lotNo ?? "—"}
</Typography>
</Box>
</Box>

<Alert severity="info">
{t("After you scan to choose, the system will update the pick line to the lot you confirmed.")}
</Alert>
<Alert severity="info">
{t("Or use the Confirm button below if you cannot scan again (same as scanning the other lot again).")}
</Alert>
{isFailure ? (
<Alert severity="info">
{t(
"You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch."
)}
</Alert>
) : (
<>
<Alert severity="info">
{t(
"After you scan to choose, the system will update the pick line to the lot you confirmed."
)}
</Alert>
<Alert severity="info">
{t(
"Or use the Confirm button below if you cannot scan again (same as scanning the other lot again)."
)}
</Alert>
</>
)}
</Stack>
</DialogContent>

<DialogActions>
<Button
onClick={onClose}
variant="outlined"
disabled={isLoading}
>
<Button onClick={onClose} variant="outlined" disabled={isLoading}>
{t("Cancel")}
</Button>
<Button
onClick={onConfirm}
variant="contained"
color="warning"
disabled={isLoading}
>
<Button onClick={onConfirm} variant="contained" color="warning" disabled={isLoading}>
{isLoading ? t("Processing...") : t("Confirm")}
</Button>
</DialogActions>
@@ -138,4 +154,4 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
);
};

export default LotConfirmationModal;
export default LotConfirmationModal;

+ 12
- 0
src/i18n/zh/pickOrder.json Прегледај датотеку

@@ -329,6 +329,9 @@
"If you confirm, the system will:":"如果您確認,系統將:",
"After you scan to choose, the system will update the pick line to the lot you confirmed.":"確認後,系統會將您選擇的批次套用到對應提料行。",
"Or use the Confirm button below if you cannot scan again (same as scanning the other lot again).":"若無法再掃描,可按下「確認」以切換為剛才掃描到的批次(與再掃一次該批次 QR 相同)。",
"Lot switch failed":"批次切換失敗",
"The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry.":"系統無法切換至掃描的批次。請核對下方批次後按「確認」重試。",
"You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch.":"您也可以再掃描:掃描建議批次 QR 可保留該行;掃描欲切換批次 QR 可再次嘗試切換。",
"QR code verified.":"QR 碼驗證成功。",
"Order Finished":"訂單完成",
"Submitted Status":"提交狀態",
@@ -465,5 +468,14 @@
"Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。",
"Lot confirmation failed. Please try again.": "確認批號失敗,請重試。",
"Powder Mixture": "箱料粉",
"This lot is not yet putaway": "此批次尚未上架",
"Cannot resolve new inventory lot line": "無法解析新批號庫存行(請確認已上架且資料正確)。",
"Pick order line item is null": "提料單行未關聯物料。",
"New lot line item does not match pick order line item": "新批號行的物料與提料單行不一致。",
"Pick order line {{id}} not found": "找不到有關提料單行的資料。",
"SuggestedPickLot not found for pickOrderLineId {{polId}}": "找不到該提料單行的建議揀貨批",
"SuggestedPickLot qty is invalid: {{qty}}": "建議揀貨數量無效:{{qty}}。",
"Reject switch lot: available {{available}} less than required {{required}}": "此批次貨品已被其他送貨單留起,請掃描其他批次。",
"Reject switch lot: picked {{picked}} already greater or equal required {{required}}": "換批被拒:已揀數量({{picked}})已達或超過建議量({{required}}),無法再拆分換批。",
"Lot status is unavailable. Cannot switch or bind; pick line was not updated.": "批號狀態為「不可用」,無法換批或綁定;揀貨行未更新。"
}

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