Bladeren bron

feat: 子流程

Lesan 6 maanden geleden
bovenliggende
commit
9698fee364

+ 3 - 0
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> {
     DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
     TRIGGER_NODE(15, "触发器", "serviceTask"),
 
+    CHILD_PROCESS(20, "子流程", "callActivity"),
+    ASYNC_CHILD_PROCESS(21, "异步子流程", "callActivity"),
+
     // 50 ~ 条件分支
     CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
     CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),

+ 1 - 0
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 {
     TIMEOUT_REJECT("审批超时,系统自动不通过"),
     ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
     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_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
     ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),

+ 32 - 0
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;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
+import org.flowable.bpmn.model.IOParameter;
 import org.hibernate.validator.constraints.URL;
 
 import java.util.List;
@@ -122,6 +123,8 @@ public class BpmSimpleModelNodeVO {
      */
     private TriggerSetting triggerSetting;
 
+    private ChildProcessSetting childProcessSetting;
+
     @Schema(description = "任务监听器")
     @Valid
     @Data
@@ -397,4 +400,33 @@ public class BpmSimpleModelNodeVO {
             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;
+
+    }
 }

+ 7 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java

@@ -51,6 +51,13 @@ public class BpmnVariableConstants {
      */
     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";
+
     /**
      * 流程实例的变量 - 流程开始时间
      *

+ 64 - 1
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;
 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.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.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;
 import static java.util.Arrays.asList;
@@ -42,7 +44,8 @@ public class SimpleModelUtils {
         List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
                 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
                 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));
     }
 
@@ -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) {
         return id + "_join";
     }

+ 25 - 12
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.bpm.service.task;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.*;
 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.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_SKIP_START_USER_NODE;
 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())) {
-                    // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
-                    // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
-                    Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
-                            String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
+                // 发起人节点
+                BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
+                if (bpmnModel == null) {
+                    log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
+                    return;
+                }
+                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)) {
-                        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);
 
                         // 情况一:自动跳过