【功能新增】工作流:审批类型,区分人工审批、自动通过、自动拒绝

This commit is contained in:
YunaiV 2024-08-17 18:21:30 +08:00
parent 0d738fa397
commit 17c7fa44c1
13 changed files with 127 additions and 41 deletions

View File

@ -14,7 +14,7 @@ import java.util.Arrays;
*/
@Getter
@AllArgsConstructor
public enum BpmApproveMethodEnum implements IntArrayValuable {
public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
RANDOM(1, "随机挑选一人审批"),
RATIO(2, "多人会签(按通过比例)"), // 会签按通过比例
@ -31,9 +31,9 @@ public enum BpmApproveMethodEnum implements IntArrayValuable {
*/
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmApproveMethodEnum::getMethod).toArray();
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();
public static BpmApproveMethodEnum valueOf(Integer method) {
public static BpmUserTaskApproveMethodEnum valueOf(Integer method) {
return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values());
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户任务的审批类型枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable {
USER(1), // 人工审批
AUTO_APPROVE(2), // 自动通过
AUTO_REJECT(3); // 自动拒绝
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人为空时处理类型枚举
*
@ -19,11 +21,13 @@ public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable {
ASSIGN_ADMIN(4), // 转交给流程管理员
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return new int[0];
return ARRAYS;
}
}

View File

@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人与发起人相同时处理类型枚举
*
@ -17,11 +19,13 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl
SKIP(2), // 自动跳过参考飞书1如果当前节点还有其他审批人则交由其他审批人进行审批2如果当前节点没有其他审批人则该节点自动通过
TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批参考飞书若部门负责人为空则自动通过
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return new int[0];
return ARRAYS;
}
}

View File

@ -30,6 +30,8 @@ public enum BpmReasonEnum {
ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
;
private final String reason;

View File

@ -59,8 +59,12 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "候选人参数")
private String candidateParam; // 用于审批抄送节点
@Schema(description = "审批节点类型", example = "1")
@InEnum(BpmUserTaskApproveTypeEnum.class)
private Integer approveType; // 用于审批节点
@Schema(description = "多人审批方式", example = "1")
@InEnum(BpmApproveMethodEnum.class)
@InEnum(BpmUserTaskApproveMethodEnum.class)
private Integer approveMethod; // 用于审批节点
@Schema(description = "通过比例", example = "100")
@ -155,8 +159,6 @@ public class BpmSimpleModelNodeVO {
private Boolean enable;
}
// Map<String, Integer> formPermissions; 表单权限仅发起审批抄送节点会使用
// TODO @芋艿 没有人的策略
// TODO @芋艿条件建议可以固化的一些选项然后有个表达式兜底要支持
}

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
@ -52,7 +52,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
// 第二步获取任务的所有处理人
Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊如果没有处理人的情况下至少有一个 null 空元素保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务避免自动通过
// 特殊如果没有处理人的情况下至少有一个 null 空元素避免自动通过
// 这样保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
// 用途1审批人为空时2审批类型为自动通过自动拒绝时
assigneeUserIds = SetUtils.asSet((Long) null);
}
execution.setVariable(super.collectionVariable, assigneeUserIds);

View File

@ -2,15 +2,14 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@ -44,9 +43,11 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步获取任务的所有处理人
Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序
Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊如果没有处理人的情况下至少有一个 null 空元素保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务避免自动通过
// 特殊如果没有处理人的情况下至少有一个 null 空元素避免自动通过
// 这样保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
// 用途1审批人为空时2审批类型为自动通过自动拒绝时
assigneeUserIds = SetUtils.asSet((Long) null);
}
execution.setVariable(super.collectionVariable, assigneeUserIds);

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
@ -56,18 +57,31 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
return;
}
// 特殊审批人为空根据配置是否要自动通过自动拒绝
// 特殊处理需要自动通过不通过的情况
Integer approveType = BpmnModelUtils.parseApproveType(userTask);
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTask);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
} else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
// 特殊情况一人工审核审批人为空根据配置是否要自动通过自动拒绝
if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
} else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
}
// 特殊情况二自动审核审批类型为自动通过不通过
} else {
if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason()));
} else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason()));
}
}
}

