|
|
|
@@ -33,7 +33,7 @@ import { |
|
|
|
recordPickExecutionIssue, |
|
|
|
fetchFGPickOrders, // ✅ Add this import |
|
|
|
FGPickOrderResponse, |
|
|
|
|
|
|
|
stockReponse, |
|
|
|
|
|
|
|
checkPickOrderCompletion, |
|
|
|
fetchAllPickOrderLotsHierarchical, |
|
|
|
@@ -369,6 +369,7 @@ 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); |
|
|
|
@@ -546,12 +547,17 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
routerIndex: lot.router?.index, |
|
|
|
routerRoute: lot.router?.route, |
|
|
|
routerArea: lot.router?.area, |
|
|
|
noLot: false, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// ✅ 没有 lots 的情况(null stock) |
|
|
|
// ✅ 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id |
|
|
|
const firstStockout = line.stockouts && line.stockouts.length > 0 |
|
|
|
? line.stockouts[0] |
|
|
|
: null; |
|
|
|
|
|
|
|
flatLotData.push({ |
|
|
|
pickOrderConsoCode: mergedPickOrder.consoCode, |
|
|
|
pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // ✅ 修复:consoCodes 是数组 |
|
|
|
pickOrderTargetDate: mergedPickOrder.targetDate, |
|
|
|
pickOrderStatus: mergedPickOrder.status, |
|
|
|
|
|
|
|
@@ -565,30 +571,31 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
uomDesc: line.item.uomDesc, |
|
|
|
uomShortDesc: line.item.uomShortDesc, |
|
|
|
|
|
|
|
// ✅ Null stock 字段 |
|
|
|
lotId: null, |
|
|
|
lotNo: null, |
|
|
|
// ✅ Null stock 字段 - 从 stockouts 数组中获取 |
|
|
|
lotId: firstStockout?.lotId || null, |
|
|
|
lotNo: firstStockout?.lotNo || null, |
|
|
|
expiryDate: null, |
|
|
|
location: null, |
|
|
|
location: firstStockout?.location || null, |
|
|
|
stockUnit: line.item.uomDesc, |
|
|
|
availableQty: 0, |
|
|
|
availableQty: firstStockout?.availableQty || 0, |
|
|
|
requiredQty: line.requiredQty, |
|
|
|
actualPickQty: 0, |
|
|
|
actualPickQty: firstStockout?.qty || 0, |
|
|
|
inQty: 0, |
|
|
|
outQty: 0, |
|
|
|
holdQty: 0, |
|
|
|
lotStatus: 'unavailable', |
|
|
|
lotAvailability: 'insufficient_stock', |
|
|
|
processingStatus: 'pending', |
|
|
|
processingStatus: firstStockout?.status || 'pending', |
|
|
|
suggestedPickLotId: null, |
|
|
|
stockOutLineId: null, |
|
|
|
stockOutLineStatus: null, |
|
|
|
stockOutLineQty: 0, |
|
|
|
stockOutLineId: firstStockout?.id || null, // ✅ 使用 stockouts 数组中的 id |
|
|
|
stockOutLineStatus: firstStockout?.status || null, |
|
|
|
stockOutLineQty: firstStockout?.qty || 0, |
|
|
|
|
|
|
|
routerId: null, |
|
|
|
routerIndex: 999999, |
|
|
|
routerRoute: null, |
|
|
|
routerArea: null, |
|
|
|
noLot: true, |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
@@ -684,14 +691,22 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); |
|
|
|
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { |
|
|
|
console.log(`✅ Processing QR Code for lot: ${lotNo}`); |
|
|
|
|
|
|
|
// ✅ 检查 lotNo 是否为 null 或 undefined(包括字符串 "null") |
|
|
|
if (!lotNo || lotNo === 'null' || lotNo.trim() === '') { |
|
|
|
console.error("❌ Invalid lotNo: null, undefined, or empty"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ Use current data without refreshing to avoid infinite loop |
|
|
|
const currentLotData = combinedLotData; |
|
|
|
console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo)); |
|
|
|
|
|
|
|
const matchingLots = currentLotData.filter(lot => |
|
|
|
lot.lotNo === lotNo || |
|
|
|
lot.lotNo?.toLowerCase() === lotNo.toLowerCase() |
|
|
|
); |
|
|
|
// ✅ 修复:在比较前确保 lotNo 不为 null |
|
|
|
const lotNoLower = lotNo.toLowerCase(); |
|
|
|
const matchingLots = currentLotData.filter(lot => { |
|
|
|
if (!lot.lotNo) return false; // ✅ 跳过 null lotNo |
|
|
|
return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower; |
|
|
|
}); |
|
|
|
|
|
|
|
if (matchingLots.length === 0) { |
|
|
|
console.error(`❌ Lot not found: ${lotNo}`); |
|
|
|
@@ -1451,10 +1466,34 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
stopScan(); |
|
|
|
resetScan(); |
|
|
|
}, [stopScan, resetScan]); |
|
|
|
const handlelotnull = useCallback(async (lot: any) => { |
|
|
|
// ✅ 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId |
|
|
|
const stockOutLineId = lot.stockOutLineId; |
|
|
|
|
|
|
|
if (!stockOutLineId) { |
|
|
|
console.error("❌ No stockOutLineId found for lot:", lot); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
await updateStockOutLineStatus({ |
|
|
|
id: stockOutLineId, // ✅ 现在这个值应该来自 stockouts 数组的 id |
|
|
|
status: 'completed', |
|
|
|
qty: 0 |
|
|
|
}); |
|
|
|
await fetchAllCombinedLotData(); |
|
|
|
}, [fetchAllCombinedLotData]); |
|
|
|
const handleSubmitAllScanned = useCallback(async () => { |
|
|
|
const scannedLots = combinedLotData.filter(lot => |
|
|
|
lot.stockOutLineStatus === 'checked' // Only submit items that are scanned but not yet submitted |
|
|
|
); |
|
|
|
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"); |
|
|
|
@@ -1467,6 +1506,20 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
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}`); |
|
|
|
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; |
|
|
|
@@ -1486,7 +1539,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
}); |
|
|
|
|
|
|
|
// Update inventory |
|
|
|
if (submitQty > 0) { |
|
|
|
if (submitQty > 0 && lot.lotId) { |
|
|
|
await updateInventoryLotLineQuantities({ |
|
|
|
inventoryLotLineId: lot.lotId, |
|
|
|
qty: submitQty, |
|
|
|
@@ -1526,12 +1579,37 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
} finally { |
|
|
|
setIsSubmittingAll(false); |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext]); |
|
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]); |
|
|
|
|
|
|
|
// ✅ Calculate scanned items count |
|
|
|
const scannedItemsCount = useMemo(() => { |
|
|
|
return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; |
|
|
|
}, [combinedLotData]); |
|
|
|
// ✅ Calculate scanned items count (should match handleSubmitAllScanned filter logic) |
|
|
|
const scannedItemsCount = useMemo(() => { |
|
|
|
const filtered = combinedLotData.filter(lot => { |
|
|
|
// ✅ 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含 |
|
|
|
if (lot.noLot === true) { |
|
|
|
const status = lot.stockOutLineStatus?.toLowerCase(); |
|
|
|
const include = status !== 'completed' && status !== 'rejected'; |
|
|
|
if (include) { |
|
|
|
console.log(`📊 Including noLot item: ${lot.itemName || lot.itemCode}, status: ${lot.stockOutLineStatus}`); |
|
|
|
} |
|
|
|
return include; |
|
|
|
} |
|
|
|
// ✅ 正常情况:只包含 checked 状态 |
|
|
|
return lot.stockOutLineStatus === 'checked'; |
|
|
|
}); |
|
|
|
|
|
|
|
// ✅ 添加调试日志 |
|
|
|
const noLotCount = filtered.filter(l => l.noLot === true).length; |
|
|
|
const normalCount = filtered.filter(l => l.noLot !== true).length; |
|
|
|
console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`); |
|
|
|
console.log(`📊 All items breakdown:`, { |
|
|
|
total: combinedLotData.length, |
|
|
|
noLot: combinedLotData.filter(l => l.noLot === true).length, |
|
|
|
normal: combinedLotData.filter(l => l.noLot !== true).length |
|
|
|
}); |
|
|
|
|
|
|
|
return filtered.length; |
|
|
|
}, [combinedLotData]); |
|
|
|
|
|
|
|
// ✅ ADD THIS: Auto-stop scan when no data available |
|
|
|
useEffect(() => { |
|
|
|
@@ -1782,7 +1860,8 @@ paginatedData.map((lot, index) => { |
|
|
|
// color: isIssueLot ? 'warning.main' : lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit', |
|
|
|
}} |
|
|
|
> |
|
|
|
{lot.lotNo || t('⚠️ No Stock Available')} |
|
|
|
{lot.lotNo || |
|
|
|
t('⚠️ No Stock Available')} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
</TableCell> |
|
|
|
@@ -1809,8 +1888,20 @@ paginatedData.map((lot, index) => { |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
) : isIssueLot ? ( |
|
|
|
null |
|
|
|
) : isIssueLot&&lot.stockOutLineStatus?.toLowerCase() == 'partially_completed' ? ( |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> |
|
|
|
<Checkbox |
|
|
|
checked={true} |
|
|
|
disabled={true} |
|
|
|
readOnly={true} |
|
|
|
size="large" |
|
|
|
sx={{ |
|
|
|
color: 'error.main', |
|
|
|
'&.Mui-checked': { color: 'error.main' }, |
|
|
|
transform: 'scale(1.3)', |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
) : null} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
@@ -1821,8 +1912,7 @@ paginatedData.map((lot, index) => { |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
size="small" |
|
|
|
onClick={() => handlePickExecutionForm(lot)} |
|
|
|
disabled={true} |
|
|
|
onClick={() => handlelotnull(lot)} |
|
|
|
sx={{ |
|
|
|
fontSize: '0.7rem', |
|
|
|
py: 0.5, |
|
|
|
@@ -1831,7 +1921,7 @@ paginatedData.map((lot, index) => { |
|
|
|
borderColor: 'warning.main', |
|
|
|
color: 'warning.main' |
|
|
|
}} |
|
|
|
title="Rejected lot - Issue only" |
|
|
|
|
|
|
|
> |
|
|
|
{t("Issue")} |
|
|
|
</Button> |
|
|
|
|