Merge branch 'feature/bpm' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

This commit is contained in:
YunaiV 2025-03-30 10:53:17 +08:00
commit a53c7dafd3
20 changed files with 238 additions and 89 deletions

View File

@ -42,6 +42,7 @@ public interface ErrorCodeConstants {
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, "下一个任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
// ========== 流程任务 1-009-005-000 ==========
ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");

View File

@ -18,6 +18,7 @@ public enum BpmReasonEnum {
REJECT_TASK("审批不通过任务,原因:{}"), // 场景用户审批不通过任务修改文案时需要注意 isRejectReason 方法
CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景用户主动取消流程
CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景管理员取消流程
CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:主流程已取消"),
// ========== 流程任务的独有原因 ==========

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.bpm.controller.admin.base.dept;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "部门精简信息 VO")
@Data
public class DeptSimpleBaseVO {
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部")
private String name;
}

View File

@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -53,6 +55,8 @@ public class BpmModelController {
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@GetMapping("/list")
@Operation(summary = "获得模型分页")
@ -79,14 +83,19 @@ public class BpmModelController {
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
deploymentMap.keySet());
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
// 获得 User Map
// 获得 User MapDept Map
Set<Long> userIds = convertSetByFlatMap(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
});
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Set<Long> deptIds = convertSetByFlatMap(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty();
});
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
return success(BpmModelConvert.INSTANCE.buildModelList(list,
formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap));
}
@GetMapping("/get")

View File

@ -59,6 +59,9 @@ public class BpmModelMetaInfoVO {
@Schema(description = "可发起用户编号数组", example = "[1,2,3]")
private List<Long> startUserIds;
@Schema(description = "可发起部门编号数组", example = "[2,4,6]")
private List<Long> startDeptIds;
@Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
@NotEmpty(message = "可管理用户编号数组不能为空")
private List<Long> managerUserIds;
@ -88,6 +91,12 @@ public class BpmModelMetaInfoVO {
@Schema(description = "流程后置通知设置", example = "{}")
private HttpRequestSetting processAfterTriggerSetting;
@Schema(description = "任务前置通知设置", example = "{}")
private HttpRequestSetting taskBeforeTriggerSetting;
@Schema(description = "任务后置通知设置", example = "{}")
private HttpRequestSetting taskAfterTriggerSetting;
@Schema(description = "流程 ID 规则")
@Data
@Valid

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
@ -39,6 +40,9 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO {
@Schema(description = "可发起的用户数组")
private List<UserSimpleBaseVO> startUsers;
@Schema(description = "可发起的部门数组")
private List<DeptSimpleBaseVO> startDepts;
@Schema(description = "BPMN XML")
private String bpmnXml;

View File

@ -112,7 +112,7 @@ public class BpmSimpleModelNodeVO {
/**
* 条件节点设置
*/
private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
@Schema(description = "路由分支组", example = "[]")
private List<RouterSetting> routerGroups;
@ -241,7 +241,7 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "条件设置")
@Data
@Valid
// 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
// 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
public static class ConditionSetting {
@Schema(description = "条件类型", example = "1")

View File

@ -148,7 +148,6 @@ public class BpmProcessInstanceController {
processDefinition, processDefinitionInfo, startUser, dept));
}
// TODO @lesan子流程子流程如果取消主流程应该是通过还是不通过哈还是禁用掉子流程的取消
@DeleteMapping("/cancel-by-start-user")
@Operation(summary = "用户取消流程实例", description = "取消发起的流程")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")

View File

@ -72,6 +72,9 @@ public class BpmApprovalDetailRespVO {
@Schema(description = "候选人用户列表")
private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
@Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf")
private String processInstanceId; // 当且仅当该节点是子流程节点时才会有值CallActivity processInstanceId 字段
}
@Schema(description = "活动节点的任务信息")

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil;
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.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
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.BpmModelRespVO;
@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmPro
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
@ -43,7 +45,8 @@ public interface BpmModelConvert {
Map<String, BpmCategoryDO> categoryMap,
Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap,
Map<Long, AdminUserRespDTO> userMap) {
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
List<BpmModelRespVO> result = convertList(list, model -> {
BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
@ -52,7 +55,8 @@ public interface BpmModelConvert {
ProcessDefinition processDefinition = model.getDeploymentId() != null ?
processDefinitionMap.get(model.getDeploymentId()) : null;
List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers);
List<DeptRespDTO> startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null;
return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts);
});
// 排序
result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
@ -61,7 +65,7 @@ public interface BpmModelConvert {
default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) {
BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null, null);
if (ArrayUtil.isNotEmpty(bpmnBytes)) {
modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
}
@ -72,7 +76,7 @@ public interface BpmModelConvert {
default BpmModelRespVO buildModel0(Model model,
BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category,
Deployment deployment, ProcessDefinition processDefinition,
List<AdminUserRespDTO> startUsers) {
List<AdminUserRespDTO> startUsers, List<DeptRespDTO> startDepts) {
BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName())
.setKey(model.getKey()).setCategory(model.getCategory())
.setCreateTime(DateUtils.of(model.getCreateTime()));
@ -94,8 +98,9 @@ public interface BpmModelConvert {
modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
}
}
// User
modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class));
// UserDept
modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class))
.setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class));
return modelRespVO;
}

