feat: 子流程

This commit is contained in:
Lesan 2025-02-21 09:52:43 +08:00
parent ceba5b8cec
commit 9698fee364
6 changed files with 132 additions and 13 deletions

View File

@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable<Integer> {
DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
TRIGGER_NODE(15, "触发器", "serviceTask"), TRIGGER_NODE(15, "触发器", "serviceTask"),
CHILD_PROCESS(20, "子流程", "callActivity"),
ASYNC_CHILD_PROCESS(21, "异步子流程", "callActivity"),
// 50 ~ 条件分支 // 50 ~ 条件分支
CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),

View File

@ -26,6 +26,7 @@ public enum BpmReasonEnum {
TIMEOUT_REJECT("审批超时,系统自动不通过"), TIMEOUT_REJECT("审批超时,系统自动不通过"),
ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),

View File

@ -10,6 +10,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.flowable.bpmn.model.IOParameter;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import java.util.List; import java.util.List;
@ -122,6 +123,8 @@ public class BpmSimpleModelNodeVO {
*/ */
private TriggerSetting triggerSetting; private TriggerSetting triggerSetting;
private ChildProcessSetting childProcessSetting;
@Schema(description = "任务监听器") @Schema(description = "任务监听器")
@Valid @Valid
@Data @Data
@ -397,4 +400,33 @@ public class BpmSimpleModelNodeVO {
private Map<String, Object> updateFormFields; private Map<String, Object> updateFormFields;
} }
} }
@Schema(description = "子流程节点配置")
@Data
@Valid
public static class ChildProcessSetting {
@Schema(description = "被调用流程", example = "xxx")
@NotEmpty(message = "被调用流程不能为空")
private String calledElement;
@Schema(description = "被调用流程名称", example = "xxx")
@NotEmpty(message = "被调用流程名称不能为空")
private String calledElementName;
@Schema(description = "是否异步", example = "false")
@NotNull(message = "是否异步不能为空")
private Boolean async;
@Schema(description = "输入参数(主->子)", example = "[]")
private List<IOParameter> inVariable;
@Schema(description = "输出参数(子->主)", example = "[]")
private List<IOParameter> outVariable;
@Schema(description = "是否自动跳过子流程发起节点", example = "false")
@NotNull(message = "是否自动跳过子流程发起节点不能为空")
private Boolean skipStartUserNode;
}
} }

View File

@ -51,6 +51,13 @@ public class BpmnVariableConstants {
*/ */
public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
/**
* 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
/** /**
* 流程实例的变量 - 流程开始时间 * 流程实例的变量 - 流程开始时间
* *

View File

@ -22,6 +22,8 @@ import org.springframework.util.MultiValueMap;
import java.util.*; import java.util.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION; import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@ -42,7 +44,8 @@ public class SimpleModelUtils {
List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
new DelayTimerNodeConvert(), new TriggerNodeConvert(), new DelayTimerNodeConvert(), new TriggerNodeConvert(),
new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert()); new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(),
new ChildProcessConvert(), new AsyncChildProcessConvert());
converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
} }
@ -773,6 +776,66 @@ public class SimpleModelUtils {
} }
private static class ChildProcessConvert implements NodeConvert {
@Override
public CallActivity convert(BpmSimpleModelNodeVO node) {
BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting();
List<IOParameter> inVariable = childProcessSetting.getInVariable() == null ?
new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariable());
CallActivity callActivity = new CallActivity();
callActivity.setId(node.getId());
callActivity.setName(node.getName());
callActivity.setCalledElementType("key");
// 1. 是否异步
callActivity.setAsynchronous(node.getChildProcessSetting().getAsync());
// 2. 调用的子流程
callActivity.setCalledElement(childProcessSetting.getCalledElement());
callActivity.setProcessInstanceName(childProcessSetting.getCalledElementName());
// 3. 是否自动跳过子流程发起节点
if (Boolean.TRUE.equals(childProcessSetting.getSkipStartUserNode())) {
IOParameter ioParameter = new IOParameter();
ioParameter.setSourceExpression("true");
ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
inVariable.add(ioParameter);
} else {
IOParameter ioParameter = new IOParameter();
ioParameter.setSourceExpression("false");
ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
inVariable.add(ioParameter);
}
// 4. 子变量传递
// 默认需要传递的一些变量
// 4.1 流程状态
IOParameter ioParameter = new IOParameter();
ioParameter.setSource(PROCESS_INSTANCE_VARIABLE_STATUS);
ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_STATUS);
inVariable.add(ioParameter);
callActivity.setInParameters(inVariable);
// 5. 主变量传递
if (childProcessSetting.getOutVariable() != null && !childProcessSetting.getOutVariable().isEmpty()) {
callActivity.setOutParameters(childProcessSetting.getOutVariable());
}
return callActivity;
}
@Override
public BpmSimpleModelNodeTypeEnum getType() {
return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS;
}
}
private static class AsyncChildProcessConvert extends ChildProcessConvert {
@Override
public BpmSimpleModelNodeTypeEnum getType() {
return BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS;
}
}
private static String buildGatewayJoinId(String id) { private static String buildGatewayJoinId(String id) {
return id + "_join"; return id + "_join";
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
/** /**
@ -1212,19 +1214,30 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
} }
// 审批人与提交人为同一人时根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 // 发起人节点
if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略 if (bpmnModel == null) {
// TODO 芋艿优化未来有没更好的判断方式另外还要考虑清理机制就是说下次处理了之后就移除这个标识 log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), return;
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); }
FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略
// TODO 芋艿优化未来有没更好的判断方式另外还要考虑清理机制就是说下次处理了之后就移除这个标识
Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
if (userTaskElement.getId().equals(START_USER_NODE_ID) &&
(skipStartUserNodeFlag == null || Boolean.TRUE.equals(skipStartUserNodeFlag)) &&
!Boolean.TRUE.equals(returnTaskFlag)) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
return;
}
// 当不为发起人节点时审批人与提交人为同一人时根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
if (!userTaskElement.getId().equals(START_USER_NODE_ID) &&
StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
if (bpmnModel == null) {
log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
return;
}
FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
// 情况一自动跳过 // 情况一自动跳过