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

This commit is contained in:
jason 2024-08-21 20:55:54 +08:00
parent 3a433e8226
commit 5679b4ef95
6 changed files with 127 additions and 65 deletions

View File

@ -19,18 +19,20 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
// TODO @jaosn-1014-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) {

View File

@ -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;
/**

View File

@ -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 = "开始";
}

View File

@ -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";
/**
* 任务的变量 - 状态
*

View File

@ -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());

View File

@ -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 审批通过其它或签 BC 等任务会被 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);
// 判断是否为回退或者驳回
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;
}
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) {
// 情况一自动跳过
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_DEPT_LEADER_NOT_FOUND.getReason()));
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.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;
// 情况二转交给部门负责人审批
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;
}
// 如果部门负责人是自己还是自己审批吧~
}
// 如果部门负责人是自己还是自己审批吧~
}
}