Commit 9698fee36447d3d14a7812c06c6e25f8e4844fb8

Authored by Lesan
1 parent ceba5b8c

feat: 子流程

yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
@@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable<Integer> { @@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable<Integer> {
30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), 30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
31 TRIGGER_NODE(15, "触发器", "serviceTask"), 31 TRIGGER_NODE(15, "触发器", "serviceTask"),
32 32
  33 + CHILD_PROCESS(20, "子流程", "callActivity"),
  34 + ASYNC_CHILD_PROCESS(21, "异步子流程", "callActivity"),
  35 +
33 // 50 ~ 条件分支 36 // 50 ~ 条件分支
34 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 37 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
35 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), 38 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
@@ -26,6 +26,7 @@ public enum BpmReasonEnum { @@ -26,6 +26,7 @@ public enum BpmReasonEnum {
26 TIMEOUT_REJECT("审批超时,系统自动不通过"), 26 TIMEOUT_REJECT("审批超时,系统自动不通过"),
27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), 27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), 28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
  29 + ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"),
29 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), 30 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
30 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), 31 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
31 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), 32 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -10,6 +10,7 @@ import jakarta.validation.Valid; @@ -10,6 +10,7 @@ import jakarta.validation.Valid;
10 import jakarta.validation.constraints.NotEmpty; 10 import jakarta.validation.constraints.NotEmpty;
11 import jakarta.validation.constraints.NotNull; 11 import jakarta.validation.constraints.NotNull;
12 import lombok.Data; 12 import lombok.Data;
  13 +import org.flowable.bpmn.model.IOParameter;
13 import org.hibernate.validator.constraints.URL; 14 import org.hibernate.validator.constraints.URL;
14 15
15 import java.util.List; 16 import java.util.List;
@@ -122,6 +123,8 @@ public class BpmSimpleModelNodeVO { @@ -122,6 +123,8 @@ public class BpmSimpleModelNodeVO {
122 */ 123 */
123 private TriggerSetting triggerSetting; 124 private TriggerSetting triggerSetting;
124 125
  126 + private ChildProcessSetting childProcessSetting;
  127 +
125 @Schema(description = "任务监听器") 128 @Schema(description = "任务监听器")
126 @Valid 129 @Valid
127 @Data 130 @Data
@@ -397,4 +400,33 @@ public class BpmSimpleModelNodeVO { @@ -397,4 +400,33 @@ public class BpmSimpleModelNodeVO {
397 private Map<String, Object> updateFormFields; 400 private Map<String, Object> updateFormFields;
398 } 401 }
399 } 402 }
  403 +
  404 + @Schema(description = "子流程节点配置")
  405 + @Data
  406 + @Valid
  407 + public static class ChildProcessSetting {
  408 +
  409 + @Schema(description = "被调用流程", example = "xxx")
  410 + @NotEmpty(message = "被调用流程不能为空")
  411 + private String calledElement;
  412 +
  413 + @Schema(description = "被调用流程名称", example = "xxx")
  414 + @NotEmpty(message = "被调用流程名称不能为空")
  415 + private String calledElementName;
  416 +
  417 + @Schema(description = "是否异步", example = "false")
  418 + @NotNull(message = "是否异步不能为空")
  419 + private Boolean async;
  420 +
  421 + @Schema(description = "输入参数(主->子)", example = "[]")
  422 + private List<IOParameter> inVariable;
  423 +
  424 + @Schema(description = "输出参数(子->主)", example = "[]")
  425 + private List<IOParameter> outVariable;
  426 +
  427 + @Schema(description = "是否自动跳过子流程发起节点", example = "false")
  428 + @NotNull(message = "是否自动跳过子流程发起节点不能为空")
  429 + private Boolean skipStartUserNode;
  430 +
  431 + }