View File

@ -151,6 +151,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
@TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
private List<Long> startUserIds;
/**
* 可发起部门编号数组
*
* 关联 {@link AdminUserRespDTO#getDeptId()} 字段的数组
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> startDeptIds;
/**
* 可管理用户编号数组
*
@ -199,4 +207,16 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting;
/**
* 任务前置通知设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting taskBeforeTriggerSetting;
/**
* 任务后置通知设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
}

View File

@ -47,7 +47,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.TASK_CREATED)
.add(FlowableEngineEventType.TASK_ASSIGNED)
// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过所以不需要监听了
.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过这里仅处理任务后置通知
.add(FlowableEngineEventType.ACTIVITY_CANCELLED)
.add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
.build();
@ -66,6 +66,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
taskService.processTaskAssigned((Task) event.getEntity());
}
@Override
protected void taskCompleted(FlowableEngineEntityEvent event) {
taskService.processTaskCompleted((Task) event.getEntity());
}
@Override
protected void activityCancelled(FlowableActivityCancelledEvent event) {
List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());

View File

@ -662,6 +662,10 @@ public class SimpleModelUtils {
* 构造条件表达式
*/
public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
// 并行网关不需要设置条件
if (conditionSetting == null) {
return null;
}
return buildConditionExpression(conditionSetting.getConditionType(), conditionSetting.getConditionExpression(),
conditionSetting.getConditionGroups());
}
@ -813,7 +817,6 @@ public class SimpleModelUtils {
callActivity.setCalledElementType("key");
// 1. 是否异步
if (node.getChildProcessSetting().getAsync()) {
// TODO @lesan: 这里目前测试没有跳过执行call activity 后面的节点
callActivity.setAsynchronous(true);
}

View File

@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitio
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
@ -50,6 +52,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
@Resource
private BpmProcessDefinitionInfoMapper processDefinitionMapper;
@Resource
private AdminUserApi adminUserApi;
@Override
public ProcessDefinition getProcessDefinition(String id) {
return repositoryService.getProcessDefinition(id);
@ -88,14 +93,24 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
if (processDefinition == null) {
return false;
}
// 为空则所有人都可以发起
if (CollUtil.isEmpty(processDefinition.getStartUserIds())) {
return true;
}
// 不为空则需要存在里面
// 校验用户是否在允许发起的用户列表中
if (CollUtil.isNotEmpty(processDefinition.getStartUserIds())) {
return processDefinition.getStartUserIds().contains(userId);
}
// 校验用户是否在允许发起的部门列表中
if (CollUtil.isNotEmpty(processDefinition.getStartDeptIds())) {
AdminUserRespDTO user = adminUserApi.getUser(userId);
return user != null
&& user.getDeptId() != null
&& processDefinition.getStartDeptIds().contains(user.getDeptId());
}
// 都为空则所有人都可以发起
return true;
}
@Override
public List<Deployment> getDeploymentList(Set<String> ids) {
if (CollUtil.isEmpty(ids)) {

View File

@ -14,7 +14,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
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.*;
@ -63,7 +62,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@ -263,30 +261,31 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
processVariables.putAll(reqVO.getProcessVariables());
}
// 3 获取当前任务节点的信息
// 3.1 获取下一个将要执行的节点集合
// 3. 获取下一个将要执行的节点集合
FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
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())
List<ActivityNode> nextActivityNodes = convertList(nextFlowNodes, node -> new ActivityNode().setId(node.getId())
.setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
.setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
// TODO @小北先把 candidateUserIds 设置完然后最后拼接 candidateUsers 信息这样如果有多个节点就不用重复查询啦类似 buildApprovalDetail 思路
// TODO 先拼接处 List ActivityNode
// TODO 接着再起一段处理 adminUserApi.getUserMap(candidateUserIds)deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId))
.setCandidateUsers(candidateUsers);
});
.setCandidateUserIds(getTaskCandidateUserList(bpmnModel, node.getId(),
loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables)));
if (CollUtil.isNotEmpty(nextActivityNodes)) {
return nextActivityNodes;
}
// 4. 拼接基础信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(nextActivityNodes, ActivityNode::getCandidateUserIds, Collection::stream));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
nextActivityNodes.forEach(node -> node.setCandidateUsers(convertList(node.getCandidateUserIds(), userId -> {
AdminUserRespDTO user = userMap.get(userId);
if (user != null) {
return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap);
}
return null;
})));
return nextActivityNodes;
}
@Override
@ -388,8 +387,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
// 遍历 tasks 列表只处理已结束的 UserTask
// 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities 的话它无法成为一个节点
// TODO @芋艿子流程只有activity这里获取不到已结束的子流程
// TODO @lesan子流程基于 activities 查询出 usertaskcallactivity然后拼接如果是子流程就是可以点击过去
List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
@ -411,7 +408,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 遍历 activities只处理已结束的 StartEventEndEvent
List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_CALL_ACTIVITY, ELEMENT_EVENT_END)));
endActivities.forEach(activity -> {
// StartEvent只处理 BPMN 的场景因为SIMPLE 情况下已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
@ -445,7 +442,20 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
approvalNodes.add(endNode);
}
// CallActivity
if (ELEMENT_CALL_ACTIVITY.equals(activity.getActivityType())) {
ActivityNode callActivity = new ActivityNode().setId(activity.getId())
.setName(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getName())
.setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus)
.setStartTime(DateUtils.of(activity.getStartTime()))
.setEndTime(DateUtils.of(activity.getEndTime()))
.setProcessInstanceId(activity.getProcessInstanceId());
approvalNodes.add(callActivity);
}
});
// 按照时间排序
approvalNodes.sort(Comparator.comparing(ActivityNode::getStartTime));
return approvalNodes;
}
@ -465,7 +475,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
HistoricActivityInstance::getActivityId);
// 按照 activityId 分组构建 ApprovalNodeInfo 节点
// TODO @lesan子流程在子流程进行审批的时候HistoricActivityInstance 里面可以拿到 runActivities.get(0).getCalledProcessInstanceId()要不要支持跳转
Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);
return convertList(runningTaskMap.entrySet(), entry -> {
String activityId = entry.getKey();
@ -511,6 +520,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
approvalTaskInfo.getAssignee())); // 委派或者向前加签情况需要先比较 owner
activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));
}
if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) {
activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId());
}
return activityNode;
});
}
@ -824,6 +836,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
&& Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW);
}
// 1.4 子流程不允许取消
if (StrUtil.isNotBlank(instance.getSuperExecutionId())) {
throw exception(PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW);
}
// 2. 取消流程
updateProcessInstanceCancel(cancelReqVO.getId(),
@ -850,7 +866,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmProcessInstanceStatusEnum.CANCEL.getStatus());
runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);
// 2. 结束流程
// 2. 取消所有子流程
List<ProcessInstance> childProcessInstances = runtimeService.createProcessInstanceQuery()
.superProcessInstanceId(id).list();
childProcessInstances.forEach(processInstance -> updateProcessInstanceCancel(
processInstance.getProcessInstanceId(), BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason()));
// 3. 结束流程
taskService.moveTaskToEnd(id, reason);
}
@ -915,10 +937,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(instance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse());
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
}
}
});
@ -937,10 +956,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(instance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse());
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
});
}

