From a290399ad89d6996d1ebf9e953037d8fdde4e900 Mon Sep 17 00:00:00 2001 From: aike <17730485278@139.com> Date: Fri, 27 Jun 2025 16:38:10 +0800 Subject: [PATCH] =?UTF-8?q?demand:=20=20=E7=BB=84=E6=A0=91=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=A2=9E=E5=8A=A0=E6=A0=B9=E6=8D=AE=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BF=87=E6=BB=A4=EF=BC=8C=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E6=8E=A5=E5=8F=A3=E9=80=82=E5=BA=94=E6=89=93?= =?UTF-8?q?=E6=95=A3=E7=9A=84=E4=BA=A7=E5=93=81=E7=B1=BB=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E7=BB=84=E6=89=B9=E6=AC=A1=E4=BE=9B=E5=BA=94=E5=95=86=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E4=B8=8D=E5=87=86=E7=A1=AE=E4=BF=AE=E5=A4=8D=EF=BC=8C?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=8E=A5=E5=8F=A3=E9=87=8D=E5=A4=8D=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=20committer:=20heyu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../erp/emis/service/EmisBaseService.java | 180 ++++++++++++------ .../impl/EmisTmsSiteBatchMissServiceImpl.java | 4 +- .../impl/EmisTransPlanRuleServiceImpl.java | 77 +++++++- .../resources/mapper/EmisSupplierMapper.xml | 4 +- .../mapper/EmisTransPlanRuleMapper.xml | 4 +- 5 files changed, 200 insertions(+), 69 deletions(-) diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java index 3e517bad0..d3f955d64 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java @@ -4011,7 +4011,7 @@ public class EmisBaseService { return weekDays; } - public List buildTree(List allRules) { + public List buildTree(List allRules, EmisTransPlanRule emisTransPlanRule) { // 按lineCode分组 Map> groupByLine = allRules.stream() .collect(Collectors.groupingBy(EmisTransPlanRule::getLineCode)); @@ -4067,6 +4067,19 @@ public class EmisBaseService { entry.setValue(deduped); } + Map> tempMap = new HashMap<>(); + if (StringUtil.isNotBlank(emisTransPlanRule.getProductType())) { + Iterator>> iterator = groupByProductType.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + if (emisTransPlanRule.getProductType().equals(entry.getKey())) { + tempMap.put(entry.getKey(), entry.getValue()); + groupByProductType = tempMap; + break; + } + } + } + // 处理每个productType分组 for (Map.Entry> productEntry : groupByProductType.entrySet()) { List productRules = productEntry.getValue(); @@ -4527,7 +4540,7 @@ public class EmisBaseService { public List selectEmisTransPlanRuleTree(List lineCodes) { log.info("Start querying supplier route plan tree in EmisBaseService"); List list = emisTransPlanRuleMapper.selectEmisTransPlanRuleByLineCodes(lineCodes); - List tree = buildTree(list); + List tree = buildTree(list, new EmisTransPlanRule()); log.info("Queried supplier route plan tree in EmisBaseService, node count={}", tree != null ? tree.size() : 0); return tree; } @@ -4773,68 +4786,127 @@ public class EmisBaseService { return hasCurrentLevelMatch && hasNextLevelMatch; } + /** + * 新版漏组分组与筛选逻辑: + * 1. 先将rule从根节点拆成每个children只有一个的所有路径(allPaths)。 + * 2. 统计每条path的匹配度(path上所有节点与missRecords的匹配数之和)。 + * 3. 过滤出匹配度最高的path(可多个)。 + * 4. 对这些path的节点分组去重并做漏组处理。 + */ private void setMissRecords(List missRecords, EmisTransPlanRule rule, List filterRules, EmisWaybill waybill) { - // 1. 递归收集所有节点,按ancestors长度分组 - Map> levelMap = new HashMap<>(); - collectByLevel(rule, levelMap); + // 1. 获取所有路径 + List> allPaths = new ArrayList<>(); + getAllPaths(rule, new ArrayList<>(), allPaths); - // 2. 逐级处理 - for (Map.Entry> entry : levelMap.entrySet()) - { - List levelRules = entry.getValue(); - boolean matched = false; - for (EmisTransPlanRule levelRule : levelRules) { - boolean isMatched = filterRules.stream() - .anyMatch(filterRule -> - filterRule.getStartSiteCode().equals(levelRule.getStartSiteCode()) && - filterRule.getNextSiteCode().equals(levelRule.getNextSiteCode()) && - compareSupplierCodes(filterRule.getSupplierCode(), - levelRule.getSupplierCode())); - if (isMatched) { - matched = true; - break; + // 2. 统计每条路径的匹配度 + Map, Integer> pathMatchCountMap = new HashMap<>(); + for (List path : allPaths) { + int pathMatchCount = 0; + for (EmisTransPlanRule node : path) { + for (EmisTransPlanRule filterRule : filterRules) { + if (Objects.equals(node.getStartSiteCode(), filterRule.getStartSiteCode()) && + Objects.equals(node.getNextSiteCode(), filterRule.getNextSiteCode()) && + compareSupplierCodes(node.getSupplierCode(), filterRule.getSupplierCode())) { + pathMatchCount++; + } } } - if (!matched) { - // 该级无匹配,只生成一条漏组记录,字段用逗号拼接 - String startSiteCodes = - levelRules.stream().map(EmisTransPlanRule::getStartSiteCode).distinct() - .collect(Collectors.joining(",")); - String nextSiteCodes = - levelRules.stream().map(EmisTransPlanRule::getNextSiteCode).distinct() - .collect(Collectors.joining(",")); - String supplierCodes = - levelRules.stream().map(EmisTransPlanRule::getSupplierCode).distinct() - .collect(Collectors.joining(",")); - String managers = - levelRules.stream().map(EmisTransPlanRule::getManager).filter(Objects::nonNull) - .distinct().collect(Collectors.joining(",")); - String missReason = levelRules.stream() - .map(r -> String.format("%s-%s-%s", r.getStartSiteName(), - r.getNextSiteName(), r.getSupplierName())) - .distinct() - .collect(Collectors.joining(" 或 ")); - EmisTmsSiteBatchMiss miss = createMissRecord(waybill); - miss.setMissReason("组批次有遗漏/错误,请根据推荐路线排查: " + missReason); - miss.setStartSiteCode(startSiteCodes); - miss.setNextSiteCode(nextSiteCodes); - miss.setSupplierCode(supplierCodes); - miss.setManager(managers); - // 其他字段取第一个节点 - EmisTransPlanRule first = levelRules.get(0); - miss.setTransPlanRuleId(first.getId()); - miss.setLineCode(first.getLineCode()); - miss.setProductType(first.getProductType()); - miss.setStartDate(first.getStartDate()); - miss.setEndDate(first.getEndDate()); - miss.setMissType("0"); - missRecords.add(miss); + pathMatchCountMap.put(path, pathMatchCount); + } + + // 3. 过滤出匹配度最高的path(可多个) + int maxMatch = pathMatchCountMap.values().stream().max(Integer::compareTo).orElse(0); + List> bestPaths = pathMatchCountMap.entrySet().stream() + .filter(e -> e.getValue() == maxMatch) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + // 4. 对这些path的节点分组去重并做漏组处理 + // 先收集bestPaths中的所有节点 + List allNodes = new ArrayList<>(); + for (List path : bestPaths) { + allNodes.addAll(path); + } + + // 按ancestors长度分组去重 + Map> levelMap = new HashMap<>(); + for (EmisTransPlanRule node : allNodes) { + int level = 0; + if (node.getAncestors() != null && !node.getAncestors().isEmpty()) { + level = node.getAncestors().split(",").length; + } + levelMap.computeIfAbsent(level, k -> new ArrayList<>()).add(node); + } + + // 每一级仅按ID去重(不再合并供应商) + for (List rules : levelMap.values()) { + List distinct = rules.stream().collect(Collectors.collectingAndThen( + Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(EmisTransPlanRule::getId))), + ArrayList::new)); + rules.clear(); + rules.addAll(distinct); + } + + // 每一级与filterRules做比较,生成漏组记录 + for (Map.Entry> entry : levelMap.entrySet()) { + List levelRules = entry.getValue(); + // 按 startSiteCode + nextSiteCode 分组 + Map> groupMap = new HashMap<>(); + for (EmisTransPlanRule ruleItem : levelRules) { + String key = ruleItem.getStartSiteCode() + "_" + ruleItem.getNextSiteCode(); + groupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(ruleItem); + } + for (List group : groupMap.values()) { + // 判断该组是否有匹配 + boolean matched = group.stream().anyMatch(levelRule -> filterRules.stream().anyMatch( + filterRule -> Objects.equals(filterRule.getStartSiteCode(), levelRule.getStartSiteCode()) && + Objects.equals(filterRule.getNextSiteCode(), levelRule.getNextSiteCode()) && + compareSupplierCodes(filterRule.getSupplierCode(), levelRule.getSupplierCode()))); + if (!matched && !group.isEmpty()) { + // 聚合supplierCode、supplierName等 + String supplierCodes = group.stream().map(EmisTransPlanRule::getSupplierCode) + .filter(Objects::nonNull).distinct().collect(Collectors.joining(",")); + String supplierNames = group.stream().map(EmisTransPlanRule::getSupplierName) + .filter(Objects::nonNull).distinct().collect(Collectors.joining(",")); + EmisTransPlanRule first = group.get(0); + String missReason = String.format("%s-%s-%s", first.getStartSiteName(), + first.getNextSiteName(), supplierNames); + EmisTmsSiteBatchMiss miss = createMissRecord(waybill); + miss.setMissReason("组批次有遗漏/错误,请根据推荐路线排查: " + missReason); + miss.setStartSiteCode(first.getStartSiteCode()); + miss.setNextSiteCode(first.getNextSiteCode()); + miss.setSupplierCode(supplierCodes); + miss.setManager(first.getManager()); + miss.setTransPlanRuleId(first.getId()); + miss.setLineCode(first.getLineCode()); + miss.setProductType(first.getProductType()); + miss.setStartDate(first.getStartDate()); + miss.setEndDate(first.getEndDate()); + miss.setMissType("0"); + missRecords.add(miss); + } } } } + // 辅助方法:递归获取所有从根到叶子的路径 + private void getAllPaths(EmisTransPlanRule node, List current, + List> result) { + if (node == null) + return; + current.add(node); + if (node.getChildren() == null || node.getChildren().isEmpty()) { + result.add(new ArrayList<>(current)); + } else { + for (EmisTransPlanRule child : node.getChildren()) { + getAllPaths(child, current, result); + } + } + current.remove(current.size() - 1); + } + // 辅助方法:递归收集所有节点,按ancestors长度分组 private void collectByLevel(EmisTransPlanRule node, Map> levelMap) { int level = 0; diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTmsSiteBatchMissServiceImpl.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTmsSiteBatchMissServiceImpl.java index e7f22f735..4a52fcc53 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTmsSiteBatchMissServiceImpl.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTmsSiteBatchMissServiceImpl.java @@ -7,12 +7,10 @@ import java.util.concurrent.Future; import java.util.concurrent.Executors; import cn.hutool.core.collection.CollectionUtil; -import com.beust.jcommander.internal.Lists; import com.xdadan.erp.emis.domain.*; import com.xdadan.erp.emis.mapper.*; import com.xdadan.erp.emis.service.EmisBaseService; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -304,7 +302,7 @@ public class EmisTmsSiteBatchMissServiceImpl extends EmisBaseService implements } } executor.shutdown(); - List tree = buildTree(allRules); + List tree = buildTree(allRules, new EmisTransPlanRule()); log.info("Queried supplier route plan tree, node count={}", tree != null ? tree.size() : 0); return tree; } diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTransPlanRuleServiceImpl.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTransPlanRuleServiceImpl.java index f933798a9..fa8a0fdbb 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTransPlanRuleServiceImpl.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTransPlanRuleServiceImpl.java @@ -123,7 +123,7 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm } // 构建树形结构 - return buildTree(allRules); + return buildTree(allRules, emisTransPlanRule); } @Override @@ -206,21 +206,82 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm } private void checkDuplicate(EmisTransPlanRule emisTransPlanRule) throws EmisBizError { - List existingRules = emisTransPlanRuleMapper.checkDuplicate(emisTransPlanRule); + // 检查正向规则(起始网点到下一网点) + List existingRules = checkDuplicateDirection(emisTransPlanRule, + emisTransPlanRule.getStartSiteCode(), emisTransPlanRule.getNextSiteCode()); if (existingRules != null && !existingRules.isEmpty()) { throw new EmisBizError(EmisBizErrorType.EXISTS, "已存在相同的规则配置"); } - EmisTransPlanRule emisTransPlanRule1 = new EmisTransPlanRule(); - emisTransPlanRule1.setLineCode(emisTransPlanRule.getLineCode()); - emisTransPlanRule1.setProductType(emisTransPlanRule.getProductType()); - emisTransPlanRule1.setStartSiteCode(emisTransPlanRule.getNextSiteCode()); - emisTransPlanRule1.setNextSiteCode(emisTransPlanRule.getStartSiteCode()); - List existingRules1 = emisTransPlanRuleMapper.checkDuplicate(emisTransPlanRule); + + // 检查反向规则(下一网点到起始网点) + List existingRules1 = checkDuplicateDirection(emisTransPlanRule, + emisTransPlanRule.getNextSiteCode(), emisTransPlanRule.getStartSiteCode()); if (existingRules1 != null && !existingRules1.isEmpty()) { throw new EmisBizError(EmisBizErrorType.EXISTS, "已存在相同的规则配置"); } } + private List checkDuplicateDirection(EmisTransPlanRule emisTransPlanRule, + String startSiteCode, String nextSiteCode) { + // 构建查询条件 + EmisTransPlanRule queryRule = new EmisTransPlanRule(); + queryRule.setLineCode(emisTransPlanRule.getLineCode()); + queryRule.setStartSiteCode(startSiteCode); + queryRule.setNextSiteCode(nextSiteCode); + queryRule.setStartDate(emisTransPlanRule.getStartDate()); + queryRule.setEndDate(emisTransPlanRule.getEndDate()); + + // 查询所有匹配的规则 + List allRules = emisTransPlanRuleMapper.checkDuplicate(queryRule); + + if (allRules == null || allRules.isEmpty()) { + return new ArrayList<>(); + } + + // 检查productType和supplierCode是否有交集 + String newProductType = emisTransPlanRule.getProductType(); + String newSupplierCode = emisTransPlanRule.getSupplierCode(); + + for (EmisTransPlanRule existingRule : allRules) { + // 检查产品类型是否有交集 + boolean productTypeOverlap = hasOverlap(newProductType, existingRule.getProductType()); + // 检查供应商编码是否有交集 + boolean supplierCodeOverlap = hasOverlap(newSupplierCode, existingRule.getSupplierCode()); + + // 如果产品类型和供应商编码都有交集,则认为重复 + if (productTypeOverlap && supplierCodeOverlap) { + return Arrays.asList(existingRule); + } + } + + return new ArrayList<>(); + } + + /** + * 检查两个逗号分隔的字符串是否有交集 + * + * @param str1 第一个字符串(逗号分隔) + * @param str2 第二个字符串(逗号分隔) + * @return 是否有交集 + */ + private boolean hasOverlap(String str1, String str2) { + if (StringUtil.isBlank(str1) || StringUtil.isBlank(str2)) { + return false; + } + + Set set1 = new HashSet<>(Arrays.asList(str1.split(","))); + Set set2 = new HashSet<>(Arrays.asList(str2.split(","))); + + // 检查是否有交集 + for (String item : set1) { + if (set2.contains(item.trim())) { + return true; + } + } + + return false; + } + private static void checkName(EmisTransPlanRule emisTransPlanRule) throws EmisBizError { if (StringUtil.isBlank(emisTransPlanRule.getManager())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "节点负责人不能为空"); diff --git a/emis-biz/src/main/resources/mapper/EmisSupplierMapper.xml b/emis-biz/src/main/resources/mapper/EmisSupplierMapper.xml index cf0e30d22..19fc28ed4 100644 --- a/emis-biz/src/main/resources/mapper/EmisSupplierMapper.xml +++ b/emis-biz/src/main/resources/mapper/EmisSupplierMapper.xml @@ -260,11 +260,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"