From aa0be69556df7be29d393bac63875a49b020c1a8 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 15:06:50 +0800 Subject: [PATCH] =?UTF-8?q?PDA=E6=94=B6=E8=B4=A7=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StockReceiptOrderApplicationService.java | 306 +++++++++++++++++- .../facade/IReceiptMaterialDetailService.java | 10 + .../ReceiptMaterialDetailImpl.java | 80 +++++ .../repository/todo/StockReceiptOrderDO.java | 6 + .../StockReceiptOrderDomainService.java | 28 ++ .../StockReceiptOrderAppApi.java | 14 + 6 files changed, 438 insertions(+), 6 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 cdd68428c..76fc585f0 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,6 +54,7 @@ import java.lang.reflect.Field; import java.math.BigDecimal; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -1224,6 +1225,13 @@ public class StockReceiptOrderApplicationService { return stockReceiptOrderPO; } + /** + * PDA:点击卡片后查询当前计划行下全部分批收货子行(level=2,库表真实数据,不含 getInfoByApp 的 parent_copy)。 + */ + public List listLevel2ChildrenByParentForApp(Long receiptOrderId, Long parentMaterialDetailId) { + return stockReceiptOrderDomainService.listLevel2ChildrenByParentForApp(receiptOrderId, parentMaterialDetailId); + } + /** * PDA 界面「总净重/总毛重/体积/面积」绑定 total*:有计划时展示待收(pending*);无计划联表时保留原 total*(累计实绩)。 */ @@ -1419,11 +1427,20 @@ public class StockReceiptOrderApplicationService { // 主项子项结构:将 children 第一条(parent_copy)赋值给主项并删除 assignFirstChildToParentAndRemove(stockReceiptOrderDO); } + // 供 assembleParamByReceipt 识别:方案二将单次实收拆成 level2 子行;fromPda 须在装配前设置 + // 若仅有 getInfoByApp 挂的 parent_copy:assign 后 children 已空,不可再 skip,否则从不走 inject、只会 UPDATE 主行 + // 注意 ensureFirstItemAsParent 可能替换 materialDetailList,须用 DO 上最新列表判断 + List listAfter = stockReceiptOrderDO.getMaterialDetailList(); + boolean skipInjectAfterAssign = firstItemHadChildrenBefore + && !CollectionUtils.isEmpty(listAfter) + && !CollectionUtils.isEmpty(listAfter.get(0).getChildren()); + stockReceiptOrderDO.setPdaSkipInjectBatchChildLine(skipInjectAfterAssign); + stockReceiptOrderDO.setFromPda(true); StockReceiptOrderDO assembleStockReceiptOrderDO = assembleParamByReceipt(stockReceiptOrderDO, false); if (CollectionUtils.isEmpty(assembleStockReceiptOrderDO.getMaterialDetailList())) { throw new ServiceException("无有效收货数据,请确认已填写收货数量(收货数量不能全部为0)"); } - assembleStockReceiptOrderDO.setFromPda(true); // PDA收货完成,上架单是否下发=是 + assembleStockReceiptOrderDO.setFromPda(true); // PDA收货完成,上架单是否下发=是(与装配前一致) return stockReceiptOrderDomainService.receivingGoods(assembleStockReceiptOrderDO); } @@ -1445,13 +1462,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=2 在库中 quantity 常为 0,不能再用单行 plan-already 校验,须按计划父行汇总已收。 + */ private void validateOverPlanNode(ReceiptMaterialDetailDO item, Map dbById, - Map dbByUniqueId) { + Map dbByUniqueId, + List dbList) { if (item == null) { return; } @@ -1463,8 +1484,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; + ReceiptMaterialDetailPO planParent = resolveReceiptPlanParentRow(db, dbList); + BigDecimal plan = planParent != null && planParent.getQuantity() != null + ? planParent.getQuantity() : BigDecimal.ZERO; + BigDecimal already = sumAlreadyReceiptUnderPlanParent(planParent, dbList); BigDecimal pending = plan.subtract(already); if (pending.compareTo(BigDecimal.ZERO) < 0) { pending = BigDecimal.ZERO; @@ -1476,11 +1499,84 @@ public class StockReceiptOrderApplicationService { } if (!CollectionUtils.isEmpty(item.getChildren())) { for (ReceiptMaterialDetailDO child : item.getChildren()) { - validateOverPlanNode(child, dbById, dbByUniqueId); + validateOverPlanNode(child, dbById, dbByUniqueId, dbList); } } } + /** 分批收货:计划数量在 level=1 父行;子行 parent_unique_id 指向父 receipt.unique_id 或 in_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_unique_id 指向父)的已收数量之和 */ + private static BigDecimal sumAlreadyReceiptUnderPlanParent(ReceiptMaterialDetailPO planParent, + List dbList) { + if (planParent == null || CollectionUtils.isEmpty(dbList)) { + return BigDecimal.ZERO; + } + Long uid = planParent.getUniqueId(); + Long inUid = planParent.getInUniqueId(); + BigDecimal sum = BigDecimal.ZERO; + for (ReceiptMaterialDetailPO x : dbList) { + if (x == null) { + continue; + } + boolean sameRow = Objects.equals(planParent.getMaterialDetailId(), x.getMaterialDetailId()); + boolean child = x.getLevel() != null && x.getLevel() == 2 && x.getParentUniqueId() != null + && ((uid != null && uid.equals(x.getParentUniqueId())) + || (inUid != null && inUid.equals(x.getParentUniqueId()))); + if (sameRow || child) { + BigDecimal ar = x.getAlreadyReceiptQuantity() != null ? x.getAlreadyReceiptQuantity() : BigDecimal.ZERO; + sum = sum.add(ar); + } + } + return sum; + } + + /** + * 首笔收货(计划父行及子行累计已收为 0、且尚无 level=2 子行)不拆批;第二笔起再 INSERT 子行。 + */ + 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; + } + /** * 收货时:将 children 中第一条的值赋给主项,并删除第一条。 * PDA 收两条时只生成 2 条(主项 + 第二条子项),主项展示第一条子项的数据。 @@ -1558,6 +1654,196 @@ public class StockReceiptOrderApplicationService { stockReceiptOrderDO.setMaterialDetailList(newList); } + /** + * PDA 单次仅传主行(无 children)时,按业务可拆成一条 level=2 子行(方案二)。 + * 首笔收货仅落主行不拆子行;自第二笔起(计划父行+子行已有累计实收,或已存在子行)再拆新子行。 + * 主行上的本次数量、毛/净重、体/面积实绩迁移到子行;主行本次字段清零,避免与 assemble 内主行+子行汇总重复。 + * 支持:有件数无称重、仅有称重无件数(与 pickThisReceiptMetricIncrement 口径一致)。 + */ + private void injectPdaFlatReceiptAsSyntheticChild(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO dbLine, + List allDbLines) { + if (line == null || dbLine == null) { + return; + } + // PDA 详情无真实子行时也会挂 parent_copy(与主行 uniqueId 相同),需忽略后才能走拆批 + stripPdaUiParentCopyChildren(line, dbLine); + // getInfoByApp 会把已落库的分批子行挂在 children 里展示;再次收货时本次增量在主行 receiptQuantity,须去掉这些「历史子行」否则 inject 误判已有子项而不再拆新行 + stripPdaExistingDisplayedChildrenForResplit(line, dbLine, allDbLines); + // 历史误把主行 UPDATE 成 level=2 且 parent_unique_id=0:仍应按主行拆出新子行并回写纠正 + 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(); + // 勿拷贝 level/parentUniqueId:PDA 详情里 parent_copy 常为 level=2,若带到子行或残留主行会把主行 UPDATE 成子行,无法 INSERT 新子行 + BeanUtils.copyProperties(line, child, "children", "materialDetailId", "uniqueId", "level", "parentUniqueId"); + child.setChildren(null); + child.setLevel(2); + child.setParentUniqueId(dbLine.getUniqueId()); + // 子行本次实绩:与主行请求一致;累计字段清空,由 assemble 按本批次重算 + 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); + } + + /** + * 去掉 getInfoByApp 为展示挂的 parent_copy:子项与主行 uniqueId 相同,并非真实收货子行。 + */ + 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); + } + if (realOnly.isEmpty()) { + line.setChildren(null); + } else { + line.setChildren(realOnly); + } + } + + /** + * 分批收货第二笔及以后:详情接口会把已存在的 level=2 子行带回 children,仅用于展示。 + * 本次提交仍以主行 receiptQuantity 为增量并应再 INSERT 新子行,故从请求中移除这些历史子行,避免 inject 因 children 非空而跳过。 + */ + private static void stripPdaExistingDisplayedChildrenForResplit(ReceiptMaterialDetailDO line, + ReceiptMaterialDetailPO dbLine, + List allDbLines) { + if (line == null || CollectionUtils.isEmpty(line.getChildren()) || dbLine == null) { + return; + } + if (CollectionUtils.isEmpty(allDbLines)) { + return; + } + Long pUid = dbLine.getUniqueId(); + Long pInUid = dbLine.getInUniqueId(); + Set historyUniqueIds = new HashSet<>(); + Set historyDetailIds = new HashSet<>(); + for (ReceiptMaterialDetailPO x : allDbLines) { + if (x == null || x.getLevel() == null || x.getLevel() != 2 || x.getParentUniqueId() == null) { + continue; + } + boolean under = (pUid != null && pUid.equals(x.getParentUniqueId())) + || (pInUid != null && pInUid.equals(x.getParentUniqueId())); + if (!under) { + continue; + } + if (x.getUniqueId() != null) { + historyUniqueIds.add(x.getUniqueId()); + } + if (x.getMaterialDetailId() != null) { + historyDetailIds.add(x.getMaterialDetailId()); + } + } + if (historyUniqueIds.isEmpty() && historyDetailIds.isEmpty()) { + return; + } + List kept = new ArrayList<>(); + for (ReceiptMaterialDetailDO c : line.getChildren()) { + if (c == null) { + continue; + } + boolean history = (c.getUniqueId() != null && historyUniqueIds.contains(c.getUniqueId())) + || (c.getMaterialDetailId() != null && historyDetailIds.contains(c.getMaterialDetailId())); + if (history) { + continue; + } + kept.add(c); + } + if (kept.isEmpty()) { + line.setChildren(null); + } else { + line.setChildren(kept); + } + } + + /** 库中 level=2 但无有效父级,实为被误更新的顶层计划行(旧逻辑污染) */ + private static boolean isCorruptedStandaloneLineAsLevel2(ReceiptMaterialDetailPO db) { + if (db == null || db.getLevel() == null || db.getLevel() != 2) { + return false; + } + Long p = db.getParentUniqueId(); + return p == null || p == 0L; + } + + /** + * 库表主行层级回写到装配结果:level=1;或误标为子行的孤立主行一并纠正为 level=1。 + */ + private static void preserveParentReceiptLineHierarchyFromDb(ReceiptMaterialDetailPO dbLine, + ReceiptMaterialDetailDO merged) { + if (dbLine == null || merged == null) { + return; + } + if (dbLine.getLevel() != null && dbLine.getLevel() == 1) { + merged.setLevel(1); + merged.setParentUniqueId(dbLine.getParentUniqueId()); + } else if (isCorruptedStandaloneLineAsLevel2(dbLine)) { + merged.setLevel(1); + merged.setParentUniqueId(null); + } + } + + /** 主行相对 DB 是否本次在毛/净重/体积/面积上有增量(与 assemble 中 hasReceiptByMetrics 一致) */ + private static boolean hasPdaInjectMetricIncrement(ReceiptMaterialDetailDO line, ReceiptMaterialDetailPO db) { + if (line == null || db == null) { + return false; + } + BigDecimal dbG = db.getAlreadyReceiptGrossWeight() != null ? db.getAlreadyReceiptGrossWeight() : BigDecimal.ZERO; + BigDecimal dbN = db.getAlreadyReceiptNetWeight() != null ? db.getAlreadyReceiptNetWeight() : BigDecimal.ZERO; + BigDecimal dbV = db.getAlreadyReceiptVolume() != null ? db.getAlreadyReceiptVolume() : BigDecimal.ZERO; + BigDecimal dbA = db.getAlreadyReceiptArea() != null ? db.getAlreadyReceiptArea() : BigDecimal.ZERO; + return pickThisReceiptMetricIncrement(line.getReceiptGrossWeight(), line.getTotalGrossWeight(), dbG).compareTo(BigDecimal.ZERO) > 0 + || pickThisReceiptMetricIncrement(line.getReceiptNetWeight(), line.getTotalNetWeight(), dbN).compareTo(BigDecimal.ZERO) > 0 + || pickThisReceiptMetricIncrement(line.getReceiptVolume(), line.getTotalVolume(), dbV).compareTo(BigDecimal.ZERO) > 0 + || pickThisReceiptMetricIncrement(line.getReceiptArea(), line.getTotalArea(), dbA).compareTo(BigDecimal.ZERO) > 0; + } + /** * @description 完成收货(PC端) * @author ZhouGY @@ -1898,6 +2184,7 @@ public class StockReceiptOrderApplicationService { ? receiptMaterialDetailDO.getAlreadyReceiptQuantity() : BigDecimal.ZERO; returnReceiptMaterialDetailDO.setAlreadyReceiptQuantity(parentRetAlreadyQty); returnReceiptMaterialDetailDO.setReceiptQuantity(parentRetAlreadyQty); + preserveParentReceiptLineHierarchyFromDb(receiptMaterialDetailPO, returnReceiptMaterialDetailDO); if (receiptMaterialDetailPO.getReceiptStatus() == 1) { if (receiptMaterialDetailPO.getSerialNumberManage() == 1) { if (CollectionUtils.isEmpty(receiptMaterialDetailDO.getMaterialDetailSerialNumberList())) { @@ -1971,6 +2258,11 @@ public class StockReceiptOrderApplicationService { receiptMaterialDetailDbMapByUniqueId, returnReceiptMaterialDetailDOList, wholeOrderReceiptQtySumHolder, materialKindsCompleteHolder); continue; } + // 方案二(子行拆批):PDA 仅回传主行本次量、无 children 时,拆成一条 level=2 子行落库,主行本次量清零,避免与 groupReceived 口径重复累加 + if (Boolean.TRUE.equals(stockReceiptOrderDO.getFromPda()) + && !Boolean.TRUE.equals(stockReceiptOrderDO.getPdaSkipInjectBatchChildLine())) { + injectPdaFlatReceiptAsSyntheticChild(receiptMaterialDetailDO, receiptMaterialDetailPO, receiptMaterialDetailPODbList); + } // receiptQuantity 空值保护,PDA 未传时按 0 处理 BigDecimal receiptQuantity = receiptMaterialDetailDO.getReceiptQuantity(); if (receiptQuantity == null) { @@ -2205,6 +2497,8 @@ public class StockReceiptOrderApplicationService { ? receiptMaterialDetailDO.getAlreadyReceiptQuantity() : BigDecimal.ZERO; returnReceiptMaterialDetailDO.setAlreadyReceiptQuantity(parentRetAlreadyQty); returnReceiptMaterialDetailDO.setReceiptQuantity(parentRetAlreadyQty); + // PDA getInfoByApp 下挂的 parent_copy(level=2) 回传后会被合并到主行 DO,勿让 request 覆盖库里的主项 level=1,否则 saveOrUpdate 只 UPDATE 原行、不会出现新子行 + preserveParentReceiptLineHierarchyFromDb(receiptMaterialDetailPO, returnReceiptMaterialDetailDO); if (receiptMaterialDetailPO.getReceiptStatus() == 1){ //一级列表 首次收货 //是否开启了序列号管理 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..4583a81e3 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,16 @@ public interface IReceiptMaterialDetailService extends IService queryListChildren(ReceiptMaterialDetailDO receiptMaterialDetailDO); + /** + * PDA:按收货单号 + 父明细主键查询该计划行下所有 level=2 子行(分批收货履历),含批次属性合并。 + * 若 {@code parentMaterialDetailId} 对应库中为子行,会先解析为其所属计划父行再查子集。 + * 若未拆出任何子行但主计划行上已有实收,则返回一条与主行一致的快照,便于「已收货列表」展示。 + * + * @param receiptOrderNumber 收货单号 + * @param parentMaterialDetailId 父卡片对应 receipt_material_detail.material_detail_id + */ + 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 191d43ded..6dc70375b 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 子行时:主行上仍有累计实收,列表需在子行之外再带一条「主行首笔」快照(否则只剩子行 500,看不到主行 200) + 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/stockReceiptOrder/repository/todo/StockReceiptOrderDO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/repository/todo/StockReceiptOrderDO.java index 612ad1a7c..e6b8163b8 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/repository/todo/StockReceiptOrderDO.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/repository/todo/StockReceiptOrderDO.java @@ -171,6 +171,12 @@ public class StockReceiptOrderDO extends StockInOrderBaseDO { @ApiModelProperty("是否来自PDA收货:true-PDA收货完成,false-PC收货完成(用于上架单是否下发:PDA=是,PC=否)") private Boolean fromPda; + /** + * PDA 已执行 assignFirstChildToParentAndRemove(首条子项已合并到主项),禁止再将主项本次量拆成新子行,避免与子项重复。 + */ + @ApiModelProperty(hidden = true) + private Boolean pdaSkipInjectBatchChildLine; + /** * PC 收货确认:以页面提交数据为全量实绩覆盖落库(由 Application 层根据入口设置,非前端传入) */ diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/service/StockReceiptOrderDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/service/StockReceiptOrderDomainService.java index b527af8be..3a50a96e6 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/service/StockReceiptOrderDomainService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockReceiptOrder/service/StockReceiptOrderDomainService.java @@ -256,6 +256,34 @@ public class StockReceiptOrderDomainService { return stockReceiptOrderPO; } + /** + * PDA:指定计划行(卡片)下所有 level=2 分批收货子行(库表真实子行,不含 parent_copy),附带序列号。 + * + * @param receiptOrderId 收货单主键 + * @param parentMaterialDetailId 卡片对应 receipt_material_detail.material_detail_id(可误传为某子行 id,将解析到其父计划行) + */ + public List 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 收货明细 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 f4fde3d96..4e71b6abf 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,19 @@ public class StockReceiptOrderAppApi extends BaseController { return AjaxResult.success(stockReceiptOrderApplicationService.getInfoByApp(receiptOrderId, receiptStatus)); } + /** + * PDA:查询当前卡片(计划行)下全部分批收货子行(level=2,库表真实子行,无 parent_copy) + */ + @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); + } + /** * 收货 */