Browse Source

updateSubmitLotConfirm

master
CANCERYS\kw093 1 week ago
parent
commit
88021fa636
5 changed files with 450 additions and 162 deletions
  1. +40
    -4
      src/app/api/pickOrder/actions.ts
  2. +9
    -3
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  3. +394
    -151
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  4. +1
    -1
      src/components/Jodetail/JobPickExecution.tsx
  5. +6
    -3
      src/i18n/zh/pickOrder.json

+ 40
- 4
src/app/api/pickOrder/actions.ts View File

@@ -474,8 +474,44 @@ export interface UpdateStockOutLineStatusByQRCodeAndLotNoRequest {
itemId: number,
status: string
}

export interface batchSubmitListRequest {
userId: number;
lines: batchSubmitListLineRequest[];
}
export interface batchSubmitListLineRequest {
stockOutLineId: number; // 修复:改为 stockOutLineId(不是 stockInLineId)
pickOrderLineId: number;
inventoryLotLineId: number | null; // 添加:后端需要的字段
requiredQty: number;
actualPickQty: number;
stockOutLineStatus: string;
pickOrderConsoCode: string;
noLot: boolean;
// 移除:lotNo 和 stockInLineId(后端不需要)
}

export const batchSubmitList = async (data: batchSubmitListRequest) => {
// ✅ 确保发送的是对象,不是数组
const requestBody = Array.isArray(data) ? data[0] : data;
console.log("📤 batchSubmitList - Request body type:", Array.isArray(requestBody) ? "array" : "object");
console.log("📤 batchSubmitList - Request body:", JSON.stringify(requestBody, null, 2));
const response = await serverFetchJson<PostPickOrderResponse<batchSubmitListRequest>>(
`${BASE_API_URL}/stockOutLine/batchSubmitList`,
{
method: "POST",
body: JSON.stringify(requestBody), // ✅ 确保是对象
headers: {
"Content-Type": "application/json", // ✅ 明确指定 Content-Type
},
},
);
return response;
};
export const updateStockOutLineStatusByQRCodeAndLotNo = async (data: UpdateStockOutLineStatusByQRCodeAndLotNoRequest) => {
console.log("🚀 Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data);
console.log(" Frontend: Calling updateStockOutLineStatusByQRCodeAndLotNo with data:", data);
try {
const response = await serverFetchJson<PostPickOrderResponse<UpdateStockOutLineStatusByQRCodeAndLotNoRequest>>(
@@ -535,9 +571,9 @@ export const updatePickExecutionIssueStatus = async (
revalidateTag("pickExecutionIssues");
return result;
};
export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string): Promise<StoreLaneSummary> {
export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise<StoreLaneSummary> {
const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD');
const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}`;
const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`;
const response = await serverFetchJson<StoreLaneSummary>(
url,
{
@@ -927,7 +963,7 @@ export interface LotSubstitutionConfirmRequest {
pickOrderLineId: number;
stockOutLineId: number;
originalSuggestedPickLotId: number;
newInventoryLotLineId: number;
newInventoryLotNo: string;
}
export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => {
const response = await serverFetchJson<PostPickOrderResponse>(


+ 9
- 3
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -79,7 +79,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
// onNormalPickSubmit,
// selectedRowId,
}) => {
const { t } = useTranslation();
const { t } = useTranslation("pickOrder");
const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({});
const [errors, setErrors] = useState<FormErrors>({});
const [loading, setLoading] = useState(false);
@@ -206,8 +206,8 @@ const validateForm = (): boolean => {
if (total !== req) {
const diff = req - total;
const errorMsg = diff > 0
? t('Total must equal Required Qty. Missing: {{diff}}', { diff })
: t('Total must equal Required Qty. Exceeds by: {{diff}}', { diff: Math.abs(diff) });
? t('Total must equal Required Qty. Missing: {diff}', { diff })
: t('Total must equal Required Qty. Exceeds by: {diff}', { diff: Math.abs(diff) });
newErrors.actualPickQty = errorMsg;
newErrors.missQty = errorMsg;
newErrors.badItemQty = errorMsg;
@@ -320,6 +320,8 @@ const validateForm = (): boolean => {
fullWidth
label={t('Actual Pick Qty')}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }}
value={formData.actualPickQty ?? ''}
onChange={(e) => handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.actualPickQty}
@@ -333,6 +335,8 @@ const validateForm = (): boolean => {
fullWidth
label={t('Missing item Qty')}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }}
value={formData.missQty || 0}
onChange={(e) => handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.missQty}
@@ -346,6 +350,8 @@ const validateForm = (): boolean => {
fullWidth
label={t('Bad Item Qty')}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }}
value={formData.badItemQty || 0}
onChange={(e) => handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.badItemQty}


+ 394
- 151
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx View File

@@ -46,7 +46,11 @@ import {
fetchDoPickOrderDetail, // 必须添加
DoPickOrderDetail, // 必须添加
fetchFGPickOrdersByUserId ,
batchQrSubmit
batchQrSubmit,
batchSubmitList, // 添加:导入 batchSubmitList
batchSubmitListRequest, // 添加:导入类型
batchSubmitListLineRequest

} from "@/app/api/pickOrder/actions";

import FGPickOrderInfoCard from "./FGPickOrderInfoCard";
@@ -327,7 +331,127 @@ const QrCodeModal: React.FC<{
</Modal>
);
};
const ManualLotConfirmationModal: React.FC<{
open: boolean;
onClose: () => void;
onConfirm: (expectedLotNo: string, scannedLotNo: string) => void;
expectedLot: {
lotNo: string;
itemCode: string;
itemName: string;
} | null;
scannedLot: {
lotNo: string;
itemCode: string;
itemName: string;
} | null;
isLoading?: boolean;
}> = ({ open, onClose, onConfirm, expectedLot, scannedLot, isLoading = false }) => {
const { t } = useTranslation("pickOrder");
const [expectedLotInput, setExpectedLotInput] = useState<string>('');
const [scannedLotInput, setScannedLotInput] = useState<string>('');
const [error, setError] = useState<string>('');

// 当模态框打开时,预填充输入框
useEffect(() => {
if (open) {
setExpectedLotInput(expectedLot?.lotNo || '');
setScannedLotInput(scannedLot?.lotNo || '');
setError('');
}
}, [open, expectedLot, scannedLot]);

const handleConfirm = () => {
if (!expectedLotInput.trim() || !scannedLotInput.trim()) {
setError(t("Please enter both expected and scanned lot numbers."));
return;
}
if (expectedLotInput.trim() === scannedLotInput.trim()) {
setError(t("Expected and scanned lot numbers cannot be the same."));
return;
}

onConfirm(expectedLotInput.trim(), scannedLotInput.trim());
};

return (
<Modal open={open} onClose={onClose}>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
p: 3,
borderRadius: 2,
minWidth: 500,
}}>
<Typography variant="h6" gutterBottom color="warning.main">
{t("Manual Lot Confirmation")}
</Typography>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" gutterBottom>
<strong>{t("Expected Lot Number")}:</strong>
</Typography>
<TextField
fullWidth
size="small"
value={expectedLotInput}
onChange={(e) => {
setExpectedLotInput(e.target.value);
setError('');
}}
placeholder={expectedLot?.lotNo || t("Enter expected lot number")}
sx={{ mb: 2 }}
error={!!error && !expectedLotInput.trim()}
/>
</Box>

<Box sx={{ mb: 2 }}>
<Typography variant="body2" gutterBottom>
<strong>{t("Scanned Lot Number")}:</strong>
</Typography>
<TextField
fullWidth
size="small"
value={scannedLotInput}
onChange={(e) => {
setScannedLotInput(e.target.value);
setError('');
}}
placeholder={scannedLot?.lotNo || t("Enter scanned lot number")}
sx={{ mb: 2 }}
error={!!error && !scannedLotInput.trim()}
/>
</Box>

{error && (
<Box sx={{ mb: 2, p: 1, backgroundColor: '#ffebee', borderRadius: 1 }}>
<Typography variant="body2" color="error">
{error}
</Typography>
</Box>
)}

<Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
<Button onClick={onClose} variant="outlined" disabled={isLoading}>
{t("Cancel")}
</Button>
<Button
onClick={handleConfirm}
variant="contained"
color="warning"
disabled={isLoading || !expectedLotInput.trim() || !scannedLotInput.trim()}
>
{isLoading ? t("Processing...") : t("Confirm")}
</Button>
</Box>
</Box>
</Modal>
);
};
const PickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab, onRefreshReleasedOrderCount }) => {
const { t } = useTranslation("pickOrder");
const router = useRouter();
@@ -346,7 +470,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const [qrScanInput, setQrScanInput] = useState<string>('');
const [qrScanError, setQrScanError] = useState<boolean>(false);
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
const [manualLotConfirmationOpen, setManualLotConfirmationOpen] = useState(false);
const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});

