Skip to content

Commit 5c4aa1b

Browse files
committed
feat: approval flow service get next node
1 parent 5f7ca59 commit 5c4aa1b

5 files changed

Lines changed: 275 additions & 20 deletions

File tree

backend/crm/src/main/java/cn/cordys/crm/approval/domain/ApprovalNodeCondition.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ public class ApprovalNodeCondition {
1414
@Schema(description = "流程ID")
1515
private String flowId;
1616

17-
@Schema(description = "条件表达式JSON数组")
18-
private String ruleExpression;
17+
@Schema(description = "条件配置JSON格式")
18+
private String conditionConfig;
1919
}
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
package cn.cordys.crm.approval.dto.request;
22

3+
import cn.cordys.common.dto.condition.CombineSearch;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import lombok.Data;
56
import lombok.EqualsAndHashCode;
67

7-
import java.util.List;
8-
98
@Data
109
@EqualsAndHashCode(callSuper = true)
1110
@Schema(description = "条件节点请求")
1211
public class ApprovalNodeConditionRequest extends ApprovalNodeRequest {
1312

14-
@Schema(description = "条件表达式列表")
15-
private List<ConditionRule> rules;
16-
17-
@Data
18-
public static class ConditionRule {
19-
@Schema(description = "字段名")
20-
private String field;
21-
@Schema(description = "操作符")
22-
private String operator;
23-
@Schema(description = "值")
24-
private String value;
25-
}
13+
@Schema(description = "条件配置")
14+
private CombineSearch conditionConfig;
2615
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cn.cordys.crm.approval.dto.response;
22

3+
import cn.cordys.common.dto.condition.CombineSearch;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import lombok.Data;
56
import lombok.EqualsAndHashCode;
@@ -9,6 +10,6 @@
910
@Schema(description = "条件节点响应")
1011
public class ApprovalNodeConditionResponse extends ApprovalNodeResponse {
1112

12-
@Schema(description = "条件表达式")
13-
private String ruleExpression;
13+
@Schema(description = "条件配置")
14+
private CombineSearch conditionConfig;
1415
}

backend/crm/src/main/java/cn/cordys/crm/approval/service/ApprovalFlowService.java

Lines changed: 266 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import cn.cordys.aspectj.constants.LogType;
66
import cn.cordys.aspectj.context.OperationLogContext;
77
import cn.cordys.aspectj.dto.LogContextInfo;
8+
import cn.cordys.common.domain.BaseModuleFieldValue;
9+
import cn.cordys.common.dto.condition.CombineSearch;
10+
import cn.cordys.common.dto.condition.FilterCondition;
811
import cn.cordys.common.dto.OptionDTO;
912
import cn.cordys.common.exception.GenericException;
1013
import cn.cordys.common.pager.PageUtils;
@@ -449,7 +452,7 @@ else if (nodeRequest instanceof ApprovalNodeConditionRequest) {
449452
"rules");
450453
conditionNode.setId(nodeId);
451454
conditionNode.setFlowId(flowId);
452-
conditionNode.setRuleExpression(JSON.toJSONString(conditionRequest.getRules()));
455+
conditionNode.setConditionConfig(JSON.toJSONString(conditionRequest.getConditionConfig()));
453456
approvalNodeConditionMapper.insert(conditionNode);
454457
}
455458

@@ -564,6 +567,268 @@ private List<StatusPermissionDTO> parseStatusPermissions(List<OptionDTO> permiss
564567
return updatedPermissions;
565568
}
566569

