|
|
@@ -581,14 +581,29 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
if (combinedLotData.length === 0) { |
|
|
if (combinedLotData.length === 0) { |
|
|
return { completed: 0, total: 0 }; |
|
|
return { completed: 0, total: 0 }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const nonPendingCount = combinedLotData.filter(lot => { |
|
|
|
|
|
const status = lot.stockOutLineStatus?.toLowerCase(); |
|
|
|
|
|
return status !== 'pending'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use same logic as scannedItemsCount: count only lines that are actually scanned/submittable |
|
|
|
|
|
const scannedCount = combinedLotData.filter(lot => { |
|
|
|
|
|
const status = lot.stockOutLineStatus; |
|
|
|
|
|
if (!status) return false; |
|
|
|
|
|
|
|
|
|
|
|
if (lot.noLot === true) { |
|
|
|
|
|
return ( |
|
|
|
|
|
status === 'checked' || |
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
status === 'checked' || |
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
|
|
); |
|
|
}).length; |
|
|
}).length; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
completed: nonPendingCount, |
|
|
|
|
|
|
|
|
completed: scannedCount, |
|
|
total: combinedLotData.length |
|
|
total: combinedLotData.length |
|
|
}; |
|
|
}; |
|
|
}, [combinedLotData]); |
|
|
}, [combinedLotData]); |
|
|
@@ -2740,46 +2755,33 @@ const handleSubmitAllScanned = useCallback(async () => { |
|
|
try { |
|
|
try { |
|
|
// 转换为 batchSubmitList 所需的格式(与后端 QrPickBatchSubmitRequest 匹配) |
|
|
// 转换为 batchSubmitList 所需的格式(与后端 QrPickBatchSubmitRequest 匹配) |
|
|
const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { |
|
|
const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { |
|
|
// 1. 需求数量 |
|
|
|
|
|
|
|
|
// 1. 需求数量:优先用 lot.requiredQty,没有就用 pickOrderLineRequiredQty |
|
|
const requiredQty = |
|
|
const requiredQty = |
|
|
Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); |
|
|
Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); |
|
|
|
|
|
|
|
|
// 2. 当前已经拣到的数量(数据库里的 qty) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 当前已经拣到的数量(数据库里对应的是 stock_out_line.qty) |
|
|
const currentActualPickQty = Number(lot.actualPickQty || 0); |
|
|
const currentActualPickQty = Number(lot.actualPickQty || 0); |
|
|
|
|
|
|
|
|
// 🔹 判断是否走“只改状态模式” |
|
|
|
|
|
// 这里先给一个简单条件示例:如果你不想再补拣,只想把当前数量标记完成, |
|
|
|
|
|
// 就让这个条件为 true(后面你可以根据业务加 UI 开关或别的 flag)。 |
|
|
|
|
|
const onlyComplete = lot.stockOutLineStatus === "partially_completed"; |
|
|
|
|
|
// lot.stockOutLineStatus === "partially_completed" && false === true; |
|
|
|
|
|
|
|
|
|
|
|
let targetActual: number; |
|
|
|
|
|
let newStatus: string; |
|
|
|
|
|
|
|
|
|
|
|
if (onlyComplete) { |
|
|
|
|
|
// ✅ 只改状态:目标数量 = 当前数量,不再补拣 |
|
|
|
|
|
targetActual = currentActualPickQty; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 还需要拣多少:不能为负数 |
|
|
|
|
|
const remainingQty = Math.max(0, requiredQty - currentActualPickQty); |
|
|
|
|
|
|
|
|
|
|
|
// 4. 本次批量提交后的目标累计值 = 当前 + 剩余 |
|
|
|
|
|
const cumulativeQty = currentActualPickQty + remainingQty; |
|
|
|
|
|
|
|
|
|
|
|
// 5. 根据“目标累计值是否达到需求”决定状态 |
|
|
|
|
|
let newStatus = "partially_completed"; |
|
|
|
|
|
if (requiredQty > 0 && cumulativeQty >= requiredQty) { |
|
|
newStatus = "completed"; |
|
|
newStatus = "completed"; |
|
|
} else { |
|
|
|
|
|
// ✅ 补拣模式:把剩余全部拣完 |
|
|
|
|
|
const remainingQty = Math.max(0, requiredQty - currentActualPickQty); |
|
|
|
|
|
const cumulativeQty = currentActualPickQty + remainingQty; |
|
|
|
|
|
|
|
|
|
|
|
targetActual = cumulativeQty; |
|
|
|
|
|
|
|
|
|
|
|
newStatus = "partially_completed"; |
|
|
|
|
|
if (requiredQty > 0 && cumulativeQty >= requiredQty) { |
|
|
|
|
|
newStatus = "completed"; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
stockOutLineId: Number(lot.stockOutLineId) || 0, |
|
|
stockOutLineId: Number(lot.stockOutLineId) || 0, |
|
|
pickOrderLineId: Number(lot.pickOrderLineId), |
|
|
pickOrderLineId: Number(lot.pickOrderLineId), |
|
|
|
|
|
// ⚠️ 这里按你现在的写法是用 lot.lotId,当心是否真的是 inventoryLotLineId |
|
|
inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, |
|
|
inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, |
|
|
requiredQty, |
|
|
requiredQty, |
|
|
// 后端用 targetActual - 当前 qty 算增量,onlyComplete 时就是 0 |
|
|
|
|
|
actualPickQty: targetActual, |
|
|
|
|
|
|
|
|
// 传“目标累计值”,后端会用它减去当前数据库里的 qty 得到增量 |
|
|
|
|
|
actualPickQty: cumulativeQty, |
|
|
stockOutLineStatus: newStatus, |
|
|
stockOutLineStatus: newStatus, |
|
|
pickOrderConsoCode: String(lot.pickOrderConsoCode || ""), |
|
|
pickOrderConsoCode: String(lot.pickOrderConsoCode || ""), |
|
|
noLot: Boolean(lot.noLot === true), |
|
|
noLot: Boolean(lot.noLot === true), |
|
|
@@ -2838,36 +2840,29 @@ const handleSubmitAllScanned = useCallback(async () => { |
|
|
} |
|
|
} |
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount]); |
|
|
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount]); |
|
|
|
|
|
|
|
|
// Calculate scanned items count |
|
|
|
|
|
// Calculate scanned items count (should match handleSubmitAllScanned filter logic) |
|
|
|
|
|
const scannedItemsCount = useMemo(() => { |
|
|
|
|
|
const filtered = combinedLotData.filter(lot => { |
|
|
|
|
|
const status = lot.stockOutLineStatus; |
|
|
|
|
|
// ✅ 与 handleSubmitAllScanned 完全保持一致 |
|
|
|
|
|
if (lot.noLot === true) { |
|
|
|
|
|
return status === 'checked' || |
|
|
|
|
|
|
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE'; |
|
|
|
|
|
} |
|
|
|
|
|
return status === 'checked' || |
|
|
|
|
|
|
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE'; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 添加调试日志 |
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
|
// Calculate scanned items count (must match handleSubmitAllScanned filter logic) |
|
|
|
|
|
const scannedItemsCount = useMemo(() => { |
|
|
|
|
|
const filtered = combinedLotData.filter(lot => { |
|
|
|
|
|
const status = lot.stockOutLineStatus; |
|
|
|
|
|
if (!status) return false; |
|
|
|
|
|
|
|
|
|
|
|
if (lot.noLot === true) { |
|
|
|
|
|
return ( |
|
|
|
|
|
status === 'checked' || |
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
status === 'checked' || |
|
|
|
|
|
status === 'partially_completed' || |
|
|
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return filtered.length; |
|
|
|
|
|
}, [combinedLotData]); |
|
|
/* |
|
|
/* |
|
|
// ADD THIS: Auto-stop scan when no data available |
|
|
// ADD THIS: Auto-stop scan when no data available |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
@@ -3183,71 +3178,82 @@ paginatedData.map((lot, index) => { |
|
|
</TableCell> |
|
|
</TableCell> |
|
|
|
|
|
|
|
|
<TableCell align="center"> |
|
|
<TableCell align="center"> |
|
|
{(() => { |
|
|
|
|
|
const status = lot.stockOutLineStatus?.toLowerCase(); |
|
|
|
|
|
const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; |
|
|
|
|
|
const isNoLot = !lot.lotNo; |
|
|
|
|
|
|
|
|
|
|
|
// rejected lot:显示红色勾选(已扫描但被拒绝) |
|
|
|
|
|
if (isRejected && !isNoLot) { |
|
|
|
|
|
return ( |
|
|
|
|
|
<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> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 正常 lot:已扫描(checked/partially_completed/completed) |
|
|
|
|
|
if (!isNoLot && status !== 'pending' && status !== 'rejected') { |
|
|
|
|
|
return ( |
|
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> |
|
|
|
|
|
<Checkbox |
|
|
|
|
|
checked={true} |
|
|
|
|
|
disabled={true} |
|
|
|
|
|
readOnly={true} |
|
|
|
|
|
size="large" |
|
|
|
|
|
sx={{ |
|
|
|
|
|
color: 'success.main', |
|
|
|
|
|
'&.Mui-checked': { color: 'success.main' }, |
|
|
|
|
|
transform: 'scale(1.3)', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</Box> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// noLot 且已完成/部分完成:显示红色勾选 |
|
|
|
|
|
if (isNoLot && (status === 'partially_completed' || status === 'completed')) { |
|
|
|
|
|
return ( |
|
|
|
|
|
<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> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
})()} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
|
|
|
{(() => { |
|
|
|
|
|
const rawStatus = lot.stockOutLineStatus; |
|
|
|
|
|
const status = rawStatus?.toLowerCase(); |
|
|
|
|
|
const isRejected = status === "rejected" || lot.lotAvailability === "rejected"; |
|
|
|
|
|
const isNoLot = !lot.lotNo; |
|
|
|
|
|
|
|
|
|
|
|
// rejected lot:显示红色勾选(已扫描但被拒绝) |
|
|
|
|
|
if (isRejected && !isNoLot) { |
|
|
|
|
|
return ( |
|
|
|
|
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}> |
|
|
|
|
|
<Checkbox |
|
|
|
|
|
checked |
|
|
|
|
|
disabled |
|
|
|
|
|
readOnly |
|
|
|
|
|
size="large" |
|
|
|
|
|
sx={{ |
|
|
|
|
|
color: "error.main", |
|
|
|
|
|
"&.Mui-checked": { color: "error.main" }, |
|
|
|
|
|
transform: "scale(1.3)", |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</Box> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 正常 lot:已扫描(只有在明确的 scanned 状态时显示勾) |
|
|
|
|
|
const isScannedNormal = |
|
|
|
|
|
!isNoLot && |
|
|
|
|
|
!!status && |
|
|
|
|
|
(status === "checked" || |
|
|
|
|
|
status === "partially_completed" || |
|
|
|
|
|
status === "partially_complete" || |
|
|
|
|
|
status === "completed" || |
|
|
|
|
|
status === "complete"); |
|
|
|
|
|
|
|
|
|
|
|
if (isScannedNormal) { |
|
|
|
|
|
return ( |
|
|
|
|
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}> |
|
|
|
|
|
<Checkbox |
|
|
|
|
|
checked |
|
|
|
|
|
disabled |
|
|
|
|
|
readOnly |
|
|
|
|
|
size="large" |
|
|
|
|
|
sx={{ |
|
|
|
|
|
color: "success.main", |
|
|
|
|
|
"&.Mui-checked": { color: "success.main" }, |
|
|
|
|
|
transform: "scale(1.3)", |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</Box> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// noLot 且已完成/部分完成:显示红色勾选 |
|
|
|
|
|
if (isNoLot && (status === "partially_completed" || status === "completed")) { |
|
|
|
|
|
return ( |
|
|
|
|
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}> |
|
|
|
|
|
<Checkbox |
|
|
|
|
|
checked |
|
|
|
|
|
disabled |
|
|
|
|
|
readOnly |
|
|
|
|
|
size="large" |
|
|
|
|
|
sx={{ |
|
|
|
|
|
color: "error.main", |
|
|
|
|
|
"&.Mui-checked": { color: "error.main" }, |
|
|
|
|
|
transform: "scale(1.3)", |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</Box> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 其他情况(包括 status 为 null/undefined):不显示勾 |
|
|
|
|
|
return null; |
|
|
|
|
|
})()} |
|
|
|
|
|
</TableCell> |
|
|
<TableCell align="center"> |
|
|
<TableCell align="center"> |
|
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}> |
|
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}> |
|
|
{(() => { |
|
|
{(() => { |
|
|
|