Browse Source

仿钉钉流程设计- 新增发起人节点

jason 11 months ago
parent
commit
5679b4ef95

+ 12 - 10
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java

@@ -19,18 +19,20 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
 
     // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批;
     // @芋艿 感觉还是用 START_NODE . END_NODE 比较好.
+    // 0 1 开始和结束
     START_NODE(0, "开始节点"),
-    END_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后;
+    END_NODE(1, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后;
 
-    APPROVE_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改
-    COPY_NODE(2, "抄送人节点"),
+    // 10 ~ 49 各种节点
+    START_USER_NODE(10, "发起人节点"), // 发起人节点。前端的开始节点,Id 固定
+    APPROVE_NODE(11, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改
+    COPY_NODE(12, "抄送人节点"),
 
-    CONDITION_NODE(3, "条件节点"), // 用于构建流转条件的表达式
-    CONDITION_BRANCH_NODE(4, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支?
-    PARALLEL_BRANCH_NODE(5, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关
-//    PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"),
-    INCLUSIVE_BRANCH_FORK_NODE(7, "包容网关分叉节点"),
-    INCLUSIVE_BRANCH_JOIN_NODE(8, "包容网关聚合节点"),
+    // 50 ~ 条件分支
+    CONDITION_NODE(50, "条件节点"), // 用于构建流转条件的表达式
+    CONDITION_BRANCH_NODE(51, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支?
+    PARALLEL_BRANCH_NODE(52, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关
+    INCLUSIVE_BRANCH_NODE(53, "包容分叉节点"),
     // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~
     // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。
     ;
@@ -48,7 +50,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
     public static boolean isBranchNode(Integer type) {
         return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
                 || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
-                || Objects.equals(INCLUSIVE_BRANCH_FORK_NODE.getType(), type) ;
+                || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type) ;
     }
 
     public static BpmSimpleModelNodeType valueOf(Integer type) {

+ 0 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
@@ -9,7 +8,6 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.springframework.stereotype.Component;
 
-import java.util.List;
 import java.util.Set;
 
 /**

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

@@ -99,4 +99,14 @@ public interface BpmnModelConstants {
      */
     String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable";
 
+    /**
+     * BPMN Start Event Node Id
+     */
+    String START_EVENT_NODE_ID = "StartEvent";
+
+    /**
+     * BPMN Start Event Node Name
+     */
+    String START_EVENT_NODE_NAME = "开始";
+
 }

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

@@ -30,6 +30,12 @@ public class BpmnVariableConstants {
      */
     public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
 
+    /**
+     * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
+     *
+     * @see ProcessInstance#getProcessVariables()
+     */
+    public  static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s";
     /**
      * 任务的变量 - 状态
      *

+ 56 - 18
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -9,7 +9,10 @@ import cn.hutool.core.util.*;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
-import cn.iocoder.yudao.module.bpm.enums.definition.*;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups;
@@ -17,12 +20,18 @@ import org.flowable.bpmn.BpmnAutoLayout;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
 import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
@@ -76,19 +85,29 @@ public class SimpleModelUtils {
         process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么?
         bpmnModel.addProcess(process);
 
-        // 前端模型数据结构
-        // 从 SimpleModel 构建 FlowNode 并添加到 Main Process
-        traverseNodeToBuildFlowNode(simpleModelNode, process);
+        // 目前前端的第一个节点是 发起人节点。这里构建一个StartNode. 用于创建 Bpmn 的 StartEvent 节点
+        BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode();
+        startNode.setChildNode(simpleModelNode);
+        // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process
+        traverseNodeToBuildFlowNode(startNode, process);
         // 找到 end event
         EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent);
 
         // 构建并添加节点之间的连线 Sequence Flow
-        traverseNodeToBuildSequenceFlow(process, simpleModelNode, endEvent.getId());
+        traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
         // 自动布局
         new BpmnAutoLayout(bpmnModel).execute();
         return bpmnModel;
     }
 
+    private static BpmSimpleModelNodeVO buildStartSimpleModelNode() {
+        BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO();
+        startNode.setId(START_EVENT_NODE_ID);
+        startNode.setName(START_EVENT_NODE_NAME);
+        startNode.setType(START_NODE.getType());
+        return startNode;
+    }
+
     // TODO @芋艿:在优化下这个注释
     private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
         // 1.1 无效节点返回
@@ -288,6 +307,11 @@ public class SimpleModelUtils {
                 list.add(endEvent);
                 break;
             }
+            case START_USER_NODE: { // 发起人节点
+                UserTask userTask = convertStartUserNode(node);
+                list.add(userTask);
+                break;
+            }
             case APPROVE_NODE: { // 审批节点
                 List<FlowElement> flowElements = convertApproveNode(node);
                 list.addAll(flowElements);
@@ -309,14 +333,8 @@ public class SimpleModelUtils {
                 break;
             }
 
-            case INCLUSIVE_BRANCH_FORK_NODE: {
-                InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.TRUE);
-                list.add(inclusiveGateway);
-                break;
-            }
-            case INCLUSIVE_BRANCH_JOIN_NODE: {
-                InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.FALSE);
-                list.add(inclusiveGateway);
+            case INCLUSIVE_BRANCH_NODE: {
+                // TODO jason 待实现
                 break;
             }
             default: {
@@ -326,6 +344,11 @@ public class SimpleModelUtils {
         return list;
     }
 
+    private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) {
+        return buildBpmnStartUserTask(node);
+    }
+
+
     private static List<FlowElement> convertApproveNode(BpmSimpleModelNodeVO node) {
         List<FlowElement> flowElements = new ArrayList<>();
         UserTask userTask = buildBpmnUserTask(node);
@@ -444,7 +467,7 @@ public class SimpleModelUtils {
 
         // 如果不是审批人节点,则直接返回
         addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType()));
-        if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) {
+        if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) {
             return userTask;
         }
 
@@ -576,17 +599,32 @@ public class SimpleModelUtils {
 
     // ========== 各种 build 节点的方法 ==========
 
-    private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) {
+    private static StartEvent  convertStartNode(BpmSimpleModelNodeVO node) {
         StartEvent startEvent = new StartEvent();
         startEvent.setId(node.getId());
         startEvent.setName(node.getName());
-
         // TODO 芋艿 + jason:要不要在开启节点后面,加一个“发起人”任务节点,然后自动审批通过
         // @芋艿 这个是不是由前端来实现。 默认开始节点后面跟一个 “发起人”的审批节点(审批人是发起人自己)。
-        // 我看有些平台这个审批节点允许删除,有些不允许。由用户决定
         return startEvent;
     }
 
+    private static UserTask buildBpmnStartUserTask(BpmSimpleModelNodeVO node) {
+        UserTask userTask = new UserTask();
+        userTask.setId(node.getId());
+        userTask.setName(node.getName());
+        // 人工审批
+        addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString());
+        // 候选人策略为发起人自己
+        addCandidateElements(START_USER.getStrategy(),null, userTask);
+        // 添加表单字段权限属性元素
+        addFormFieldsPermission(node.getFieldsPermission(), userTask);
+        // 添加操作按钮配置属性元素.
+        addButtonsSetting(node.getButtonsSetting(), userTask);
+        // 使用自动通过策略。TODO @芋艿 复用了SKIP, 是否需要新加一个策略
+        addAssignStartUserHandlerType(SKIP.getType(), userTask);
+
+        return userTask;
+    }
     private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) {
         EndEvent endEvent = new EndEvent();
         endEvent.setId(node.getId());

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

@@ -59,6 +59,7 @@ import java.util.stream.Stream;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 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.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
 
 /**
  * 流程任务实例 Service 实现类
@@ -194,7 +195,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         // 为什么判断 assignee 非空的情况下?
         // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
         if (StrUtil.isNotBlank(task.getAssignee())
-            && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
+                && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
             throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
         }
         return task;
@@ -618,6 +619,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
         });
 
+        // 设置流程变量节点驳回标记。用于驳回到节点。不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略 而自动通过
+        runtimeService.setVariable(currentTask.getProcessInstanceId(),
+                String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
         // 3. 执行驳回
         runtimeService.createChangeActivityStateBuilder()
                 .processInstanceId(currentTask.getProcessInstanceId())
@@ -894,7 +898,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
     /**
      * 重要补充说明:该方法目前主要有两个情况会调用到:
-     *
+     * <p>
      * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消
      * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消
      */
@@ -933,46 +937,50 @@ public class BpmTaskServiceImpl implements BpmTaskService {
                     log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId());
                     return;
                 }
-
                 // 审批人与提交人为同一人时,根据策略进行处理
                 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
-                    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);
-
-                    // 情况一:自动跳过
-                    if (ObjectUtils.equalsAny(assignStartUserHandlerType,
-                            BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
-                        getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
-                                .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason()));
-                        return;
-                    }
-                    // 情况二:转交给部门负责人审批
-                    if (ObjectUtils.equalsAny(assignStartUserHandlerType,
-                            BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) {
-                        AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
-                        Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId());
-                        DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null;
-                        Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId());
-                        // 找不到部门负责人的情况下,自动审批通过
-                        // noinspection DataFlowIssue
-                        if (dept.getLeaderUserId() == null) {
-                            getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
-                                    .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason()));
+                    // 判断是否为回退或者驳回
+                    Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
+                            String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
+                    if (!BooleanUtil.isTrue(returnTaskFlag)) { // 如果是回退或者驳回不走这个策略
+                        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
+                        if (bpmnModel == null) {
+                            log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
                             return;
                         }
-                        // 找得到部门负责人的情况下,修改负责人
-                        if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) {
-                            getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO()
-                                    .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId())
-                                    .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason()));
+                        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+                        Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
+
+                        // 情况一:自动跳过
+                        if (ObjectUtils.equalsAny(assignStartUserHandlerType,
+                                BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
+                            getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
+                                    .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason()));
                             return;
                         }
-                        // 如果部门负责人是自己,还是自己审批吧~
+                        // 情况二:转交给部门负责人审批
+                        if (ObjectUtils.equalsAny(assignStartUserHandlerType,
+                                BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) {
+                            AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
+                            Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId());
+                            DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null;
+                            Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId());
+                            // 找不到部门负责人的情况下,自动审批通过
+                            // noinspection DataFlowIssue
+                            if (dept.getLeaderUserId() == null) {
+                                getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
+                                        .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason()));
+                                return;
+                            }
+                            // 找得到部门负责人的情况下,修改负责人
+                            if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) {
+                                getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO()
+                                        .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId())
+                                        .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason()));
+                                return;
+                            }
+                            // 如果部门负责人是自己,还是自己审批吧~
+                        }
                     }
                 }