上架数量显示异常

This commit is contained in:
秦鸿展
2026-04-15 10:06:46 +08:00
parent f63917ed03
commit fdb70db6f6
2 changed files with 81 additions and 26 deletions
@@ -522,8 +522,8 @@ public class StockShelfOrderApplicationService {
}
/**
* 扁平化明细:主行保留父子结构;展示「已上架」= 主行累计 + 各子行累计(不含 parent_copy 镜像行,避免与主行重复)。
* 上架状态按「汇总已上架 qtySum」与主行计划量比较推导,与域服务分批/完成上架的 1/2/3/4/5 含义一致
* 扁平化明细:主行保留父子结构;主行「已上架数量」与上架状态仍按主行+子行汇总(与分批场景一致)。
* 主行净重/毛重/体积/面积<strong>不再</strong>用主+子汇总覆盖,保留库表主行单行值(子行仍为自身行明细)
*
* @param shelfOrderCompleted 上架单是否已整单完成(status=3);为 true 时少上为 4,与域服务完成口径一致
*/
@@ -553,17 +553,9 @@ public class StockShelfOrderApplicationService {
continue;
}
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);
parent.setAlreadyShelvesQuantity(qtySum);
parent.setAlreadyShelvesNetWeight(netSum);
parent.setAlreadyShelvesGrossWeight(grossSum);
parent.setAlreadyShelvesVolume(volSum);
parent.setAlreadyShelvesArea(areaSum);
if (derived != null) {
parent.setShelvesStatus(derived);
}
@@ -724,6 +716,36 @@ public class StockShelfOrderApplicationService {
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} */
private static ShelfMaterialDetailPO resolveShelfPlanParentRow(ShelfMaterialDetailPO db,
List<ShelfMaterialDetailPO> dbList) {
@@ -1072,12 +1094,12 @@ public class StockShelfOrderApplicationService {
BigDecimal alreadyVol = d.getAlreadyShelvesVolume() != null ? d.getAlreadyShelvesVolume() : 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) {
alreadyNet = planParent.getAlreadyShelvesNetWeight() != null ? planParent.getAlreadyShelvesNetWeight() : BigDecimal.ZERO;
alreadyGross = planParent.getAlreadyShelvesGrossWeight() != null ? planParent.getAlreadyShelvesGrossWeight() : BigDecimal.ZERO;
alreadyVol = planParent.getAlreadyShelvesVolume() != null ? planParent.getAlreadyShelvesVolume() : BigDecimal.ZERO;
alreadyArea = planParent.getAlreadyShelvesArea() != null ? planParent.getAlreadyShelvesArea() : BigDecimal.ZERO;
alreadyNet = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesNetWeight);
alreadyGross = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesGrossWeight);
alreadyVol = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesVolume);
alreadyArea = sumShelfPlanRowAggregatedAlreadyMetric(planParent, ShelfMaterialDetailPO::getAlreadyShelvesArea);
}
// 参考收货:只对「有计划值」的字段覆盖 total*,否则保留原 total*
@@ -407,6 +407,8 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
// 如果获取失败,记录日志但继续处理
}
}
// 仅按「本入库单」与 material_inventory.in_order_number 比对批次是否冲突;避免其它单据已落库的批次拦截本单首笔收货
String currentInOrderNumber = resolveCurrentInOrderNumberForBatchReceipt(stockReceiptOrderDO);
//批量新增或修改物料信息
List<ReceiptMaterialDetail> receiptMaterialDetailList = new ArrayList<>();
@@ -416,7 +418,7 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
for (ReceiptMaterialDetailDO receiptMaterialDetailDO : receiptMaterialDetailDOList){
extractBatchAttributesToReceiptDetail(receiptMaterialDetailDO);
// 自动生成批次号并校验唯一性(包括子项)
generateAndValidateBatchNumber(receiptMaterialDetailDO, shipperId);
generateAndValidateBatchNumber(receiptMaterialDetailDO, shipperId, currentInOrderNumber);
// 当 materialMoreDetailList 为空时(如 PDA 未传),从 receiptMaterialDetailDO 的 batch_number、material_status_code 等字段构建,确保更多属性能保存并回显
if (CollectionUtils.isEmpty(receiptMaterialDetailDO.getMaterialMoreDetailList())) {
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 自动生成批次号并校验唯一性
* @author Auto
* @date 2026/1/28
* @param receiptMaterialDetailDO 收货明细
* @param shipperId 货主ID
* @param currentInOrderNumber 当前入库单号,与 material_inventory.in_order_number 一致时才视为本单内批次冲突
* @return void
*/
private void generateAndValidateBatchNumber(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId) {
private void generateAndValidateBatchNumber(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId,
String currentInOrderNumber) {
if (receiptMaterialDetailDO == null) {
return;
}
// 处理当前明细
processBatchNumberForDetail(receiptMaterialDetailDO, shipperId);
processBatchNumberForDetail(receiptMaterialDetailDO, shipperId, currentInOrderNumber);
// 递归处理子项
List<ReceiptMaterialDetailDO> children = receiptMaterialDetailDO.getChildren();
if (!CollectionUtils.isEmpty(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
* @param receiptMaterialDetailDO 收货明细
* @param shipperId 货主ID
* @param currentInOrderNumber 当前入库单号
* @return void
*/
private void processBatchNumberForDetail(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId) {
private void processBatchNumberForDetail(ReceiptMaterialDetailDO receiptMaterialDetailDO, Long shipperId,
String currentInOrderNumber) {
if (receiptMaterialDetailDO == null) {
return;
}
@@ -1252,21 +1280,25 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
receiptMaterialDetailDO.setBatchNumber(batchNumber);
}
// 校验批次号唯一性:货主+商品+库存属性值相同时,批次号不能重复
// 校验批次号:仅本入库单范围内,货主+商品+库存属性+批次在库存中是否已存在(不同入库单允许相同批次号)
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 =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaterialInventory>()
.eq(MaterialInventory::getShipperId, shipperId)
.eq(MaterialInventory::getMaterialBaseInfoId, materialBaseInfoId)
.eq(MaterialInventory::getBatchNumber, batchNumber)
.eq(MaterialInventory::getDelFlag, 1);
.eq(MaterialInventory::getDelFlag, 1)
.eq(MaterialInventory::getInOrderNumber, currentInOrderNumber);
// 如果库存属性值不为空,需要精确匹配;如果为空,查询库存属性值为空或null的记录
if (materialStatusCode != null && !materialStatusCode.trim().isEmpty()) {
wrapper.eq(MaterialInventory::getMaterialStatusCode, materialStatusCode);
} else {
// 如果库存属性值为空,查询库存属性值为null或空字符串的记录
wrapper.and(w -> w.isNull(MaterialInventory::getMaterialStatusCode)
.or()
.eq(MaterialInventory::getMaterialStatusCode, ""));
@@ -1274,12 +1306,13 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
List<MaterialInventory> existingInventoryList = materialInventoryMapper.selectList(wrapper);
// 如果存在相同的货主+商品+库存属性值+批次号的记录,抛出异常
if (!CollectionUtils.isEmpty(existingInventoryList)) {
String statusCodeDesc = (materialStatusCode != null && !materialStatusCode.trim().isEmpty())
? ",库存属性值:" + materialStatusCode
: ",库存属性值:空";
throw new ServiceException("批次号【" + batchNumber + "已存在,货主ID【" + shipperId + "】+商品ID【" + materialBaseInfoId + "" + statusCodeDesc + "相同的记录不允许使用重复的批次号");
throw new ServiceException("批次号【" + batchNumber + "在本入库单「" + currentInOrderNumber
+ "」下已存在库存,货主ID【" + shipperId + "】+商品ID【" + materialBaseInfoId + "" + statusCodeDesc
+ ",不允许重复使用");
}
}
}