Merge pull request 'develop' (#78) from develop into master

Reviewed-on: http://git.xdadan.loc/tanex/emis-service/pulls/78
This commit is contained in:
heyu 2025-06-30 10:44:10 +08:00
commit e0d43752c5
8 changed files with 438 additions and 106 deletions

View File

@ -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<String> cycles = emisTransPlanRuleService.checkTree(emisTransPlanRule);
if (cycles == null || cycles.isEmpty()) {
cycles = new java.util.ArrayList<>();
cycles.add("未检测到循环引用");
}
return getDataTable(cycles);
}
}

View File

@ -173,4 +173,11 @@ public interface EmisTransPlanRuleMapper {
* @return
*/
List<EmisTransPlanRule> checkTransPlanRule(EmisTransPlanRule emisTransPlanRule);
/**
*
* @param emisTransPlanRule
* @return
*/
List<EmisTransPlanRule> selectEmisTransPlanRuleListWithoutName(EmisTransPlanRule emisTransPlanRule);
}

View File

@ -4011,7 +4011,7 @@ public class EmisBaseService {
return weekDays;
}
public List<EmisTransPlanRule> buildTree(List<EmisTransPlanRule> allRules) {
public List<EmisTransPlanRule> buildTree(List<EmisTransPlanRule> allRules, EmisTransPlanRule emisTransPlanRule) {
// 按lineCode分组
Map<String, List<EmisTransPlanRule>> groupByLine = allRules.stream()
.collect(Collectors.groupingBy(EmisTransPlanRule::getLineCode));
@ -4067,6 +4067,19 @@ public class EmisBaseService {
entry.setValue(deduped);
}
Map<String, List<EmisTransPlanRule>> tempMap = new HashMap<>();
if (StringUtil.isNotBlank(emisTransPlanRule.getProductType())) {
Iterator<Map.Entry<String, List<EmisTransPlanRule>>> iterator = groupByProductType.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<EmisTransPlanRule>> entry = iterator.next();
if (emisTransPlanRule.getProductType().equals(entry.getKey())) {
tempMap.put(entry.getKey(), entry.getValue());
groupByProductType = tempMap;
break;
}
}
}
// 处理每个productType分组
for (Map.Entry<String, List<EmisTransPlanRule>> productEntry : groupByProductType.entrySet()) {
List<EmisTransPlanRule> productRules = productEntry.getValue();
@ -4191,46 +4204,69 @@ public class EmisBaseService {
List<Long> 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<EmisTransPlanRule> copiedChildren = new ArrayList<>();
for (EmisTransPlanRule child : source.getChildren()) {
copiedChildren.add(deepCopyNode(child, visitedIds, visitPath));
}
copy.setChildren(copiedChildren);
}
// 递归复制子节点
if (source.getChildren() != null) {
List<EmisTransPlanRule> 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<EmisTransPlanRule> 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<EmisTransPlanRule> selectEmisTransPlanRuleTree(List<String> lineCodes) {
log.info("Start querying supplier route plan tree in EmisBaseService");
List<EmisTransPlanRule> list = emisTransPlanRuleMapper.selectEmisTransPlanRuleByLineCodes(lineCodes);
List<EmisTransPlanRule> tree = buildTree(list);
List<EmisTransPlanRule> 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<EmisTmsSiteBatchMiss> missRecords,
EmisTransPlanRule rule,
List<EmisTransPlanRule> filterRules, EmisWaybill waybill) {
// 1. 递归收集所有节点,按ancestors长度分组
Map<Integer, List<EmisTransPlanRule>> levelMap = new HashMap<>();
collectByLevel(rule, levelMap);
// 1. 获取所有路径
List<List<EmisTransPlanRule>> allPaths = new ArrayList<>();
getAllPaths(rule, new ArrayList<>(), allPaths);
// 2. 逐级处理
for (Map.Entry<Integer, List<EmisTransPlanRule>> entry : levelMap.entrySet())
{
List<EmisTransPlanRule> 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<List<EmisTransPlanRule>, Integer> pathMatchCountMap = new HashMap<>();
for (List<EmisTransPlanRule> 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<List<EmisTransPlanRule>> bestPaths = pathMatchCountMap.entrySet().stream()
.filter(e -> e.getValue() == maxMatch)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 4. 对这些path的节点分组去重并做漏组处理
// 先收集bestPaths中的所有节点
List<EmisTransPlanRule> allNodes = new ArrayList<>();
for (List<EmisTransPlanRule> path : bestPaths) {
allNodes.addAll(path);
}
// 按ancestors长度分组去重
Map<Integer, List<EmisTransPlanRule>> 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<EmisTransPlanRule> rules : levelMap.values()) {
List<EmisTransPlanRule> 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<Integer, List<EmisTransPlanRule>> entry : levelMap.entrySet()) {
List<EmisTransPlanRule> levelRules = entry.getValue();
// 按 startSiteCode + nextSiteCode 分组
Map<String, List<EmisTransPlanRule>> groupMap = new HashMap<>();
for (EmisTransPlanRule ruleItem : levelRules) {
String key = ruleItem.getStartSiteCode() + "_" + ruleItem.getNextSiteCode();
groupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(ruleItem);
}
for (List<EmisTransPlanRule> 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<EmisTransPlanRule> current,
List<List<EmisTransPlanRule>> 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<Integer, List<EmisTransPlanRule>> levelMap) {
int level = 0;

View File

@ -74,4 +74,12 @@ public interface IEmisTransPlanRuleService {
* @return 结果
*/
int draftSubmitRule(EmisTransPlanRule emisTransPlanRule) throws EmisBizError;
/**
* 校验供应商线路规划树结构中的循环引用
*
* @param emisTransPlanRule 查询条件
* @return 循环引用提示信息列表
*/
List<String> checkTree(EmisTransPlanRule emisTransPlanRule);
}

View File

@ -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<EmisTransPlanRule> tree = buildTree(allRules);
List<EmisTransPlanRule> tree = buildTree(allRules, new EmisTransPlanRule());
log.info("Queried supplier route plan tree, node count={}", tree != null ? tree.size() : 0);
return tree;
}

View File

@ -82,6 +82,12 @@ public class EmisTransPlanRuleServiceImpl extends EmisBaseService implements IEm
checkDate(emisTransPlanRule);
// 检查重复
checkDuplicate(emisTransPlanRule);
// 校验循环引用
EmisTransPlanRule checkRule = new EmisTransPlanRule();
checkRule.setProductType(emisTransPlanRule.getProductType());
List<EmisTransPlanRule> 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<EmisTransPlanRule> allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleListWithoutName(checkRule);
allRules.add(emisTransPlanRule);
buildTree(allRules, emisTransPlanRule);
return emisTransPlanRuleMapper.insertEmisTransPlanRule(emisTransPlanRule);
}
private void nameToCode(EmisTransPlanRule emisTransPlanRule) throws EmisBizError {
// 线路
List<Map<String, String>> lineInfo = emisTransPlanRuleMapper.selectLineInfoByNames(Collections.singletonList(emisTransPlanRule.getLineName()));
List<Map<String, String>> 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<Map<String, String>> productTypeInfo = emisTransPlanRuleMapper.selectProductTypeInfoByNamesAndLineCode(Arrays.asList(productTypeNames),lineInfo.get(0).get("code"));
List<Map<String, String>> 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<Map<String, String>> startSiteInfo = emisTransPlanRuleMapper.selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getStartSiteName()));
List<Map<String, String>> 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<Map<String, String>> nextSiteInfo = emisTransPlanRuleMapper.selectSiteInfoByNames(Collections.singletonList(emisTransPlanRule.getNextSiteName()));
List<Map<String, String>> 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<Map<String, String>> supplierInfo = emisTransPlanRuleMapper.selectSupplierInfoByNames(Arrays.asList(supplierNames));
List<Map<String, String>> 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<EmisTransPlanRule> existingRules = emisTransPlanRuleMapper.checkDuplicate(emisTransPlanRule);
// 检查正向规则(起始网点到下一网点)
List<EmisTransPlanRule> 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<EmisTransPlanRule> existingRules1 = emisTransPlanRuleMapper.checkDuplicate(emisTransPlanRule);
// 检查反向规则(下一网点到起始网点)
List<EmisTransPlanRule> existingRules1 = checkDuplicateDirection(emisTransPlanRule,
emisTransPlanRule.getNextSiteCode(), emisTransPlanRule.getStartSiteCode());
if (existingRules1 != null && !existingRules1.isEmpty()) {
throw new EmisBizError(EmisBizErrorType.EXISTS, "已存在相同的规则配置");
}
}
private List<EmisTransPlanRule> 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<EmisTransPlanRule> 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<String> set1 = new HashSet<>(Arrays.asList(str1.split(",")));
Set<String> 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<String> checkTree(EmisTransPlanRule emisTransPlanRule) {
List<EmisTransPlanRule> allRules = emisTransPlanRuleMapper.selectEmisTransPlanRuleList(emisTransPlanRule);
return buildTreeForCheck(allRules);
}
/**
* 仅校验循环引用并收集所有循环引用提示
*
* @param allRules 所有规则
* @return 循环引用提示信息列表
*/
public List<String> buildTreeForCheck(List<EmisTransPlanRule> allRules) {
List<String> cycleMessages = new ArrayList<>();
// 按lineCode+productType分组
Map<String, List<EmisTransPlanRule>> 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<EmisTransPlanRule> group : groupMap.values()) {
// 构建邻接表:startSiteCode -> List<rule>
Map<String, List<EmisTransPlanRule>> adjMap = new HashMap<>();
for (EmisTransPlanRule rule : group) {
if (rule.getStartSiteCode() != null) {
adjMap.computeIfAbsent(rule.getStartSiteCode(), k -> new ArrayList<>()).add(rule);
}
}
// 全局visited,避免重复DFS
Set<Long> 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<String, List<EmisTransPlanRule>> adjMap,
Set<Long> pathSet,
List<EmisTransPlanRule> path,
List<String> cycleMessages,
Set<Long> 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<EmisTransPlanRule> 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<EmisTransPlanRule> 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());
}
}

View File

@ -260,11 +260,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectSupplierInfoByConditions" parameterType="emisSupplier" resultMap="EmisSupplierResult">
select distinct r.supplier_code as code,
(SELECT GROUP_CONCAT(s.name)
(SELECT GROUP_CONCAT(s.name ORDER BY FIND_IN_SET(s.code, r.supplier_code))
FROM emis_supplier s
WHERE FIND_IN_SET(s.code, r.supplier_code)
AND s.del_flag = '0') as name,
(SELECT GROUP_CONCAT(s.name_en)
(SELECT GROUP_CONCAT(s.name_en ORDER BY FIND_IN_SET(s.code, r.supplier_code))
FROM emis_supplier s
WHERE FIND_IN_SET(s.code, r.supplier_code)
AND s.del_flag = '0') as name_en

View File

@ -61,6 +61,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by id desc
</select>
<select id="selectEmisTransPlanRuleListWithoutName" parameterType="EmisTransPlanRule" resultMap="EmisTransPlanRuleResult">
select r.*
from emis_trans_plan_rule r
<where>
r.del_flag = '0'
<if test="lineCode != null and lineCode != ''"> and r.line_code = #{lineCode}</if>
<if test="productType != null and productType != ''"> and ( FIND_IN_SET(#{productType},r.product_type) )</if>
<if test="startSiteCode != null and startSiteCode != ''"> and r.start_site_code = #{startSiteCode}</if>
<if test="nextSiteCode != null and nextSiteCode != ''"> and r.next_site_code = #{nextSiteCode}</if>
<if test="manager != null and manager != ''"> and r.manager = #{manager}</if>
<if test="supplierCode != null and supplierCode != ''"> and ( FIND_IN_SET(#{supplierCode},r.supplier_code) )</if>
<if test="startDate != null"> and r.start_date = #{startDate}</if>
<if test="endDate != null"> and r.end_date = #{endDate}</if>
</where>
order by id desc
</select>
<select id="checkTransPlanRule" parameterType="EmisTransPlanRule" resultMap="EmisTransPlanRuleResult">
select r.*
from emis_trans_plan_rule r
@ -128,14 +145,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="checkDuplicate" parameterType="EmisTransPlanRule" resultMap="EmisTransPlanRuleResult">
<include refid="selectEmisTransPlanRuleVo"/>
select r.*
from emis_trans_plan_rule r
<where>
r.del_flag = '0'
<if test="lineCode != null and lineCode != ''">and r.line_code = #{lineCode}</if>
<if test="productType != null and productType != ''">and r.product_type=#{productType}</if>
<if test="startSiteCode != null and startSiteCode != ''">and r.start_site_code = #{startSiteCode}</if>
<if test="nextSiteCode != null and nextSiteCode != ''">and r.next_site_code = #{nextSiteCode}</if>
<if test="supplierCode != null and supplierCode != ''">and r.supplier_code=#{supplierCode}</if>
<if test="startDate != null">and r.start_date = #{startDate}</if>
<if test="endDate != null">and r.end_date = #{endDate}</if>
</where>
order by id desc
</select>