570+
/**
571+
* 获取下一个节点
572+
* 条件节点根据字段值进行匹配,其他类型节点直接返回
573+
*/
574+
public ApprovalNodeResponse getNextNode(String nodeId, List<BaseModuleFieldValue> fieldValues) {
575+
List<ApprovalNodeResponse> nextNodes = getNextNodes(nodeId);
576+
577+
if (CollectionUtils.isEmpty(nextNodes)) {
578+
return null;
579+
}
580+
581+
// 检查是否存在条件节点
582+
boolean hasConditionNode = nextNodes.stream()
583+
.anyMatch(n -> ApprovalNodeTypeEnum.CONDITION.name().equals(n.getNodeType()));
584+
585+
if (!hasConditionNode) {
586+
// 非条件节点,通常只有一个,直接返回第一个
587+
return nextNodes.getFirst();
588+
}
589+
590+
// 条件节点匹配逻辑:按 sort 顺序依次匹配所有条件节点
591+
// 条件节点 + 默认节点组成 if-else 组合
592+
ApprovalNodeResponse defaultNode = null;
593+
594+
for (ApprovalNodeResponse nextNode : nextNodes) {
595+
if (nextNode instanceof ApprovalNodeConditionResponse conditionNode) {
596+
// 匹配条件节点,如果匹配成功则立即返回
597+
if (matchCondition(conditionNode.getConditionConfig(), fieldValues)) {
598+
return conditionNode;
599+
}
600+
} else if (ApprovalNodeTypeEnum.DEFAULT.name().equals(nextNode.getNodeType())) {
601+
// 记录 DEFAULT 节点,等所有条件节点检查完毕后再返回
602+
defaultNode = nextNode;
603+
}
604+
}
605+
606+
// 所有条件节点都不匹配,返回 DEFAULT 节点(如果存在)
607+
return defaultNode;
608+
}
609+
610+
/**
611+
* 匹配条件
612+
*/
613+
private boolean matchCondition(CombineSearch combineSearch, List<BaseModuleFieldValue> fieldValues) {
614+
if (combineSearch == null || CollectionUtils.isEmpty(combineSearch.getConditions())) {
615+
return false;
616+
}
617+
618+
// 构建字段值映射
619+
Map<String, Object> fieldValueMap = fieldValues.stream()
620+
.filter(BaseModuleFieldValue::valid)
621+
.collect(Collectors.toMap(BaseModuleFieldValue::getFieldId, BaseModuleFieldValue::getFieldValue));
622+
623+
List<FilterCondition> conditions = combineSearch.getConditions();
624+
String searchMode = combineSearch.getSearchMode();
625+
626+
// 根据匹配模式进行条件判断
627+
if (CombineSearch.SearchMode.AND.name().equals(searchMode)) {
628+
// AND 模式:所有条件都必须满足
629+
return conditions.stream().allMatch(condition -> matchSingleCondition(condition, fieldValueMap));
630+
} else {
631+
// OR 模式:任一条件满足即可
632+
return conditions.stream().anyMatch(condition -> matchSingleCondition(condition, fieldValueMap));
633+
}
634+
}
635+
636+
/**
637+
* 匹配单个条件
638+
*/
639+
private boolean matchSingleCondition(FilterCondition condition, Map<String, Object> fieldValueMap) {
640+
String fieldName = condition.getName();
641+
Object actualValue = fieldValueMap.get(fieldName);
642+
643+
// 获取操作符枚举
644+
FilterCondition.CombineConditionOperator operator;
645+
try {
646+
operator = FilterCondition.CombineConditionOperator.valueOf(condition.getOperator());
647+
} catch (IllegalArgumentException e) {
648+
return false;
649+
}
650+
651+
// 处理空值判断操作符
652+
if (operator == FilterCondition.CombineConditionOperator.EMPTY) {
653+
return actualValue == null;
654+
}
655+
if (operator == FilterCondition.CombineConditionOperator.NOT_EMPTY) {
656+
return actualValue != null;
657+
}
658+
659+
if (actualValue == null) {
660+
return false;
661+
}
662+
663+
Object expectedValue = condition.getValue();
664+
665+
try {
666+
return switch (operator) {
667+
case EQUALS -> matchEquals(actualValue, expectedValue);
668+
case NOT_EQUALS -> !matchEquals(actualValue, expectedValue);
669+
case CONTAINS -> matchContains(actualValue, expectedValue);
670+
case NOT_CONTAINS -> !matchContains(actualValue, expectedValue);
671+
case IN -> matchIn(actualValue, expectedValue);
672+
case NOT_IN -> !matchIn(actualValue, expectedValue);
673+
case GT -> matchCompare(actualValue, expectedValue) > 0;
674+
case LT -> matchCompare(actualValue, expectedValue) < 0;
675+
case GE -> matchCompare(actualValue, expectedValue) >= 0;
676+
case LE -> matchCompare(actualValue, expectedValue) <= 0;
677+
case BETWEEN -> matchBetween(actualValue, expectedValue);
678+
case DYNAMICS, COUNT_GT, COUNT_LT -> false;
679+
default -> false;
680+
};
681+
} catch (Exception e) {
682+
return false;
683+
}
684+
}
685+
686+
/**
687+
* 等于匹配
688+
*/
689+
private boolean matchEquals(Object actualValue, Object expectedValue) {
690+
if (actualValue instanceof List<?> actualList && expectedValue instanceof List<?> expectedList) {
691+
return actualList.equals(expectedList);
692+
}
693+
return Objects.equals(actualValue, expectedValue);
694+
}
695+
696+
/**
697+
* 包含匹配
698+
*/
699+
private boolean matchContains(Object actualValue, Object expectedValue) {
700+
String actualStr = String.valueOf(actualValue);
701+
String expectedStr = String.valueOf(expectedValue);
702+
return actualStr.contains(expectedStr);
703+
}
704+
705+
/**
706+
* IN 匹配(实际值在期望值列表中)
707+
*/
708+
private boolean matchIn(Object actualValue, Object expectedValue) {
709+
if (expectedValue instanceof List<?> expectedList) {
710+
if (actualValue instanceof List<?> actualList) {
711+
// 多值匹配:交集非空
712+
return actualList.stream().anyMatch(expectedList::contains);
713+
}
714+
return expectedList.contains(actualValue);
715+
}
716+
return false;
717+
}
718+
719+
/**
720+
* 比较匹配(用于数字或日期比较)
721+
*/
722+
private int matchCompare(Object actualValue, Object expectedValue) {
723+
if (actualValue instanceof Number actualNum && expectedValue instanceof Number expectedNum) {
724+
return Double.compare(actualNum.doubleValue(), expectedNum.doubleValue());
725+
}
726+
// 尝试转换为数字比较
727+
try {
728+
double actual = Double.parseDouble(String.valueOf(actualValue));
729+
double expected = Double.parseDouble(String.valueOf(expectedValue));
730+
return Double.compare(actual, expected);
731+
} catch (NumberFormatException e) {
732+
// 字符串比较
733+
return String.valueOf(actualValue).compareTo(String.valueOf(expectedValue));
734+
}
735+
}
736+
737+
/**
738+
* BETWEEN 匹配(实际值在范围内)
739+
*/
740+
private boolean matchBetween(Object actualValue, Object expectedValue) {
741+
if (expectedValue instanceof List<?> rangeList && rangeList.size() == 2) {
742+
Object min = rangeList.get(0);
743+
Object max = rangeList.get(1);
744+
return matchCompare(actualValue, min) >= 0 && matchCompare(actualValue, max) <= 0;
745+
}
746+
return false;
747+
}
748+
749+
/**
750+
* 获取下一层节点列表
751+
*/
752+
public List<ApprovalNodeResponse> getNextNodes(String nodeId) {
753+
ApprovalNode node = approvalNodeMapper.selectByPrimaryKey(nodeId);
754+
if (node == null) {
755+
throw new GenericException(CrmHttpResultCode.NOT_FOUND);
756+
}
757+
758+
String flowId = node.getFlowId();
759+
760+
// 查询节点连接关系
761+
ApprovalNodeLink linkCriteria = new ApprovalNodeLink();
762+
linkCriteria.setFlowId(flowId);
763+
linkCriteria.setFromNodeId(nodeId);
764+
List<ApprovalNodeLink> links = approvalNodeLinkMapper.select(linkCriteria);
765+
766+
if (CollectionUtils.isEmpty(links)) {
767+
return List.of();
768+
}
769+
770+
// 获取下一层节点ID列表(按sort排序)
771+
List<String> nextNodeIds = links.stream()
772+
.sorted(Comparator.comparing(ApprovalNodeLink::getSort))
773+
.map(ApprovalNodeLink::getToNodeId)
774+
.collect(Collectors.toList());
775+
776+
// 批量查询节点
777+
List<ApprovalNode> nodes = approvalNodeMapper.selectByIds(nextNodeIds);
778+
Map<String, ApprovalNode> nodeMap = nodes.stream()
779+
.collect(Collectors.toMap(ApprovalNode::getId, n -> n));
780+
781+
// 批量查询审批人节点配置
782+
List<ApprovalNodeApprover> approverNodes = approvalNodeApproverMapper.selectByIds(nextNodeIds);
783+
Map<String, ApprovalNodeApprover> approverNodeMap = approverNodes.stream()
784+
.collect(Collectors.toMap(ApprovalNodeApprover::getId, n -> n));
785+
786+
// 批量查询条件节点配置
787+
List<ApprovalNodeCondition> conditionNodes = approvalNodeConditionMapper.selectByIds(nextNodeIds);
788+
Map<String, ApprovalNodeCondition> conditionNodeMap = conditionNodes.stream()
789+
.collect(Collectors.toMap(ApprovalNodeCondition::getId, n -> n));
790+
791+
// 组装节点响应
792+
return nextNodeIds.stream()
793+
.map(nextNodeId -> {
794+
ApprovalNode nextNode = nodeMap.get(nextNodeId);
795+
if (nextNode == null) {
796+
return null;
797+
}
798+
799+
// 审批人节点
800+
if (ApprovalNodeTypeEnum.APPROVER.name().equals(nextNode.getNodeType())) {
801+
ApprovalNodeApprover approverNode = approverNodeMap.get(nextNodeId);
802+
if (approverNode != null) {
803+
ApprovalNodeApproverResponse approverResponse = BeanUtils.copyBean(
804+
new ApprovalNodeApproverResponse(), nextNode);
805+
BeanUtils.copyBean(approverResponse, approverNode);
806+
return approverResponse;
807+
}
808+
}
809+
810+
// 条件节点
811+
if (ApprovalNodeTypeEnum.CONDITION.name().equals(nextNode.getNodeType())) {
812+
ApprovalNodeCondition conditionNode = conditionNodeMap.get(nextNodeId);
813+
if (conditionNode != null) {
814+
ApprovalNodeConditionResponse conditionResponse = BeanUtils.copyBean(
815+
new ApprovalNodeConditionResponse(), nextNode);
816+
// 手动解析条件配置(因为类型不同,BeanUtils 无法自动复制)
817+
if (StringUtils.isNotBlank(conditionNode.getConditionConfig())) {
818+
conditionResponse.setConditionConfig(
819+
JSON.parseObject(conditionNode.getConditionConfig(), CombineSearch.class));
820+
}
821+
return conditionResponse;
822+
}
823+
}
824+
825+
// 其他类型节点
826+
return BeanUtils.copyBean(new ApprovalNodeResponse(), nextNode);
827+
})
828+
.filter(Objects::nonNull)
829+
.collect(Collectors.toList());
830+
}
831+
567832
/**
568833
* 根据权限ID查找对应的权限列表
569834
*/

backend/crm/src/main/resources/migration/1.7.0/ddl/V1.7.0_2__ga_ddl.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ CREATE INDEX idx_flow_id ON approval_node_approver(flow_id ASC);
7171
CREATE TABLE approval_node_condition(
7272
`id` VARCHAR(32) NOT NULL COMMENT 'id' ,
7373
`flow_id` VARCHAR(32) NOT NULL COMMENT '流程ID' ,
74-
`rule_expression` VARCHAR(2000) NOT NULL COMMENT '条件表达式JSON数组' ,
74+
`condition_config` VARCHAR(2000) NOT NULL COMMENT '条件配置JSON' ,
7575
PRIMARY KEY (id,flow_id)
7676
) COMMENT = '条件节点配置表'
7777
ENGINE = InnoDB

0 commit comments

Comments
 (0)