View File

@ -6,7 +6,9 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
@ -20,10 +22,7 @@ import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG;
@ -61,7 +60,14 @@ public class BpmTaskCandidateInvoker {
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
// 遍历所有的 UserTask校验审批人配置
userTaskList.forEach(userTask -> {
// 1. 非空校验
// 1.1 非人工审批无需校验审批人配置
Integer approveType = BpmnModelUtils.parseApproveType(userTask);
if (ObjectUtils.equalsAny(approveType,
BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
return;
}
// 1.2 非空校验
Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
String param = BpmnModelUtils.parseCandidateParam(userTask);
if (strategy == null) {
@ -84,6 +90,14 @@ public class BpmTaskCandidateInvoker {
*/
@DataPermission(enable = false) // 忽略数据权限避免因为过滤导致找不到候选人
public Set<Long> calculateUsers(DelegateExecution execution) {
// 审批类型非人工审核时不进行计算候选人原因是后续会自动通过不通过
Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement());
if (ObjectUtils.equalsAny(approveType,
BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
return new HashSet<>();
}
Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
// 1.1 计算任务的候选人

View File

@ -56,12 +56,16 @@ public interface BpmnModelConstants {
*/
String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
/**
* BPMN UserTask 的扩展属性用于标记用户任务的审批类型
*/
String USER_TASK_APPROVE_TYPE = "approveType";
/**
* BPMN UserTask 的扩展属性用于标记用户任务的审批方式
*/
String USER_TASK_APPROVE_METHOD = "approveMethod";
// TODO @jason这个命名可能有个 fieldsPermissions 更合适点可能 formPermissions 会更更合适
/**
* BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
*/
@ -76,6 +80,7 @@ public interface BpmnModelConstants {
*/
String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission";
// TODO @jason上面是 fieldsPermission然后这里是 buttonsSettings感觉有点不统一然后 BpmSimpleModelNodeVO 里面是 fieldsPermissionbuttonsSetting
/**
* BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置
*/

View File

@ -45,6 +45,10 @@ public class BpmnModelUtils {
return candidateParam;
}
public static Integer parseApproveType(FlowElement userTask) {
return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
}
public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);

View File

@ -9,10 +9,7 @@ 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.BpmApproveMethodEnum;
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.*;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups;
import org.flowable.bpmn.BpmnAutoLayout;
@ -448,9 +445,11 @@ public class SimpleModelUtils {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
// 设置审批任务的截止时间
if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
// 如果不是审批人节点则直接返回
addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType()));
if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) {
return userTask;
}
// 添加候选人元素
@ -467,6 +466,10 @@ public class SimpleModelUtils {
addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
// 添加用户任务的空处理元素
addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
// 设置审批任务的截止时间
if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
}
return userTask;
}
@ -494,8 +497,8 @@ public class SimpleModelUtils {
}
private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) {
BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
if (approveMethodEnum == null || approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
return;
}
// 添加审批方式的扩展属性
@ -504,19 +507,19 @@ public class SimpleModelUtils {
MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
// 设置 collectionVariable本系统用不到仅仅为了 Flowable 校验不报错
multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY) {
if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION);
multiInstanceCharacteristics.setSequential(false);
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
} else if (bpmApproveMethodEnum == BpmApproveMethodEnum.SEQUENTIAL) {
} else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) {
multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION);
multiInstanceCharacteristics.setSequential(true);
multiInstanceCharacteristics.setLoopCardinality("1");
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
} else if (bpmApproveMethodEnum == BpmApproveMethodEnum.RATIO) {
} else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) {
Assert.notNull(approveRatio, "通过比例不能为空");
double approvePct = approveRatio / (double) 100;
multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct)));
multiInstanceCharacteristics.setCompletionCondition(
String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100)));
multiInstanceCharacteristics.setSequential(false);
}
userTask.setLoopCharacteristics(multiInstanceCharacteristics);