From f63917ed03af9f2c283ed4450057cd305283750d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=A6=E9=B8=BF=E5=B1=95?= <18031053041@163.com>
Date: Tue, 14 Apr 2026 21:36:50 +0800
Subject: [PATCH] =?UTF-8?q?PDA=E6=94=B6=E8=B4=A7=E4=B8=8A=E6=9E=B6?=
=?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=86=85=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../StockReceiptOrderApplicationService.java | 770 +++++++++++++++--
.../StockShelfOrderApplicationService.java | 781 +++++++++++++++++-
.../facade/IReceiptMaterialDetailService.java | 5 +
.../ReceiptMaterialDetailImpl.java | 80 ++
.../facade/IShelfMaterialDetailService.java | 5 +
.../persistence/ShelfMaterialDetailImpl.java | 132 ++-
.../todo/ShelfMaterialDetailDO.java | 7 +
.../StockReceiptOrderDomainService.java | 135 ++-
.../repository/po/StockShelfOrderPO.java | 17 +
.../service/StockShelfOrderDomainService.java | 247 +++++-
.../StockReceiptOrderAppApi.java | 15 +
.../StockShelfOrderAppApi.java | 17 +-
12 files changed, 2111 insertions(+), 100 deletions(-)
diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockReceiptOrder/StockReceiptOrderApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockReceiptOrder/StockReceiptOrderApplicationService.java
index 9d314b6eb..452b33403 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockReceiptOrder/StockReceiptOrderApplicationService.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockReceiptOrder/StockReceiptOrderApplicationService.java
@@ -54,11 +54,14 @@ import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -296,8 +299,9 @@ public class StockReceiptOrderApplicationService {
stockReceiptOrderPO.setTotalGrossWeight(totalGrossWeight);
stockReceiptOrderPO.setTotalVolume(totalVolume);
stockReceiptOrderPO.setTotalArea(totalArea);
- // 扁平化明细:将子项收货数据合并到主项
- flattenMaterialDetailList(receiptMaterialDetailPOList);
+ // 扁平化明细:将子项收货数据合并到主项(按汇总已收推导状态,见方法内注释)
+ flattenMaterialDetailList(receiptMaterialDetailPOList,
+ stockReceiptOrderPO.getStatus() != null && stockReceiptOrderPO.getStatus() == 3);
// PC 端子项计划数量不展示:递归清空所有子项的 quantity
clearChildQuantityRecursively(receiptMaterialDetailPOList);
// 计算待收货数量 = 计划数量 - 已收货数量
@@ -419,40 +423,159 @@ public class StockReceiptOrderApplicationService {
}
/**
- * 扁平化明细:主行保留自身 receiptQuantity,alreadyReceiptQuantity 汇总主行+所有子项;保留父子结构,子项作为 children 展示
- * @param receiptMaterialDetailPOList 物料明细列表(树形,level 1 父级含 children)
+ * 扁平化明细:主行保留父子结构;展示「已收」= 主行累计 + 各子行累计(不含 parent_copy 镜像行,避免与主行重复)。
+ * 件数、净重/毛重/体积/面积均用库表 already_receipt_* 口径(非 receipt_quantity)。
+ *
+ * 子行除挂在 parent.children 外,若因树构建异常被提升为顶级或与父脱节,仍通过本单全表明细按 parent_unique_id 索引参与汇总,避免「只算一条已收」。
+ * 已收件数/毛净重体面积与 {@link #sumAlreadyReceiptUnderPlanParent} / {@link #sumAlreadyMetricUnderPlanParent} 一致(与超计划校验、PDA 收货增量同一口径)。
+ *
+ * 收货状态:须按「汇总已收 qtySum」与「主行计划量」比较推导,不能仅用库表主/子行 {@code receipt_status} 取大值;
+ * 否则主行+子行数量已收满时各表行仍可能为「收货中=2」,界面长期不显示已收货。
+ *
+ * @param receiptOrderCompleted 收货单是否已整单完成(status=3);为 true 时少收为 4,与域服务完成口径一致
*/
- private void flattenMaterialDetailList(List receiptMaterialDetailPOList) {
+ private void flattenMaterialDetailList(List receiptMaterialDetailPOList,
+ boolean receiptOrderCompleted) {
if (CollectionUtils.isEmpty(receiptMaterialDetailPOList)) {
return;
}
- for (ReceiptMaterialDetailPO parent : receiptMaterialDetailPOList) {
- List children = parent.getChildren();
- if (CollectionUtils.isEmpty(children)) {
+ List allNodes = new ArrayList<>();
+ collectReceiptMaterialDetailNodesRecursively(receiptMaterialDetailPOList, allNodes);
+ Map> childrenByParentUid = new HashMap<>();
+ for (ReceiptMaterialDetailPO node : allNodes) {
+ if (node == null || node.getLevel() == null || node.getLevel() != 2) {
continue;
}
- // 若存在 parent_copy(子项 uniqueId 与主项相同),主行数量仅取自子项,避免重复累加
- boolean hasParentCopy = children.stream().anyMatch(c -> parent.getUniqueId() != null && parent.getUniqueId().equals(c.getUniqueId()));
- BigDecimal alreadyReceiptQuantitySum = (hasParentCopy ? BigDecimal.ZERO : (parent.getReceiptQuantity() != null ? parent.getReceiptQuantity() : BigDecimal.ZERO));
- Integer maxReceiptStatus = parent.getReceiptStatus();
- for (ReceiptMaterialDetailPO child : children) {
- if (child.getReceiptQuantity() != null) {
- alreadyReceiptQuantitySum = alreadyReceiptQuantitySum.add(child.getReceiptQuantity());
+ Long puid = node.getParentUniqueId();
+ if (puid == null || Long.valueOf(0).equals(puid)) {
+ continue;
+ }
+ childrenByParentUid.computeIfAbsent(puid, k -> new ArrayList<>()).add(node);
+ }
+ for (ReceiptMaterialDetailPO parent : receiptMaterialDetailPOList) {
+ if (parent == null) {
+ continue;
+ }
+ if (parent.getLevel() != null && parent.getLevel() != 1) {
+ continue;
+ }
+ // 全单范围内汇总「计划父行 + 其下 level=2」与收货/校验逻辑一致,避免仅依赖树上子集时漏掉主行首笔或脱节子行
+ BigDecimal qtySum = sumAlreadyReceiptUnderPlanParent(parent, allNodes);
+ BigDecimal netSum = sumAlreadyMetricUnderPlanParent(parent, allNodes, ReceiptMaterialDetailPO::getAlreadyReceiptNetWeight);
+ BigDecimal grossSum = sumAlreadyMetricUnderPlanParent(parent, allNodes, ReceiptMaterialDetailPO::getAlreadyReceiptGrossWeight);
+ BigDecimal volSum = sumAlreadyMetricUnderPlanParent(parent, allNodes, ReceiptMaterialDetailPO::getAlreadyReceiptVolume);
+ BigDecimal areaSum = sumAlreadyMetricUnderPlanParent(parent, allNodes, ReceiptMaterialDetailPO::getAlreadyReceiptArea);
+ Integer derivedReceiptStatus = deriveReceiptStatusFromAggregatedReceivedVsPlan(qtySum, parent.getQuantity(),
+ receiptOrderCompleted);
+
+ parent.setAlreadyReceiptQuantity(qtySum);
+ parent.setAlreadyReceiptNetWeight(netSum);
+ parent.setAlreadyReceiptGrossWeight(grossSum);
+ parent.setAlreadyReceiptVolume(volSum);
+ parent.setAlreadyReceiptArea(areaSum);
+ if (derivedReceiptStatus != null) {
+ parent.setReceiptStatus(derivedReceiptStatus);
+ }
+
+ LinkedHashMap mergedChildren = new LinkedHashMap<>();
+ mergeReceiptChildRowsIntoMap(mergedChildren, parent.getChildren());
+ Long uid = parent.getUniqueId();
+ Long inUid = parent.getInUniqueId();
+ if (uid != null) {
+ mergeReceiptChildRowsIntoMap(mergedChildren, childrenByParentUid.get(uid));
+ }
+ if (inUid != null && !inUid.equals(uid)) {
+ mergeReceiptChildRowsIntoMap(mergedChildren, childrenByParentUid.get(inUid));
+ }
+ if (mergedChildren.isEmpty()) {
+ continue;
+ }
+ for (ReceiptMaterialDetailPO child : mergedChildren.values()) {
+ if (child == null) {
+ continue;
}
- if (child.getReceiptStatus() != null && (maxReceiptStatus == null || child.getReceiptStatus() > maxReceiptStatus)) {
- maxReceiptStatus = child.getReceiptStatus();
+ // parent_copy:与主行同一条库记录镜像,不累加(否则与主行重复)
+ if (parent.getUniqueId() != null && parent.getUniqueId().equals(child.getUniqueId())) {
+ child.setQuantity(null);
+ continue;
}
child.setQuantity(null);
+ // 与主行同卡片的真实子行:回显与主行一致(避免库表仍停留在收货中=2)
+ if (derivedReceiptStatus != null && child.getLevel() != null && child.getLevel() == 2) {
+ child.setReceiptStatus(derivedReceiptStatus);
+ }
}
- parent.setAlreadyReceiptQuantity(alreadyReceiptQuantitySum);
- // 累计已称毛重不按子行汇总到主行:多批/多子行各自一条计划毛重,汇总会大于主行计划,导致 计划−已称=0 且界面显示 0E-8;待收毛重按本行 already_receipt_gross_weight 与 total_gross_weight 计算即可
- if (maxReceiptStatus != null) {
- parent.setReceiptStatus(maxReceiptStatus);
- }
- // 保留父子结构,子项在 parent.children 中,前端按树形展示
}
}
+ /**
+ * 主行展示用收货状态:按「主行+子行汇总已收」与主行计划量比较。
+ * 与 {@link com.mhd.wms.domain.stockReceiptOrder.service.StockReceiptOrderDomainService} 中分批/完成收货的 1/2/3/4/5 含义一致。
+ */
+ private static Integer deriveReceiptStatusFromAggregatedReceivedVsPlan(BigDecimal aggregatedReceived, BigDecimal planQty,
+ boolean receiptOrderCompleted) {
+ BigDecimal r = aggregatedReceived != null ? aggregatedReceived : BigDecimal.ZERO;
+ BigDecimal p = planQty != null ? planQty : BigDecimal.ZERO;
+ if (receiptOrderCompleted) {
+ int cmp = r.compareTo(p);
+ if (cmp > 0) {
+ return 5;
+ }
+ if (cmp < 0) {
+ return 4;
+ }
+ return 3;
+ }
+ if (r.compareTo(BigDecimal.ZERO) == 0) {
+ return 1;
+ }
+ int cmp = r.compareTo(p);
+ if (cmp < 0) {
+ return 2;
+ }
+ if (cmp > 0) {
+ return 5;
+ }
+ return 3;
+ }
+
+ private static void collectReceiptMaterialDetailNodesRecursively(List list, List out) {
+ if (CollectionUtils.isEmpty(list)) {
+ return;
+ }
+ for (ReceiptMaterialDetailPO n : list) {
+ if (n == null) {
+ continue;
+ }
+ out.add(n);
+ collectReceiptMaterialDetailNodesRecursively(n.getChildren(), out);
+ }
+ }
+
+ /** 按 unique_id(缺省时 material_detail_id)去重合并子行,避免树内与索引重复计数 */
+ private static void mergeReceiptChildRowsIntoMap(Map byKey, List children) {
+ if (CollectionUtils.isEmpty(children)) {
+ return;
+ }
+ for (ReceiptMaterialDetailPO c : children) {
+ if (c == null) {
+ continue;
+ }
+ Long key = c.getUniqueId();
+ if (key == null) {
+ key = c.getMaterialDetailId();
+ }
+ if (key == null) {
+ continue;
+ }
+ byKey.putIfAbsent(key, c);
+ }
+ }
+
+ private static BigDecimal nzBig(BigDecimal v) {
+ return v != null ? v : BigDecimal.ZERO;
+ }
+
/**
* PC 端子项计划数量不展示:递归清空所有子项的 quantity
*/
@@ -594,9 +717,16 @@ public class StockReceiptOrderApplicationService {
}
/**
- * 计划/待收:plan* 来自入库单联表(与首次创建时 total 同源);pending* = plan − 累计已收,仅接口计算不落库。
+ * 计划/待收:plan* 来自入库单联表;pending* = plan* − 累计已收(展示即「计划 − 已收」)。
+ * 主行 already* 须在 {@link #flattenMaterialDetailList} 后表示该卡「全部已收」。
+ * level=2 子行仍保留单行库表 already*,但 pending* 与主行同口径:使用传入的 {@code planParent}(对应 level=1 主行)上已汇总的已收,避免「只减子行本批」导致待收偏大。
+ * 仅接口计算不落库。
*/
private void fillPlanAndPendingMetricsRecursively(List list) {
+ fillPlanAndPendingMetricsRecursively(list, null);
+ }
+
+ private void fillPlanAndPendingMetricsRecursively(List list, ReceiptMaterialDetailPO planParent) {
if (CollectionUtils.isEmpty(list)) {
return;
}
@@ -605,12 +735,26 @@ public class StockReceiptOrderApplicationService {
continue;
}
if (!isPdaReceiptDetailFullyReceived(d)) {
- d.setPendingNetWeight(calcPendingMetric(d.getPlanNetWeight(), d.getAlreadyReceiptNetWeight()));
- d.setPendingGrossWeight(calcPendingMetric(d.getPlanGrossWeight(), d.getAlreadyReceiptGrossWeight()));
- d.setPendingVolume(calcPendingMetric(d.getPlanVolume(), d.getAlreadyReceiptVolume()));
- d.setPendingArea(calcPendingMetric(d.getPlanArea(), d.getAlreadyReceiptArea()));
+ BigDecimal netAlready = d.getAlreadyReceiptNetWeight();
+ BigDecimal grossAlready = d.getAlreadyReceiptGrossWeight();
+ BigDecimal volAlready = d.getAlreadyReceiptVolume();
+ BigDecimal areaAlready = d.getAlreadyReceiptArea();
+ if (planParent != null && d.getLevel() != null && d.getLevel() == 2) {
+ netAlready = planParent.getAlreadyReceiptNetWeight();
+ grossAlready = planParent.getAlreadyReceiptGrossWeight();
+ volAlready = planParent.getAlreadyReceiptVolume();
+ areaAlready = planParent.getAlreadyReceiptArea();
+ }
+ d.setPendingNetWeight(calcPendingMetric(d.getPlanNetWeight(), netAlready));
+ d.setPendingGrossWeight(calcPendingMetric(d.getPlanGrossWeight(), grossAlready));
+ d.setPendingVolume(calcPendingMetric(d.getPlanVolume(), volAlready));
+ d.setPendingArea(calcPendingMetric(d.getPlanArea(), areaAlready));
}
- fillPlanAndPendingMetricsRecursively(d.getChildren());
+ ReceiptMaterialDetailPO nextPlanParent = planParent;
+ if (d.getLevel() != null && d.getLevel() == 1) {
+ nextPlanParent = d;
+ }
+ fillPlanAndPendingMetricsRecursively(d.getChildren(), nextPlanParent);
}
}
@@ -1133,7 +1277,13 @@ public class StockReceiptOrderApplicationService {
/**
* 获取收货单详细信息(PDA 端)
- * 在 Domain 层结果基础上,补充包装规格填充,与 PC 端 getInfo 一致
+ * 在 Domain 层结果基础上,补充包装规格填充,与 PC 端 getInfo 一致。
+ *
+ * 净重/毛重/体积/面积展示口径(待收 pending*;未完成明细覆盖后的 total* 与之一致):
+ * 待收 = 计划(plan*) − 累计已收(already_receipt_*)。
+ * 对 level=1 主行(一张卡):already_receipt_* 在 {@link #flattenMaterialDetailList} 中已汇总为「主行本行 + 其下全部真实子行」的累计已收,
+ * 即「计划 − 全部已收」。子行单行库表 already_receipt_* 仍保留(便于看本批已收);子行 pending*(及后续覆盖的 total*)
+ * 与主行同口径:plan* − 主行上该卡累计已收,避免仅按子行本行已收算出偏大的待收。
*/
public StockReceiptOrderPO getInfoByApp(Long receiptOrderId, Integer receiptStatus)
{
@@ -1191,8 +1341,9 @@ public class StockReceiptOrderApplicationService {
fillBarCodeFromMaterialBarCodeRecursively(materialDetailList, materialBarCodeMap);
// 第一次收货:主项无子项时,收货数量默认等于计划数量
fillFirstReceiptQuantityDefault(materialDetailList);
- // PDA 端与 PC 端一致:主行收货数量取第一条子项,已收货数量汇总所有子项
- flattenMaterialDetailList(materialDetailList);
+ // 主行:件数/毛净重体面积已收 = 主行库表 + 其下子行汇总,用于「计划 − 全部已收」
+ flattenMaterialDetailList(materialDetailList,
+ stockReceiptOrderPO.getStatus() != null && stockReceiptOrderPO.getStatus() == 3);
// 须先算待收指标再清空子项 quantity;已收尾明细(3/4/5)不按「计划−待收」计算,见 isPdaReceiptDetailFullyReceived
if (receiptStatus != null && receiptStatus == 1) {
fillPendingDisplayFieldsRecursively(materialDetailList);
@@ -1201,16 +1352,58 @@ public class StockReceiptOrderApplicationService {
fillPendingReceiptQuantityRecursively(materialDetailList);
syncReceiptQuantityCumulativeDisplayRecursively(materialDetailList);
}
+ // pending* = plan* − already_receipt_*(主行 already* 已为全部已收汇总)
fillPlanAndPendingMetricsRecursively(materialDetailList);
- // PDA 绑定 total* 展示「待收」= plan − 已收;已收货行不覆盖
+ // 未完成行:total* 与 pending* 同值,即「计划总的 − 全部已收」
overwriteTotalWithPendingForPdaDisplayRecursively(materialDetailList);
clearChildQuantityRecursively(materialDetailList);
+ // 仅在未按 tab 筛明细时纠正整单头状态;筛成「待收/已收」子集时不能据子集推断整单是否已收完
+ if (receiptStatus == null) {
+ refreshPdaReceiptOrderHeaderStatusFromDerivedPlanLines(stockReceiptOrderPO, materialDetailList);
+ }
}
return stockReceiptOrderPO;
}
/**
- * PDA 界面「总净重/总毛重/体积/面积」绑定 total*:有计划时展示待收(pending*);无计划联表时保留原 total*(累计实绩)。
+ * PDA 详情:header.status 直接来自库表;若库表仍为部分收货(2),但 flatten 后各计划主行已是收尾明细状态且整单收货数已不少于计划,则将返回头状态纠正为已收货(3),避免列表/详情长期显示「部分收货」。
+ */
+ private void refreshPdaReceiptOrderHeaderStatusFromDerivedPlanLines(StockReceiptOrderPO order,
+ List planRoots) {
+ if (order == null || order.getStatus() == null || order.getStatus() != 2) {
+ return;
+ }
+ if (CollectionUtils.isEmpty(planRoots)) {
+ return;
+ }
+ for (ReceiptMaterialDetailPO p : planRoots) {
+ if (p == null) {
+ continue;
+ }
+ if (p.getLevel() != null && p.getLevel() != 1) {
+ continue;
+ }
+ if (!isPdaTerminalReceiptDetailStatus(p.getReceiptStatus())) {
+ return;
+ }
+ }
+ BigDecimal rq = order.getReceiptQuantity() != null ? order.getReceiptQuantity() : BigDecimal.ZERO;
+ BigDecimal pq = order.getQuantity() != null ? order.getQuantity() : BigDecimal.ZERO;
+ if (rq.compareTo(pq) >= 0) {
+ order.setStatus(3);
+ }
+ }
+
+ /**
+ * PDA:指定计划行(卡片)下分批收货子行列表(level=2),域服务已挂序列号。
+ */
+ public List listLevel2ChildrenByParentForApp(Long receiptOrderId, Long parentMaterialDetailId) {
+ return stockReceiptOrderDomainService.listLevel2ChildrenByParentForApp(receiptOrderId, parentMaterialDetailId);
+ }
+
+ /**
+ * PDA 详情:将 total* 覆盖为待收(与 pending* 同值),便于前端仍绑 total* 时展示「计划 − 主项+子项全部已收」。
+ * 回传收货时由 {@link #pickThisReceiptMetricIncrementPda} 识别并按件数比例分摊增量。
*/
private void overwriteTotalWithPendingForPdaDisplayRecursively(List list) {
if (CollectionUtils.isEmpty(list)) {
@@ -1391,8 +1584,16 @@ public class StockReceiptOrderApplicationService {
*/
public Boolean receivingGoodsByApp(StockReceiptOrderDO stockReceiptOrderDO){
List list = stockReceiptOrderDO.getMaterialDetailList();
- // 超计划提示:实际收货数量 > 待收数量(计划-已收) 时,先提示确认
- checkOverPlanReceiptForApp(stockReceiptOrderDO);
+ StockReceiptOrderPO dbOrder = stockReceiptOrderDomainService.getInfo(stockReceiptOrderDO.getReceiptOrderId());
+ if (dbOrder == null) {
+ throw new ServiceException("收货单不存在");
+ }
+ ReceiptMaterialDetailDO queryAll = new ReceiptMaterialDetailDO();
+ queryAll.setReceiptOrderNumber(dbOrder.getReceiptOrderNumber());
+ List dbListAll = receiptMaterialDetailDomainService.queryList(queryAll);
+ preserveParentReceiptLineHierarchyFromDb(stockReceiptOrderDO, dbListAll);
+ // 超计划:按「计划父行 + 其下 level=2 子行」累计已收 算待收
+ checkOverPlanReceiptForApp(stockReceiptOrderDO, dbListAll);
// 记录 PDA 传入时第一项是否已有 children
boolean firstItemHadChildrenBefore = !CollectionUtils.isEmpty(list)
&& !CollectionUtils.isEmpty(list.get(0).getChildren());
@@ -1404,6 +1605,30 @@ public class StockReceiptOrderApplicationService {
// 主项子项结构:将 children 第一条(parent_copy)赋值给主项并删除
assignFirstChildToParentAndRemove(stockReceiptOrderDO);
}
+ // assign 后若仍有真实子行,不再 inject(避免与已有子项结构冲突)
+ boolean skipInjectAfterAssign = firstItemHadChildrenBefore
+ && !CollectionUtils.isEmpty(stockReceiptOrderDO.getMaterialDetailList())
+ && !CollectionUtils.isEmpty(stockReceiptOrderDO.getMaterialDetailList().get(0).getChildren());
+ Map dbByIdForInject = dbListAll.stream()
+ .filter(x -> x.getMaterialDetailId() != null)
+ .collect(Collectors.toMap(ReceiptMaterialDetailPO::getMaterialDetailId, Function.identity(), (a, b) -> a));
+ Map dbByUidForInject = dbListAll.stream()
+ .filter(x -> x.getUniqueId() != null)
+ .collect(Collectors.toMap(ReceiptMaterialDetailPO::getUniqueId, Function.identity(), (a, b) -> a));
+ int injectIdx = 0;
+ for (ReceiptMaterialDetailDO line : stockReceiptOrderDO.getMaterialDetailList()) {
+ ReceiptMaterialDetailPO dbLine = null;
+ if (line.getMaterialDetailId() != null) {
+ dbLine = dbByIdForInject.get(line.getMaterialDetailId());
+ }
+ if (dbLine == null && line.getUniqueId() != null) {
+ dbLine = dbByUidForInject.get(line.getUniqueId());
+ }
+ if (dbLine != null && !(skipInjectAfterAssign && injectIdx == 0)) {
+ injectPdaFlatReceiptAsSyntheticChild(line, dbLine, dbListAll);
+ }
+ injectIdx++;
+ }
StockReceiptOrderDO assembleStockReceiptOrderDO = assembleParamByReceipt(stockReceiptOrderDO, false);
if (CollectionUtils.isEmpty(assembleStockReceiptOrderDO.getMaterialDetailList())) {
throw new ServiceException("无有效收货数据,请确认已填写收货数量(收货数量不能全部为0)");
@@ -1412,17 +1637,13 @@ public class StockReceiptOrderApplicationService {
return stockReceiptOrderDomainService.receivingGoods(assembleStockReceiptOrderDO);
}
- private void checkOverPlanReceiptForApp(StockReceiptOrderDO stockReceiptOrderDO) {
+ private void checkOverPlanReceiptForApp(StockReceiptOrderDO stockReceiptOrderDO, List dbList) {
if (stockReceiptOrderDO == null || CollectionUtils.isEmpty(stockReceiptOrderDO.getMaterialDetailList())) {
return;
}
- StockReceiptOrderPO dbOrder = stockReceiptOrderDomainService.getInfo(stockReceiptOrderDO.getReceiptOrderId());
- if (dbOrder == null) {
+ if (CollectionUtils.isEmpty(dbList)) {
return;
}
- ReceiptMaterialDetailDO queryDO = new ReceiptMaterialDetailDO();
- queryDO.setReceiptOrderNumber(dbOrder.getReceiptOrderNumber());
- List dbList = receiptMaterialDetailDomainService.queryList(queryDO);
Map dbById = dbList.stream()
.filter(x -> x.getMaterialDetailId() != null)
.collect(Collectors.toMap(ReceiptMaterialDetailPO::getMaterialDetailId, Function.identity(), (a, b) -> a));
@@ -1430,13 +1651,17 @@ public class StockReceiptOrderApplicationService {
.filter(x -> x.getUniqueId() != null)
.collect(Collectors.toMap(ReceiptMaterialDetailPO::getUniqueId, Function.identity(), (a, b) -> a));
for (ReceiptMaterialDetailDO item : stockReceiptOrderDO.getMaterialDetailList()) {
- validateOverPlanNode(item, dbById, dbByUniqueId);
+ validateOverPlanNode(item, dbById, dbByUniqueId, dbList);
}
}
+ /**
+ * 分批收货:计划数量在 level=1 父行;待收 = 计划父行 quantity −(父行+其下 level=2 子行 already_receipt 汇总)
+ */
private void validateOverPlanNode(ReceiptMaterialDetailDO item,
Map dbById,
- Map dbByUniqueId) {
+ Map dbByUniqueId,
+ List dbList) {
if (item == null) {
return;
}
@@ -1448,9 +1673,10 @@ public class StockReceiptOrderApplicationService {
db = dbByUniqueId.get(item.getUniqueId());
}
if (db != null) {
- BigDecimal plan = db.getQuantity() != null ? db.getQuantity() : BigDecimal.ZERO;
- BigDecimal already = db.getAlreadyReceiptQuantity() != null ? db.getAlreadyReceiptQuantity() : BigDecimal.ZERO;
- BigDecimal pending = plan.subtract(already);
+ ReceiptMaterialDetailPO plan = resolveReceiptPlanParentRow(db, dbList);
+ BigDecimal planQty = plan != null && plan.getQuantity() != null ? plan.getQuantity() : BigDecimal.ZERO;
+ BigDecimal sumAlready = sumAlreadyReceiptUnderPlanParent(plan, dbList);
+ BigDecimal pending = planQty.subtract(sumAlready);
if (pending.compareTo(BigDecimal.ZERO) < 0) {
pending = BigDecimal.ZERO;
}
@@ -1461,11 +1687,321 @@ public class StockReceiptOrderApplicationService {
}
if (!CollectionUtils.isEmpty(item.getChildren())) {
for (ReceiptMaterialDetailDO child : item.getChildren()) {
- validateOverPlanNode(child, dbById, dbByUniqueId);
+ validateOverPlanNode(child, dbById, dbByUniqueId, dbList);
}
}
}
+ /** 雪花/长整型 id 相较,避免部分环境下引用类型不一致 */
+ private static boolean sameLongId(Long a, Long b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return a.longValue() == b.longValue();
+ }
+
+ /**
+ * 是否为本计划父行下的 level=2 实子行(可参与已收汇总)。
+ * 排除 parent_unique_id 空/0;排除与主行同 unique_id 的镜像行(同源只计主行对象一次)。
+ */
+ private static boolean isLevel2ChildRowUnderPlanParent(ReceiptMaterialDetailPO planParent, ReceiptMaterialDetailPO x) {
+ if (planParent == null || x == null) {
+ return false;
+ }
+ if (x.getLevel() == null || x.getLevel() != 2) {
+ return false;
+ }
+ Long puid = x.getParentUniqueId();
+ if (puid == null || Long.valueOf(0).equals(puid)) {
+ return false;
+ }
+ Long uid = planParent.getUniqueId();
+ Long inUid = planParent.getInUniqueId();
+ boolean linked = (uid != null && sameLongId(uid, puid)) || (inUid != null && sameLongId(inUid, puid));
+ if (!linked) {
+ return false;
+ }
+ if (planParent.getUniqueId() != null && sameLongId(planParent.getUniqueId(), x.getUniqueId())) {
+ return false;
+ }
+ return true;
+ }
+
+ /** 计划父行:level=1 自身;level=2 则按 parent_unique_id 找到主计划行 */
+ private static ReceiptMaterialDetailPO resolveReceiptPlanParentRow(ReceiptMaterialDetailPO db,
+ List dbList) {
+ if (db == null || CollectionUtils.isEmpty(dbList)) {
+ return db;
+ }
+ if (db.getLevel() == null || db.getLevel() == 1) {
+ return db;
+ }
+ if (db.getLevel() == 2 && db.getParentUniqueId() != null) {
+ Long puid = db.getParentUniqueId();
+ for (ReceiptMaterialDetailPO x : dbList) {
+ if (x == null) {
+ continue;
+ }
+ if (puid.equals(x.getUniqueId()) || puid.equals(x.getInUniqueId())) {
+ return x;
+ }
+ }
+ }
+ return db;
+ }
+
+ /**
+ * 父行自身 + 其下 level=2(parent 指向父)的已收数量之和。
+ * 主行本行必须取传入的 {@code planParent} 上的累计字段,再在列表中只加子行;勿依赖在 {@code dbList} 中再次按 material_detail_id
+ * 匹配主行,否则 JDBC 类型/映射不一致时会出现「只加子行、主行 100 丢失」。
+ */
+ private static BigDecimal sumAlreadyReceiptUnderPlanParent(ReceiptMaterialDetailPO planParent,
+ List dbList) {
+ if (planParent == null || CollectionUtils.isEmpty(dbList)) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal sum = planParent.getAlreadyReceiptQuantity() != null
+ ? planParent.getAlreadyReceiptQuantity() : BigDecimal.ZERO;
+ for (ReceiptMaterialDetailPO x : dbList) {
+ if (x == null || !isLevel2ChildRowUnderPlanParent(planParent, x)) {
+ continue;
+ }
+ BigDecimal ar = x.getAlreadyReceiptQuantity() != null ? x.getAlreadyReceiptQuantity() : BigDecimal.ZERO;
+ sum = sum.add(ar);
+ }
+ return sum;
+ }
+
+ /** 与 {@link #sumAlreadyReceiptUnderPlanParent} 同范围,汇总任意「已收」指标(毛/净重、体积、面积等) */
+ private static BigDecimal sumAlreadyMetricUnderPlanParent(ReceiptMaterialDetailPO planParent,
+ List dbList,
+ Function alreadyGetter) {
+ if (planParent == null || CollectionUtils.isEmpty(dbList) || alreadyGetter == null) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal base = alreadyGetter.apply(planParent);
+ BigDecimal sum = base != null ? base : BigDecimal.ZERO;
+ for (ReceiptMaterialDetailPO x : dbList) {
+ if (x == null || !isLevel2ChildRowUnderPlanParent(planParent, x)) {
+ continue;
+ }
+ BigDecimal v = alreadyGetter.apply(x);
+ sum = sum.add(v != null ? v : BigDecimal.ZERO);
+ }
+ return sum;
+ }
+
+ /** 首笔不拆子行;已有子行或主行+子行已有累计实收时再拆 */
+ private static boolean shouldPdaSplitIntoChildRow(ReceiptMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (dbLine == null || CollectionUtils.isEmpty(allDbLines)) {
+ return true;
+ }
+ ReceiptMaterialDetailPO plan = resolveReceiptPlanParentRow(dbLine, allDbLines);
+ if (plan == null) {
+ return true;
+ }
+ Long uid = plan.getUniqueId();
+ Long inUid = plan.getInUniqueId();
+ boolean hasL2 = allDbLines.stream().anyMatch(x -> x != null && x.getLevel() != null && x.getLevel() == 2
+ && x.getParentUniqueId() != null
+ && ((uid != null && uid.equals(x.getParentUniqueId()))
+ || (inUid != null && inUid.equals(x.getParentUniqueId()))));
+ if (hasL2) {
+ return true;
+ }
+ BigDecimal sum = sumAlreadyReceiptUnderPlanParent(plan, allDbLines);
+ return sum.compareTo(BigDecimal.ZERO) > 0;
+ }
+
+ private static boolean isCorruptedStandaloneLineAsLevel2(ReceiptMaterialDetailPO dbLine) {
+ if (dbLine == null || dbLine.getLevel() == null || dbLine.getLevel() != 2) {
+ return false;
+ }
+ return dbLine.getParentUniqueId() == null || Objects.equals(dbLine.getParentUniqueId(), 0L);
+ }
+
+ /** 合成子行挂在计划父行的 uniqueId;误标为 level=2 且无父时,按同物料 level=1 行定位 */
+ private static Long resolveParentUniqueIdForPdaInjectChild(ReceiptMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (dbLine == null) {
+ return null;
+ }
+ if (isCorruptedStandaloneLineAsLevel2(dbLine) && dbLine.getMaterialDetailId() != null
+ && !CollectionUtils.isEmpty(allDbLines)) {
+ for (ReceiptMaterialDetailPO x : allDbLines) {
+ if (x != null && Objects.equals(x.getMaterialDetailId(), dbLine.getMaterialDetailId())
+ && x.getLevel() != null && x.getLevel() == 1) {
+ return x.getUniqueId();
+ }
+ }
+ }
+ ReceiptMaterialDetailPO plan = resolveReceiptPlanParentRow(dbLine, allDbLines);
+ return plan != null ? plan.getUniqueId() : dbLine.getUniqueId();
+ }
+
+ /** 请求行与库主计划行 level 不一致时纠正,避免误当子行 */
+ private void preserveParentReceiptLineHierarchyFromDb(StockReceiptOrderDO order, List dbList) {
+ if (order == null || CollectionUtils.isEmpty(order.getMaterialDetailList()) || CollectionUtils.isEmpty(dbList)) {
+ return;
+ }
+ Map byId = dbList.stream()
+ .filter(x -> x.getMaterialDetailId() != null)
+ .collect(Collectors.toMap(ReceiptMaterialDetailPO::getMaterialDetailId, Function.identity(), (a, b) -> a));
+ Map byUid = dbList.stream()
+ .filter(x -> x.getUniqueId() != null)
+ .collect(Collectors.toMap(ReceiptMaterialDetailPO::getUniqueId, Function.identity(), (a, b) -> a));
+ for (ReceiptMaterialDetailDO line : order.getMaterialDetailList()) {
+ ReceiptMaterialDetailPO db = null;
+ if (line.getMaterialDetailId() != null) {
+ db = byId.get(line.getMaterialDetailId());
+ }
+ if (db == null && line.getUniqueId() != null) {
+ db = byUid.get(line.getUniqueId());
+ }
+ if (db != null && db.getLevel() != null && db.getLevel() == 1
+ && line.getLevel() != null && line.getLevel() == 2) {
+ line.setLevel(1);
+ line.setParentUniqueId(null);
+ }
+ }
+ }
+
+ private static void stripPdaUiParentCopyChildren(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO dbLine) {
+ if (line == null || CollectionUtils.isEmpty(line.getChildren())) {
+ return;
+ }
+ Long parentUid = line.getUniqueId() != null ? line.getUniqueId() : (dbLine != null ? dbLine.getUniqueId() : null);
+ List realOnly = new ArrayList<>();
+ for (ReceiptMaterialDetailDO c : line.getChildren()) {
+ if (c == null) {
+ continue;
+ }
+ if (parentUid != null && parentUid.equals(c.getUniqueId())) {
+ continue;
+ }
+ realOnly.add(c);
+ }
+ line.setChildren(realOnly);
+ }
+
+ /** getInfoByApp 挂在 children 里的已落库子行,再次收货时需去掉,否则 inject 误判已有子项 */
+ private static void stripPdaExistingDisplayedChildrenForResplit(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (line == null || CollectionUtils.isEmpty(line.getChildren()) || dbLine == null || CollectionUtils.isEmpty(allDbLines)) {
+ return;
+ }
+ Long puid = dbLine.getUniqueId();
+ Long pInUid = dbLine.getInUniqueId();
+ List keep = new ArrayList<>();
+ for (ReceiptMaterialDetailDO c : line.getChildren()) {
+ if (c == null) {
+ continue;
+ }
+ Long mid = c.getMaterialDetailId();
+ Long cuid = c.getUniqueId();
+ boolean isExistingDbChild = false;
+ for (ReceiptMaterialDetailPO x : allDbLines) {
+ if (x == null || x.getLevel() == null || x.getLevel() != 2 || x.getParentUniqueId() == null) {
+ continue;
+ }
+ boolean parentOk = (puid != null && puid.equals(x.getParentUniqueId()))
+ || (pInUid != null && pInUid.equals(x.getParentUniqueId()));
+ if (!parentOk) {
+ continue;
+ }
+ if (mid != null && mid.equals(x.getMaterialDetailId())) {
+ isExistingDbChild = true;
+ break;
+ }
+ if (cuid != null && cuid.equals(x.getUniqueId())) {
+ isExistingDbChild = true;
+ break;
+ }
+ }
+ if (!isExistingDbChild) {
+ keep.add(c);
+ }
+ }
+ line.setChildren(keep);
+ }
+
+ private static boolean hasPdaInjectMetricIncrement(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO dbLine) {
+ if (line == null || dbLine == null) {
+ return false;
+ }
+ BigDecimal dbNet = dbLine.getAlreadyReceiptNetWeight() != null ? dbLine.getAlreadyReceiptNetWeight() : BigDecimal.ZERO;
+ BigDecimal dbGross = dbLine.getAlreadyReceiptGrossWeight() != null ? dbLine.getAlreadyReceiptGrossWeight() : BigDecimal.ZERO;
+ BigDecimal dbVol = dbLine.getAlreadyReceiptVolume() != null ? dbLine.getAlreadyReceiptVolume() : BigDecimal.ZERO;
+ BigDecimal dbArea = dbLine.getAlreadyReceiptArea() != null ? dbLine.getAlreadyReceiptArea() : BigDecimal.ZERO;
+ return pickThisReceiptMetricIncrement(line.getReceiptNetWeight(), line.getTotalNetWeight(), dbNet).compareTo(BigDecimal.ZERO) > 0
+ || pickThisReceiptMetricIncrement(line.getReceiptGrossWeight(), line.getTotalGrossWeight(), dbGross).compareTo(BigDecimal.ZERO) > 0
+ || pickThisReceiptMetricIncrement(line.getReceiptVolume(), line.getTotalVolume(), dbVol).compareTo(BigDecimal.ZERO) > 0
+ || pickThisReceiptMetricIncrement(line.getReceiptArea(), line.getTotalArea(), dbArea).compareTo(BigDecimal.ZERO) > 0;
+ }
+
+ /**
+ * PDA 单次仅传主行(无真实子结构需拆时):按业务可拆成一条 level=2 子行。
+ * 首笔仅落主行不拆;第二笔起或已有子行再拆。脏主行(误标 level=2)纠正不受「首笔不拆」限制。
+ */
+ private void injectPdaFlatReceiptAsSyntheticChild(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (line == null || dbLine == null) {
+ return;
+ }
+ stripPdaUiParentCopyChildren(line, dbLine);
+ stripPdaExistingDisplayedChildrenForResplit(line, dbLine, allDbLines);
+ if (dbLine.getLevel() != null && dbLine.getLevel() == 2 && !isCorruptedStandaloneLineAsLevel2(dbLine)) {
+ return;
+ }
+ if (!CollectionUtils.isEmpty(line.getChildren())) {
+ return;
+ }
+ if (!shouldPdaSplitIntoChildRow(dbLine, allDbLines) && !isCorruptedStandaloneLineAsLevel2(dbLine)) {
+ return;
+ }
+ BigDecimal rq = line.getReceiptQuantity();
+ boolean hasQty = rq != null && rq.compareTo(BigDecimal.ZERO) > 0;
+ boolean hasMetricBatch = hasPdaInjectMetricIncrement(line, dbLine);
+ if (!hasQty && !hasMetricBatch) {
+ return;
+ }
+ ReceiptMaterialDetailDO child = new ReceiptMaterialDetailDO();
+ BeanUtils.copyProperties(line, child, "children", "materialDetailId", "uniqueId", "level", "parentUniqueId");
+ child.setUniqueId(idGenerator.snowflakeId(SnowFlakeConstants.wmsId));
+ child.setChildren(null);
+ child.setLevel(2);
+ child.setParentUniqueId(resolveParentUniqueIdForPdaInjectChild(dbLine, allDbLines));
+ if (hasQty) {
+ child.setReceiptQuantity(rq);
+ }
+ child.setReceiptGrossWeight(line.getReceiptGrossWeight());
+ child.setReceiptNetWeight(line.getReceiptNetWeight());
+ child.setReceiptVolume(line.getReceiptVolume());
+ child.setReceiptArea(line.getReceiptArea());
+ child.setTotalGrossWeight(line.getTotalGrossWeight());
+ child.setTotalNetWeight(line.getTotalNetWeight());
+ child.setTotalVolume(line.getTotalVolume());
+ child.setTotalArea(line.getTotalArea());
+ child.setAlreadyReceiptQuantity(null);
+ child.setAlreadyReceiptGrossWeight(null);
+ child.setAlreadyReceiptNetWeight(null);
+ child.setAlreadyReceiptVolume(null);
+ child.setAlreadyReceiptArea(null);
+ line.setChildren(Collections.singletonList(child));
+ line.setReceiptQuantity(BigDecimal.ZERO);
+ line.setReceiptGrossWeight(null);
+ line.setReceiptNetWeight(null);
+ line.setReceiptVolume(null);
+ line.setReceiptArea(null);
+ line.setTotalGrossWeight(null);
+ line.setTotalNetWeight(null);
+ line.setTotalVolume(null);
+ line.setTotalArea(null);
+ line.setLevel(1);
+ line.setParentUniqueId(null);
+ }
+
/**
* 收货时:将 children 中第一条的值赋给主项,并删除第一条。
* PDA 收两条时只生成 2 条(主项 + 第二条子项),主项展示第一条子项的数据。
@@ -1706,6 +2242,65 @@ public class StockReceiptOrderApplicationService {
return totalField;
}
+ private static boolean isApproxEqualReceiptMetric(BigDecimal a, BigDecimal b) {
+ if (a == null && b == null) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ return a.subtract(b).abs().compareTo(new BigDecimal("0.000001")) <= 0;
+ }
+
+ /**
+ * PDA:详情里 total* 为「待收」= 计划 − 组内主项+子项全部已收;提交时若仍等于当前组剩余指标,
+ * 则按本行本次收货件数占「组内待收件数」比例分摊本次毛/净重/体积/面积增量。
+ * 否则按 {@link #pickThisReceiptMetricIncrement}(累计实绩)处理。
+ */
+ private static BigDecimal pickThisReceiptMetricIncrementPda(
+ BigDecimal receiptField,
+ BigDecimal totalField,
+ BigDecimal dbSingleRowAlready,
+ ReceiptMaterialDetailPO dbLine,
+ List dbList,
+ Function planMetricGetter,
+ Function alreadyMetricGetter,
+ BigDecimal thisReceiptQty) {
+ if (receiptField != null) {
+ return receiptField;
+ }
+ if (totalField == null) {
+ return BigDecimal.ZERO;
+ }
+ if (dbLine == null || CollectionUtils.isEmpty(dbList) || thisReceiptQty == null
+ || thisReceiptQty.compareTo(BigDecimal.ZERO) <= 0) {
+ return pickThisReceiptMetricIncrement(receiptField, totalField, dbSingleRowAlready);
+ }
+ ReceiptMaterialDetailPO plan = resolveReceiptPlanParentRow(dbLine, dbList);
+ if (plan == null) {
+ return pickThisReceiptMetricIncrement(receiptField, totalField, dbSingleRowAlready);
+ }
+ BigDecimal planMetric = planMetricGetter.apply(plan);
+ if (planMetric == null) {
+ return pickThisReceiptMetricIncrement(receiptField, totalField, dbSingleRowAlready);
+ }
+ BigDecimal sumAlready = sumAlreadyMetricUnderPlanParent(plan, dbList, alreadyMetricGetter);
+ BigDecimal remainingMetric = planMetric.subtract(sumAlready);
+ if (remainingMetric.compareTo(BigDecimal.ZERO) < 0) {
+ remainingMetric = BigDecimal.ZERO;
+ }
+ BigDecimal planQty = plan.getQuantity() != null ? plan.getQuantity() : BigDecimal.ZERO;
+ BigDecimal sumAlreadyQty = sumAlreadyReceiptUnderPlanParent(plan, dbList);
+ BigDecimal pendingQty = planQty.subtract(sumAlreadyQty);
+ if (pendingQty.compareTo(BigDecimal.ZERO) <= 0) {
+ return pickThisReceiptMetricIncrement(receiptField, totalField, dbSingleRowAlready);
+ }
+ if (isApproxEqualReceiptMetric(totalField, remainingMetric)) {
+ return remainingMetric.multiply(thisReceiptQty).divide(pendingQty, 8, RoundingMode.HALF_UP);
+ }
+ return pickThisReceiptMetricIncrement(receiptField, totalField, dbSingleRowAlready);
+ }
+
/**
* PC 收货确认:页面提交为「当前全量实绩」,直接取 receipt* / total* 落库,不与历史累计做差或相加。
*/
@@ -1979,14 +2574,25 @@ public class StockReceiptOrderApplicationService {
? receiptMaterialDetailPO.getAlreadyReceiptNetWeight() : BigDecimal.ZERO;
BigDecimal dbParentGrossForGate = receiptMaterialDetailPO.getAlreadyReceiptGrossWeight() != null
? receiptMaterialDetailPO.getAlreadyReceiptGrossWeight() : BigDecimal.ZERO;
- boolean hasReceiptByMetrics = pickThisReceiptMetricIncrement(
+ BigDecimal gateParentQty = receiptQuantity;
+ boolean hasReceiptByMetrics = pickThisReceiptMetricIncrementPda(
receiptMaterialDetailDO.getReceiptNetWeight(),
receiptMaterialDetailDO.getTotalNetWeight(),
- dbParentNetForGate).compareTo(BigDecimal.ZERO) > 0
- || pickThisReceiptMetricIncrement(
+ dbParentNetForGate,
+ receiptMaterialDetailPO,
+ receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanNetWeight,
+ ReceiptMaterialDetailPO::getAlreadyReceiptNetWeight,
+ gateParentQty).compareTo(BigDecimal.ZERO) > 0
+ || pickThisReceiptMetricIncrementPda(
receiptMaterialDetailDO.getReceiptGrossWeight(),
receiptMaterialDetailDO.getTotalGrossWeight(),
- dbParentGrossForGate).compareTo(BigDecimal.ZERO) > 0;
+ dbParentGrossForGate,
+ receiptMaterialDetailPO,
+ receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanGrossWeight,
+ ReceiptMaterialDetailPO::getAlreadyReceiptGrossWeight,
+ gateParentQty).compareTo(BigDecimal.ZERO) > 0;
if (!hasReceiptByMetrics && !CollectionUtils.isEmpty(children)) {
for (ReceiptMaterialDetailDO c : children) {
if (c == null) {
@@ -2000,8 +2606,19 @@ public class StockReceiptOrderApplicationService {
? cdb.getAlreadyReceiptNetWeight() : BigDecimal.ZERO;
BigDecimal cDbGross = cdb != null && cdb.getAlreadyReceiptGrossWeight() != null
? cdb.getAlreadyReceiptGrossWeight() : BigDecimal.ZERO;
- if (pickThisReceiptMetricIncrement(c.getReceiptNetWeight(), c.getTotalNetWeight(), cDbNet).compareTo(BigDecimal.ZERO) > 0
- || pickThisReceiptMetricIncrement(c.getReceiptGrossWeight(), c.getTotalGrossWeight(), cDbGross).compareTo(BigDecimal.ZERO) > 0) {
+ BigDecimal gateChildQty = c.getReceiptQuantity() != null ? c.getReceiptQuantity() : BigDecimal.ZERO;
+ if (pickThisReceiptMetricIncrementPda(
+ c.getReceiptNetWeight(), c.getTotalNetWeight(), cDbNet, cdb,
+ receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanNetWeight,
+ ReceiptMaterialDetailPO::getAlreadyReceiptNetWeight,
+ gateChildQty).compareTo(BigDecimal.ZERO) > 0
+ || pickThisReceiptMetricIncrementPda(
+ c.getReceiptGrossWeight(), c.getTotalGrossWeight(), cDbGross, cdb,
+ receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanGrossWeight,
+ ReceiptMaterialDetailPO::getAlreadyReceiptGrossWeight,
+ gateChildQty).compareTo(BigDecimal.ZERO) > 0) {
hasReceiptByMetrics = true;
break;
}
@@ -2044,10 +2661,22 @@ public class StockReceiptOrderApplicationService {
BigDecimal childDbAlreadyArea = receiptMaterialDetailPOChildDb != null && receiptMaterialDetailPOChildDb.getAlreadyReceiptArea() != null
? receiptMaterialDetailPOChildDb.getAlreadyReceiptArea() : BigDecimal.ZERO;
BigDecimal childQty = receiptMaterialDetailDOChild.getReceiptQuantity() != null ? receiptMaterialDetailDOChild.getReceiptQuantity() : BigDecimal.ZERO;
- BigDecimal childGross = pickThisReceiptMetricIncrement(receiptMaterialDetailDOChild.getReceiptGrossWeight(), receiptMaterialDetailDOChild.getTotalGrossWeight(), childDbAlreadyGross);
- BigDecimal childNet = pickThisReceiptMetricIncrement(receiptMaterialDetailDOChild.getReceiptNetWeight(), receiptMaterialDetailDOChild.getTotalNetWeight(), childDbAlreadyNet);
- BigDecimal childVol = pickThisReceiptMetricIncrement(receiptMaterialDetailDOChild.getReceiptVolume(), receiptMaterialDetailDOChild.getTotalVolume(), childDbAlreadyVol);
- BigDecimal childArea = pickThisReceiptMetricIncrement(receiptMaterialDetailDOChild.getReceiptArea(), receiptMaterialDetailDOChild.getTotalArea(), childDbAlreadyArea);
+ BigDecimal childGross = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDOChild.getReceiptGrossWeight(), receiptMaterialDetailDOChild.getTotalGrossWeight(),
+ childDbAlreadyGross, receiptMaterialDetailPOChildDb, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanGrossWeight, ReceiptMaterialDetailPO::getAlreadyReceiptGrossWeight, childQty);
+ BigDecimal childNet = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDOChild.getReceiptNetWeight(), receiptMaterialDetailDOChild.getTotalNetWeight(),
+ childDbAlreadyNet, receiptMaterialDetailPOChildDb, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanNetWeight, ReceiptMaterialDetailPO::getAlreadyReceiptNetWeight, childQty);
+ BigDecimal childVol = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDOChild.getReceiptVolume(), receiptMaterialDetailDOChild.getTotalVolume(),
+ childDbAlreadyVol, receiptMaterialDetailPOChildDb, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanVolume, ReceiptMaterialDetailPO::getAlreadyReceiptVolume, childQty);
+ BigDecimal childArea = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDOChild.getReceiptArea(), receiptMaterialDetailDOChild.getTotalArea(),
+ childDbAlreadyArea, receiptMaterialDetailPOChildDb, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanArea, ReceiptMaterialDetailPO::getAlreadyReceiptArea, childQty);
// PC 端子行常只回填 totalNetWeight/totalGrossWeight,不回传 receiptQuantity;用本次称重增量兜底为件数(与千克等重量计量物料一致)
if (childQty.compareTo(BigDecimal.ZERO) == 0 && childNet.compareTo(BigDecimal.ZERO) > 0) {
childQty = childNet;
@@ -2126,10 +2755,22 @@ public class StockReceiptOrderApplicationService {
BigDecimal dbParentBeforeNet = receiptMaterialDetailPO.getAlreadyReceiptNetWeight() != null ? receiptMaterialDetailPO.getAlreadyReceiptNetWeight() : BigDecimal.ZERO;
BigDecimal dbParentBeforeVol = receiptMaterialDetailPO.getAlreadyReceiptVolume() != null ? receiptMaterialDetailPO.getAlreadyReceiptVolume() : BigDecimal.ZERO;
BigDecimal dbParentBeforeArea = receiptMaterialDetailPO.getAlreadyReceiptArea() != null ? receiptMaterialDetailPO.getAlreadyReceiptArea() : BigDecimal.ZERO;
- BigDecimal parentReceiptGross = pickThisReceiptMetricIncrement(receiptMaterialDetailDO.getReceiptGrossWeight(), receiptMaterialDetailDO.getTotalGrossWeight(), dbParentBeforeGross);
- BigDecimal parentReceiptNet = pickThisReceiptMetricIncrement(receiptMaterialDetailDO.getReceiptNetWeight(), receiptMaterialDetailDO.getTotalNetWeight(), dbParentBeforeNet);
- BigDecimal parentReceiptVol = pickThisReceiptMetricIncrement(receiptMaterialDetailDO.getReceiptVolume(), receiptMaterialDetailDO.getTotalVolume(), dbParentBeforeVol);
- BigDecimal parentReceiptArea = pickThisReceiptMetricIncrement(receiptMaterialDetailDO.getReceiptArea(), receiptMaterialDetailDO.getTotalArea(), dbParentBeforeArea);
+ BigDecimal parentReceiptGross = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDO.getReceiptGrossWeight(), receiptMaterialDetailDO.getTotalGrossWeight(),
+ dbParentBeforeGross, receiptMaterialDetailPO, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanGrossWeight, ReceiptMaterialDetailPO::getAlreadyReceiptGrossWeight, mainItemActualQuantity);
+ BigDecimal parentReceiptNet = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDO.getReceiptNetWeight(), receiptMaterialDetailDO.getTotalNetWeight(),
+ dbParentBeforeNet, receiptMaterialDetailPO, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanNetWeight, ReceiptMaterialDetailPO::getAlreadyReceiptNetWeight, mainItemActualQuantity);
+ BigDecimal parentReceiptVol = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDO.getReceiptVolume(), receiptMaterialDetailDO.getTotalVolume(),
+ dbParentBeforeVol, receiptMaterialDetailPO, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanVolume, ReceiptMaterialDetailPO::getAlreadyReceiptVolume, mainItemActualQuantity);
+ BigDecimal parentReceiptArea = pickThisReceiptMetricIncrementPda(
+ receiptMaterialDetailDO.getReceiptArea(), receiptMaterialDetailDO.getTotalArea(),
+ dbParentBeforeArea, receiptMaterialDetailPO, receiptMaterialDetailPODbList,
+ ReceiptMaterialDetailPO::getPlanArea, ReceiptMaterialDetailPO::getAlreadyReceiptArea, mainItemActualQuantity);
// 主行 already_* 只累计主行本行毛净重体面积;子行各自落子行记录,避免主行重复叠加子行导致与数量口径不一致
BigDecimal mainGrossIncrement = parentReceiptGross;
BigDecimal mainNetIncrement = parentReceiptNet;
@@ -2187,6 +2828,11 @@ public class StockReceiptOrderApplicationService {
ReceiptMaterialDetailDO returnReceiptMaterialDetailDO = new ReceiptMaterialDetailDO();
BeanUtils.copyProperties(receiptMaterialDetailPO, returnReceiptMaterialDetailDO);
BeanUtils.copyProperties(receiptMaterialDetailDO, returnReceiptMaterialDetailDO, IgnoreNullUtil.getNullPropertyNames(receiptMaterialDetailDO));
+ // 主行本次收货件数为 0、仅子行有收货时:不从请求覆盖主行批次号(避免 PDA 把子行新批次回写到主行,冲掉首笔批次)
+ if (mainItemActualQuantity.compareTo(BigDecimal.ZERO) == 0
+ && !CollectionUtils.isEmpty(returnReceiptMaterialDetailDOChildList)) {
+ returnReceiptMaterialDetailDO.setBatchNumber(receiptMaterialDetailPO.getBatchNumber());
+ }
returnReceiptMaterialDetailDO.setTotalNetWeight(receiptMaterialDetailDO.getTotalNetWeight());
returnReceiptMaterialDetailDO.setTotalGrossWeight(receiptMaterialDetailDO.getTotalGrossWeight());
returnReceiptMaterialDetailDO.setTotalVolume(receiptMaterialDetailDO.getTotalVolume());
diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockShelfOrder/StockShelfOrderApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockShelfOrder/StockShelfOrderApplicationService.java
index b4db95550..21906dc2e 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockShelfOrder/StockShelfOrderApplicationService.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockShelfOrder/StockShelfOrderApplicationService.java
@@ -57,9 +57,11 @@ import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -204,31 +206,17 @@ public class StockShelfOrderApplicationService {
/**
* 获取上架单详细信息
+ *
* @param shelfOrderId 上架单ID
- * @param addParentCopyWhenNoChildren 主项无子项时是否添加 parent_copy:true-PDA 详情页从 children 读取数据需要;false-PC 端收货无子项时上架不显示虚拟子项
+ * @param addParentCopyWhenNoChildren 历史参数名保留:为 true 时表示走 {@link #getInfo(Long, boolean, Integer)} 中的 PDA 详情流水线(扁平化、待上架、重量展示等);不再向 children 注入
+ * parent_copy,避免与主行重复两条;无子项时由前端从主项字段读取,与 {@link com.mhd.wms.domain.stockShelfOrder.service.StockShelfOrderDomainService#getInfoByApp(Long)} 一致。为 false 时走 PC 端汇总与扁平化。
*/
public StockShelfOrderPO getInfo(Long shelfOrderId, boolean addParentCopyWhenNoChildren) {
StockShelfOrderPO stockShelfOrderPO = stockShelfOrderDomainService.getInfo(shelfOrderId);
ShelfMaterialDetailDO shelfMaterialDetailDO = new ShelfMaterialDetailDO();
shelfMaterialDetailDO.setShelfOrderNumber(stockShelfOrderPO.getShelfOrderNumber());
List shelfMaterialDetailPOList = shelfMaterialDetailDomainService.queryListChildren(shelfMaterialDetailDO);
- // PDA:主项无子项时添加 parent_copy 到 children,供详情页展示(详情页从 children 读取数据);PC:不添加,避免收货无子项时上架显示虚拟子项
- if (addParentCopyWhenNoChildren) {
- for (ShelfMaterialDetailPO parent : shelfMaterialDetailPOList) {
- if (CollectionUtils.isEmpty(parent.getChildren())) {
- ShelfMaterialDetailPO parentCopy = new ShelfMaterialDetailPO();
- BeanUtils.copyProperties(parent, parentCopy, "children");
- parentCopy.setLevel(2);
- parentCopy.setParentUniqueId(parent.getUniqueId());
- parentCopy.setQuantity(null);
- parentCopy.setChildren(new ArrayList<>());
- List children = new ArrayList<>();
- children.add(parentCopy);
- parent.setChildren(children);
- }
- }
- }
-
+
// 性能优化:批量获取批次属性,避免N+1查询问题
// 1. 收集所有唯一的materialBaseInfoId
Set materialBaseInfoIdSet = shelfMaterialDetailPOList.stream()
@@ -322,11 +310,42 @@ public class StockShelfOrderApplicationService {
// 第一次上架:主项无子项时,上架数量默认等于收货数量(quantity 由上架单生成时取自收货数量),供 PDA 表单默认展示
fillFirstShelvesQuantityDefault(shelfMaterialDetailPOList);
-
+
+ // PC 端(addParentCopyWhenNoChildren==false):与收货单 getInfo 相同流水线——先按行递归汇总部净重/毛重/体面积,再扁平化主行、清空子行计划数量、待上架数量,最后按「计划−已上架」覆盖 total* 展示(对齐收货详情计算口径)
+ if (!addParentCopyWhenNoChildren) {
+ BigDecimal totalNetWeight = BigDecimal.ZERO;
+ BigDecimal totalGrossWeight = BigDecimal.ZERO;
+ BigDecimal totalVolume = BigDecimal.ZERO;
+ BigDecimal totalArea = BigDecimal.ZERO;
+ if (!CollectionUtils.isEmpty(shelfMaterialDetailPOList)) {
+ for (ShelfMaterialDetailPO detail : shelfMaterialDetailPOList) {
+ totalNetWeight = totalNetWeight.add(sumShelfDetailRecursively(detail, ShelfMaterialDetailPO::getTotalNetWeight));
+ totalGrossWeight = totalGrossWeight.add(sumShelfDetailRecursively(detail, ShelfMaterialDetailPO::getTotalGrossWeight));
+ totalVolume = totalVolume.add(sumShelfDetailRecursively(detail, ShelfMaterialDetailPO::getTotalVolume));
+ totalArea = totalArea.add(sumShelfDetailRecursively(detail, ShelfMaterialDetailPO::getTotalArea));
+ }
+ }
+ stockShelfOrderPO.setTotalNetWeight(totalNetWeight);
+ stockShelfOrderPO.setTotalGrossWeight(totalGrossWeight);
+ stockShelfOrderPO.setTotalVolume(totalVolume);
+ stockShelfOrderPO.setTotalArea(totalArea);
+ boolean shelfOrderCompleted = stockShelfOrderPO.getStatus() != null && stockShelfOrderPO.getStatus() == 3;
+ flattenShelfMaterialDetailList(shelfMaterialDetailPOList, shelfOrderCompleted);
+ clearChildQuantityShelfRecursively(shelfMaterialDetailPOList);
+ fillPendingShelvesQuantityRecursively(shelfMaterialDetailPOList);
+ Map planWeightMetrics = loadReceiptPlanWeightMetrics(stockShelfOrderPO.getReceiptOrderNumber());
+ overwriteTotalWithPendingForPdaWeightDisplayRecursively(shelfMaterialDetailPOList, planWeightMetrics);
+ syncMaterialMoreDetailWeightValuesRecursively(shelfMaterialDetailPOList);
+ }
+
stockShelfOrderPO.setMaterialDetailList(shelfMaterialDetailPOList);
- log.info("返回上架单信息,shelfOrderId={}, materialDetailList大小={}",
- stockShelfOrderPO.getShelfOrderId(),
- shelfMaterialDetailPOList != null ? shelfMaterialDetailPOList.size() : 0);
+ log.info("返回上架单信息,shelfOrderId={}, materialDetailList大小={}, totalNetWeight={}, totalGrossWeight={}, totalVolume={}, totalArea={}",
+ stockShelfOrderPO.getShelfOrderId(),
+ shelfMaterialDetailPOList != null ? shelfMaterialDetailPOList.size() : 0,
+ stockShelfOrderPO.getTotalNetWeight(),
+ stockShelfOrderPO.getTotalGrossWeight(),
+ stockShelfOrderPO.getTotalVolume(),
+ stockShelfOrderPO.getTotalArea());
return stockShelfOrderPO;
}
@@ -351,9 +370,12 @@ public class StockShelfOrderApplicationService {
if (shelfStatus != null) {
materialDetailList = filterShelfMaterialDetailsByShelfStatus(materialDetailList, shelfStatus);
}
- // 已上架完成(3/4/5):主项无子项时注入的 parent_copy 与主项为同一明细,子行可能仍带「本次提交」的 shelves_quantity=0,
- // 导致「上架数量」误显示为 0;与主项对齐累计已上架数量后再做展示计算
+ // 兼容历史:若 children 中仍有镜像子行(与主项同 uniqueId),已收尾时与主项对齐累计已上架,再扁平化/剔除镜像
alignParentCopyShelfQuantityWithParentForTerminal(materialDetailList);
+ boolean shelfOrderCompleted = stockShelfOrderPO.getStatus() != null && stockShelfOrderPO.getStatus() == 3;
+ flattenShelfMaterialDetailList(materialDetailList, shelfOrderCompleted);
+ // 与域层 getInfoByApp 一致:不再注入 parent_copy;若旧客户端缓存等仍带镜像子行,从树中剔除以免详情列表出现两条
+ removeShelfParentCopyMirrorChildrenRecursively(materialDetailList);
// PDA 待上架数量 = 计划数量 - 已上架数量,仅接口计算(不落库)
fillPendingShelvesQuantityRecursively(materialDetailList);
// PDA 展示:shelvesQuantity 显示为累计已上架数量(与 receipt 口径一致);已上架完成行不再用本次提交量
@@ -364,10 +386,19 @@ public class StockShelfOrderApplicationService {
Map planWeightMetrics = loadReceiptPlanWeightMetrics(stockShelfOrderPO.getReceiptOrderNumber());
overwriteTotalWithPendingForPdaWeightDisplayRecursively(materialDetailList, planWeightMetrics);
syncMaterialMoreDetailWeightValuesRecursively(materialDetailList);
+ // 与收货 PDA getInfoByApp 一致:保留 children;仅清空子行「计划数量」quantity(见 clearChildQuantityRecursively),不在此剔除分库位 level=2 子行
+ clearChildQuantityShelfRecursively(materialDetailList);
stockShelfOrderPO.setMaterialDetailList(materialDetailList);
return stockShelfOrderPO;
}
+ /**
+ * PDA:按父计划行查询分批上架子行(level=2),与收货 {@link com.mhd.wms.application.service.stockReceiptOrder.StockReceiptOrderApplicationService#listLevel2ChildrenByParentForApp} 对称。
+ */
+ public List listLevel2ChildrenByParentForApp(Long shelfOrderId, Long parentMaterialDetailId) {
+ return stockShelfOrderDomainService.listLevel2ChildrenByParentForApp(shelfOrderId, parentMaterialDetailId);
+ }
+
private List filterShelfMaterialDetailsByShelfStatus(List source, Integer shelfStatus) {
if (CollectionUtils.isEmpty(source) || shelfStatus == null) {
return source;
@@ -399,6 +430,46 @@ public class StockShelfOrderApplicationService {
return true;
}
+ /**
+ * 递归汇总明细及其子项的某字段值(与收货 {@link com.mhd.wms.application.service.stockReceiptOrder.StockReceiptOrderApplicationService} 中
+ * sumDetailRecursively 一致,用于 PC 单头 total*)
+ */
+ private static BigDecimal sumShelfDetailRecursively(ShelfMaterialDetailPO detail, Function getter) {
+ BigDecimal sum = BigDecimal.ZERO;
+ if (detail == null || getter == null) {
+ return sum;
+ }
+ BigDecimal v = getter.apply(detail);
+ if (v != null) {
+ sum = sum.add(v);
+ }
+ List children = detail.getChildren();
+ if (!CollectionUtils.isEmpty(children)) {
+ for (ShelfMaterialDetailPO child : children) {
+ sum = sum.add(sumShelfDetailRecursively(child, getter));
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * PC 端子项计划数量不展示:递归清空所有子项的 quantity(与收货 clearChildQuantityRecursively 一致)
+ */
+ private void clearChildQuantityShelfRecursively(List shelfMaterialDetailPOList) {
+ if (CollectionUtils.isEmpty(shelfMaterialDetailPOList)) {
+ return;
+ }
+ for (ShelfMaterialDetailPO detail : shelfMaterialDetailPOList) {
+ List children = detail.getChildren();
+ if (!CollectionUtils.isEmpty(children)) {
+ for (ShelfMaterialDetailPO child : children) {
+ child.setQuantity(null);
+ clearChildQuantityShelfRecursively(child.getChildren());
+ }
+ }
+ }
+ }
+
/**
* PDA 待上架数量递归计算:待上架数量 = max(0, 计划数量 - 已上架数量)
*/
@@ -450,6 +521,396 @@ public class StockShelfOrderApplicationService {
}
}
+ /**
+ * 扁平化明细:主行保留父子结构;展示「已上架」= 主行累计 + 各子行累计(不含 parent_copy 镜像行,避免与主行重复)。
+ * 上架状态按「汇总已上架 qtySum」与主行计划量比较推导,与域服务分批/完成上架的 1/2/3/4/5 含义一致。
+ *
+ * @param shelfOrderCompleted 上架单是否已整单完成(status=3);为 true 时少上为 4,与域服务完成口径一致
+ */
+ private void flattenShelfMaterialDetailList(List shelfMaterialDetailPOList,
+ boolean shelfOrderCompleted) {
+ if (CollectionUtils.isEmpty(shelfMaterialDetailPOList)) {
+ return;
+ }
+ List allNodes = new ArrayList<>();
+ collectShelfMaterialDetailNodesRecursively(shelfMaterialDetailPOList, allNodes);
+ Map> childrenByParentUid = new HashMap<>();
+ for (ShelfMaterialDetailPO node : allNodes) {
+ if (node == null || node.getLevel() == null || node.getLevel() != 2) {
+ continue;
+ }
+ Long puid = node.getParentUniqueId();
+ if (puid == null || Long.valueOf(0).equals(puid)) {
+ continue;
+ }
+ childrenByParentUid.computeIfAbsent(puid, k -> new ArrayList<>()).add(node);
+ }
+ for (ShelfMaterialDetailPO parent : shelfMaterialDetailPOList) {
+ if (parent == null) {
+ continue;
+ }
+ if (parent.getLevel() != null && parent.getLevel() != 1) {
+ 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);
+ }
+
+ LinkedHashMap mergedChildren = new LinkedHashMap<>();
+ mergeShelfChildRowsIntoMap(mergedChildren, parent.getChildren());
+ Long uid = parent.getUniqueId();
+ Long inUid = parent.getInUniqueId();
+ if (uid != null) {
+ mergeShelfChildRowsIntoMap(mergedChildren, childrenByParentUid.get(uid));
+ }
+ if (inUid != null && !inUid.equals(uid)) {
+ mergeShelfChildRowsIntoMap(mergedChildren, childrenByParentUid.get(inUid));
+ }
+ if (mergedChildren.isEmpty()) {
+ continue;
+ }
+ for (ShelfMaterialDetailPO child : mergedChildren.values()) {
+ if (child == null) {
+ continue;
+ }
+ if (parent.getUniqueId() != null && parent.getUniqueId().equals(child.getUniqueId())) {
+ child.setQuantity(null);
+ continue;
+ }
+ child.setQuantity(null);
+ if (derived != null && child.getLevel() != null && child.getLevel() == 2) {
+ child.setShelvesStatus(derived);
+ }
+ }
+ }
+ }
+
+ /**
+ * 主行展示用上架状态:按「主行+子行汇总已上架」与主行计划量比较。
+ */
+ private static Integer deriveShelfStatusFromAggregatedVsPlan(BigDecimal aggregatedShelves, BigDecimal planQty,
+ boolean shelfOrderCompleted) {
+ BigDecimal r = aggregatedShelves != null ? aggregatedShelves : BigDecimal.ZERO;
+ BigDecimal p = planQty != null ? planQty : BigDecimal.ZERO;
+ if (shelfOrderCompleted) {
+ int cmp = r.compareTo(p);
+ if (cmp > 0) {
+ return 5;
+ }
+ if (cmp < 0) {
+ return 4;
+ }
+ return 3;
+ }
+ if (r.compareTo(BigDecimal.ZERO) == 0) {
+ return 1;
+ }
+ int cmp = r.compareTo(p);
+ if (cmp < 0) {
+ return 2;
+ }
+ if (cmp > 0) {
+ return 5;
+ }
+ return 3;
+ }
+
+ private static void collectShelfMaterialDetailNodesRecursively(List list, List out) {
+ if (CollectionUtils.isEmpty(list)) {
+ return;
+ }
+ for (ShelfMaterialDetailPO n : list) {
+ if (n == null) {
+ continue;
+ }
+ out.add(n);
+ collectShelfMaterialDetailNodesRecursively(n.getChildren(), out);
+ }
+ }
+
+ private static void mergeShelfChildRowsIntoMap(Map byKey, List children) {
+ if (CollectionUtils.isEmpty(children)) {
+ return;
+ }
+ for (ShelfMaterialDetailPO c : children) {
+ if (c == null) {
+ continue;
+ }
+ Long key = c.getUniqueId();
+ if (key == null) {
+ key = c.getMaterialDetailId();
+ }
+ if (key == null) {
+ continue;
+ }
+ byKey.putIfAbsent(key, c);
+ }
+ }
+
+ private static boolean sameLongId(Long a, Long b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return a.longValue() == b.longValue();
+ }
+
+ private static boolean isLevel2ChildShelfRowUnderPlanParent(ShelfMaterialDetailPO planParent, ShelfMaterialDetailPO x) {
+ if (planParent == null || x == null) {
+ return false;
+ }
+ if (x.getLevel() == null || x.getLevel() != 2) {
+ return false;
+ }
+ Long puid = x.getParentUniqueId();
+ if (puid == null || Long.valueOf(0).equals(puid)) {
+ return false;
+ }
+ Long uid = planParent.getUniqueId();
+ Long inUid = planParent.getInUniqueId();
+ boolean linked = (uid != null && sameLongId(uid, puid)) || (inUid != null && sameLongId(inUid, puid));
+ if (!linked) {
+ return false;
+ }
+ if (planParent.getUniqueId() != null && sameLongId(planParent.getUniqueId(), x.getUniqueId())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static BigDecimal sumAlreadyShelvesUnderPlanParent(ShelfMaterialDetailPO planParent,
+ List dbList) {
+ if (planParent == null || CollectionUtils.isEmpty(dbList)) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal sum = planParent.getAlreadyShelvesQuantity() != null
+ ? planParent.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
+ for (ShelfMaterialDetailPO x : dbList) {
+ if (x == null || !isLevel2ChildShelfRowUnderPlanParent(planParent, x)) {
+ continue;
+ }
+ BigDecimal ar = x.getAlreadyShelvesQuantity() != null ? x.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
+ sum = sum.add(ar);
+ }
+ return sum;
+ }
+
+ private static BigDecimal sumAlreadyShelfMetricUnderPlanParent(ShelfMaterialDetailPO planParent,
+ List dbList,
+ Function alreadyGetter) {
+ if (planParent == null || CollectionUtils.isEmpty(dbList) || alreadyGetter == null) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal base = alreadyGetter.apply(planParent);
+ BigDecimal sum = base != null ? base : BigDecimal.ZERO;
+ for (ShelfMaterialDetailPO x : dbList) {
+ if (x == null || !isLevel2ChildShelfRowUnderPlanParent(planParent, x)) {
+ continue;
+ }
+ BigDecimal v = alreadyGetter.apply(x);
+ sum = sum.add(v != null ? v : BigDecimal.ZERO);
+ }
+ return sum;
+ }
+
+ /** 计划父行:level=1 自身;level=2 则按 parent_unique_id 定位主计划行(对齐收货 {@code resolveReceiptPlanParentRow}) */
+ private static ShelfMaterialDetailPO resolveShelfPlanParentRow(ShelfMaterialDetailPO db,
+ List dbList) {
+ if (db == null || CollectionUtils.isEmpty(dbList)) {
+ return db;
+ }
+ if (db.getLevel() == null || db.getLevel() == 1) {
+ return db;
+ }
+ if (db.getLevel() == 2 && db.getParentUniqueId() != null) {
+ Long puid = db.getParentUniqueId();
+ for (ShelfMaterialDetailPO x : dbList) {
+ if (x == null) {
+ continue;
+ }
+ if (puid.equals(x.getUniqueId()) || puid.equals(x.getInUniqueId())) {
+ return x;
+ }
+ }
+ }
+ return db;
+ }
+
+ /**
+ * 首次可只落主行不拆;已有 level=2 子行或主行+子行已有累计已上架时再拆(对齐收货 {@code shouldPdaSplitIntoChildRow})
+ */
+ private static boolean shouldPdaSplitIntoShelfChildRow(ShelfMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (dbLine == null || CollectionUtils.isEmpty(allDbLines)) {
+ return true;
+ }
+ ShelfMaterialDetailPO plan = resolveShelfPlanParentRow(dbLine, allDbLines);
+ if (plan == null) {
+ return true;
+ }
+ Long uid = plan.getUniqueId();
+ Long inUid = plan.getInUniqueId();
+ boolean hasL2 = allDbLines.stream().anyMatch(x -> x != null && x.getLevel() != null && x.getLevel() == 2
+ && x.getParentUniqueId() != null
+ && ((uid != null && uid.equals(x.getParentUniqueId()))
+ || (inUid != null && inUid.equals(x.getParentUniqueId()))));
+ if (hasL2) {
+ return true;
+ }
+ BigDecimal sum = sumAlreadyShelvesUnderPlanParent(plan, allDbLines);
+ return sum.compareTo(BigDecimal.ZERO) > 0;
+ }
+
+ private static boolean isCorruptedStandaloneLineAsLevel2Shelf(ShelfMaterialDetailPO dbLine) {
+ if (dbLine == null || dbLine.getLevel() == null || dbLine.getLevel() != 2) {
+ return false;
+ }
+ return dbLine.getParentUniqueId() == null || Objects.equals(dbLine.getParentUniqueId(), 0L);
+ }
+
+ private static void stripPdaUiParentCopyChildrenShelf(ShelfMaterialDetailDO line, ShelfMaterialDetailPO dbLine) {
+ if (line == null || CollectionUtils.isEmpty(line.getChildren())) {
+ return;
+ }
+ Long parentUid = line.getUniqueId() != null ? line.getUniqueId() : (dbLine != null ? dbLine.getUniqueId() : null);
+ List realOnly = new ArrayList<>();
+ for (ShelfMaterialDetailDO c : line.getChildren()) {
+ if (c == null) {
+ continue;
+ }
+ if (parentUid != null && parentUid.equals(c.getUniqueId())) {
+ continue;
+ }
+ realOnly.add(c);
+ }
+ line.setChildren(realOnly);
+ }
+
+ /** getInfo 挂在 children 里的已落库子行,再次上架时需去掉,否则无法走「拆新子行」注入(对齐收货 stripPdaExistingDisplayedChildrenForResplit) */
+ private static void stripPdaExistingDisplayedChildrenForResplitShelf(ShelfMaterialDetailDO line,
+ ShelfMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (line == null || CollectionUtils.isEmpty(line.getChildren()) || dbLine == null || CollectionUtils.isEmpty(allDbLines)) {
+ return;
+ }
+ Long puid = dbLine.getUniqueId();
+ Long pInUid = dbLine.getInUniqueId();
+ List keep = new ArrayList<>();
+ for (ShelfMaterialDetailDO c : line.getChildren()) {
+ if (c == null) {
+ continue;
+ }
+ Long mid = c.getMaterialDetailId();
+ Long cuid = c.getUniqueId();
+ boolean isExistingDbChild = false;
+ for (ShelfMaterialDetailPO x : allDbLines) {
+ if (x == null || x.getLevel() == null || x.getLevel() != 2 || x.getParentUniqueId() == null) {
+ continue;
+ }
+ boolean parentOk = (puid != null && puid.equals(x.getParentUniqueId()))
+ || (pInUid != null && pInUid.equals(x.getParentUniqueId()));
+ if (!parentOk) {
+ continue;
+ }
+ if (mid != null && mid.equals(x.getMaterialDetailId())) {
+ isExistingDbChild = true;
+ break;
+ }
+ if (cuid != null && cuid.equals(x.getUniqueId())) {
+ isExistingDbChild = true;
+ break;
+ }
+ }
+ if (!isExistingDbChild) {
+ keep.add(c);
+ }
+ }
+ line.setChildren(keep);
+ }
+
+ /** 本次上架毛/净重、体积、面积增量(对齐收货 pickThisReceiptMetricIncrement;上架无 receipt* 字段,用 total*) */
+ private static BigDecimal pickThisShelfMetricIncrement(BigDecimal receiptField, BigDecimal totalField,
+ BigDecimal dbTotalAlreadyBefore) {
+ if (receiptField != null) {
+ return receiptField;
+ }
+ if (totalField == null) {
+ return BigDecimal.ZERO;
+ }
+ if (dbTotalAlreadyBefore == null) {
+ return totalField;
+ }
+ if (totalField.compareTo(dbTotalAlreadyBefore) == 0) {
+ return BigDecimal.ZERO;
+ }
+ if (totalField.compareTo(dbTotalAlreadyBefore) > 0) {
+ return totalField.subtract(dbTotalAlreadyBefore);
+ }
+ return totalField;
+ }
+
+ private static boolean hasPdaShelfMetricIncrement(ShelfMaterialDetailDO line, ShelfMaterialDetailPO dbLine) {
+ if (line == null || dbLine == null) {
+ return false;
+ }
+ BigDecimal dbNet = dbLine.getTotalNetWeight() != null ? dbLine.getTotalNetWeight() : BigDecimal.ZERO;
+ BigDecimal dbGross = dbLine.getTotalGrossWeight() != null ? dbLine.getTotalGrossWeight() : BigDecimal.ZERO;
+ BigDecimal dbVol = dbLine.getTotalVolume() != null ? dbLine.getTotalVolume() : BigDecimal.ZERO;
+ BigDecimal dbArea = dbLine.getTotalArea() != null ? dbLine.getTotalArea() : BigDecimal.ZERO;
+ return pickThisShelfMetricIncrement(null, line.getTotalNetWeight(), dbNet).compareTo(BigDecimal.ZERO) > 0
+ || pickThisShelfMetricIncrement(null, line.getTotalGrossWeight(), dbGross).compareTo(BigDecimal.ZERO) > 0
+ || pickThisShelfMetricIncrement(null, line.getTotalVolume(), dbVol).compareTo(BigDecimal.ZERO) > 0
+ || pickThisShelfMetricIncrement(null, line.getTotalArea(), dbArea).compareTo(BigDecimal.ZERO) > 0;
+ }
+
+ private static Long resolveParentUniqueIdForPdaInjectChildShelf(ShelfMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (dbLine == null) {
+ return null;
+ }
+ if (isCorruptedStandaloneLineAsLevel2Shelf(dbLine) && dbLine.getMaterialDetailId() != null
+ && !CollectionUtils.isEmpty(allDbLines)) {
+ for (ShelfMaterialDetailPO x : allDbLines) {
+ if (x != null && Objects.equals(dbLine.getMaterialDetailId(), x.getMaterialDetailId())
+ && x.getLevel() != null && x.getLevel() == 1) {
+ return x.getUniqueId();
+ }
+ }
+ }
+ ShelfMaterialDetailPO plan = resolveShelfPlanParentRow(dbLine, allDbLines);
+ return plan != null ? plan.getUniqueId() : dbLine.getUniqueId();
+ }
+
+ /**
+ * PDA 详情:从树上移除与主行同 material_detail_id / unique_id 的镜像子行(旧逻辑注入的 parent_copy),避免与主行并列两条。
+ */
+ private void removeShelfParentCopyMirrorChildrenRecursively(List list) {
+ if (CollectionUtils.isEmpty(list)) {
+ return;
+ }
+ for (ShelfMaterialDetailPO parent : list) {
+ if (parent == null) {
+ continue;
+ }
+ List ch = parent.getChildren();
+ if (!CollectionUtils.isEmpty(ch)) {
+ ch.removeIf(c -> isShelfParentCopyPo(parent, c));
+ removeShelfParentCopyMirrorChildrenRecursively(ch);
+ }
+ }
+ }
+
/**
* 判断子项是否为 getInfo 时注入的 parent_copy(与主项同 materialDetailId 或 uniqueId)
*/
@@ -551,9 +1012,23 @@ public class StockShelfOrderApplicationService {
return result;
}
+ /**
+ * PDA/PC 详情:未完成行将 total* 覆盖为「待上架重量/体积」= 计划 − 累计已上架。
+ * 与收货 {@link com.mhd.wms.application.service.stockReceiptOrder.StockReceiptOrderApplicationService#fillPlanAndPendingMetricsRecursively} 一致:
+ * level=2 子行计算时用计划主行(level=1)上已汇总的 alreadyShelves*,不能用子行本行累计,
+ * 否则「计划」是整张卡的计划,「已减」却只有子行一批,待上架会偏大。
+ */
private void overwriteTotalWithPendingForPdaWeightDisplayRecursively(
List list,
Map planWeightMetrics
+ ) {
+ overwriteTotalWithPendingForPdaWeightDisplayRecursively(list, planWeightMetrics, null);
+ }
+
+ private void overwriteTotalWithPendingForPdaWeightDisplayRecursively(
+ List list,
+ Map planWeightMetrics,
+ ShelfMaterialDetailPO planParent
) {
if (CollectionUtils.isEmpty(list)) {
return;
@@ -562,6 +1037,10 @@ public class StockShelfOrderApplicationService {
if (d == null) {
continue;
}
+ ShelfMaterialDetailPO nextPlanParent = planParent;
+ if (d.getLevel() != null && d.getLevel() == 1) {
+ nextPlanParent = d;
+ }
// 已上架:不覆盖,展示累计已上架实绩(与 DB 同口径)
if (isShelfTerminalShelvesStatus(d.getShelvesStatus())) {
// 参考收货:已完成行不参与「计划−待收」,total* 直接展示累计 already
@@ -577,7 +1056,7 @@ public class StockShelfOrderApplicationService {
if (d.getAlreadyShelvesArea() != null) {
d.setTotalArea(d.getAlreadyShelvesArea());
}
- overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics);
+ overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics, nextPlanParent);
continue;
}
@@ -593,17 +1072,30 @@ public class StockShelfOrderApplicationService {
BigDecimal alreadyVol = d.getAlreadyShelvesVolume() != null ? d.getAlreadyShelvesVolume() : BigDecimal.ZERO;
BigDecimal alreadyArea = d.getAlreadyShelvesArea() != null ? d.getAlreadyShelvesArea() : BigDecimal.ZERO;
+ // level=2 子行:与收货一致,用计划主行汇总后的累计已上架(flatten 后主行已为主+子合计)
+ 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;
+ }
+
// 参考收货:只对「有计划值」的字段覆盖 total*,否则保留原 total*
if (planNet == null && planGross == null && planVol == null && planArea == null) {
- overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics);
+ overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics, nextPlanParent);
continue;
}
// 兜底:待上架(1/2) 且已上架数量为 0 时,
// DB 里的 total_* 可能被初始化为“计划值”,此时不应把它当作 already。
// 按“数量为准”把 already 权重当 0,使 pending 展示为计划值。
+ // level=2 时与收货一致:用主行累计数量判断是否已有上架实绩。
BigDecimal alreadyShelvesQty = d.getAlreadyShelvesQuantity() != null ? d.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
boolean hasAlreadyQty = alreadyShelvesQty.compareTo(BigDecimal.ZERO) > 0;
+ if (planParent != null && d.getLevel() != null && d.getLevel() == 2) {
+ BigDecimal pq = planParent.getAlreadyShelvesQuantity() != null ? planParent.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
+ hasAlreadyQty = pq.compareTo(BigDecimal.ZERO) > 0;
+ }
if (!hasAlreadyQty) {
alreadyNet = BigDecimal.ZERO;
alreadyGross = BigDecimal.ZERO;
@@ -624,7 +1116,7 @@ public class StockShelfOrderApplicationService {
d.setTotalArea(calcPendingMetric(planArea, alreadyArea));
}
- overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics);
+ overwriteTotalWithPendingForPdaWeightDisplayRecursively(d.getChildren(), planWeightMetrics, nextPlanParent);
}
}
@@ -743,6 +1235,15 @@ public class StockShelfOrderApplicationService {
public Boolean upShelfByApp(StockShelfOrderDO stockReceiptOrderDO){
// 标记 PDA:权重/数量都按“累计”语义处理(与收货保持一致)
stockReceiptOrderDO.setFromApp(Boolean.TRUE);
+ StockShelfOrderPO dbOrder = stockShelfOrderDomainService.getInfo(stockReceiptOrderDO.getShelfOrderId());
+ if (dbOrder == null) {
+ throw new ServiceException("上架单不存在");
+ }
+ ShelfMaterialDetailDO queryAll = new ShelfMaterialDetailDO();
+ queryAll.setShelfOrderNumber(dbOrder.getShelfOrderNumber());
+ List dbListAll = shelfMaterialDetailDomainService.queryList(queryAll);
+ preserveParentShelfLineHierarchyFromDb(stockReceiptOrderDO, dbListAll);
+
List shelfMaterialDetailDOList = stockReceiptOrderDO.getMaterialDetailList();
boolean firstItemHadChildrenBefore = !CollectionUtils.isEmpty(shelfMaterialDetailDOList)
&& !CollectionUtils.isEmpty(shelfMaterialDetailDOList.get(0).getChildren());
@@ -764,7 +1265,7 @@ public class StockShelfOrderApplicationService {
shelfMaterialDetailDO.setStorageSectionId(firstChild.getStorageSectionId());
shelfMaterialDetailDO.setStorageLocationId(firstChild.getStorageLocationId());
shelfMaterialDetailDO.setContainerId(firstChild.getContainerId());
- shelfMaterialDetailDO.setLevel(firstChild.getLevel());
+ // 不把子行 level 复制到主项;assembleParamByShelf 会以库表主行 level 为准
shelfMaterialDetailDO.setMaterialDetailSerialNumberList(firstChild.getMaterialDetailSerialNumberList());
// 保留其余子项(从索引1开始),统一设为 level 2
List restChildren = new ArrayList<>();
@@ -777,6 +1278,30 @@ public class StockShelfOrderApplicationService {
}
});
}
+ // 与收货 receivingGoodsByApp 一致:assign 后按库表剥离 UI 镜像/已落库展示子行,必要时合成新的 level=2 承接本次量,避免多次提交仍写主行(对齐 injectPdaFlatReceiptAsSyntheticChild)
+ boolean skipInjectAfterAssign = firstItemHadChildrenBefore
+ && !CollectionUtils.isEmpty(stockReceiptOrderDO.getMaterialDetailList())
+ && !CollectionUtils.isEmpty(stockReceiptOrderDO.getMaterialDetailList().get(0).getChildren());
+ Map dbByIdForInject = dbListAll.stream()
+ .filter(x -> x.getMaterialDetailId() != null)
+ .collect(Collectors.toMap(ShelfMaterialDetailPO::getMaterialDetailId, Function.identity(), (a, b) -> a));
+ Map dbByUidForInject = dbListAll.stream()
+ .filter(x -> x.getUniqueId() != null)
+ .collect(Collectors.toMap(ShelfMaterialDetailPO::getUniqueId, Function.identity(), (a, b) -> a));
+ int injectIdx = 0;
+ for (ShelfMaterialDetailDO line : stockReceiptOrderDO.getMaterialDetailList()) {
+ ShelfMaterialDetailPO dbLine = null;
+ if (line.getMaterialDetailId() != null) {
+ dbLine = dbByIdForInject.get(line.getMaterialDetailId());
+ }
+ if (dbLine == null && line.getUniqueId() != null) {
+ dbLine = dbByUidForInject.get(line.getUniqueId());
+ }
+ if (dbLine != null && !(skipInjectAfterAssign && injectIdx == 0)) {
+ injectPdaFlatShelfAsSyntheticChild(line, dbLine, dbListAll);
+ }
+ injectIdx++;
+ }
//上架装配参数
StockShelfOrderDO assembleStockShelfOrderDO = assembleParamByShelf(stockReceiptOrderDO);
assembleStockShelfOrderDO.setFromApp(Boolean.TRUE);
@@ -1019,6 +1544,29 @@ public class StockShelfOrderApplicationService {
if (shelfMaterialDetailPO == null){
throw new ServiceException("物料明细不存在");
}
+ // PDA 注入虚拟子行时:净重毛重体积面积只落在子行,主计划行不再把本次增量再累加一遍。
+ // upShelfByApp 中 injectPdaFlatShelfAsSyntheticChild 会先写入 children 再进入本方法,需用标志位与下面「无 children」虚拟子行等价处理。
+ boolean injectedVirtualShelfChild = Boolean.TRUE.equals(shelfMaterialDetailDO.getPdaShelfSyntheticChildInjected());
+ // PDA 常只传主行 materialDetailId、本次数量在 shelvesQuantity,不带 children:第二次起会走主行 UPDATE。
+ // 与带子项时一致:计划主行(level=1)且已有上架实绩时,用虚拟子项承接本次量,主行本次数量置 0,子循环走 INSERT 新行。
+ if (!injectedVirtualShelfChild && fromApp && CollectionUtils.isEmpty(shelfMaterialDetailDO.getChildren())) {
+ BigDecimal parentSqRaw = shelfMaterialDetailDO.getShelvesQuantity() == null ? BigDecimal.ZERO : shelfMaterialDetailDO.getShelvesQuantity();
+ BigDecimal dbAlreadyMain = shelfMaterialDetailPO.getAlreadyShelvesQuantity() != null
+ ? shelfMaterialDetailPO.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
+ if (shelfMaterialDetailPO.getLevel() != null && shelfMaterialDetailPO.getLevel() == 1
+ && dbAlreadyMain.compareTo(BigDecimal.ZERO) > 0
+ && parentSqRaw.compareTo(BigDecimal.ZERO) > 0) {
+ ShelfMaterialDetailDO virtualChild = new ShelfMaterialDetailDO();
+ BeanUtils.copyProperties(shelfMaterialDetailDO, virtualChild);
+ virtualChild.setMaterialDetailId(null);
+ virtualChild.setUniqueId(null);
+ shelfMaterialDetailDO.setChildren(Collections.singletonList(virtualChild));
+ shelfMaterialDetailDO.setShelvesQuantity(BigDecimal.ZERO);
+ injectedVirtualShelfChild = true;
+ // 请求主行仍带本次扫描库位;末尾 copy 到 return 时会覆盖库表主行。子行已承载新库位,主行保留库表原库位。
+ restorePlanLineStorageFromDb(shelfMaterialDetailDO, shelfMaterialDetailPO);
+ }
+ }
List children = shelfMaterialDetailDO.getChildren();
BigDecimal childrenShelvesQuantitySum = BigDecimal.ZERO;
if (!CollectionUtils.isEmpty(children)) {
@@ -1033,6 +1581,15 @@ public class StockShelfOrderApplicationService {
//未进行上架直接跳过
continue;
}
+ // PDA:本次上架量只在子行、主行 shelvesQuantity=0 时,upShelfByApp 会把「第一个子行」的仓库/库位回填到主行 DO(见首子赋值逻辑),assemble 末尾再 merge 到 return,会把本次库位写回库表主行覆盖原库位。
+ // 与虚拟子行分支里的 restorePlanLineStorageFromDb 一致:主行恢复为库表值;库表主行尚无库位时不恢复,以免清空后无法走下方 setStorageLocationInfo。
+ if (fromApp && !CollectionUtils.isEmpty(shelfMaterialDetailDO.getChildren())
+ && shelvesQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ Long dbMainLocId = shelfMaterialDetailPO.getStorageLocationId();
+ if (dbMainLocId != null && dbMainLocId != 0L) {
+ restorePlanLineStorageFromDb(shelfMaterialDetailDO, shelfMaterialDetailPO);
+ }
+ }
BigDecimal mainItemActualQuantity = shelvesQuantity;//主项本次上架数(不含子项)
BigDecimal childrenActualQuantity = BigDecimal.ZERO;//子项本次上架总数
// PDA 权重/体积/面积:记录“本次增量”
@@ -1079,6 +1636,19 @@ public class StockShelfOrderApplicationService {
}
}
BigDecimal childShelvesQty = shelfMaterialDetailDOChild.getShelvesQuantity() != null ? shelfMaterialDetailDOChild.getShelvesQuantity() : BigDecimal.ZERO;
+ // PDA:计划主行(level=1)或已落库的子行(level=2)上已有上架实绩时,再次携带同一 materialDetailId 提交应与「收货」分拆一致:INSERT 新子行(level=2),不在同一行上累加覆盖;否则会 UPDATE 旧子行导致“第二次上架盖住第一次”。
+ if (fromApp && shelfMaterialDetailPOChildDb != null) {
+ Integer dbPlanLevel = shelfMaterialDetailPOChildDb.getLevel();
+ BigDecimal dbAlreadyShelved = shelfMaterialDetailPOChildDb.getAlreadyShelvesQuantity() != null
+ ? shelfMaterialDetailPOChildDb.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
+ boolean lineHasShelvedHistory = Integer.valueOf(1).equals(dbPlanLevel)
+ || Integer.valueOf(2).equals(dbPlanLevel);
+ if (lineHasShelvedHistory
+ && dbAlreadyShelved.compareTo(BigDecimal.ZERO) > 0
+ && childShelvesQty.compareTo(BigDecimal.ZERO) > 0) {
+ shelfMaterialDetailPOChildDb = null;
+ }
+ }
if (fromApp) {
// 子项未上架数量时,不累加重量增量
BigDecimal childNetInc = childShelvesQty.compareTo(BigDecimal.ZERO) > 0
@@ -1180,7 +1750,9 @@ public class StockShelfOrderApplicationService {
}
setStorageLocationInfo(returnShelfMaterialDetailDOChild);
returnShelfMaterialDetailDOChild.setAllowModify(2);
- returnShelfMaterialDetailDOChild.setLevel(2);
+ // 库表已存在的行:保留其 level(计划主行=1、分批子行=2)。不可一律写 2,否则 PDA 把计划行 id 放在 children 里时会误把主行改成子行。
+ Integer dbChildLevel = shelfMaterialDetailPOChildDb.getLevel();
+ returnShelfMaterialDetailDOChild.setLevel(dbChildLevel != null ? dbChildLevel : 2);
returnShelfMaterialDetailDOChild.setParentUniqueId(shelfMaterialDetailPO.getUniqueId());
// 子项不维护独立计划数量,计划量仅由主项承载
returnShelfMaterialDetailDOChild.setQuantity(null);
@@ -1192,6 +1764,8 @@ public class StockShelfOrderApplicationService {
BeanUtils.copyProperties(shelfMaterialDetailPO, returnShelfMaterialDetailDOChild, "materialDetailId","createBy","createByName","createTime",
"updateBy","updateByName","updateTime");
BeanUtils.copyProperties(shelfMaterialDetailDOChild, returnShelfMaterialDetailDOChild, IgnoreNullUtil.getNullPropertyNames(shelfMaterialDetailDOChild));
+ // 新子行必须 INSERT;请求里若仍带上一笔子行的 materialDetailId,上面会把本段误判为 UPDATE
+ returnShelfMaterialDetailDOChild.setMaterialDetailId(null);
if (fromApp) {
// 子项不存在:new already = 0 + 本次增量(并不超过计划)
WeightMetrics childPlan = shelfMaterialDetailDOChild.getInUniqueId() != null
@@ -1253,6 +1827,30 @@ public class StockShelfOrderApplicationService {
childrenActualQuantity = childrenActualQuantity.add(returnShelfMaterialDetailDOChild.getShelvesQuantity());
}
}
+ // PDA 分批上架:子行已承载本次量时,主行平面 shelvesQuantity 不应再计入主项累计(与 inject 成功后主行=0 一致)。
+ // inject 因 UI 仍带 children 等提前 return 时,主行与子行会双计,此处兜底。
+ boolean pdaSplitChildCarriesThisShelf = false;
+ if (fromApp && shouldPdaSplitIntoShelfChildRow(shelfMaterialDetailPO, shelfMaterialDetailPODbList)
+ && !CollectionUtils.isEmpty(shelfMaterialDetailDOChildList)) {
+ boolean anyChildPositiveQty = false;
+ for (ShelfMaterialDetailDO ch : shelfMaterialDetailDOChildList) {
+ if (ch != null && ch.getShelvesQuantity() != null
+ && ch.getShelvesQuantity().compareTo(BigDecimal.ZERO) > 0) {
+ anyChildPositiveQty = true;
+ break;
+ }
+ }
+ if (anyChildPositiveQty && shelfMaterialDetailPO.getLevel() != null
+ && shelfMaterialDetailPO.getLevel() == 1) {
+ mainItemActualQuantity = BigDecimal.ZERO;
+ pdaSplitChildCarriesThisShelf = true;
+ // inject 未把主行 shelvesQuantity 置 0 时,上面「shelvesQuantity==0 才 restore」分支不执行,主行 DO 仍带本次子行库位,merge 会覆盖库表主项库位;与子行兜底双计一致,此处恢复计划主行库位。
+ Long dbMainLocId = shelfMaterialDetailPO.getStorageLocationId();
+ if (dbMainLocId != null && dbMainLocId != 0L) {
+ restorePlanLineStorageFromDb(shelfMaterialDetailDO, shelfMaterialDetailPO);
+ }
+ }
+ }
BigDecimal dbParentAlready = shelfMaterialDetailPO.getAlreadyShelvesQuantity() != null
? shelfMaterialDetailPO.getAlreadyShelvesQuantity() : BigDecimal.ZERO;
// PC:already_shelves_quantity 使用本次上架量;PDA 保持累计口径
@@ -1280,10 +1878,22 @@ public class StockShelfOrderApplicationService {
? shelfMaterialDetailPO.getTotalArea()
: BigDecimal.ZERO;
- BigDecimal newAlreadyNet = oldAlreadyNet.add(netIncrement);
- BigDecimal newAlreadyGross = oldAlreadyGross.add(grossIncrement);
- BigDecimal newAlreadyVol = oldAlreadyVol.add(volIncrement);
- BigDecimal newAlreadyArea = oldAlreadyArea.add(areaIncrement);
+ BigDecimal newAlreadyNet;
+ BigDecimal newAlreadyGross;
+ BigDecimal newAlreadyVol;
+ BigDecimal newAlreadyArea;
+ if (injectedVirtualShelfChild || pdaSplitChildCarriesThisShelf) {
+ // 与分拆子行一致:本次 total_* 增量只落在子行;主行保持库表累计不变(否则 netIncrement 已由子循环写入子行,父行再 old+net 会双计)。
+ newAlreadyNet = oldAlreadyNet;
+ newAlreadyGross = oldAlreadyGross;
+ newAlreadyVol = oldAlreadyVol;
+ newAlreadyArea = oldAlreadyArea;
+ } else {
+ newAlreadyNet = oldAlreadyNet.add(netIncrement);
+ newAlreadyGross = oldAlreadyGross.add(grossIncrement);
+ newAlreadyVol = oldAlreadyVol.add(volIncrement);
+ newAlreadyArea = oldAlreadyArea.add(areaIncrement);
+ }
WeightMetrics plan = shelfMaterialDetailPO.getInUniqueId() != null
? planWeightMetrics.get(shelfMaterialDetailPO.getReceiptUniqueId() != null
@@ -1334,6 +1944,8 @@ public class StockShelfOrderApplicationService {
ShelfMaterialDetailDO returnShelfMaterialDetailDO = new ShelfMaterialDetailDO();
BeanUtils.copyProperties(shelfMaterialDetailPO, returnShelfMaterialDetailDO);
BeanUtils.copyProperties(shelfMaterialDetailDO, returnShelfMaterialDetailDO, IgnoreNullUtil.getNullPropertyNames(shelfMaterialDetailDO));
+ // 主行 level 以库表为准,避免 upShelfByApp 等路径把 firstChild.level 合到主项后覆盖为 2
+ returnShelfMaterialDetailDO.setLevel(shelfMaterialDetailPO.getLevel());
if (shelfMaterialDetailPO.getShelvesStatus() == 1){
//首次上架
//是否开启了序列号管理
@@ -1355,6 +1967,109 @@ public class StockShelfOrderApplicationService {
return returnStockShelfOrderDO;
}
+ /**
+ * 将计划主行上的仓库/库区/库位/容器恢复为库表现值,避免 PDA 二次上架时把「本次子行库位」写回主行。
+ */
+ private void restorePlanLineStorageFromDb(ShelfMaterialDetailDO parentDo, ShelfMaterialDetailPO dbPo) {
+ if (parentDo == null || dbPo == null) {
+ return;
+ }
+ parentDo.setWarehouseId(dbPo.getWarehouseId());
+ parentDo.setWarehouseCode(dbPo.getWarehouseCode());
+ parentDo.setWarehouseName(dbPo.getWarehouseName());
+ parentDo.setStorageSectionId(dbPo.getStorageSectionId());
+ parentDo.setStorageCode(dbPo.getStorageCode());
+ parentDo.setStorageName(dbPo.getStorageName());
+ parentDo.setStorageLocationId(dbPo.getStorageLocationId());
+ parentDo.setStorageLocationCode(dbPo.getStorageLocationCode());
+ parentDo.setStorageLocationName(dbPo.getStorageLocationName());
+ parentDo.setContainerId(dbPo.getContainerId());
+ parentDo.setContainerCode(dbPo.getContainerCode());
+ parentDo.setContainerType(dbPo.getContainerType());
+ parentDo.setContainerTypeName(dbPo.getContainerTypeName());
+ }
+
+ /**
+ * 请求行与库主计划行 level 不一致时纠正,避免误当子行(对齐收货 preserveParentReceiptLineHierarchyFromDb)。
+ */
+ private void preserveParentShelfLineHierarchyFromDb(StockShelfOrderDO order, List dbList) {
+ if (order == null || CollectionUtils.isEmpty(order.getMaterialDetailList()) || CollectionUtils.isEmpty(dbList)) {
+ return;
+ }
+ Map byId = dbList.stream()
+ .filter(x -> x.getMaterialDetailId() != null)
+ .collect(Collectors.toMap(ShelfMaterialDetailPO::getMaterialDetailId, Function.identity(), (a, b) -> a));
+ Map byUid = dbList.stream()
+ .filter(x -> x.getUniqueId() != null)
+ .collect(Collectors.toMap(ShelfMaterialDetailPO::getUniqueId, Function.identity(), (a, b) -> a));
+ for (ShelfMaterialDetailDO line : order.getMaterialDetailList()) {
+ ShelfMaterialDetailPO db = null;
+ if (line.getMaterialDetailId() != null) {
+ db = byId.get(line.getMaterialDetailId());
+ }
+ if (db == null && line.getUniqueId() != null) {
+ db = byUid.get(line.getUniqueId());
+ }
+ if (db != null && db.getLevel() != null && db.getLevel() == 1
+ && line.getLevel() != null && line.getLevel() == 2) {
+ line.setLevel(1);
+ line.setParentUniqueId(null);
+ }
+ }
+ }
+
+ /**
+ * PDA 单次传主行平面(无真实子结构需拆时):与收货 {@code injectPdaFlatReceiptAsSyntheticChild} 对齐——
+ * 去掉 UI 镜像行与已落库展示子行后,将本次上架数量与 total* 指标挪到新建 level=2 子行,主行清零并恢复库表库位,避免第 2、3 次仍累加到主项、覆盖主行库位。
+ */
+ private void injectPdaFlatShelfAsSyntheticChild(ShelfMaterialDetailDO line, ShelfMaterialDetailPO dbLine,
+ List allDbLines) {
+ if (line == null || dbLine == null) {
+ return;
+ }
+ stripPdaUiParentCopyChildrenShelf(line, dbLine);
+ stripPdaExistingDisplayedChildrenForResplitShelf(line, dbLine, allDbLines);
+ if (dbLine.getLevel() != null && dbLine.getLevel() == 2 && !isCorruptedStandaloneLineAsLevel2Shelf(dbLine)) {
+ return;
+ }
+ if (!CollectionUtils.isEmpty(line.getChildren())) {
+ return;
+ }
+ if (!shouldPdaSplitIntoShelfChildRow(dbLine, allDbLines) && !isCorruptedStandaloneLineAsLevel2Shelf(dbLine)) {
+ return;
+ }
+ BigDecimal sq = line.getShelvesQuantity();
+ boolean hasQty = sq != null && sq.compareTo(BigDecimal.ZERO) > 0;
+ boolean hasMetricBatch = hasPdaShelfMetricIncrement(line, dbLine);
+ if (!hasQty && !hasMetricBatch) {
+ return;
+ }
+ ShelfMaterialDetailDO child = new ShelfMaterialDetailDO();
+ BeanUtils.copyProperties(line, child, "children", "materialDetailId", "uniqueId", "level", "parentUniqueId");
+ child.setUniqueId(idGenerator.snowflakeId(SnowFlakeConstants.wmsId));
+ child.setChildren(null);
+ child.setLevel(2);
+ child.setParentUniqueId(resolveParentUniqueIdForPdaInjectChildShelf(dbLine, allDbLines));
+ if (hasQty) {
+ child.setShelvesQuantity(sq);
+ }
+ child.setTotalNetWeight(line.getTotalNetWeight());
+ child.setTotalGrossWeight(line.getTotalGrossWeight());
+ child.setTotalVolume(line.getTotalVolume());
+ child.setTotalArea(line.getTotalArea());
+ child.setAlreadyShelvesQuantity(null);
+ line.setChildren(Collections.singletonList(child));
+ line.setShelvesQuantity(BigDecimal.ZERO);
+ line.setTotalNetWeight(null);
+ line.setTotalGrossWeight(null);
+ line.setTotalVolume(null);
+ line.setTotalArea(null);
+ line.setLevel(1);
+ line.setParentUniqueId(null);
+ restorePlanLineStorageFromDb(line, dbLine);
+ line.setPdaShelfSyntheticChildInjected(true);
+ }
+
/**
* @description 设置上架库位信息
* @author ZhouGY
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/facade/IReceiptMaterialDetailService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/facade/IReceiptMaterialDetailService.java
index ba14406e7..815bb3cf9 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/facade/IReceiptMaterialDetailService.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/facade/IReceiptMaterialDetailService.java
@@ -26,6 +26,11 @@ public interface IReceiptMaterialDetailService extends IService queryListChildren(ReceiptMaterialDetailDO receiptMaterialDetailDO);
+ /**
+ * PDA:按收货单号 + 父计划行 material_detail_id,查询其下 level=2 子行;若无子行但主行已有实收,返回主行快照。
+ */
+ List queryLevel2ChildrenByParentMaterialDetailId(String receiptOrderNumber, Long parentMaterialDetailId);
+
/**
* 新增物料明细
*/
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/persistence/ReceiptMaterialDetailImpl.java b/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/persistence/ReceiptMaterialDetailImpl.java
index 2e93f4599..e6ba18b33 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/persistence/ReceiptMaterialDetailImpl.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/receiptMaterialDetail/repository/persistence/ReceiptMaterialDetailImpl.java
@@ -50,6 +50,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -146,6 +147,85 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl queryLevel2ChildrenByParentMaterialDetailId(String receiptOrderNumber,
+ Long parentMaterialDetailId) {
+ List empty = new ArrayList<>();
+ if (receiptOrderNumber == null || parentMaterialDetailId == null) {
+ return empty;
+ }
+ ReceiptMaterialDetailDO q = new ReceiptMaterialDetailDO();
+ q.setReceiptOrderNumber(receiptOrderNumber);
+ List all = receiptMaterialDetailMapper.queryList(q);
+ if (CollectionUtils.isEmpty(all)) {
+ return empty;
+ }
+ ReceiptMaterialDetailPO anchor = all.stream()
+ .filter(p -> parentMaterialDetailId.equals(p.getMaterialDetailId()))
+ .findFirst()
+ .orElse(null);
+ if (anchor == null) {
+ return empty;
+ }
+ ReceiptMaterialDetailPO parent = anchor;
+ if (anchor.getLevel() != null && anchor.getLevel() == 2 && anchor.getParentUniqueId() != null) {
+ Long puid = anchor.getParentUniqueId();
+ ReceiptMaterialDetailPO plan = all.stream()
+ .filter(row -> puid.equals(row.getUniqueId()) || puid.equals(row.getInUniqueId()))
+ .findFirst()
+ .orElse(null);
+ if (plan != null) {
+ parent = plan;
+ }
+ }
+ Long pid = parent.getUniqueId();
+ Long inPid = parent.getInUniqueId();
+ if (pid == null && inPid == null) {
+ return empty;
+ }
+ final Long fpid = pid;
+ final Long finPid = inPid;
+ List children = all.stream()
+ .filter(m -> m.getLevel() != null && m.getLevel() == 2)
+ .filter(m -> {
+ Long p = m.getParentUniqueId();
+ if (p == null) {
+ return false;
+ }
+ return (fpid != null && p.equals(fpid)) || (finPid != null && p.equals(finPid));
+ })
+ .sorted(Comparator.comparing(ReceiptMaterialDetailPO::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder()))
+ .thenComparing(ReceiptMaterialDetailPO::getMaterialDetailId, Comparator.nullsLast(Comparator.naturalOrder())))
+ .collect(Collectors.toList());
+ children.forEach(ch -> {
+ List materialMoreDetailList = buildMaterialMoreDetailListByMaterialBinding(ch);
+ fillAttributeValueFromInMaterialDetail(ch, materialMoreDetailList);
+ ch.setMaterialMoreDetailList(materialMoreDetailList);
+ });
+ // 首笔只落主计划行、后续批次才 INSERT 子行时:主行上仍有累计实收,列表需在子行之外再带一条「主行首笔」快照
+ if (!children.isEmpty() && parent.getLevel() != null && parent.getLevel() == 1
+ && parent.getAlreadyReceiptQuantity() != null
+ && parent.getAlreadyReceiptQuantity().compareTo(BigDecimal.ZERO) > 0) {
+ ReceiptMaterialDetailPO mainSnap = new ReceiptMaterialDetailPO();
+ BeanUtils.copyProperties(parent, mainSnap);
+ List mainMore = buildMaterialMoreDetailListByMaterialBinding(mainSnap);
+ fillAttributeValueFromInMaterialDetail(mainSnap, mainMore);
+ mainSnap.setMaterialMoreDetailList(mainMore);
+ children.add(0, mainSnap);
+ }
+ // 未拆 level=2 子行、实收全部记在主计划行时:仍返回一条与主行一致的快照,供 PDA「已收货列表」展示
+ if (children.isEmpty() && parent.getAlreadyReceiptQuantity() != null
+ && parent.getAlreadyReceiptQuantity().compareTo(BigDecimal.ZERO) > 0) {
+ ReceiptMaterialDetailPO mainSnap = new ReceiptMaterialDetailPO();
+ BeanUtils.copyProperties(parent, mainSnap);
+ List materialMoreDetailList = buildMaterialMoreDetailListByMaterialBinding(mainSnap);
+ fillAttributeValueFromInMaterialDetail(mainSnap, materialMoreDetailList);
+ mainSnap.setMaterialMoreDetailList(materialMoreDetailList);
+ children.add(mainSnap);
+ }
+ return children;
+ }
+
private void collectUniqueIdsRecursively(ReceiptMaterialDetailPO po, java.util.Set set) {
if (po == null || po.getUniqueId() == null) return;
set.add(po.getUniqueId());
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/facade/IShelfMaterialDetailService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/facade/IShelfMaterialDetailService.java
index a2598f51e..4ecf44072 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/facade/IShelfMaterialDetailService.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/facade/IShelfMaterialDetailService.java
@@ -26,6 +26,11 @@ public interface IShelfMaterialDetailService extends IService queryListChildren(ShelfMaterialDetailDO shelfMaterialDetailDO);
+ /**
+ * PDA:指定计划行(卡片 material_detail_id)下所有 level=2 分批上架子行;可含首笔仅落主行时的主行快照。
+ */
+ public List queryLevel2ChildrenByParentMaterialDetailId(String shelfOrderNumber, Long parentMaterialDetailId);
+
/**
* 新增物料明细
*/
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/persistence/ShelfMaterialDetailImpl.java b/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/persistence/ShelfMaterialDetailImpl.java
index 15a19c181..35aa716e3 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/persistence/ShelfMaterialDetailImpl.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/shelfMaterialDetail/repository/persistence/ShelfMaterialDetailImpl.java
@@ -31,8 +31,10 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -123,6 +125,103 @@ public class ShelfMaterialDetailImpl extends ServiceImpl queryLevel2ChildrenByParentMaterialDetailId(String shelfOrderNumber,
+ Long parentMaterialDetailId) {
+ List empty = new ArrayList<>();
+ if (shelfOrderNumber == null || parentMaterialDetailId == null) {
+ return empty;
+ }
+ ShelfMaterialDetailDO q = new ShelfMaterialDetailDO();
+ q.setShelfOrderNumber(shelfOrderNumber);
+ List all = shelfMaterialDetailMapper.queryList(q);
+ if (CollectionUtils.isEmpty(all)) {
+ return empty;
+ }
+ ShelfMaterialDetailPO anchor = all.stream()
+ .filter(p -> parentMaterialDetailId.equals(p.getMaterialDetailId()))
+ .findFirst()
+ .orElse(null);
+ if (anchor == null) {
+ return empty;
+ }
+ ShelfMaterialDetailPO parent = anchor;
+ if (anchor.getLevel() != null && anchor.getLevel() == 2 && anchor.getParentUniqueId() != null) {
+ Long puid = anchor.getParentUniqueId();
+ ShelfMaterialDetailPO plan = all.stream()
+ .filter(row -> puid.equals(row.getUniqueId()) || puid.equals(row.getInUniqueId()))
+ .findFirst()
+ .orElse(null);
+ if (plan != null) {
+ parent = plan;
+ }
+ }
+ Long pid = parent.getUniqueId();
+ Long inPid = parent.getInUniqueId();
+ if (pid == null && inPid == null) {
+ return empty;
+ }
+ final Long fpid = pid;
+ final Long finPid = inPid;
+ List children = all.stream()
+ .filter(m -> m.getLevel() != null && m.getLevel() == 2)
+ .filter(m -> {
+ Long p = m.getParentUniqueId();
+ if (p == null) {
+ return false;
+ }
+ return (fpid != null && p.equals(fpid)) || (finPid != null && p.equals(finPid));
+ })
+ .sorted(Comparator.comparing(ShelfMaterialDetailPO::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder()))
+ .thenComparing(ShelfMaterialDetailPO::getMaterialDetailId, Comparator.nullsLast(Comparator.naturalOrder())))
+ .collect(Collectors.toList());
+ children.forEach(this::enrichShelfRowMaterialMoreDetail);
+ // 首笔只落主计划行、后续批次才 INSERT 子行时:主行上仍有累计已上架,列表需在子行之外再带一条「主行首笔」快照
+ if (!children.isEmpty() && parent.getLevel() != null && parent.getLevel() == 1
+ && parent.getAlreadyShelvesQuantity() != null
+ && parent.getAlreadyShelvesQuantity().compareTo(BigDecimal.ZERO) > 0) {
+ ShelfMaterialDetailPO mainSnap = new ShelfMaterialDetailPO();
+ BeanUtils.copyProperties(parent, mainSnap);
+ enrichShelfRowMaterialMoreDetail(mainSnap);
+ children.add(0, mainSnap);
+ }
+ // 未拆 level=2 子行、已上架全部记在主计划行时:仍返回一条与主行一致的快照,供 PDA「历史上架列表」展示
+ if (children.isEmpty() && parent.getAlreadyShelvesQuantity() != null
+ && parent.getAlreadyShelvesQuantity().compareTo(BigDecimal.ZERO) > 0) {
+ ShelfMaterialDetailPO mainSnap = new ShelfMaterialDetailPO();
+ BeanUtils.copyProperties(parent, mainSnap);
+ enrichShelfRowMaterialMoreDetail(mainSnap);
+ children.add(mainSnap);
+ }
+ return children;
+ }
+
+ /**
+ * 与 queryListChildren 中单行一致:批次更多属性 + 从上架明细填充 attributeValue。
+ */
+ private void enrichShelfRowMaterialMoreDetail(ShelfMaterialDetailPO row) {
+ if (row == null) {
+ return;
+ }
+ MaterialMoreDetailDO materialMoreDetailDO = new MaterialMoreDetailDO();
+ Long materialDetailUniqueIdForQuery = row.getReceiptUniqueId() != null
+ ? row.getReceiptUniqueId() : row.getUniqueId();
+ materialMoreDetailDO.setMaterialDetailUniqueId(materialDetailUniqueIdForQuery);
+ List materialMoreDetailList = IMaterialMoreDetailService.queryList(materialMoreDetailDO);
+ if (CollectionUtils.isEmpty(materialMoreDetailList)) {
+ AjaxResult ajaxResult = systemServiceFeign.getBatchDetailListByMaterialBaseInfoId(row.getMaterialBaseInfoId());
+ if (ajaxResult != null && "200".equals(String.valueOf(ajaxResult.get("code")))) {
+ List batchList = JSON.parseArray(JSONObject.toJSONString(ajaxResult.get("data")), BatchDetailFeignPO.class);
+ materialMoreDetailList = filterAndConvertToMaterialMoreDetail(batchList);
+ }
+ }
+ if (materialMoreDetailList == null) {
+ materialMoreDetailList = new ArrayList<>();
+ }
+ fillAttributeValueFromShelfMaterialDetail(row, materialMoreDetailList);
+ row.setMaterialMoreDetailList(materialMoreDetailList);
+ }
+
/**
* 过滤 isDisplay=1 的批次属性并转换为 MaterialMoreDetailPO,与收货/PC 端一致
*/
@@ -309,9 +408,13 @@ public class ShelfMaterialDetailImpl extends ServiceImpl shelfMaterialDetailChildList = new ArrayList<>();
if (!CollectionUtils.isEmpty(inMaterialDetailDOChildList)){
for (ShelfMaterialDetailDO shelfMaterialDetailDOChild : inMaterialDetailDOChildList){
+ // 与收货 batchReceiptUpdate 一致:不强行 setLevel(2)。PDA 可能在 children 中带 parent_copy(与主行同 material_detail_id/unique_id),
+ // 若再强制 level=2,saveOrUpdateBatch 后写会覆盖主行,把主行改成 level=2。
+ if (isShelfParentCopyMirrorRow(shelfMaterialDetailDO, shelfMaterialDetailDOChild)) {
+ continue;
+ }
ShelfMaterialDetail shelfMaterialDetailChild = new ShelfMaterialDetail();
BeanUtils.copyProperties(shelfMaterialDetailDOChild, shelfMaterialDetailChild);
- shelfMaterialDetailChild.setLevel(2);
shelfMaterialDetailChild.setCreateBy(loginUser.getUserid());
shelfMaterialDetailChild.setCreateByName(loginUser.getUsername());
shelfMaterialDetailChild.setCreateTime(new Date());
@@ -359,6 +462,33 @@ public class ShelfMaterialDetailImpl extends ServiceImpl listLevel2ChildrenByParentForApp(Long receiptOrderId, Long parentMaterialDetailId) {
+ StockReceiptOrderPO order = stockReceiptOrderService.getInfo(receiptOrderId);
+ if (order == null) {
+ throw new ServiceException("收货单不存在");
+ }
+ List children = receiptMaterialDetailService.queryLevel2ChildrenByParentMaterialDetailId(
+ order.getReceiptOrderNumber(), parentMaterialDetailId);
+ if (CollectionUtils.isEmpty(children)) {
+ return new ArrayList<>();
+ }
+ InMaterialDetailSerialNumberDO snDo = new InMaterialDetailSerialNumberDO();
+ snDo.setOrderNumber(order.getReceiptOrderNumber());
+ List snList = inMaterialDetailSerialNumberService.queryList(snDo);
+ Map> snMap = snList.stream()
+ .collect(Collectors.groupingBy(InMaterialDetailSerialNumberPO::getMaterialDetailUniqueId));
+ for (ReceiptMaterialDetailPO ch : children) {
+ List sns = ch.getUniqueId() != null ? snMap.get(ch.getUniqueId()) : null;
+ ch.setMaterialDetailSerialNumberList(!CollectionUtils.isEmpty(sns) ? sns : new ArrayList<>());
+ }
+ return children;
+ }
+
/**
* 根据物料绑定的批次属性动态获取更多属性(按 materialBaseInfoId,不再使用 MaterialGoodsRule.batchId)
* @param receiptMaterialDetailPO 收货明细
@@ -640,6 +668,91 @@ public class StockReceiptOrderDomainService {
* @author ZhouGY
* @date 2024/6/14 17:27
*/
+ /**
+ * 整单完成判断:只认计划主行(level 空或 1)。分批子行(level=2)不应单独占一票,否则易与主行同组已收尾仍计为「未完结」。
+ */
+ private static boolean isReceiptPlanLineForOrderCompletionGate(ReceiptMaterialDetailDO d) {
+ Integer lv = d.getLevel();
+ return lv == null || lv == 1;
+ }
+
+ private static boolean sameLongIdForReceiptGate(Long a, Long b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return a.longValue() == b.longValue();
+ }
+
+ /**
+ * 是否与 {@link com.mhd.wms.application.service.stockReceiptOrder.StockReceiptOrderApplicationService} 中
+ * PDA flatten / 超收校验用的「计划父行下 level=2」范围一致。
+ */
+ private static boolean isLevel2ChildUnderPlanForReceiptCompletionGate(ReceiptMaterialDetailPO plan, ReceiptMaterialDetailPO x) {
+ if (plan == null || x == null) {
+ return false;
+ }
+ if (x.getLevel() == null || x.getLevel() != 2) {
+ return false;
+ }
+ Long puid = x.getParentUniqueId();
+ if (puid == null || Long.valueOf(0).equals(puid)) {
+ return false;
+ }
+ Long uid = plan.getUniqueId();
+ Long inUid = plan.getInUniqueId();
+ boolean linked = (uid != null && sameLongIdForReceiptGate(uid, puid))
+ || (inUid != null && sameLongIdForReceiptGate(inUid, puid));
+ if (!linked) {
+ return false;
+ }
+ return plan.getUniqueId() == null || !sameLongIdForReceiptGate(plan.getUniqueId(), x.getUniqueId());
+ }
+
+ private static BigDecimal nzBdGate(BigDecimal v) {
+ return v != null ? v : BigDecimal.ZERO;
+ }
+
+ /** 计划父行自身 already_receipt + 其下归属子行,与 PDA 汇总口径一致 */
+ private static BigDecimal sumAlreadyReceiptUnderPlanForWholeOrderGate(ReceiptMaterialDetailPO plan,
+ List allRows) {
+ if (plan == null || CollectionUtils.isEmpty(allRows)) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal sum = nzBdGate(plan.getAlreadyReceiptQuantity());
+ for (ReceiptMaterialDetailPO x : allRows) {
+ if (isLevel2ChildUnderPlanForReceiptCompletionGate(plan, x)) {
+ sum = sum.add(nzBdGate(x.getAlreadyReceiptQuantity()));
+ }
+ }
+ return sum;
+ }
+
+ private static boolean isReceiptPlanLinePoForGate(ReceiptMaterialDetailPO p) {
+ if (p == null) {
+ return false;
+ }
+ Integer lv = p.getLevel();
+ return lv == null || lv == 1;
+ }
+
+ /**
+ * 计划主行是否仍阻塞整单完成:状态为待收/收货中,且(主+子)累计已收仍低于计划量。
+ * 多批次拆子行后主行 receipt_status 可能长期为 2,但累计已收已满,不得再阻塞。
+ */
+ private static boolean isPlanLineStillBlockingWholeOrderCompletion(ReceiptMaterialDetailPO plan,
+ List allDbLines) {
+ Integer st = plan.getReceiptStatus();
+ if (st == null || st >= 3) {
+ return false;
+ }
+ BigDecimal planQty = plan.getQuantity();
+ if (planQty == null) {
+ planQty = BigDecimal.ZERO;
+ }
+ BigDecimal sumAlready = sumAlreadyReceiptUnderPlanForWholeOrderGate(plan, allDbLines);
+ return sumAlready.compareTo(planQty) < 0;
+ }
+
@Transactional(rollbackFor = Exception.class)
public Boolean updateStockReceiptOrder(StockReceiptOrderDO stockReceiptOrderDO, StockReceiptOrderPO stockReceiptOrderPO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -664,8 +777,26 @@ public class StockReceiptOrderDomainService {
stockReceiptOrder.setUpdateByName(loginUser.getUsername());
stockReceiptOrder.setUpdateTime(new Date());
stockReceiptOrderService.updateById(stockReceiptOrder);
- //获取所有明细中的收货状态小于3的数量--未完成收货的明细
- long receiptMaterialQuantity = stockReceiptOrderDO.getMaterialDetailList().stream().filter(receiptMaterialDetailDO -> receiptMaterialDetailDO.getReceiptStatus() < 3).count();
+ // 必须以落库后的全量明细为准:请求体里的主行 receipt_status 在多批次拆子行后可能仍为「收货中」,
+ // 但主行+子行 already_receipt 已满计划量,不得阻塞 completeReceipt。
+ long receiptMaterialQuantity = 0L;
+ if (StringUtils.isNotBlank(stockReceiptOrderPO.getReceiptOrderNumber())) {
+ ReceiptMaterialDetailDO qAll = new ReceiptMaterialDetailDO();
+ qAll.setReceiptOrderNumber(stockReceiptOrderPO.getReceiptOrderNumber());
+ List allDbLines = receiptMaterialDetailService.queryList(qAll);
+ if (!CollectionUtils.isEmpty(allDbLines)) {
+ receiptMaterialQuantity = allDbLines.stream()
+ .filter(StockReceiptOrderDomainService::isReceiptPlanLinePoForGate)
+ .filter(p -> isPlanLineStillBlockingWholeOrderCompletion(p, allDbLines))
+ .count();
+ }
+ } else {
+ receiptMaterialQuantity = stockReceiptOrderDO.getMaterialDetailList().stream()
+ .filter(d -> d != null && isReceiptPlanLineForOrderCompletionGate(d))
+ .filter(receiptMaterialDetailDO -> receiptMaterialDetailDO.getReceiptStatus() != null
+ && receiptMaterialDetailDO.getReceiptStatus() < 3)
+ .count();
+ }
//收货数量大于等于计划数量 直接完成收货
if (stockReceiptOrder.getReceiptQuantity().compareTo(stockReceiptOrderPO.getQuantity()) >= 0 && receiptMaterialQuantity == 0){
completeReceipt(stockReceiptOrderPO.getReceiptOrderId(), stockReceiptOrderDO.getFromPda());
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/repository/po/StockShelfOrderPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/repository/po/StockShelfOrderPO.java
index abe8d130d..4d1b8910c 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/repository/po/StockShelfOrderPO.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/repository/po/StockShelfOrderPO.java
@@ -105,4 +105,21 @@ public class StockShelfOrderPO extends StockInOrderBasePO {
@ApiModelProperty("物料明细")
private List materialDetailList;
+ /** 详情接口按行递归汇总(与收货单 getInfo 一致),供 PC 展示 */
+ @ApiModelProperty("总净重(KG)")
+ @Excel(name = "总净重(KG)")
+ private BigDecimal totalNetWeight;
+
+ @ApiModelProperty("总毛重(KG)")
+ @Excel(name = "总毛重(KG)")
+ private BigDecimal totalGrossWeight;
+
+ @ApiModelProperty("总体积(CBM)")
+ @Excel(name = "总体积(CBM)")
+ private BigDecimal totalVolume;
+
+ @ApiModelProperty("总面积(SQM)")
+ @Excel(name = "总面积(SQM)")
+ private BigDecimal totalArea;
+
}
diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/service/StockShelfOrderDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/service/StockShelfOrderDomainService.java
index bad4ab18d..6b2f3435c 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/service/StockShelfOrderDomainService.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockShelfOrder/service/StockShelfOrderDomainService.java
@@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.mhd.common.core.constant.SnowFlakeConstants;
import com.mhd.common.core.domain.po.UserPo;
@@ -257,7 +258,33 @@ public class StockShelfOrderDomainService {
return stockShelfOrderPO;
}
-
+ /**
+ * PDA:指定计划行(卡片)下所有 level=2 分批上架子行(库表真实子行,不含 parent_copy),附带序列号。
+ *
+ * @param shelfOrderId 上架单主键
+ * @param parentMaterialDetailId 卡片对应 shelf_material_detail.material_detail_id(可误传为某子行 id,将解析到其父计划行)
+ */
+ public List listLevel2ChildrenByParentForApp(Long shelfOrderId, Long parentMaterialDetailId) {
+ StockShelfOrderPO order = stockShelfOrderService.getInfo(shelfOrderId);
+ if (order == null) {
+ throw new ServiceException("上架单不存在");
+ }
+ List children = shelfMaterialDetailService.queryLevel2ChildrenByParentMaterialDetailId(
+ order.getShelfOrderNumber(), parentMaterialDetailId);
+ if (CollectionUtils.isEmpty(children)) {
+ return new ArrayList<>();
+ }
+ InMaterialDetailSerialNumberDO snDo = new InMaterialDetailSerialNumberDO();
+ snDo.setOrderNumber(order.getShelfOrderNumber());
+ List snList = inMaterialDetailSerialNumberService.queryList(snDo);
+ Map> snMap = snList.stream()
+ .collect(Collectors.groupingBy(InMaterialDetailSerialNumberPO::getMaterialDetailUniqueId));
+ for (ShelfMaterialDetailPO ch : children) {
+ List sns = ch.getUniqueId() != null ? snMap.get(ch.getUniqueId()) : null;
+ ch.setMaterialDetailSerialNumberList(!CollectionUtils.isEmpty(sns) ? sns : new ArrayList<>());
+ }
+ return children;
+ }
/**
* @description 任务下发
@@ -313,6 +340,10 @@ public class StockShelfOrderDomainService {
genContainerUsageRecord(stockShelfOrderDO);
//修改物料明细信息
shelfMaterialDetailService.batchShelfUpdate(stockShelfOrderDO);
+ LoginUser loginUserUpShelf = SecurityUtils.getLoginUser();
+ String userRealNameUpShelf = resolveUserRealNameForShelf(loginUserUpShelf);
+ refreshShelfMaterialDetailShelvesStatusAfterPartialUpShelf(stockShelfOrderPO.getShelfOrderNumber(), loginUserUpShelf,
+ userRealNameUpShelf);
//同步更新入库单明细的上架数量/上架状态
syncShelvesQuantityToInMaterialDetail(stockShelfOrderDO);
//生成 上架作业单
@@ -652,11 +683,225 @@ public class StockShelfOrderDomainService {
if (abnormal == 1){
genInOrderAbnormal(stockShelfOrderPO);
}
+ String userRealNameComplete = resolveUserRealNameForShelf(loginUser);
+ updateShelfMaterialDetailShelvesStatusWhenOrderCompleted(stockShelfOrderPO, loginUser, userRealNameComplete);
//推送业务数据到BMS系统
// pushSyncBmsDomainService.pushInShelfOrder(stockShelfOrderPO.getInOrderNumber());
return true;
}
+ /**
+ * 分批/单次上架提交后:按主行+子行汇总「累计已上架」与主行计划量比较,刷新各明细 shelves_status(待上 1 / 上中 2 / 满 3 / 超 5)
+ */
+ private void refreshShelfMaterialDetailShelvesStatusAfterPartialUpShelf(String shelfOrderNumber,
+ LoginUser loginUser, String userRealName) {
+ if (StringUtils.isBlank(shelfOrderNumber) || loginUser == null) {
+ return;
+ }
+ Date updateTime = new Date();
+ List details = shelfMaterialDetailService.list(
+ new QueryWrapper().lambda()
+ .eq(ShelfMaterialDetail::getShelfOrderNumber, shelfOrderNumber)
+ .eq(ShelfMaterialDetail::getDelFlag, 1));
+ if (CollectionUtils.isEmpty(details)) {
+ return;
+ }
+ Map> childrenByParentUniqueId = new HashMap<>();
+ for (ShelfMaterialDetail d : details) {
+ if (isShelfDetailChildRow(d) && d.getParentUniqueId() != null) {
+ childrenByParentUniqueId.computeIfAbsent(d.getParentUniqueId(), k -> new ArrayList<>()).add(d);
+ }
+ }
+ Set parentUniqueIdsInOrder = details.stream()
+ .filter(d -> !isShelfDetailChildRow(d))
+ .map(ShelfMaterialDetail::getUniqueId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ for (ShelfMaterialDetail parent : details) {
+ if (isShelfDetailChildRow(parent)) {
+ continue;
+ }
+ Integer status;
+ List children = parent.getUniqueId() == null ? null
+ : childrenByParentUniqueId.get(parent.getUniqueId());
+ if (!CollectionUtils.isEmpty(children)) {
+ BigDecimal groupShelved = nzShelf(parent.getAlreadyShelvesQuantity());
+ for (ShelfMaterialDetail ch : children) {
+ groupShelved = groupShelved.add(nzShelf(ch.getAlreadyShelvesQuantity()));
+ }
+ status = deriveShelfDetailStatusWhileOrderOpen(groupShelved, parent.getQuantity());
+ } else {
+ status = deriveShelfDetailStatusWhileOrderOpen(parent.getAlreadyShelvesQuantity(), parent.getQuantity());
+ }
+ parent.setShelvesStatus(status);
+ parent.setUpdateBy(loginUser.getUserid());
+ parent.setUpdateByName(userRealName);
+ parent.setUpdateTime(updateTime);
+ if (!CollectionUtils.isEmpty(children)) {
+ for (ShelfMaterialDetail ch : children) {
+ ch.setShelvesStatus(status);
+ ch.setUpdateBy(loginUser.getUserid());
+ ch.setUpdateByName(userRealName);
+ ch.setUpdateTime(updateTime);
+ }
+ }
+ }
+ for (ShelfMaterialDetail d : details) {
+ if (isShelfDetailChildRow(d) && d.getParentUniqueId() != null
+ && !parentUniqueIdsInOrder.contains(d.getParentUniqueId())) {
+ d.setShelvesStatus(deriveShelfDetailStatusWhileOrderOpen(d.getAlreadyShelvesQuantity(), d.getQuantity()));
+ d.setUpdateBy(loginUser.getUserid());
+ d.setUpdateByName(userRealName);
+ d.setUpdateTime(updateTime);
+ }
+ }
+ flushShelfMaterialDetailShelvesStatusColumnsOnly(details, loginUser, userRealName);
+ }
+
+ /**
+ * 整单完成上架:按累计已上架与计划比较,更新明细 shelves_status(3 已上满 / 4 少上 / 5 超上)
+ */
+ private void updateShelfMaterialDetailShelvesStatusWhenOrderCompleted(StockShelfOrderPO stockShelfOrderPO,
+ LoginUser loginUser, String userRealName) {
+ if (stockShelfOrderPO == null || StringUtils.isBlank(stockShelfOrderPO.getShelfOrderNumber()) || loginUser == null) {
+ return;
+ }
+ Date updateTime = new Date();
+ List details = shelfMaterialDetailService.list(
+ new QueryWrapper().lambda()
+ .eq(ShelfMaterialDetail::getShelfOrderNumber, stockShelfOrderPO.getShelfOrderNumber())
+ .eq(ShelfMaterialDetail::getDelFlag, 1));
+ if (CollectionUtils.isEmpty(details)) {
+ return;
+ }
+ Map> childrenByParentUniqueId = new HashMap<>();
+ for (ShelfMaterialDetail d : details) {
+ if (isShelfDetailChildRow(d) && d.getParentUniqueId() != null) {
+ childrenByParentUniqueId.computeIfAbsent(d.getParentUniqueId(), k -> new ArrayList<>()).add(d);
+ }
+ }
+ Set parentUniqueIdsInOrder = details.stream()
+ .filter(d -> !isShelfDetailChildRow(d))
+ .map(ShelfMaterialDetail::getUniqueId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ for (ShelfMaterialDetail parent : details) {
+ if (isShelfDetailChildRow(parent)) {
+ continue;
+ }
+ Integer status;
+ List children = parent.getUniqueId() == null ? null
+ : childrenByParentUniqueId.get(parent.getUniqueId());
+ if (!CollectionUtils.isEmpty(children)) {
+ BigDecimal groupShelved = nzShelf(parent.getAlreadyShelvesQuantity());
+ for (ShelfMaterialDetail ch : children) {
+ groupShelved = groupShelved.add(nzShelf(ch.getAlreadyShelvesQuantity()));
+ }
+ status = resolveShelfDetailStatusOnOrderComplete(groupShelved, parent.getQuantity());
+ } else {
+ status = resolveShelfDetailStatusOnOrderComplete(parent.getAlreadyShelvesQuantity(), parent.getQuantity());
+ }
+ parent.setShelvesStatus(status);
+ parent.setUpdateBy(loginUser.getUserid());
+ parent.setUpdateByName(userRealName);
+ parent.setUpdateTime(updateTime);
+ if (!CollectionUtils.isEmpty(children)) {
+ for (ShelfMaterialDetail ch : children) {
+ ch.setShelvesStatus(status);
+ ch.setUpdateBy(loginUser.getUserid());
+ ch.setUpdateByName(userRealName);
+ ch.setUpdateTime(updateTime);
+ }
+ }
+ }
+ for (ShelfMaterialDetail d : details) {
+ if (isShelfDetailChildRow(d) && d.getParentUniqueId() != null
+ && !parentUniqueIdsInOrder.contains(d.getParentUniqueId())) {
+ d.setShelvesStatus(resolveShelfDetailStatusOnOrderComplete(d.getAlreadyShelvesQuantity(), d.getQuantity()));
+ d.setUpdateBy(loginUser.getUserid());
+ d.setUpdateByName(userRealName);
+ d.setUpdateTime(updateTime);
+ }
+ }
+ flushShelfMaterialDetailShelvesStatusColumnsOnly(details, loginUser, userRealName);
+ }
+
+ /**
+ * 只落库上架状态及审计字段。{@link ShelfMaterialDetail} 查询结果中 total_* 与 alreadyShelves* 曾映射同一列,
+ * 使用 {@code updateBatchById} 会生成重复的 SET 列名(达梦报错)。此处按主键逐条 {@code update(wrapper)} 仅更新必要列。
+ */
+ private void flushShelfMaterialDetailShelvesStatusColumnsOnly(List details, LoginUser loginUser,
+ String userRealName) {
+ if (CollectionUtils.isEmpty(details) || loginUser == null) {
+ return;
+ }
+ for (ShelfMaterialDetail d : details) {
+ if (d == null || d.getMaterialDetailId() == null) {
+ continue;
+ }
+ shelfMaterialDetailService.update(null, new LambdaUpdateWrapper()
+ .set(ShelfMaterialDetail::getShelvesStatus, d.getShelvesStatus())
+ .set(ShelfMaterialDetail::getUpdateBy, loginUser.getUserid())
+ .set(ShelfMaterialDetail::getUpdateByName, userRealName)
+ .set(ShelfMaterialDetail::getUpdateTime, d.getUpdateTime() != null ? d.getUpdateTime() : new Date())
+ .eq(ShelfMaterialDetail::getMaterialDetailId, d.getMaterialDetailId()));
+ }
+ }
+
+ private static boolean isShelfDetailChildRow(ShelfMaterialDetail d) {
+ return d.getLevel() != null && d.getLevel() == 2
+ && d.getParentUniqueId() != null && d.getParentUniqueId() != 0L;
+ }
+
+ private static BigDecimal nzShelf(BigDecimal v) {
+ return v != null ? v : BigDecimal.ZERO;
+ }
+
+ /** 进行中:1 待上架 / 2 上架中 / 3 已上满 / 5 超上 */
+ private static Integer deriveShelfDetailStatusWhileOrderOpen(BigDecimal alreadyShelvesQuantity, BigDecimal quantity) {
+ BigDecimal already = alreadyShelvesQuantity != null ? alreadyShelvesQuantity : BigDecimal.ZERO;
+ BigDecimal plan = quantity != null ? quantity : BigDecimal.ZERO;
+ if (already.compareTo(BigDecimal.ZERO) == 0) {
+ return 1;
+ }
+ int cmp = already.compareTo(plan);
+ if (cmp < 0) {
+ return 2;
+ }
+ if (cmp > 0) {
+ return 5;
+ }
+ return 3;
+ }
+
+ /** 完成整单:3 已上满 / 4 少上 / 5 超上 */
+ private static Integer resolveShelfDetailStatusOnOrderComplete(BigDecimal alreadyShelvesQuantity, BigDecimal quantity) {
+ BigDecimal already = alreadyShelvesQuantity != null ? alreadyShelvesQuantity : BigDecimal.ZERO;
+ BigDecimal plan = quantity != null ? quantity : BigDecimal.ZERO;
+ int cmp = already.compareTo(plan);
+ if (cmp > 0) {
+ return 5;
+ }
+ if (cmp < 0) {
+ return 4;
+ }
+ return 3;
+ }
+
+ private static String resolveUserRealNameForShelf(LoginUser loginUser) {
+ if (loginUser == null) {
+ return "";
+ }
+ if (loginUser.getUserPo() != null && loginUser.getUserPo().getUserName() != null
+ && !loginUser.getUserPo().getUserName().trim().isEmpty()) {
+ return loginUser.getUserPo().getUserName();
+ }
+ if (loginUser.getRealname() != null && !loginUser.getRealname().trim().isEmpty()) {
+ return loginUser.getRealname();
+ }
+ return loginUser.getUsername() != null ? loginUser.getUsername() : "";
+ }
+
private synchronized void stockIn(List shelfMaterialDetailDOList, StockShelfOrderDO stockReceiptOrderDO) {
for (ShelfMaterialDetail shelfMaterialDetail : shelfMaterialDetailDOList) {
Long materialBaseInfoId = shelfMaterialDetail.getMaterialBaseInfoId();
diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockReceiptOrder/StockReceiptOrderAppApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockReceiptOrder/StockReceiptOrderAppApi.java
index c754e9311..40e3234fd 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockReceiptOrder/StockReceiptOrderAppApi.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockReceiptOrder/StockReceiptOrderAppApi.java
@@ -6,6 +6,7 @@ import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.core.web.page.TableDataInfoApi;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.wms.application.service.stockReceiptOrder.StockReceiptOrderApplicationService;
+import com.mhd.wms.domain.receiptMaterialDetail.repository.po.ReceiptMaterialDetailPO;
import com.mhd.wms.domain.stockReceiptOrder.repository.po.StockReceiptOrderPO;
import com.mhd.wms.domain.stockReceiptOrder.repository.todo.StockReceiptOrderDO;
import com.mhd.wms.interfaces.assember.stockReceiptOrder.StockReceiptOrderAssembler;
@@ -62,6 +63,20 @@ public class StockReceiptOrderAppApi extends BaseController {
return AjaxResult.success(stockReceiptOrderApplicationService.getInfoByApp(receiptOrderId, receiptStatus));
}
+ /**
+ * PDA:查询某计划行(卡片)下所有 level=2 分批收货子行(含首笔仅落主行时的主行快照),附带序列号。
+ */
+ @ApiOperation("按父计划行查询分批收货子行(level=2)")
+ @GetMapping("/listLevel2ChildrenByParent")
+ public AjaxResult listLevel2ChildrenByParent(
+ @ApiParam(value = "收货单ID", required = true) @RequestParam Long receiptOrderId,
+ @ApiParam(value = "收货明细 material_detail_id(计划行或子行均可,会解析到计划父行)", required = true)
+ @RequestParam Long materialDetailId) {
+ List list = stockReceiptOrderApplicationService.listLevel2ChildrenByParentForApp(
+ receiptOrderId, materialDetailId);
+ return AjaxResult.success(list);
+ }
+
/**
* 收货
*/
diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockShelfOrder/StockShelfOrderAppApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockShelfOrder/StockShelfOrderAppApi.java
index 37e94a3a9..50bd9dda0 100644
--- a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockShelfOrder/StockShelfOrderAppApi.java
+++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApp/stockShelfOrder/StockShelfOrderAppApi.java
@@ -5,11 +5,13 @@ import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.core.web.page.TableDataInfoApi;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.wms.application.service.stockShelfOrder.StockShelfOrderApplicationService;
+import com.mhd.wms.domain.shelfMaterialDetail.repository.po.ShelfMaterialDetailPO;
import com.mhd.wms.domain.stockShelfOrder.repository.po.StockShelfOrderPO;
import com.mhd.wms.domain.stockShelfOrder.repository.todo.StockShelfOrderDO;
import com.mhd.wms.interfaces.assember.stockShelfOrder.StockShelfOrderAssembler;
import com.mhd.wms.interfaces.dto.stockShelfOrder.StockShelfOrderDTO;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -47,7 +49,7 @@ public class StockShelfOrderAppApi extends BaseController {
}
/**
- * 获取上架单详细信息(PDA:主项无子项时添加 parent_copy,供详情页从 children 读取数据)
+ * 获取上架单详细信息(PDA:与收货 getInfoByApp 一致保留父子结构;子行计划数量在服务端清空,见 StockShelfOrderApplicationService#getInfo)
*/
@ApiOperation("获取上架单")
@GetMapping(value = "/getInfo")
@@ -58,6 +60,19 @@ public class StockShelfOrderAppApi extends BaseController {
return AjaxResult.success(stockShelfOrderApplicationService.getInfo(shelfOrderId, true, shelfStatus));
}
+ /**
+ * PDA:查询某计划行(卡片)下所有 level=2 分批上架子行(含首笔仅落主行时的主行快照),附带序列号。
+ */
+ @ApiOperation("按父计划行查询分批上架子行(level=2)")
+ @GetMapping("/listLevel2ChildrenByParent")
+ public AjaxResult listLevel2ChildrenByParent(
+ @ApiParam(value = "上架单ID", required = true) @RequestParam Long shelfOrderId,
+ @ApiParam(value = "上架明细 material_detail_id(计划行或子行均可,会解析到计划父行)", required = true)
+ @RequestParam Long materialDetailId) {
+ List list = stockShelfOrderApplicationService.listLevel2ChildrenByParentForApp(
+ shelfOrderId, materialDetailId);
+ return AjaxResult.success(list);
+ }
@ApiOperation("上架")
@PostMapping("/upShelf")