@@ -640,6 +764,149 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
}
}, [currentUserId, checkAllLotsCompleted]); // 移除 selectedPickOrderId 依赖
// Add effect to check completion when lot data changes
const handleManualLotConfirmation = useCallback(async (currentLotNo: string, newLotNo: string) => {
console.log(`🔍 Manual lot confirmation: Current=${currentLotNo}, New=${newLotNo}`);
// 使用第一个输入框的 lot number 查找当前数据
const currentLot = combinedLotData.find(lot =>
lot.lotNo && lot.lotNo === currentLotNo
);
if (!currentLot) {
console.error(`❌ Current lot not found: ${currentLotNo}`);
alert(t("Current lot number not found. Please verify and try again."));
return;
}
if (!currentLot.stockOutLineId) {
console.error("❌ No stockOutLineId found for current lot");
alert(t("No stock out line found for current lot. Please contact administrator."));
return;
}
setIsConfirmingLot(true);
try {
// 调用 updateStockOutLineStatusByQRCodeAndLotNo API
// 第一个 lot 用于获取 pickOrderLineId, stockOutLineId, itemId
// 第二个 lot 作为 inventoryLotNo
const res = await updateStockOutLineStatusByQRCodeAndLotNo({
pickOrderLineId: currentLot.pickOrderLineId,
inventoryLotNo: newLotNo, // 第二个输入框的值
stockOutLineId: currentLot.stockOutLineId,
itemId: currentLot.itemId,
status: "checked",
});
console.log("📥 updateStockOutLineStatusByQRCodeAndLotNo result:", res);
if (res.code === "checked" || res.code === "SUCCESS") {
// ✅ 更新本地状态
const entity = res.entity as any;
setCombinedLotData(prev => prev.map(lot => {
if (lot.stockOutLineId === currentLot.stockOutLineId &&
lot.pickOrderLineId === currentLot.pickOrderLineId) {
return {
...lot,
stockOutLineStatus: 'checked',
stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty,
};
}
return lot;
}));
setOriginalCombinedData(prev => prev.map(lot => {
if (lot.stockOutLineId === currentLot.stockOutLineId &&
lot.pickOrderLineId === currentLot.pickOrderLineId) {
return {
...lot,
stockOutLineStatus: 'checked',
stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty,
};
}
return lot;
}));
console.log("✅ Lot substitution completed successfully");
setQrScanSuccess(true);
setQrScanError(false);
// 关闭手动输入模态框
setManualLotConfirmationOpen(false);
// 刷新数据
await fetchAllCombinedLotData();
} else if (res.code === "LOT_NUMBER_MISMATCH") {
console.warn("⚠️ Backend reported LOT_NUMBER_MISMATCH:", res.message);
// ✅ 打开 lot confirmation modal 而不是显示 alert
// 从响应消息中提取 expected lot number(如果可能)
// 或者使用 currentLotNo 作为 expected lot
const expectedLotNo = currentLotNo; // 当前 lot 是期望的
// 查找新 lot 的信息(如果存在于 combinedLotData 中)
const newLot = combinedLotData.find(lot =>
lot.lotNo && lot.lotNo === newLotNo
);
// 设置 expected lot data
setExpectedLotData({
lotNo: expectedLotNo,
itemCode: currentLot.itemCode || '',
itemName: currentLot.itemName || ''
});
// 设置 scanned lot data
setScannedLotData({
lotNo: newLotNo,
itemCode: newLot?.itemCode || currentLot.itemCode || '',
itemName: newLot?.itemName || currentLot.itemName || '',
inventoryLotLineId: newLot?.lotId || null,
stockInLineId: null // 手动输入时可能没有 stockInLineId
});
// 设置 selectedLotForQr 为当前 lot
setSelectedLotForQr(currentLot);
// 关闭手动输入模态框
setManualLotConfirmationOpen(false);
// 打开 lot confirmation modal
setLotConfirmationOpen(true);
setQrScanError(false); // 不显示错误,因为会打开确认模态框
setQrScanSuccess(false);
} else if (res.code === "ITEM_MISMATCH") {
console.warn("⚠️ Backend reported ITEM_MISMATCH:", res.message);
alert(t("Item mismatch: {message}", { message: res.message || "" }));
setQrScanError(true);
setQrScanSuccess(false);
// 关闭手动输入模态框
setManualLotConfirmationOpen(false);
} else {
console.warn("⚠️ Unexpected response code:", res.code);
alert(t("Failed to update lot status. Response: {code}", { code: res.code }));
setQrScanError(true);
setQrScanSuccess(false);
// 关闭手动输入模态框
setManualLotConfirmationOpen(false);
}
} catch (error) {
console.error("❌ Error in manual lot confirmation:", error);
alert(t("Failed to confirm lot substitution. Please try again."));
setQrScanError(true);
setQrScanSuccess(false);
// 关闭手动输入模态框
setManualLotConfirmationOpen(false);
} finally {
setIsConfirmingLot(false);
}
}, [combinedLotData, fetchAllCombinedLotData, t]);
useEffect(() => {
if (combinedLotData.length > 0) {
checkAllLotsCompleted(combinedLotData);
@@ -666,13 +933,11 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return;
setIsConfirmingLot(true);
try {
let newLotLineId = scannedLotData?.inventoryLotLineId;
if (!newLotLineId && scannedLotData?.stockInLineId) {
const ld = await fetchLotDetail(scannedLotData.stockInLineId);
newLotLineId = ld.inventoryLotLineId;
}
if (!newLotLineId) {
console.error("No inventory lot line id for scanned lot");
const newLotNo = scannedLotData?.lotNo;
if (!newLotNo) {
console.error("No lot number for scanned lot");
alert(t("Cannot find lot number for scanned lot. Please verify the lot number is correct."));
setIsConfirmingLot(false);
return;
}
@@ -680,17 +945,21 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId,
newInventoryLotLineId: newLotLineId
newInventoryLotNo: newLotNo
});
setQrScanError(false);
setQrScanSuccess(false);
setQrScanInput('');
//setIsManualScanning(false);
//stopScan();
//resetScan();
setProcessedQrCodes(new Set());
setLastProcessedQr('');
// ✅ 修复:在确认后重置扫描状态,避免重复处理
resetScan();
// ✅ 修复:不要清空 processedQrCodes,而是保留当前 QR code 的标记
// 或者如果确实需要清空,应该在重置扫描后再清空
// setProcessedQrCodes(new Set());
// setLastProcessedQr('');
setQrModalOpen(false);
setPickExecutionFormOpen(false);
if(selectedLotForQr?.stockOutLineId){
@@ -700,17 +969,23 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
qty: 0
});
}
// ✅ 修复:先关闭 modal 和清空状态,再刷新数据
setLotConfirmationOpen(false);
setExpectedLotData(null);
setScannedLotData(null);
setSelectedLotForQr(null);
// ✅ 修复:刷新数据前设置刷新标志,避免在刷新期间处理新的 QR code
setIsRefreshingData(true);
await fetchAllCombinedLotData();
setIsRefreshingData(false);
} catch (error) {
console.error("Error confirming lot substitution:", error);
} finally {
setIsConfirmingLot(false);
}
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]);
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData, resetScan]);
const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
console.log(` Processing QR Code for lot: ${lotNo}`);
@@ -1147,7 +1422,14 @@ useEffect(() => {
console.log("QR code already processed, skipping...");
return;
}

if (latestQr === "{2fic}") {
console.log("🔍 Detected {2fic} shortcut - opening manual lot confirmation form");
setManualLotConfirmationOpen(true);
resetScan();
setLastProcessedQr(latestQr);
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
return; // 直接返回,不继续处理其他逻辑
}
if (latestQr && latestQr !== lastProcessedQr) {
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`);
setLastProcessedQr(latestQr);
@@ -1718,124 +2000,72 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
}
}, [fetchAllCombinedLotData, session, currentUserId, fgPickOrders]);
// ... existing code ...
const handleSubmitAllScanned = useCallback(async () => {
const startTime = performance.now();
console.log(`⏱️ [BATCH SUBMIT START]`);
const handleSubmitAllScanned = useCallback(async () => {
const startTime = performance.now();
console.log(`⏱️ [BATCH SUBMIT START]`);
console.log(`⏰ Start time: ${new Date().toISOString()}`);
const scannedLots = combinedLotData.filter(lot => {
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete
if (lot.noLot === true) {
return lot.stockOutLineStatus === 'checked' ||
lot.stockOutLineStatus === 'pending' ||
lot.stockOutLineStatus === 'partially_completed' ||
lot.stockOutLineStatus === 'PARTIALLY_COMPLETE';
const scannedLots = combinedLotData.filter(lot => {
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete
if (lot.noLot === true) {
return lot.stockOutLineStatus === 'checked' ||
lot.stockOutLineStatus === 'pending' ||
lot.stockOutLineStatus === 'partially_completed' ||
lot.stockOutLineStatus === 'PARTIALLY_COMPLETE';
}
// 正常情况:只包含 checked 状态
return lot.stockOutLineStatus === 'checked';
});
if (scannedLots.length === 0) {
console.log("No scanned items to submit");
return;
}
setIsSubmittingAll(true);
console.log(`📦 Submitting ${scannedLots.length} scanned items using batchSubmitList...`);
try {
// 转换为 batchSubmitList 所需的格式(与后端 QrPickBatchSubmitRequest 匹配)
const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty || 0;
const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty;
let newStatus = 'partially_completed';
if (cumulativeQty >= (lot.requiredQty || 0)) {
newStatus = 'completed';
}
// 正常情况:只包含 checked 状态
return lot.stockOutLineStatus === 'checked';
return {
stockOutLineId: Number(lot.stockOutLineId) || 0,
pickOrderLineId: Number(lot.pickOrderLineId),
inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null,
requiredQty: Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0),
actualPickQty: Number(cumulativeQty),
stockOutLineStatus: newStatus,
pickOrderConsoCode: String(lot.pickOrderConsoCode || ''),
noLot: Boolean(lot.noLot === true)
};
});
if (scannedLots.length === 0) {
console.log("No scanned items to submit");
return;
}
const request: batchSubmitListRequest = {
userId: currentUserId || 0,
lines: lines
};
setIsSubmittingAll(true);
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
console.log(`📤 Sending batch submit request with ${lines.length} lines`);
console.log(`📋 Request data:`, JSON.stringify(request, null, 2));
const submitStartTime = performance.now();
try {
// Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => {
// 检查是否是 noLot 情况
if (lot.noLot === true) {
// 使用 handlelotnull 处理无 lot 的情况
console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`);
await updateStockOutLineStatus({
id: lot.stockOutLineId,
status: 'completed',
qty: 0
});
console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`);
const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
const issueData: PickExecutionIssueData = {
type: "Do", // Delivery Order type
pickOrderId: pickOrderId,
pickOrderCode: pickOrderCode,
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
pickExecutionDate: dayjs().format('YYYY-MM-DD'),
pickOrderLineId: lot.pickOrderLineId,
itemId: lot.itemId,
itemCode: lot.itemCode || '',
itemDescription: lot.itemName || '',
lotId: null, // No lot available
lotNo: null, // No lot number
storeLocation: lot.location || '',
requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
actualPickQty: 0, // No items picked (no lot available)
missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
badItemQty: 0,
issueRemark: `No lot available for this item. Handled via handlelotnull.`,
pickerName: session?.user?.name || '',
};
const result = await recordPickExecutionIssue(issueData);
return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true };
}
// 正常情况:有 lot 的处理逻辑
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty;
let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) {
newStatus = 'completed';
}
console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`);
// Update stock out line
await updateStockOutLineStatus({
id: lot.stockOutLineId,
status: newStatus,
qty: cumulativeQty
});
// Update inventory
if (submitQty > 0 && lot.lotId) {
await updateInventoryLotLineQuantities({
inventoryLotLineId: lot.lotId,
qty: submitQty,
status: 'available',
operation: 'pick'
});
}
// Check if pick order is completed
if (newStatus === 'completed' && lot.pickOrderConsoCode) {
await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
}
return { success: true, lotNo: lot.lotNo };
});
// Wait for all submissions to complete
const submitStartTime = performance.now();
const submitResults = await Promise.all(submitPromises);
const submitSuccessCount = submitResults.filter(r => r.success).length;
console.log(` Batch submit completed: ${submitSuccessCount}/${scannedLots.length} items submitted`);
// Wait for all submissions to complete
const results = await Promise.all(submitPromises);
const submitTime = performance.now() - submitStartTime;
const successCount = results.filter(r => r.success).length;
// 使用 batchSubmitList API
const result = await batchSubmitList(request);
console.log(`⏱️ All submissions completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`);
console.log(`⏱️ Average time per item: ${(submitTime / scannedLots.length).toFixed(2)}ms`);
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
const submitTime = performance.now() - submitStartTime;
console.log(`⏱️ Batch submit API call completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`);
console.log(`📥 Batch submit result:`, result);
// Refresh data once after all submissions
// Refresh data once after batch submission
const refreshStartTime = performance.now();
await fetchAllCombinedLotData();
const refreshTime = performance.now() - refreshStartTime;
@@ -1845,27 +2075,31 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
console.log(`⏱️ [BATCH SUBMIT END]`);
console.log(`⏱️ Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
console.log(`⏰ End time: ${new Date().toISOString()}`);
if (successCount > 0) {
setQrScanSuccess(true);
setTimeout(() => {
setQrScanSuccess(false);
checkAndAutoAssignNext();
if (onSwitchToRecordTab) {
onSwitchToRecordTab();
}
if (onRefreshReleasedOrderCount) {
onRefreshReleasedOrderCount();
}
}, 2000);
}
} catch (error) {
console.error("Error submitting all scanned items:", error);
if (result && result.code === "SUCCESS") {
setQrScanSuccess(true);
setTimeout(() => {
setQrScanSuccess(false);
checkAndAutoAssignNext();
if (onSwitchToRecordTab) {
onSwitchToRecordTab();
}
if (onRefreshReleasedOrderCount) {
onRefreshReleasedOrderCount();
}
}, 2000);
} else {
console.error("Batch submit failed:", result);
setQrScanError(true);
} finally {
setIsSubmittingAll(false);
}
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull, onSwitchToRecordTab, onRefreshReleasedOrderCount]);
} catch (error) {
console.error("Error submitting all scanned items:", error);
setQrScanError(true);
} finally {
setIsSubmittingAll(false);
}
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount]);

// Calculate scanned items count
// Calculate scanned items count (should match handleSubmitAllScanned filter logic)
@@ -2290,10 +2524,10 @@ paginatedData.map((lot, index) => {
onClick={() => handlePickExecutionForm(lot)}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending'
//lot.lotAvailability === 'status_unavailable' ||
// lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed'
//lot.stockOutLineStatus === 'pending'
}
sx={{
fontSize: '0.7rem',
@@ -2349,7 +2583,16 @@ paginatedData.map((lot, index) => {
combinedLotData={combinedLotData}
onQrCodeSubmit={handleQrCodeSubmitFromModal}
/>
<ManualLotConfirmationModal
open={manualLotConfirmationOpen}
onClose={() => {
setManualLotConfirmationOpen(false);
}}
onConfirm={handleManualLotConfirmation}
expectedLot={expectedLotData}
scannedLot={scannedLotData}
isLoading={isConfirmingLot}
/>
{/* 保留:Lot Confirmation Modal */}
{lotConfirmationOpen && expectedLotData && scannedLotData && (
<LotConfirmationModal


+ 1
- 1
src/components/Jodetail/JobPickExecution.tsx View File

@@ -774,7 +774,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId,
newInventoryLotLineId: newLotLineId
newInventoryLotNo: scannedLotData.lotNo
});
console.log(" Lot substitution result:", substitutionResult);


+ 6
- 3
src/i18n/zh/pickOrder.json View File

@@ -109,10 +109,13 @@
"submit": "確認提交",
"print": "列印",
"bind": "綁定",
"Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}",
"Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}",

"Total must equal Required Qty. Missing: {diff}": "總數量必須等於所需數量。缺少:{{diff}}",
"Total must equal Required Qty. Exceeds by: {diff}": "總數量必須等於所需數量。超出:{{diff}}",

"Batch": "批量",
"Single": "單量",
"Release Type": "放單類型",
"Pick Order": "提料單",
"Type": "類型",
"Product Type": "貨品類型",


Loading…
Cancel
Save