!1271 feat:审批通过时,查询下一个执行节点,校验流程执行正确与否

Merge pull request !1271 from SamllNorth_Lee/feature/bpm
This commit is contained in:
芋道源码 2025-03-05 23:14:07 +00:00 committed by Gitee
commit 8a5638bdea
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
11 changed files with 334 additions and 107 deletions

View File

@ -24,6 +24,7 @@ public interface ErrorCodeConstants {
ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败原因BPMN 流程图中,没有开始事件");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败原因BPMN 流程图中,用户任务({})的名字不存在");
ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_RULE_TYPE_NOT_APPROVE_USER_SELECT = new ErrorCode(1_009_002_008, "部署流程失败原因BPMN 流程图中,用户任务({})的规则类型不能是【审批人自选】");
// ========== 流程定义 1-009-003-000 ==========
ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "任务({})的下一个执行节点审批人未配置");
// ========== 流程任务 1-009-005-000 ==========
ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");

View File

@ -178,10 +178,22 @@ public class BpmProcessInstanceController {
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
}
@GetMapping("/get-next-approval-nodes")
@Operation(summary = "获取下一个执行的流程节点")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
@SuppressWarnings("unchecked")
public CommonResult<List<BpmApprovalDetailRespVO.ActivityNode>> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) {
if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
}
return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO));
}
@GetMapping("/get-bpmn-model-view")
@Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
@Parameter(name = "id", description = "流程实例的编号", required = true)
public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
return success(processInstanceService.getProcessInstanceBpmnModelView(id));
}
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.google.common.collect.Sets;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
/**
* 审批人自选 {@link BpmTaskCandidateUserStrategy} 实现类
* 审批人在审批时选择下一个节点的审批人
*
* @author smallNorthLee
*/
@Component
public class BpmTaskCandidateApproveUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Resource
@Lazy // 延迟加载避免循环依赖
private BpmProcessInstanceService processInstanceService;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT;
}
@Override
public void validateParam(String param) {}
@Override
public boolean isParamRequired() {
return false;
}
@Override
public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
Assert.notNull(approveUserSelectAssignees, "流程实例({}) 的下一个执行节点审批人不能为空",
execution.getProcessInstanceId());
if (approveUserSelectAssignees == null) {
return Sets.newLinkedHashSet();
}
// 获得审批人
List<Long> assignees = approveUserSelectAssignees.get(execution.getCurrentActivityId());
return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
}
@Override
public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
if (processVariables == null) {
return Sets.newLinkedHashSet();
}
// 流程预测时会使用允许审批人为空如果为空前端会弹出提示选择下一个节点审批人避免流程无法进行审批时会真正校验节点是否配置审批人
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processVariables);
if (approveUserSelectAssignees == null) {
return Sets.newLinkedHashSet();
}
// 获得审批人
List<Long> assignees = approveUserSelectAssignees.get(activityId);
return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
}
}

View File

