diff --git a/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisTransPlanRuleController.java b/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisTransPlanRuleController.java index 84cd5e2bc..cc1acc16a 100644 --- a/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisTransPlanRuleController.java +++ b/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisTransPlanRuleController.java @@ -128,9 +128,23 @@ public class EmisTransPlanRuleController extends EmisBaseController { /** * 查询供应商线路规划树结构 */ - @PreAuthorize("@ss.hasPermi('emis:emisTransPlanRule:tree')") + // @PreAuthorize("@ss.hasPermi('emis:emisTransPlanRule:tree')") @GetMapping("/tree") public TableDataInfo tree(EmisTransPlanRule emisTransPlanRule) { return getDataTable(emisTransPlanRuleService.selectEmisTransPlanRuleTree(emisTransPlanRule)); } + + /** + * 校验规则是否能组树 + */ + // @PreAuthorize("@ss.hasPermi('emis:emisTransPlanRule:tree')") + @GetMapping("/checkTree") + public TableDataInfo checkTree(EmisTransPlanRule emisTransPlanRule) { + List cycles = emisTransPlanRuleService.checkTree(emisTransPlanRule); + if (cycles == null || cycles.isEmpty()) { + cycles = new java.util.ArrayList<>(); + cycles.add("未检测到循环引用"); + } + return getDataTable(cycles); + } } \ No newline at end of file diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/mapper/EmisTransPlanRuleMapper.java b/emis-biz/src/main/java/com/xdadan/erp/emis/mapper/EmisTransPlanRuleMapper.java index 2194c4b64..249f47942 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/mapper/EmisTransPlanRuleMapper.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/mapper/EmisTransPlanRuleMapper.java @@ -173,4 +173,11 @@ public interface EmisTransPlanRuleMapper { * @return */ List checkTransPlanRule(EmisTransPlanRule emisTransPlanRule); + + /** + * + * @param emisTransPlanRule + * @return + */ + List selectEmisTransPlanRuleListWithoutName(EmisTransPlanRule emisTransPlanRule); } \ No newline at end of file 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..af9a009a4 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(); @@ -4191,46 +4204,69 @@ public class EmisBaseService { List cyclePath = visitPath.subList(cycleStartIndex, visitPath.size()); cyclePath.add(source.getId()); - // 构建详细的错误信息 + // 构建详细的错误信息 StringBuilder errorMsg = new StringBuilder(); errorMsg.append("检测到循环引用!循环路径:"); for (int i = 0; i < cyclePath.size(); i++) { if (i > 0) { errorMsg.append(" -> "); } - errorMsg.append("节点ID:").append(cyclePath.get(i)); + Long id = cyclePath.get(i); + if (id == null) { + continue; + } + EmisTransPlanRule node = findNodeById(id); + if (node != null) { + errorMsg.append(node.getLineName()).append("-") + .append(node.getProductTypeName()).append("-") + .append(node.getStartSiteName()).append("-") + .append(node.getNextSiteName()).append("-") + .append(node.getSupplierName()); + } else { + errorMsg.append("节点ID:").append(id); + } } - errorMsg.append("\n循环引用发生在节点ID:").append(source.getId()); - errorMsg.append(",该节点在路径中重复出现"); - +// errorMsg.append("\n循环引用发生在节点,详细信息见上"); throw new RuntimeException(errorMsg.toString()); } } - // 添加到已访问集合和路径中 - visitedIds.add(source.getId()); - visitPath.add(source.getId()); + // 添加到已访问集合和路径中 + visitedIds.add(source.getId()); + visitPath.add(source.getId()); - EmisTransPlanRule copy = new EmisTransPlanRule(); - BeanUtils.copyProperties(source, copy); + EmisTransPlanRule copy = new EmisTransPlanRule(); + BeanUtils.copyProperties(source, copy); - // 为每个新节点生成唯一treeId - copy.setTreeId(com.xdadan.erp.common.utils.idgen.UUID.fastUUID().toString()); + // 为每个新节点生成唯一treeId + copy.setTreeId(com.xdadan.erp.common.utils.idgen.UUID.fastUUID().toString()); - // 递归复制子节点 - if (source.getChildren() != null) { - List copiedChildren = new ArrayList<>(); - for (EmisTransPlanRule child : source.getChildren()) { - copiedChildren.add(deepCopyNode(child, visitedIds, visitPath)); - } - copy.setChildren(copiedChildren); - } + // 递归复制子节点 + if (source.getChildren() != null) { + List copiedChildren = new ArrayList<>(); + for (EmisTransPlanRule child : source.getChildren()) { + copiedChildren.add(deepCopyNode(child, visitedIds, visitPath)); + } + copy.setChildren(copiedChildren); + } - // 从路径中移除当前节点(回溯) - visitPath.remove(visitPath.size() - 1); + // 从路径中移除当前节点(回溯) + visitPath.remove(visitPath.size() - 1); - return copy; - } + return copy; + } + + // 新增方法:根据ID查找节点 + private EmisTransPlanRule findNodeById(Long id) { + // 由于deepCopyNode递归时无法直接访问所有节点,这里可通过全量查询实现,实际生产建议优化 + List allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleList(new EmisTransPlanRule()); + for (EmisTransPlanRule rule : allRules) { + if (rule.getId() != null && rule.getId().equals(id)) { + return rule; + } + } + return null; + } /** * 检查月结客户首次签约日期,首次签约之前开的不允许修改 @@ -4527,7 +4563,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 +4809,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/IEmisTransPlanRuleService.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/IEmisTransPlanRuleService.java index 374ba6c06..d2436be51 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/service/IEmisTransPlanRuleService.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/IEmisTransPlanRuleService.java @@ -74,4 +74,12 @@ public interface IEmisTransPlanRuleService { * @return 结果 */ int draftSubmitRule(EmisTransPlanRule emisTransPlanRule) throws EmisBizError; + + /** + * 校验供应商线路规划树结构中的循环引用 + * + * @param emisTransPlanRule 查询条件 + * @return 循环引用提示信息列表 + */ + List checkTree(EmisTransPlanRule emisTransPlanRule); } \ No newline at end of file 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..4bc8ed11e 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 @@ -82,6 +82,12 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm checkDate(emisTransPlanRule); // 检查重复 checkDuplicate(emisTransPlanRule); + // 校验循环引用 + EmisTransPlanRule checkRule = new EmisTransPlanRule(); + checkRule.setProductType(emisTransPlanRule.getProductType()); + List allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleListWithoutName(checkRule); + allRules.add(emisTransPlanRule); + buildTree(allRules, emisTransPlanRule); return emisTransPlanRuleMapper.insertEmisTransPlanRule(emisTransPlanRule); } @@ -92,7 +98,7 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm * @return 结果 */ @Override - public int updateEmisTransPlanRule(EmisTransPlanRule emisTransPlanRule) { + public int updateEmisTransPlanRule(EmisTransPlanRule emisTransPlanRule) throws EmisBizError { return emisTransPlanRuleMapper.updateEmisTransPlanRule(emisTransPlanRule); } @@ -123,7 +129,7 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm } // 构建树形结构 - return buildTree(allRules); + return buildTree(allRules, emisTransPlanRule); } @Override @@ -150,13 +156,19 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm // 检查重复 checkDuplicate(emisTransPlanRule); - + // 校验循环引用 + EmisTransPlanRule checkRule = new EmisTransPlanRule(); + checkRule.setProductType(emisTransPlanRule.getProductType()); + List allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleListWithoutName(checkRule); + allRules.add(emisTransPlanRule); + buildTree(allRules, emisTransPlanRule); return emisTransPlanRuleMapper.insertEmisTransPlanRule(emisTransPlanRule); } private void nameToCode(EmisTransPlanRule emisTransPlanRule) throws EmisBizError { // 线路 - List> lineInfo = emisTransPlanRuleMapper.selectLineInfoByNames(Collections.singletonList(emisTransPlanRule.getLineName())); + List> lineInfo = emisTransPlanRuleMapper + .selectLineInfoByNames(Collections.singletonList(emisTransPlanRule.getLineName())); if (lineInfo == null || lineInfo.isEmpty()) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "未找到对应的线路:" + emisTransPlanRule.getLineName()); } @@ -164,9 +176,11 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm // 产品类型(支持多个,逗号分隔) String[] productTypeNames = emisTransPlanRule.getProductTypeName().split(","); - List> productTypeInfo = emisTransPlanRuleMapper.selectProductTypeInfoByNamesAndLineCode(Arrays.asList(productTypeNames),lineInfo.get(0).get("code")); + List> productTypeInfo = emisTransPlanRuleMapper + .selectProductTypeInfoByNamesAndLineCode(Arrays.asList(productTypeNames), lineInfo.get(0).get("code")); if (productTypeInfo == null || productTypeInfo.isEmpty()) { - throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "未找到对应的产品类型:" + emisTransPlanRule.getProductTypeName()); + throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, + "未找到对应的产品类型:" + emisTransPlanRule.getProductTypeName()); } if (productTypeInfo.size() != productTypeNames.length) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "部分产品类型未找到:" + emisTransPlanRule.getProductTypeName()); @@ -177,14 +191,16 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm emisTransPlanRule.setProductType(productTypeCodes); // 起始网点 - List> startSiteInfo = emisTransPlanRuleMapper.selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getStartSiteName())); + List> startSiteInfo = emisTransPlanRuleMapper + .selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getStartSiteName())); if (startSiteInfo == null || startSiteInfo.isEmpty()) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "未找到对应的起始网点:" + emisTransPlanRule.getStartSiteName()); } emisTransPlanRule.setStartSiteCode(startSiteInfo.get(0).get("code")); // 下一网点 - List> nextSiteInfo = emisTransPlanRuleMapper.selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getNextSiteName())); + List> nextSiteInfo = emisTransPlanRuleMapper + .selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getNextSiteName())); if (nextSiteInfo == null || nextSiteInfo.isEmpty()) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "未找到对应的下一网点:" + emisTransPlanRule.getNextSiteName()); } @@ -192,7 +208,8 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm // 供应商(支持多个,逗号分隔) String[] supplierNames = emisTransPlanRule.getSupplierName().split(","); - List> supplierInfo = emisTransPlanRuleMapper.selectSupplierInfoByNames(Arrays.asList(supplierNames)); + List> supplierInfo = emisTransPlanRuleMapper + .selectSupplierInfoByNames(Arrays.asList(supplierNames)); if (supplierInfo == null || supplierInfo.isEmpty()) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "未找到对应的供应商:" + emisTransPlanRule.getSupplierName()); } @@ -206,21 +223,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, "节点负责人不能为空"); @@ -232,7 +310,6 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "产品名称不能为空"); } - if (StringUtil.isBlank(emisTransPlanRule.getStartSiteName())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "起始网点名称不能为空"); } @@ -242,7 +319,7 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm if (StringUtil.isBlank(emisTransPlanRule.getSupplierName())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "供应商名称不能为空"); } - if(emisTransPlanRule.getStartSiteName().equals(emisTransPlanRule.getNextSiteName())){ + if (emisTransPlanRule.getStartSiteName().equals(emisTransPlanRule.getNextSiteName())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "同一产品类型下起始网点、下一网点不能相同"); } } @@ -266,7 +343,7 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm if (StringUtil.isBlank(emisTransPlanRule.getSupplierCode())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "供应商名称不能为空"); } - if(emisTransPlanRule.getStartSiteCode().equals(emisTransPlanRule.getNextSiteCode())){ + if (emisTransPlanRule.getStartSiteCode().equals(emisTransPlanRule.getNextSiteCode())) { throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "同一产品类型下起始网点、下一网点不能相同"); } } @@ -285,4 +362,119 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm } } + + + @Override + public List checkTree(EmisTransPlanRule emisTransPlanRule) { + List allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleList(emisTransPlanRule); + return buildTreeForCheck(allRules); + } + + /** + * 仅校验循环引用并收集所有循环引用提示 + * + * @param allRules 所有规则 + * @return 循环引用提示信息列表 + */ + public List buildTreeForCheck(List allRules) { + List cycleMessages = new ArrayList<>(); + // 按lineCode+productType分组 + Map> groupMap = new HashMap<>(); + for (EmisTransPlanRule rule : allRules) { + String[] types = rule.getProductType() != null ? rule.getProductType().split(",") : new String[0]; + String[] typeNames = rule.getProductTypeName() != null ? rule.getProductTypeName().split(",") + : new String[0]; + for (int i = 0; i < types.length; i++) { + String type = types[i].trim(); + if (!type.isEmpty()) { + EmisTransPlanRule newRule = new EmisTransPlanRule(); + BeanUtils.copyProperties(rule, newRule); + newRule.setProductType(type); + if (i < typeNames.length) { + newRule.setProductTypeName(typeNames[i].trim()); + } else { + newRule.setProductTypeName(null); + } + String key = (newRule.getLineCode() == null ? "" : newRule.getLineCode()) + "|" + type; + groupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(newRule); + } + } + } + // 每组独立校验 + for (List group : groupMap.values()) { + // 构建邻接表:startSiteCode -> List + Map> adjMap = new HashMap<>(); + for (EmisTransPlanRule rule : group) { + if (rule.getStartSiteCode() != null) { + adjMap.computeIfAbsent(rule.getStartSiteCode(), k -> new ArrayList<>()).add(rule); + } + } + // 全局visited,避免重复DFS + Set globalVisited = new HashSet<>(); + for (EmisTransPlanRule rule : group) { + if (rule.getId() != null && !globalVisited.contains(rule.getId())) { + dfsCheckCycle(rule, adjMap, new HashSet<>(), new ArrayList<>(), cycleMessages, globalVisited); + } + } + } + return cycleMessages; + } + + /** + * DFS检测循环引用并收集路径,避免死循环 + */ + private void dfsCheckCycle( + EmisTransPlanRule node, + Map> adjMap, + Set pathSet, + List path, + List cycleMessages, + Set globalVisited) { + if (node.getId() == null) + return; + if (pathSet.contains(node.getId())) { + // 检测到环,收集路径 + int idx = -1; + for (int i = 0; i < path.size(); i++) { + if (path.get(i).getId().equals(node.getId())) { + idx = i; + break; + } + } + if (idx != -1) { + List cyclePath = new ArrayList<>(path.subList(idx, path.size())); + cyclePath.add(node); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < cyclePath.size(); i++) { + if (i > 0) + sb.append(" -> "); + EmisTransPlanRule n = cyclePath.get(i); + sb.append( + (n.getLineName() == null ? "" : n.getLineName()) + "-" + + (n.getProductTypeName() == null ? "" : n.getProductTypeName()) + "-" + + (n.getStartSiteName() == null ? "" : n.getStartSiteName()) + "-" + + (n.getNextSiteName() == null ? "" : n.getNextSiteName()) + "-" + + (n.getSupplierName() == null ? "" : n.getSupplierName())); + } + cycleMessages.add("检测到循环引用: " + sb.toString()); + } + return; + } + if (globalVisited.contains(node.getId())) + return; // 已经全局遍历过,跳过 + pathSet.add(node.getId()); + path.add(node); + if (node.getNextSiteCode() != null) { + List nextList = adjMap.get(node.getNextSiteCode()); + if (nextList != null) { + for (EmisTransPlanRule next : nextList) { + dfsCheckCycle(next, adjMap, pathSet, path, cycleMessages, globalVisited); + } + } + } + path.remove(path.size() - 1); + pathSet.remove(node.getId()); + globalVisited.add(node.getId()); + } + } \ No newline at end of file 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" + +