上架数量显示异常
This commit is contained in:
+37
-15
@@ -522,8 +522,8 @@ public class StockShelfOrderApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扁平化明细:主行保留父子结构;展示「已上架」= 主行累计 + 各子行累计(不含 parent_copy 镜像行,避免与主行重复)。
|
* 扁平化明细:主行保留父子结构;主行「已上架数量」与上架状态仍按主行+子行汇总(与分批场景一致)。
|
||||||
* 上架状态按「汇总已上架 qtySum」与主行计划量比较推导,与域服务分批/完成上架的 1/2/3/4/5 含义一致。
|
* 主行净重/毛重/体积/面积<strong>不再</strong>用主+子汇总覆盖,保留库表主行单行值(子行仍为自身行明细)。
|
||||||
*
|
*
|
||||||
* @param shelfOrderCompleted 上架单是否已整单完成(status=3);为 true 时少上为 4,与域服务完成口径一致
|
* @param shelfOrderCompleted 上架单是否已整单完成(status=3);为 true 时少上为 4,与域服务完成口径一致
|
||||||
*/
|
*/
|
||||||
@@ -553,17 +553,9 @@ public class StockShelfOrderApplicationService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BigDecimal qtySum = sumAlreadyShelvesUnderPlanParent(parent, allNodes);
|
BigDecimal qtySum = sumAlreadyShelvesUnderPlanParent(parent, allNodes);
|
||||||
BigDecimal netSum = sumAlreadyShelfMetricUnderPlanParent(parent, allNodes, ShelfMaterialDetailPO::getAlreadyShelvesNetWeight);
|
|
||||||
BigDecimal grossSum = sumAlreadyShelfMetricUnderPlanParent(parent, allNodes, ShelfMaterialDetailPO::getAlreadyShelvesGrossWeight);
|
|
||||||
BigDecimal volSum = sumAlreadyShelfMetricUnderPlanParent(parent, allNodes, ShelfMaterialDetailPO::getAlreadyShelvesVolume);
|
|
||||||
BigDecimal areaSum = sumAlreadyShelfMetricUnderPlanParent(parent, allNodes, ShelfMaterialDetailPO::getAlreadyShelvesArea);
|
|
||||||
Integer derived = deriveShelfStatusFromAggregatedVsPlan(qtySum, parent.getQuantity(), shelfOrderCompleted);
|
Integer derived = deriveShelfStatusFromAggregatedVsPlan(qtySum, parent.getQuantity(), shelfOrderCompleted);
|
||||||
|
|
||||||
parent.setAlreadyShelvesQuantity(qtySum);
|
parent.setAlreadyShelvesQuantity(qtySum);
|
||||||
parent.setAlreadyShelvesNetWeight(netSum);
|
|
||||||
parent.setAlreadyShelvesGrossWeight(grossSum);
|
|
||||||
parent.setAlreadyShelvesVolume(volSum);
|
|
||||||
parent.setAlreadyShelvesArea(areaSum);
|
|
||||||
if (derived != null) {
|
if (derived != null) {
|
||||||
parent.setShelvesStatus(derived);
|
parent.setShelvesStatus(derived);
|
||||||
}
|
}
|
||||||
@@ -724,6 +716,36 @@ public class StockShelfOrderApplicationService {
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划主行维度累计(重量/体面积):主行库表单行 + 树下 level=2 子行。
|
||||||
|
* 待上架展示用「计划 − 已上架」时,子行需按整卡扣减;主行接口字段不再汇总后仍用此合计参与计算。
|
||||||
|
*/
|
||||||
|
private static BigDecimal sumShelfPlanRowAggregatedAlreadyMetric(ShelfMaterialDetailPO planParent,
|
||||||
|
Function<ShelfMaterialDetailPO, BigDecimal> alreadyGetter) {
|
||||||
|
if (planParent == null || alreadyGetter == null) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
BigDecimal base = alreadyGetter.apply(planParent);
|
||||||
|
BigDecimal sum = base != null ? base : BigDecimal.ZERO;
|
||||||
|
List<ShelfMaterialDetailPO> ch = planParent.getChildren();
|
||||||
|
if (CollectionUtils.isEmpty(ch)) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
for (ShelfMaterialDetailPO c : ch) {
|
||||||
|
if (c == null || c.getLevel() == null || c.getLevel() != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (planParent.getUniqueId() != null && planParent.getUniqueId().equals(c.getUniqueId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BigDecimal v = alreadyGetter.apply(c);
|
||||||
|
if (v != null) {
|
||||||
|
sum = sum.add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
/** 计划父行:level=1 自身;level=2 则按 parent_unique_id 定位主计划行(对齐收货 {@code resolveReceiptPlanParentRow}) */
|
/** 计划父行:level=1 自身;level=2 则按 parent_unique_id 定位主计划行(对齐收货 {@code resolveReceiptPlanParentRow}) */
|
||||||
private static ShelfMaterialDetailPO resolveShelfPlanParentRow(ShelfMaterialDetailPO db,
|
private static ShelfMaterialDetailPO resolveShelfPlanParentRow(ShelfMaterialDetailPO db,
|
||||||
List<ShelfMaterialDetailPO> dbList) {
|
List<ShelfMaterialDetailPO> dbList) {
|
||||||
@@ -1072,12 +1094,12 @@ public class StockShelfOrderApplicationService {
|
|||||||
BigDecimal alreadyVol = d.getAlreadyShelvesVolume() != null ? d.getAlreadyShelvesVolume() : BigDecimal.ZERO;
|
BigDecimal alreadyVol = d.getAlreadyShelvesVolume() != null ? d.getAlreadyShelvesVolume() : BigDecimal.ZERO;
|
||||||
BigDecimal alreadyArea = d.getAlreadyShelvesArea() != null ? d.getAlreadyShelvesArea() : BigDecimal.ZERO;
|
BigDecimal alreadyArea = d.getAlreadyShelvesArea() != null ? d.getAlreadyShelvesArea() : BigDecimal.ZERO;
|
||||||
|
|
||||||
// level=2 子行:与收货一致,用计划主行汇总后的累计已上架(flatten 后主行已为主+子合计)
|
// level=2 子行:计划为整张卡;已上架量用主行+子行 DB 累计之和(主行自身字段不再汇总时仍按整卡扣计划)
|
||||||
if (planParent != null && d.getLevel() != null && d.getLevel() == 2) {
|
if (planParent != null && d.getLevel() != null && d.getLevel() == 2) {
|
||||||
alreadyNet = planParent.getAlreadyShelvesNetWeight() != null ? planParent.getAlreadyShelvesNetWeight() : BigDecimal.ZERO;
|
alreadyNet = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesNetWeight);
|
||||||
alreadyGross = planParent.getAlreadyShelvesGrossWeight() != null ? planParent.getAlreadyShelvesGrossWeight() : BigDecimal.ZERO;
|
alreadyGross = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesGrossWeight);
|
||||||
alreadyVol = planParent.getAlreadyShelvesVolume() != null ? planParent.getAlreadyShelvesVolume() : BigDecimal.ZERO;
|
alreadyVol = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesVolume);
|
||||||
alreadyArea = planParent.getAlreadyShelvesArea() != null ? planParent.getAlreadyShelvesArea() : BigDecimal.ZERO;
|
alreadyArea = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 参考收货:只对「有计划值」的字段覆盖 total*,否则保留原 total*
|
// 参考收货:只对「有计划值」的字段覆盖 total*,否则保留原 total*
|
||||||
|
|||||||
+44
-11
@@ -407,6 +407,8 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
// 如果获取失败,记录日志但继续处理
|
// 如果获取失败,记录日志但继续处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 仅按「本入库单」与 material_inventory.in_order_number 比对批次是否冲突;避免其它单据已落库的批次拦截本单首笔收货
|
||||||
|
String currentInOrderNumber = resolveCurrentInOrderNumberForBatchReceipt(stockReceiptOrderDO);
|
||||||
|
|
||||||
//批量新增或修改物料信息
|
//批量新增或修改物料信息
|
||||||
List<ReceiptMaterialDetail> receiptMaterialDetailList = new ArrayList<>();
|
List<ReceiptMaterialDetail> receiptMaterialDetailList = new ArrayList<>();
|
||||||
@@ -416,7 +418,7 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
for (ReceiptMaterialDetailDO receiptMaterialDetailDO : receiptMaterialDetailDOList){
|
for (ReceiptMaterialDetailDO receiptMaterialDetailDO : receiptMaterialDetailDOList){
|
||||||
extractBatchAttributesToReceiptDetail(receiptMaterialDetailDO);
|
extractBatchAttributesToReceiptDetail(receiptMaterialDetailDO);
|
||||||
// 自动生成批次号并校验唯一性(包括子项)
|
// 自动生成批次号并校验唯一性(包括子项)
|
||||||
generateAndValidateBatchNumber(receiptMaterialDetailDO, shipperId);
|
generateAndValidateBatchNumber(receiptMaterialDetailDO, shipperId, currentInOrderNumber);
|
||||||
// 当 materialMoreDetailList 为空时(如 PDA 未传),从 receiptMaterialDetailDO 的 batch_number、material_status_code 等字段构建,确保更多属性能保存并回显
|
// 当 materialMoreDetailList 为空时(如 PDA 未传),从 receiptMaterialDetailDO 的 batch_number、material_status_code 等字段构建,确保更多属性能保存并回显
|
||||||
if (CollectionUtils.isEmpty(receiptMaterialDetailDO.getMaterialMoreDetailList())) {
|
if (CollectionUtils.isEmpty(receiptMaterialDetailDO.getMaterialMoreDetailList())) {
|
||||||
receiptMaterialDetailDO.setMaterialMoreDetailList(buildMaterialMoreDetailFromReceiptFields(receiptMaterialDetailDO));
|
receiptMaterialDetailDO.setMaterialMoreDetailList(buildMaterialMoreDetailFromReceiptFields(receiptMaterialDetailDO));
|
||||||
@@ -1204,27 +1206,51 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析本笔收货对应的入库单号:DO 未带时从收货单主表补全,供批次校验限定「本单」范围。
|
||||||
|
*/
|
||||||
|
private String resolveCurrentInOrderNumberForBatchReceipt(StockReceiptOrderDO stockReceiptOrderDO) {
|
||||||
|
if (stockReceiptOrderDO == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String in = stockReceiptOrderDO.getInOrderNumber();
|
||||||
|
if (StringUtils.isNotBlank(in)) {
|
||||||
|
return in.trim();
|
||||||
|
}
|
||||||
|
Long receiptOrderId = stockReceiptOrderDO.getReceiptOrderId();
|
||||||
|
if (receiptOrderId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StockReceiptOrder head = stockReceiptOrderMapper.selectById(receiptOrderId);
|
||||||
|
if (head != null && StringUtils.isNotBlank(head.getInOrderNumber())) {
|
||||||
|
return head.getInOrderNumber().trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 自动生成批次号并校验唯一性
|
* @description 自动生成批次号并校验唯一性
|
||||||
* @author Auto
|
* @author Auto
|
||||||
* @date 2026/1/28
|
* @date 2026/1/28
|
||||||
* @param receiptMaterialDetailDO 收货明细
|
* @param receiptMaterialDetailDO 收货明细
|
||||||
* @param shipperId 货主ID
|
* @param shipperId 货主ID
|
||||||
|
* @param currentInOrderNumber 当前入库单号,与 material_inventory.in_order_number 一致时才视为本单内批次冲突
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private void generateAndValidateBatchNumber(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId) {
|
private void generateAndValidateBatchNumber(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId,
|
||||||
|
String currentInOrderNumber) {
|
||||||
if (receiptMaterialDetailDO == null) {
|
if (receiptMaterialDetailDO == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理当前明细
|
// 处理当前明细
|
||||||
processBatchNumberForDetail(receiptMaterialDetailDO, shipperId);
|
processBatchNumberForDetail(receiptMaterialDetailDO, shipperId, currentInOrderNumber);
|
||||||
|
|
||||||
// 递归处理子项
|
// 递归处理子项
|
||||||
List<ReceiptMaterialDetailDO> children = receiptMaterialDetailDO.getChildren();
|
List<ReceiptMaterialDetailDO> children = receiptMaterialDetailDO.getChildren();
|
||||||
if (!CollectionUtils.isEmpty(children)) {
|
if (!CollectionUtils.isEmpty(children)) {
|
||||||
for (ReceiptMaterialDetailDO child : children) {
|
for (ReceiptMaterialDetailDO child : children) {
|
||||||
generateAndValidateBatchNumber(child, shipperId);
|
generateAndValidateBatchNumber(child, shipperId, currentInOrderNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1235,9 +1261,11 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
* @date 2026/1/28
|
* @date 2026/1/28
|
||||||
* @param receiptMaterialDetailDO 收货明细
|
* @param receiptMaterialDetailDO 收货明细
|
||||||
* @param shipperId 货主ID
|
* @param shipperId 货主ID
|
||||||
|
* @param currentInOrderNumber 当前入库单号
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private void processBatchNumberForDetail(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId) {
|
private void processBatchNumberForDetail(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId,
|
||||||
|
String currentInOrderNumber) {
|
||||||
if (receiptMaterialDetailDO == null) {
|
if (receiptMaterialDetailDO == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1252,21 +1280,25 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
receiptMaterialDetailDO.setBatchNumber(batchNumber);
|
receiptMaterialDetailDO.setBatchNumber(batchNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验批次号唯一性:货主+商品+库存属性值相同时,批次号不能重复
|
// 校验批次号:仅本入库单范围内,货主+商品+库存属性+批次在库存中是否已存在(不同入库单允许相同批次号)
|
||||||
if (shipperId != null && materialBaseInfoId != null && batchNumber != null && !batchNumber.trim().isEmpty()) {
|
if (shipperId != null && materialBaseInfoId != null && batchNumber != null && !batchNumber.trim().isEmpty()) {
|
||||||
// 使用 LambdaQueryWrapper 查询库存表中是否存在相同的货主+商品+库存属性值+批次号的记录
|
if (StringUtils.isBlank(currentInOrderNumber)) {
|
||||||
|
log.warn("batchReceiptUpdate:未解析到入库单号,跳过库存批次重复校验,receiptOrderNumber={}",
|
||||||
|
receiptMaterialDetailDO != null ? receiptMaterialDetailDO.getReceiptOrderNumber() : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaterialInventory> wrapper =
|
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaterialInventory> wrapper =
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaterialInventory>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaterialInventory>()
|
||||||
.eq(MaterialInventory::getShipperId, shipperId)
|
.eq(MaterialInventory::getShipperId, shipperId)
|
||||||
.eq(MaterialInventory::getMaterialBaseInfoId, materialBaseInfoId)
|
.eq(MaterialInventory::getMaterialBaseInfoId, materialBaseInfoId)
|
||||||
.eq(MaterialInventory::getBatchNumber, batchNumber)
|
.eq(MaterialInventory::getBatchNumber, batchNumber)
|
||||||
.eq(MaterialInventory::getDelFlag, 1);
|
.eq(MaterialInventory::getDelFlag, 1)
|
||||||
|
.eq(MaterialInventory::getInOrderNumber, currentInOrderNumber);
|
||||||
|
|
||||||
// 如果库存属性值不为空,需要精确匹配;如果为空,查询库存属性值为空或null的记录
|
// 如果库存属性值不为空,需要精确匹配;如果为空,查询库存属性值为空或null的记录
|
||||||
if (materialStatusCode != null && !materialStatusCode.trim().isEmpty()) {
|
if (materialStatusCode != null && !materialStatusCode.trim().isEmpty()) {
|
||||||
wrapper.eq(MaterialInventory::getMaterialStatusCode, materialStatusCode);
|
wrapper.eq(MaterialInventory::getMaterialStatusCode, materialStatusCode);
|
||||||
} else {
|
} else {
|
||||||
// 如果库存属性值为空,查询库存属性值为null或空字符串的记录
|
|
||||||
wrapper.and(w -> w.isNull(MaterialInventory::getMaterialStatusCode)
|
wrapper.and(w -> w.isNull(MaterialInventory::getMaterialStatusCode)
|
||||||
.or()
|
.or()
|
||||||
.eq(MaterialInventory::getMaterialStatusCode, ""));
|
.eq(MaterialInventory::getMaterialStatusCode, ""));
|
||||||
@@ -1274,12 +1306,13 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
|
|||||||
|
|
||||||
List<MaterialInventory> existingInventoryList = materialInventoryMapper.selectList(wrapper);
|
List<MaterialInventory> existingInventoryList = materialInventoryMapper.selectList(wrapper);
|
||||||
|
|
||||||
// 如果存在相同的货主+商品+库存属性值+批次号的记录,抛出异常
|
|
||||||
if (!CollectionUtils.isEmpty(existingInventoryList)) {
|
if (!CollectionUtils.isEmpty(existingInventoryList)) {
|
||||||
String statusCodeDesc = (materialStatusCode != null && !materialStatusCode.trim().isEmpty())
|
String statusCodeDesc = (materialStatusCode != null && !materialStatusCode.trim().isEmpty())
|
||||||
? ",库存属性值:" + materialStatusCode
|
? ",库存属性值:" + materialStatusCode
|
||||||
: ",库存属性值:空";
|
: ",库存属性值:空";
|
||||||
throw new ServiceException("批次号【" + batchNumber + "】已存在,货主ID【" + shipperId + "】+商品ID【" + materialBaseInfoId + "】" + statusCodeDesc + "相同的记录不允许使用重复的批次号");
|
throw new ServiceException("批次号【" + batchNumber + "】在本入库单「" + currentInOrderNumber
|
||||||
|
+ "」下已存在库存,货主ID【" + shipperId + "】+商品ID【" + materialBaseInfoId + "】" + statusCodeDesc
|
||||||
|
+ ",不允许重复使用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user