@ -24,6 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable<Integer> {
MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
POST(22, "岗位"),
USER(30, "用户"),
APPROVE_USER_SELECT(34, "审批人,在审批时选择下一个节点的审批人"), // 审批人在审批时选择下一个节点的审批人
START_USER_SELECT(35, "发起人自选"), // 申请人自己可在提交申请时选择此节点的审批人
START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点常用于发起人信息审核场景
START_USER_DEPT_LEADER(37, "发起人部门负责人"),

View File

@ -29,6 +29,12 @@ public class BpmnVariableConstants {
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
/**
* 流程实例的变量 - 审批人选择的审批人 Map
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES = "PROCESS_APPROVE_USER_SELECT_ASSIGNEES";
/**
* 流程实例的变量 - 发起用户 ID
*

View File

@ -808,53 +808,26 @@ public class BpmnModelUtils {
// 情况ExclusiveGateway 排它只有一个满足条件的如果没有就走默认的
if (currentElement instanceof ExclusiveGateway) {
// 查找满足条件的 SequenceFlow 路径
Gateway gateway = (Gateway) currentElement;
// TODO @小北当一个网关节点下存在多个满足的并行节点时只查询一个节点流程流转会存在问题需要优化具体见issuehttps://github.com/YunaiV/ruoyi-vue-pro/issues/761
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlow = gateway.getOutgoingFlows().get(0);
}
}
SequenceFlow matchSequenceFlow = findMatchSequenceFlow((Gateway) currentElement, variables);
// 遍历满足条件的 SequenceFlow 路径
if (matchSequenceFlow != null) {
simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
}
return;
}
// 情况InclusiveGateway 包容多个满足条件的如果没有就走默认的
if (currentElement instanceof InclusiveGateway) {
else if (currentElement instanceof InclusiveGateway) {
// 查找满足条件的 SequenceFlow 路径
Gateway gateway = (Gateway) currentElement;
Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (CollUtil.isEmpty(matchSequenceFlows)) {
matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlows = gateway.getOutgoingFlows();
}
}
Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlows((Gateway) currentElement, variables);
// 遍历满足条件的 SequenceFlow 路径
matchSequenceFlows.forEach(
flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
}
// 情况ParallelGateway 并行都满足都走
if (currentElement instanceof ParallelGateway) {
else if (currentElement instanceof ParallelGateway) {
Gateway gateway = (Gateway) currentElement;
// 遍历子节点
gateway.getOutgoingFlows().forEach(
nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
return;
}
}
@ -883,6 +856,10 @@ public class BpmnModelUtils {
if (targetElement == null) {
continue;
}
// 如果是结束节点直接返回
if (targetElement instanceof EndEvent) {
break;
}
// 情况一处理不同类型的网关
if (targetElement instanceof Gateway) {
Gateway gateway = (Gateway) targetElement;
@ -911,8 +888,25 @@ public class BpmnModelUtils {
*/
private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
// TODO @小北 这里和 simulateNextFlowElements 中有重复代码是否重构每个网关节点拆分出方法应该比较合理化@芋艿
// TODO @小北ok simulateNextFlowElements 里面处理网关的复用这个方法可以么
// 查找满足条件的 SequenceFlow 路径
SequenceFlow matchSequenceFlow = findMatchSequenceFlow(gateway, variables);
// 遍历满足条件的 SequenceFlow 路径
if (matchSequenceFlow != null) {
FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
}
}
/**
* 处理排它网关Exclusive Gateway选择符合条件的路径
*
* @param gateway 排他网关
* @param variables 流程变量
* @return 符合条件的路径
*/
private static SequenceFlow findMatchSequenceFlow(Gateway gateway, Map<String, Object> variables){
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
@ -924,13 +918,7 @@ public class BpmnModelUtils {
matchSequenceFlow = gateway.getOutgoingFlows().get(0);
}
}
// 遍历满足条件的 SequenceFlow 路径
if (matchSequenceFlow != null) {
FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
}
return matchSequenceFlow;
}
/**
@ -943,6 +931,26 @@ public class BpmnModelUtils {
*/
private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
// 查找满足条件的 SequenceFlow 路径集合
Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlows(gateway, variables);
// 遍历满足条件的 SequenceFlow 路径获取目标节点
matchSequenceFlows.forEach(flow -> {
FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
});
}
/**
* 处理排它网关Inclusive Gateway选择符合条件的路径
*
* @param gateway 排他网关
* @param variables 流程变量
* @return 符合条件的路径
*/
private static Collection<SequenceFlow> findMatchSequenceFlows(Gateway gateway, Map<String, Object> variables) {
// 查找满足条件的 SequenceFlow 路径
Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression()));
@ -954,15 +962,10 @@ public class BpmnModelUtils {
matchSequenceFlows = gateway.getOutgoingFlows();
}
}
// 遍历满足条件的 SequenceFlow 路径获取目标节点
matchSequenceFlows.forEach(flow -> {
FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
});
return matchSequenceFlows;
}
/**
* 处理并行网关
*

View File

@ -200,6 +200,31 @@ public class FlowableUtils {
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
}
/**
* 获得流程实例的审批用户选择的下一个节点的审批人 Map
*
* @param processInstance 流程实例
* @return 审批用户选择的下一个节点的审批人Map
*/
public static Map<String, List<Long>> getApproveUserSelectAssignees(ProcessInstance processInstance) {
return processInstance != null ? getApproveUserSelectAssignees(processInstance.getProcessVariables()) : null;
}
/**
* 获得流程实例的审批用户选择的下一个节点的审批人 Map
*
* @param processVariables 流程变量
* @return 审批用户选择的下一个节点的审批人Map Map
*/
@SuppressWarnings("unchecked")
public static Map<String, List<Long>> getApproveUserSelectAssignees(Map<String, Object> processVariables) {
if (processVariables == null) {
return null;
}
return (Map<String, List<Long>>) processVariables.get(
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
}
/**
* 获得流程实例的摘要
*

View File

@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
@ -23,9 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
@ -48,6 +47,7 @@ import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseCandidateStrategy;
/**
* 流程模型实现主要进行 Flowable {@link Model} 的维护
@ -243,7 +243,18 @@ public class BpmModelServiceImpl implements BpmModelService {
if (startEvent == null) {
throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS);
}
// 2. 校验 UserTask name 都配置了
// 2. 校验第一个用户任务的规则类型是否为 审批人自选如果是则抛出异常第一个用户任务的规则类型不允许是审批人自选因为会出现无审批人的情况
List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
if (CollUtil.isNotEmpty(outgoingFlows)){
// 2.1 获取第一个用户任务节点
FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
// 2.2 获取审批人策略
Integer candidateStrategy = parseCandidateStrategy(targetFlowElement);
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())){
throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_RULE_TYPE_NOT_APPROVE_USER_SELECT, targetFlowElement.getName());
}
}
// 3. 校验 UserTask name 都配置了
List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
userTasks.forEach(userTask -> {
if (StrUtil.isEmpty(userTask.getName())) {

View File

@ -96,6 +96,15 @@ public interface BpmProcessInstanceService {
*/
BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
/**
* 获取下一个执行节点信息
*
* @param loginUserId 登录人的用户编号
* @param reqVO 请求信息
* @return 下一个执行节点信息
*/
List<BpmApprovalDetailRespVO.ActivityNode> getNextApprovalNodes(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
/**
* 获取流程实例的 BPMN 模型视图
*
@ -172,4 +181,5 @@ public interface BpmProcessInstanceService {
* @param instance 流程任务
*/
void processProcessInstanceCompleted(ProcessInstance instance);
}

View File

@ -11,9 +11,11 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
@ -55,6 +57,7 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -166,7 +169,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
Long startUserId = loginUserId; // 流程发起人
HistoricProcessInstance historicProcessInstance = null; // 流程实例
Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量
Map<String, Object> processVariables = new HashMap<>(); // 流程变量
// 1.2 如果是流程已发起的场景则使用流程实例的数据
if (reqVO.getProcessInstanceId() != null) {
historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
@ -177,10 +180,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
// 合并 DB 和前端传递的流量变量以前端的为主
Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
if (CollUtil.isNotEmpty(processVariables)) {
historicVariables.putAll(processVariables);
if (CollUtil.isNotEmpty(historicVariables)) {
processVariables.putAll(historicVariables);
}
processVariables = historicVariables;
}
if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
processVariables.putAll(reqVO.getProcessVariables());
}
// 1.3 读取其它相关数据
ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
@ -217,12 +222,78 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo,
processVariables, activities);
// 3.3 如果是发起动作,activityId为开始节点不校验审批人自选节点
if (ObjUtil.isNotNull(reqVO.getActivityId()) && ObjUtil.equals(reqVO.getActivityId(),BpmnModelConstants.START_USER_NODE_ID)){
simulateActivityNodes.removeIf(node -> BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
}
// 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask);
}
@Override
public List<ActivityNode> getNextApprovalNodes(Long loginUserId, BpmApprovalDetailReqVO reqVO) {
// 1.1 校验任务存在
Task task = taskService.getTask(reqVO.getTaskId());
if (task == null) {
throw exception(TASK_NOT_EXISTS);
}
// 1.2 校验任务是否由当前用户审批
if (StrUtil.isNotBlank(task.getAssignee())
&& ObjectUtil.notEqual(loginUserId, NumberUtils.parseLong(task.getAssignee()))) {
throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
}
// 1.3 校验流程实例存在
ProcessInstance instance = getProcessInstance(task.getProcessInstanceId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 1.4 校验BpmnModel
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(task.getProcessDefinitionId());
if (bpmnModel == null) {
return null;
}
//1.5 流程实例是否存在
HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
if (historicProcessInstance == null) {
throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
}
// 2 设置流程变量
Map<String, Object> processVariables = new HashMap<>();
// 2.1 获取历史中流程变量
Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
if (CollUtil.isNotEmpty(historicVariables)) {
processVariables.putAll(historicVariables);
}
// 2.2 合并前端传递的流程变量以前端为准
if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
processVariables.putAll(reqVO.getProcessVariables());
}
// 3 获取当前任务节点的信息
FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
// 3.1 获取下一个将要执行的节点集合
List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
return convertList(nextFlowNodes, node -> {
List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables);
// 3.2 获取节点的审批人信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(candidateUserIds);
// 3.3 获取节点的审批人部门信息
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 3.4 存在一个节点多人审批的情况组装审批人信息
List<UserSimpleBaseVO> candidateUsers = new ArrayList<>();
userMap.forEach((key, value) -> candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(key, userMap, deptMap)));
return new ActivityNode().setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
.setId(node.getId())
.setName(node.getName())
.setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
.setCandidateUsers(candidateUsers);
});
}
@Override
@SuppressWarnings("unchecked")
public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
@ -678,8 +749,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// true不影响没配置
// skipExpression 的节点
if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
// 设置流程变量发起人自选审批人
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
startUserSelectAssignees);
// // 设置流程变量审批人自选审批人
// variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,
// startUserSelectAssignees);
}
// 3. 创建流程
@ -711,17 +786,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
Map<String, List<Long>> startUserSelectAssignees,
Map<String,Object> variables) {
Map<String, Object> variables) {
// 1. 获取预测的节点信息
BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
.setProcessDefinitionId(definition.getId())
.setProcessVariables(variables));
List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
if (CollUtil.isEmpty(activityNodes)){
if (CollUtil.isEmpty(activityNodes)) {
return;
}
// 2.1 移除掉不是发起人自选审批人节点
// 2.1 移除掉不是发起人或者审批人自选审批人节点
activityNodes.removeIf(task ->
ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
// 2.2 流程发起时要先获取当前流程的预测走向节点发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了

View File

@ -552,22 +552,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 2.3 调用 BPM complete 去完成任务
// 其中variables 是存储动态表单到 local 任务级别过滤一下避免 ProcessInstance 系统级的变量被占用
if (CollUtil.isNotEmpty(reqVO.getVariables())) {
Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
// 校验传递的参数中是否为下一个将要执行的任务节点
validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
// 如果有下一个审批人则设置到流程变量中
// TODO @小北validateNextAssignees 升级成 validateAndSetNextAssignees然后里面吧下面这一小段逻辑抽进去如何
if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
// 获取实例中的全部节点数据避免后续节点的审批人被覆盖
// TODO @小北这里有个需要讨论的点微信哈
// TODO 因为 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES 定位是发起人那么审批人选择的放在 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES目前想到两个方案
// TODO 方案一增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES然后设置到这里面然后BpmTaskCandidateStartUserSelectStrategy 也从这里读
// TODO 方案二也是增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES根据节点审批人类型放到 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEESPROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
// TODO 方案三融合成 PROCESS_INSTANCE_VARIABLE_USER_SELECT_ASSIGNEES不再区分是发起人选择还是审批人选择
Map<String, List<Long>> hisProcessVariables = FlowableUtils.getStartUserSelectAssignees(instance.getProcessVariables());
hisProcessVariables.putAll(reqVO.getNextAssignees());
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, hisProcessVariables);
}
Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
bpmnModel, reqVO.getNextAssignees(), instance);
runtimeService.setVariables(task.getProcessInstanceId(), variables);
taskService.complete(task.getId(), variables, true);
} else {
@ -591,42 +578,59 @@ public class BpmTaskServiceImpl implements BpmTaskService {
* @param nextAssignees 下一个节点审批人集合参数
* @param processInstance 流程实例
*/
private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
// 下一个节点参数为空不做处理表示流程正常流转无需选择下一个节点的审判人
if (CollUtil.isEmpty(nextAssignees)){
return variables;
}
// 1. 获取当前任务节点的信息
FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
// 2. 获取下一个将要执行的节点集合
List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
Map<String, List<Long>> processVariables = new HashMap<>();
// 3. 循环下一个将要执行的节点集合
for (FlowNode nextFlowNode : nextFlowNodes) {
// 3.1 获取下一个将要执行节点的属性是否为自选审批人等
// TODO @小北public static Integer parseCandidateStrategy(FlowElement userTask) 使用这个工具方法哈
Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
if (CollUtil.isEmpty(elements)) {
continue;
}
// 3.2 获取节点中的审批人策略
Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
// 3.3 获取流程实例中的发起人自选审批人
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
// 3.4 如果节点中的审批人策略为 发起人自选并且该节点的审批人为空
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
// 先判断前端传递的参数节点节点是否为将要执行的节点
// TODO @小北!nextAssignees.containsKey(nextFlowNode.getId()) CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) 是不是等价的
// 3.1 获取下一个将要执行节点中的审批人策略
Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
// 3.2 判断节点是否为执行节点仅校验节点
if (!nextAssignees.containsKey(nextFlowNode.getId())) {
throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
}
// 如果前端传递的节点为空则抛出异常
// TODO @小北换一个错误码哈
if (CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) {
// 3.3 获取节点中的审批人
List<Long> assignees = nextAssignees.get(nextFlowNode.getId());
// 3.4 流程变量
// 3.5 如果节点中的审批人策略为 发起人自选
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
if(processVariables == null){
processVariables = new HashMap<>();
}
List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
// 如果当前节点已经存在审批人则不允许覆盖
if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
continue;
}
// 如果节点存在但未配置审批人
if (CollUtil.isEmpty(assignees)){
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
}
// 校验通过的全部节点和审批人
processVariables.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
}
// TODO @小北加一个审批人选择的校验
// 3.6 如果节点中的审批人策略为 审批人在审批时选择下一个节点的审批人并且该节点的审批人为空
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())){
// 如果节点存在但未配置审批人
if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
}
// 校验通过的全部节点和审批人
processVariables.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
}
}
return variables;
}
/**