From 2d5da6200d936b939633b4ddfbec885a5c92f489 Mon Sep 17 00:00:00 2001 From: aike <17730485278@139.com> Date: Mon, 30 Jun 2025 09:26:06 +0800 Subject: [PATCH] =?UTF-8?q?demand:=20=20=E6=96=B0=E5=A2=9E=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=A2=9E=E5=8A=A0=E5=BE=AA=E7=8E=AF=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=A0=A1=E9=AA=8C=E7=BB=84=E6=A0=91?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20committer:=20heyu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/emis/EmisTransPlanRuleController.java | 16 +- .../emis/mapper/EmisTransPlanRuleMapper.java | 7 + .../erp/emis/service/EmisBaseService.java | 86 ++-- .../service/IEmisTransPlanRuleService.java | 8 + .../impl/EmisTransPlanRuleServiceImpl.java | 420 +++++++++++++++++- .../mapper/EmisTransPlanRuleMapper.xml | 17 + 6 files changed, 510 insertions(+), 44 deletions(-) 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 d3f955d64..4b44abbd6 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 @@ -4204,46 +4204,66 @@ 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)); - } - errorMsg.append("\n循环引用发生在节点ID:").append(source.getId()); - errorMsg.append(",该节点在路径中重复出现"); + // 构建详细的错误信息 + StringBuilder errorMsg = new StringBuilder(); + errorMsg.append("检测到循环引用!循环路径:"); + for (int i = 0; i < cyclePath.size(); i++) { + if (i > 0) { + errorMsg.append(" -> "); + } + Long id = cyclePath.get(i); + 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循环引用发生在节点,详细信息见上"); + throw new RuntimeException(errorMsg.toString()); + } + } - 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; + } /** * 检查月结客户首次签约日期,首次签约之前开的不允许修改 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/EmisTransPlanRuleServiceImpl.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/impl/EmisTransPlanRuleServiceImpl.java index fa8a0fdbb..73ebe794c 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); } @@ -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()); } @@ -293,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, "起始网点名称不能为空"); } @@ -303,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, "同一产品类型下起始网点、下一网点不能相同"); } } @@ -327,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, "同一产品类型下起始网点、下一网点不能相同"); } } @@ -346,4 +362,388 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm } } + // public List buildTree(List allRules, + // EmisTransPlanRule emisTransPlanRule) { + // // 按lineCode分组 + // Map> groupByLine = allRules.stream() + // .collect(Collectors.groupingBy(EmisTransPlanRule::getLineCode)); + // + // List result = new ArrayList<>(); + // + // // 处理每个lineCode分组 + // for (Map.Entry> lineEntry : + // groupByLine.entrySet()) { + // List lineRules = lineEntry.getValue(); + // if (lineRules.isEmpty()) { + // continue; + // } + // + // // + // 按productType分组(支持productType和productTypeName为逗号分隔的多个类型,且一一对应,且每个rule只保留一个type和typeName) + // Map> groupByProductType = new HashMap<>(); + // for (EmisTransPlanRule rule : lineRules) { + // if (rule.getProductType() != null) { + // String[] types = rule.getProductType().split(","); + // 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); + // } + // groupByProductType.computeIfAbsent(type, k -> new + // ArrayList<>()).add(newRule); + // } + // } + // } + // } + // // + // 分组内去重(lineCode、productType、startSiteCode、nextSiteCode、supplierCode、startDate、endDate都相同只保留一条) + // for (Map.Entry> entry : + // groupByProductType.entrySet()) { + // List rules = entry.getValue(); + // Set uniqueKeys = new HashSet<>(); + // List deduped = new ArrayList<>(); + // for (EmisTransPlanRule rule : rules) { + // String key = (rule.getLineCode() == null ? "" : rule.getLineCode()) + "|" + // + (rule.getProductType() == null ? "" : rule.getProductType()) + "|" + // + (rule.getStartSiteCode() == null ? "" : rule.getStartSiteCode()) + "|" + // + (rule.getNextSiteCode() == null ? "" : rule.getNextSiteCode()) + "|" + // + (rule.getSupplierCode() == null ? "" : rule.getSupplierCode()) + "|" + // + (rule.getStartDate() == null ? "" : rule.getStartDate().toString()) + "|" + // + (rule.getEndDate() == null ? "" : rule.getEndDate().toString()); + // if (uniqueKeys.add(key)) { + // deduped.add(rule); + // } + // } + // 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(); + // + // // 构建站点关系树 + // Map> nodeMap = new HashMap<>(); + // for (EmisTransPlanRule rule : productRules) { + // String key = rule.getNextSiteCode() != null ? rule.getNextSiteCode() : + // rule.getStartSiteCode(); + // if (key != null) { + // nodeMap.computeIfAbsent(key, k -> new ArrayList<>()).add(rule); + // } + // } + // + // // 按startSiteCode分组,找出所有可能的父节点 + // Map> parentGroups = productRules.stream() + // .filter(rule -> rule.getStartSiteCode() != null) + // .collect(Collectors.groupingBy(EmisTransPlanRule::getStartSiteCode)); + // + // // 设置父子关系 + // for (Map.Entry> parentGroup : + // parentGroups.entrySet()) { + // String startSiteCode = parentGroup.getKey(); + // List children = parentGroup.getValue(); + // + // // 获取所有可能的父节点 + // List parents = nodeMap.get(startSiteCode); + // if (parents != null) { + // for (EmisTransPlanRule parent : parents) { + // if (parent.getChildren() == null) { + // parent.setChildren(new ArrayList<>()); + // } + // + // // 添加所有子节点到父节点的children列表中 + // for (EmisTransPlanRule child : children) { + // if (!parent.getChildren().contains(child)) { + // parent.getChildren().add(child); + // } + // } + // } + // } + // } + // + // // 找出顶级节点(没有父节点的节点) + // List topLevelNodes = productRules.stream() + // .filter(rule -> { + // // 检查该节点是否作为其他节点的子节点 + // boolean isChild = productRules.stream() + // .anyMatch(potential -> potential.getChildren() != null + // && potential.getChildren().contains(rule)); + // return !isChild; + // }) + // .collect(Collectors.toList()); + // + // // 将顶级节点直接添加到结果中(进行深度复制) + // for (EmisTransPlanRule node : topLevelNodes) { + // result.add(deepCopyNode(node)); + // } + // } + // } + // + // // 递归设置父ID和祖先节点ID(以treeId为准) + // for (EmisTransPlanRule root : result) { + // setParentAndAncestors(root, null, ""); + // } + // + // return result; + // } + // + // /** + // * 递归设置父ID和祖先节点ID + // * + // * @param node 当前节点 + // * @param parentId 父节点ID + // * @param ancestors 祖先节点ID字符串 + // */ + // private void setParentAndAncestors(EmisTransPlanRule node, String parentId, + // String ancestors) { + // if (node == null) { + // return; + // } + // // 设置父ID + // node.setParentId(parentId); + // + // // 设置祖先节点ID + // node.setAncestors(ancestors); + // // 处理子节点,子节点的祖先包含当前节点和当前节点的所有祖先 + // if (node.getChildren() != null && !node.getChildren().isEmpty()) { + // String childAncestors = ancestors.isEmpty() ? node.getTreeId() : ancestors + + // "," + node.getTreeId(); + // for (EmisTransPlanRule child : node.getChildren()) { + // setParentAndAncestors(child, node.getTreeId(), childAncestors); + // } + // } + // } + // + // /** + // * 深度复制节点及其子节点 + // * + // * @param source 源节点 + // * @return 复制后的新节点 + // */ + // private EmisTransPlanRule deepCopyNode(EmisTransPlanRule source) { + // return deepCopyNode(source, new HashSet<>(), new ArrayList<>()); + // } + // + // /** + // * 深度复制节点及其子节点(带循环引用检测) + // * + // * @param source 源节点 + // * @param visitedIds 已访问的节点ID集合 + // * @param visitPath 访问路径,用于记录循环引用的路径 + // * @return 复制后的新节点 + // */ + // private EmisTransPlanRule deepCopyNode(EmisTransPlanRule source, Set + // visitedIds, List visitPath) { + // if (source == null) { + // return null; + // } + // + // // 检查循环引用 + // if (visitedIds.contains(source.getId())) { + // // 找到循环引用的起始位置 + // int cycleStartIndex = visitPath.indexOf(source.getId()); + // if (cycleStartIndex != -1) { + // // 构建循环路径 + // 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(" -> "); + // } + // Long id = cyclePath.get(i); + // 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循环引用发生在节点,详细信息见上"); + // throw new RuntimeException(errorMsg.toString()); + // } + // } + // + // // 添加到已访问集合和路径中 + // visitedIds.add(source.getId()); + // visitPath.add(source.getId()); + // + // EmisTransPlanRule copy = new EmisTransPlanRule(); + // BeanUtils.copyProperties(source, copy); + // + // // 为每个新节点生成唯一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); + // } + // + // // 从路径中移除当前节点(回溯) + // visitPath.remove(visitPath.size() - 1); + // + // 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; + // } + + @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/EmisTransPlanRuleMapper.xml b/emis-biz/src/main/resources/mapper/EmisTransPlanRuleMapper.xml index 937a75d42..7ceda2966 100644 --- a/emis-biz/src/main/resources/mapper/EmisTransPlanRuleMapper.xml +++ b/emis-biz/src/main/resources/mapper/EmisTransPlanRuleMapper.xml @@ -61,6 +61,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by id desc + +