View File

@ -278,6 +278,13 @@ public interface BpmTaskService {
*/
void processTaskAssigned(Task task);
/**
* 处理 Task 完成事件目前是发送任务后置通知
*
* @param task 任务实体
*/
void processTaskCompleted(Task task);
/**
* 处理 Task 审批超时事件可能会处理多个当前审批中的任务
*

View File

@ -6,12 +6,14 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
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.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
@ -23,6 +25,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
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.service.definition.BpmFormService;
@ -597,47 +600,54 @@ public class BpmTaskServiceImpl implements BpmTaskService {
*/
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
// simple 设计器第一个节点默认为发起人节点不校验是否存在审批人
if (Objects.equals(taskDefinitionKey, START_USER_NODE_ID)) {
return variables;
}
// 1. 获取下一个将要执行的节点集合
FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
// 2. 校验选择的下一个节点的审批人是否合法
Map<String, List<Long>> processVariables;
for (FlowNode nextFlowNode : nextFlowNodes) {
Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
// 2.1 情况一如果节点中的审批人策略为 发起人自选
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
// 特殊如果当前节点已经存在审批人则不允许覆盖
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
if (startUserSelectAssignees != null && CollUtil.isNotEmpty(startUserSelectAssignees.get(nextFlowNode.getId()))) {
continue;
}
// 如果节点存在但未配置审批人
List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
}
processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
// 特殊如果当前节点已经存在审批人则不允许覆盖
if (processVariables != null && CollUtil.isNotEmpty(processVariables.get(nextFlowNode.getId()))) {
// 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES
if (startUserSelectAssignees == null) {
startUserSelectAssignees = new HashMap<>();
}
startUserSelectAssignees.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
continue;
}
// 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES
if (processVariables == null) {
processVariables = new HashMap<>();
}
processVariables.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
}
// 2.2 情况二如果节点中的审批人策略为 审批人在审批时选择下一个节点的审批人并且该节点的审批人为空
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
// 如果节点存在但未配置审批人
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
}
processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
if (processVariables == null) {
processVariables = new HashMap<>();
}
// 设置 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
processVariables.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
if (approveUserSelectAssignees == null) {
approveUserSelectAssignees = new HashMap<>();
}
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, approveUserSelectAssignees);
}
}
return variables;
@ -1174,12 +1184,26 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
// 2. 处理自动通过的情况例如说1无审批人时是否自动通过不通过2人工审核是否自动通过不通过
ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
if (processInstance == null) {
log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId());
return;
}
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.
getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
if (processDefinitionInfo == null) {
log.error("[processTaskCreated][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId());
return;
}
// 2. 任务前置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
}
// 3. 处理自动通过的情况例如说1无审批人时是否自动通过不通过2人工审核是否自动通过不通过
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement);
@ -1391,6 +1415,28 @@ public class BpmTaskServiceImpl implements BpmTaskService {
});
}
@Override
public void processTaskCompleted(Task task) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
if (processInstance == null) {
log.error("[processTaskCompleted][taskId({}) 没有找到流程实例]", task.getId());
return;
}
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.
getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
if (processDefinitionInfo == null) {
log.error("[processTaskCompleted][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId());
return;
}
// 任务后置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) {

View File

@ -53,10 +53,7 @@ public class BpmUserTaskListener implements TaskListener {
listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getId()));
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
listenerHandler.getPath(),
listenerHandler.getHeader(),
listenerHandler.getBody(),
false, null);
listenerHandler.getPath(), listenerHandler.getHeader(), listenerHandler.getBody(), false, null);
// 3. 是否需要后续操作TODO 芋艿待定
}

View File

@ -45,10 +45,7 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
.setKey("taskDefineKey") // 重要回调请求 taskDefineKey 需要传给被调用方用于回调执行
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(setting.getCallbackTaskDefineKey()));
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
false, null);
setting.getUrl(), setting.getHeader(), setting.getBody(), false, null);
}
}

View File

@ -40,10 +40,7 @@ public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
// 2. 发起请求
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse());
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
}
}