400 } 432 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -52,6 +52,13 @@ public class BpmnVariableConstants { @@ -52,6 +52,13 @@ public class BpmnVariableConstants {
52 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; 52 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
53 53
54 /** 54 /**
  55 + * 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
  56 + *
  57 + * @see ProcessInstance#getProcessVariables()
  58 + */
  59 + public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
  60 +
  61 + /**
55 * 流程实例的变量 - 流程开始时间 62 * 流程实例的变量 - 流程开始时间
56 * 63 *
57 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题 64 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -22,6 +22,8 @@ import org.springframework.util.MultiValueMap; @@ -22,6 +22,8 @@ import org.springframework.util.MultiValueMap;
22 import java.util.*; 22 import java.util.*;
23 23
24 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 24 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
  25 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
  26 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS;
25 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
26 import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION; 28 import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;
27 import static java.util.Arrays.asList; 29 import static java.util.Arrays.asList;
@@ -42,7 +44,8 @@ public class SimpleModelUtils { @@ -42,7 +44,8 @@ public class SimpleModelUtils {
42 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), 44 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
43 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), 45 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
44 new DelayTimerNodeConvert(), new TriggerNodeConvert(), 46 new DelayTimerNodeConvert(), new TriggerNodeConvert(),
45 - new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert()); 47 + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(),
  48 + new ChildProcessConvert(), new AsyncChildProcessConvert());
46 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); 49 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
47 } 50 }
48 51
@@ -773,6 +776,66 @@ public class SimpleModelUtils { @@ -773,6 +776,66 @@ public class SimpleModelUtils {
773 776
774 } 777 }
775 778
  779 + private static class ChildProcessConvert implements NodeConvert {
  780 +
  781 + @Override
  782 + public CallActivity convert(BpmSimpleModelNodeVO node) {
  783 + BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting();
  784 + List<IOParameter> inVariable = childProcessSetting.getInVariable() == null ?
  785 + new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariable());
  786 + CallActivity callActivity = new CallActivity();
  787 + callActivity.setId(node.getId());
  788 + callActivity.setName(node.getName());
  789 + callActivity.setCalledElementType("key");
  790 + // 1. 是否异步
  791 + callActivity.setAsynchronous(node.getChildProcessSetting().getAsync());
  792 + // 2. 调用的子流程
  793 + callActivity.setCalledElement(childProcessSetting.getCalledElement());
  794 + callActivity.setProcessInstanceName(childProcessSetting.getCalledElementName());
  795 + // 3. 是否自动跳过子流程发起节点
  796 + if (Boolean.TRUE.equals(childProcessSetting.getSkipStartUserNode())) {
  797 + IOParameter ioParameter = new IOParameter();
  798 + ioParameter.setSourceExpression("true");
  799 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
  800 + inVariable.add(ioParameter);
  801 + } else {
  802 + IOParameter ioParameter = new IOParameter();
  803 + ioParameter.setSourceExpression("false");
  804 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
  805 + inVariable.add(ioParameter);
  806 + }
  807 + // 4. 主→子变量传递
  808 + // 默认需要传递的一些变量
  809 + // 4.1 流程状态
  810 + IOParameter ioParameter = new IOParameter();
  811 + ioParameter.setSource(PROCESS_INSTANCE_VARIABLE_STATUS);
  812 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_STATUS);
  813 + inVariable.add(ioParameter);
  814 + callActivity.setInParameters(inVariable);
  815 + // 5. 子→主变量传递
  816 + if (childProcessSetting.getOutVariable() != null && !childProcessSetting.getOutVariable().isEmpty()) {
  817 + callActivity.setOutParameters(childProcessSetting.getOutVariable());
  818 + }
  819 + return callActivity;
  820 + }
  821 +
  822 + @Override
  823 + public BpmSimpleModelNodeTypeEnum getType() {
  824 + return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS;
  825 + }
  826 +
  827 + }
  828 +
  829 + private static class AsyncChildProcessConvert extends ChildProcessConvert {
  830 +
  831 + @Override
  832 + public BpmSimpleModelNodeTypeEnum getType() {
  833 + return BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS;
  834 + }
  835 +
  836 + }
  837 +
  838 +
776 private static String buildGatewayJoinId(String id) { 839 private static String buildGatewayJoinId(String id) {
777 return id + "_join"; 840 return id + "_join";
778 } 841 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
1 package cn.iocoder.yudao.module.bpm.service.task; 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
4 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
6 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
@@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
63 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 64 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
64 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 65 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
65 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; 66 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
  67 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
66 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 68 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
67 69
68 /** 70 /**
@@ -1212,19 +1214,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1212,19 +1214,30 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1212 } 1214 }
1213 } 1215 }
1214 1216
1215 - // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理  
1216 - if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {  
1217 - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略  
1218 - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识  
1219 - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),  
1220 - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); 1217 + // 发起人节点
  1218 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
  1219 + if (bpmnModel == null) {
  1220 + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
  1221 + return;
  1222 + }
  1223 + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  1224 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
  1225 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
  1226 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1227 + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
  1228 + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1229 + PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
  1230 + if (userTaskElement.getId().equals(START_USER_NODE_ID) &&
  1231 + (skipStartUserNodeFlag == null || Boolean.TRUE.equals(skipStartUserNodeFlag)) &&
  1232 + !Boolean.TRUE.equals(returnTaskFlag)) {
  1233 + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
  1234 + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
  1235 + return;
  1236 + }
  1237 + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
  1238 + if (!userTaskElement.getId().equals(START_USER_NODE_ID) &&
  1239 + StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
1221 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { 1240 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1222 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());  
1223 - if (bpmnModel == null) {  
1224 - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());  
1225 - return;  
1226 - }  
1227 - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());  
1228 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); 1241 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
1229 1242
1230 // 情况一:自动跳过 1243 // 情况一:自动跳过