feat: 工作流-》流程实例的任务节点回退

This commit is contained in:
kehaiyou 2023-09-23 14:47:34 +08:00
parent 9630f236f8
commit b13f71424b
10 changed files with 563 additions and 40 deletions

View File

@ -0,0 +1,274 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.util.io.StringStreamSource;
import java.util.*;
/**
* 流程模型转操作工具类
*/
public class ModelUtils {
/**
* 根据节点获取入口连线
*
* @param source 起始节点
* @return 入口连线列表
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
List<SequenceFlow> sequenceFlows = new ArrayList<>();
if (source instanceof FlowNode) {
sequenceFlows = ((FlowNode) source).getIncomingFlows();
}
return sequenceFlows;
}
/**
* 根据节点获取出口连线
*
* @param source 起始节点
* @return 出口连线列表
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
List<SequenceFlow> sequenceFlows = new ArrayList<>();
if (source instanceof FlowNode) {
sequenceFlows = ((FlowNode) source).getOutgoingFlows();
}
return sequenceFlows;
}
/**
* 获取流程元素信息
*
* @param model bpmnModel对象
* @param flowElementId 元素ID
* @return 元素信息
*/
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
/**
* 找到 source 节点之前的所有用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @return
*/
public static List<UserTask> getPreUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果该节点为开始节点且存在上级子节点则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = getPreUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根据类型获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复说明循环了跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 类型为用户节点则新增父级节点
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 类型为子流程则添加子流程开始节点出口处相连的节点
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 获取子流程用户任务节点
List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到节点则说明该线路找到节点不继续向下找反之继续
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
}
}
// 继续迭代
userTaskList = getPreUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return
*/
public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复说明循环了跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型且任务节点的 Key 正在运行的任务中存在添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到节点则说明该线路找到节点不继续向下找反之继续
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 迭代从后向前扫描判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况但存在从子流程出去到父流程情况
*
* @param source 起始节点
* @param target 目标节点
* @param visitedElements 已经经过的连线的 ID用于判断线路是否重复
* @return 结果
*/
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
//不能是开始事件和子流程
if (source instanceof StartEvent && isInEventSubprocess(source)) {
return false;
}
// 根据类型获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null && sequenceFlows.size() > 0) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复说明循环了跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
// 这条线路存在目标节点这条线路完成进入下个线路
if (target.getId().equals(sourceFlowElement.getId())) {
continue;
}
// 如果目标节点为并行网关则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
boolean isSequential = isSequentialReachable(sourceFlowElement, target, visitedElements);
if (!isSequential) {
return false;
}
}
}
return true;
}
/**
* 判断当前节点是否属于不同的子流程
*
* @param flowElement 被判断的节点
* @return true表示属于子流程
*/
protected static boolean isInEventSubprocess(FlowElement flowElement) {
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
/**
* 根据正在运行的任务节点迭代获取子级任务节点列表向后找
*
* @param source 起始节点
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点且存在上级子节点则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根据类型获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复说明循环了跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型且任务节点的 Key 正在运行的任务中存在添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点则说明该线路找到节点不继续向下找反之继续
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
}

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
* Bpm 错误码枚举类 * Bpm 错误码枚举类
* * <p>
* bpm 系统使用 1-009-000-000 * bpm 系统使用 1-009-000-000
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
@ -45,7 +45,11 @@ public interface ErrorCodeConstants {
// ========== 流程任务 1-009-005-000 ========== // ========== 流程任务 1-009-005-000 ==========
ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1_009_005_000, "审批任务失败,原因:该任务不处于未审批"); ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1_009_005_000, "审批任务失败,原因:该任务不处于未审批");
ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "审批任务失败,原因:该任务的审批人不是你"); ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "审批任务失败,原因:该任务的审批人不是你");
ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
ErrorCode TASK_IS_NOT_ACTIVITY = new ErrorCode(1_009_005_003, "当前任务不属于活动状态,不能操作");
ErrorCode TASK_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_004, " 当前节点相对于目标节点,不属于串行关系,无法回退");
ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_005, " 目标节点不存在");
ErrorCode TASK_ROLLBACK_FAIL = new ErrorCode(1_009_005_006, "回退任务失败,选择回退的节点没有需要回滚的任务!请重新选择其他任务节点");
// ========== 流程任务分配规则 1-009-006-000 ========== // ========== 流程任务分配规则 1-009-006-000 ==========
ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则"); ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则");
ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在"); ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在");

View File

@ -75,4 +75,19 @@ public class BpmTaskController {
return success(true); return success(true);
} }
@GetMapping("/get-return-list")
@Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
@PreAuthorize("@ss.hasPermission('bpm:task:rollback')")
public CommonResult<List<BpmTaskRollbackRespVO>> getReturnList(String taskId) {
return success(taskService.findReturnTaskList(taskId));
}
@PutMapping("/rollback")
@Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
@PreAuthorize("@ss.hasPermission('bpm:task:rollback')")
public CommonResult<Boolean> getReturnList(@Valid @RequestBody BpmTaskRollbackReqVO reqVO) {
taskService.taskReturn(reqVO);
return success(true);
}
} }

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 回退流程任务的 Request VO")
@Data
public class BpmTaskRollbackReqVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotEmpty(message = "任务编号不能为空")
private String id;
@Schema(description = "回退到的任务Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "回退到的任务Key不能为空")
private String targetDefinitionKey;
@Schema(description = "回退意见")
private String reason;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
*
*/
@Schema(description = "管理后台 - 流程任务的 可回退的节点 Response VO")
@Data
public class BpmTaskRollbackRespVO {
@Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
private String taskDefinitionKey;
@Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批")
private String name;
}

View File

@ -6,11 +6,13 @@ 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.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRollbackRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
@ -55,8 +57,8 @@ public interface BpmTaskConvert {
} }
default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks, default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks,
Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap, Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap,
Map<Long, AdminUserRespDTO> userMap) { Map<Long, AdminUserRespDTO> userMap) {
return CollectionUtils.convertList(tasks, task -> { return CollectionUtils.convertList(tasks, task -> {
BpmTaskDonePageItemRespVO respVO = convert2(task); BpmTaskDonePageItemRespVO respVO = convert2(task);
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
@ -82,8 +84,8 @@ public interface BpmTaskConvert {
BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser); BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks,
Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance, Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance,
Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
return CollectionUtils.convertList(tasks, task -> { return CollectionUtils.convertList(tasks, task -> {
BpmTaskRespVO respVO = convert3(task); BpmTaskRespVO respVO = convert3(task);
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
@ -113,29 +115,35 @@ public interface BpmTaskConvert {
void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to); void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
@Mappings({@Mapping(source = "processInstance.id", target = "id"), @Mappings({@Mapping(source = "processInstance.id", target = "id"),
@Mapping(source = "processInstance.name", target = "name"), @Mapping(source = "processInstance.name", target = "name"),
@Mapping(source = "processInstance.startUserId", target = "startUserId"), @Mapping(source = "processInstance.startUserId", target = "startUserId"),
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
@Mapping(source = "startUser.nickname", target = "startUserNickname")}) @Mapping(source = "startUser.nickname", target = "startUserNickname")})
BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance,
AdminUserRespDTO startUser); AdminUserRespDTO startUser);
default BpmTaskExtDO convert2TaskExt(Task task) { default BpmTaskExtDO convert2TaskExt(Task task) {
BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId()) BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId())
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName()) .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
.setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId()); .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime())); taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime()));
return taskExtDO; return taskExtDO;
} }
default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
Task task) { Task task) {
BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId()) reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId())
.setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
.setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
return reqDTO; return reqDTO;
} }
@Mapping(source = "taskDefinitionKey", target = "id")
default List<BpmTaskRollbackRespVO> convertList(List<FlowElement> elementList) {
return CollectionUtils.convertList(elementList, element -> new BpmTaskRollbackRespVO()
.setName(element.getName())
.setTaskDefinitionKey(element.getId()));
}
} }

View File

@ -74,4 +74,12 @@ public interface BpmModelService {
*/ */
BpmnModel getBpmnModel(String id); BpmnModel getBpmnModel(String id);
/**
* 获得流程定义编号对应的 BPMN Model
*
* @param processDefinitionId 流程定义编号
* @return BPMN Model
*/
BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
} }

View File

@ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService {
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
} }
@Override
public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
return repositoryService.getBpmnModel(processDefinitionId);
}
private void checkKeyNCName(String key) { private void checkKeyNCName(String key) {
if (!ValidationUtils.isXmlNCName(key)) { if (!ValidationUtils.isXmlNCName(key)) {
throw exception(MODEL_KEY_VALID); throw exception(MODEL_KEY_VALID);

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
@ -128,4 +127,17 @@ public interface BpmTaskService {
*/ */
void updateTaskExtAssign(Task task); void updateTaskExtAssign(Task task);
/**
* 获取当前人物的可回退的流程集合
* @param taskId 当前的任务ID
* @return 可以回退的节点列表
*/
List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId);
/**
* 将任务回退到指定的 targetDefinitionKey 位置
* @param reqVO 回退的任务key和当前所在的任务ID
*/
void taskReturn(BpmTaskRollbackReqVO reqVO);
} }

View File

@ -8,21 +8,33 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils; 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.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.util.ModelUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; 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.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi; 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.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.engine.HistoryService; import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService; import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery; import org.flowable.task.api.TaskQuery;
@ -39,8 +51,7 @@ import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/** /**
@ -68,12 +79,17 @@ public class BpmTaskServiceImpl implements BpmTaskService {
private BpmTaskExtMapper taskExtMapper; private BpmTaskExtMapper taskExtMapper;
@Resource @Resource
private BpmMessageService messageService; private BpmMessageService messageService;
@Resource
private BpmModelService bpmModelService;
@Resource
private RuntimeService runtimeService;
@Override @Override
public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
// 查询待办任务 // 查询待办任务
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己 TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByTaskCreateTime().desc(); // 创建时间倒序 .orderByTaskCreateTime().desc(); // 创建时间倒序
if (StrUtil.isNotBlank(pageVO.getName())) { if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%"); taskQuery.taskNameLike("%" + pageVO.getName() + "%");
} }
@ -91,21 +107,21 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 获得 ProcessInstance Map // 获得 ProcessInstance Map
Map<String, ProcessInstance> processInstanceMap = Map<String, ProcessInstance> processInstanceMap =
processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
// 获得 User Map // 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
// 拼接结果 // 拼接结果
return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap), return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
taskQuery.count()); taskQuery.count());
} }
@Override @Override
public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) { public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
// 查询已办任务 // 查询已办任务
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
.taskAssignee(String.valueOf(userId)) // 分配给自己 .taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
if (StrUtil.isNotBlank(pageVO.getName())) { if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%"); taskQuery.taskNameLike("%" + pageVO.getName() + "%");
} }
@ -123,19 +139,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 获得 TaskExtDO Map // 获得 TaskExtDO Map
List<BpmTaskExtDO> bpmTaskExtDOs = List<BpmTaskExtDO> bpmTaskExtDOs =
taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId); Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
// 获得 ProcessInstance Map // 获得 ProcessInstance Map
Map<String, HistoricProcessInstance> historicProcessInstanceMap = Map<String, HistoricProcessInstance> historicProcessInstanceMap =
processInstanceService.getHistoricProcessInstanceMap( processInstanceService.getHistoricProcessInstanceMap(
convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
// 获得 User Map // 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
// 拼接结果 // 拼接结果
return new PageResult<>( return new PageResult<>(
BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
taskQuery.count()); taskQuery.count());
} }
@Override @Override
@ -189,8 +205,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 更新任务拓展表为通过 // 更新任务拓展表为通过
taskExtMapper.updateByTaskId( taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
.setReason(reqVO.getReason())); .setReason(reqVO.getReason()));
} }
@Override @Override
@ -208,8 +224,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 更新任务拓展表为不通过 // 更新任务拓展表为不通过
taskExtMapper.updateByTaskId( taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
.setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
} }
@Override @Override
@ -245,7 +261,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
@Override @Override
public void createTaskExt(Task task) { public void createTaskExt(Task task) {
BpmTaskExtDO taskExtDO = BpmTaskExtDO taskExtDO =
BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
taskExtMapper.insert(taskExtDO); taskExtMapper.insert(taskExtDO);
} }
@ -293,17 +309,17 @@ public class BpmTaskServiceImpl implements BpmTaskService {
@Override @Override
public void updateTaskExtAssign(Task task) { public void updateTaskExtAssign(Task task) {
BpmTaskExtDO taskExtDO = BpmTaskExtDO taskExtDO =
new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
taskExtMapper.updateByTaskId(taskExtDO); taskExtMapper.updateByTaskId(taskExtDO);
// 发送通知在事务提交时批量执行操作所以直接查询会无法查询到 ProcessInstance所以这里是通过监听事务的提交来实现 // 发送通知在事务提交时批量执行操作所以直接查询会无法查询到 ProcessInstance所以这里是通过监听事务的提交来实现
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override @Override
public void afterCommit() { public void afterCommit() {
ProcessInstance processInstance = ProcessInstance processInstance =
processInstanceService.getProcessInstance(task.getProcessInstanceId()); processInstanceService.getProcessInstance(task.getProcessInstanceId());
AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
messageService.sendMessageWhenTaskAssigned( messageService.sendMessageWhenTaskAssigned(
BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
} }
}); });
} }
@ -316,4 +332,144 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult(); return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult();
} }
@Override
public List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId) {
// 当前任务 task
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 根据流程定义获取流程模型信息
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
// 查询该任务的前置任务节点的key集合
Set<String> historyTaksDefinitionKeySet = getPreTaskByCurrentTask(task, bpmnModel);
if (CollUtil.isEmpty(historyTaksDefinitionKeySet)) {
return Collections.emptyList();
}
// 获取当前任务节点元素
FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
List<FlowElement> elementList = new ArrayList<>();
for (String activityId : historyTaksDefinitionKeySet) {
FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId);
//不支持串行和子流程
boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
if (isSequential) {
elementList.add(target);
}
}
return BpmTaskConvert.INSTANCE.convertList(elementList);
}
/**
* 查询当前流程实例符合条件可回退的 taskDefinitionKey 集合
*
* @param task 当前任务
* @param bpmnModel 当前流程定义对应的流程模型
* @return 符合条件的已去重的 taskDefinitionKey集合
*/
private Set<String> getPreTaskByCurrentTask(Task task, BpmnModel bpmnModel) {
// 获取当前任务节点元素
FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
//拿到当前任务节点的前置节点集合
List<UserTask> preUserTaskList = ModelUtils.getPreUserTaskList(source, null, null);
//需要保证这些节点都是在该source节点之前的
if (CollUtil.isNotEmpty(preUserTaskList)) {
return convertSet(preUserTaskList, UserTask::getId);
}
return Collections.emptySet();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void taskReturn(BpmTaskRollbackReqVO reqVO) {
// 当前任务 task
Task task = validateRollback(reqVO.getId());
// 校验源头和目标节点的关系并返回目标元素
FlowElement targetElement = validateRollbackProcessDefinition(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId());
//调用flowable框架的回退逻辑
this.handlerRollback(task, targetElement, reqVO);
// 更新任务扩展表
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult())
.setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
}
/**
* 校验当前流程是否可以操作回退
*
* @param taskId 当前任务ID
* @return
*/
private Task validateRollback(String taskId) {
// 当前任务 task
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (null == task) {
throw exception(TASK_NOT_EXISTS);
}
if (task.isSuspended()) {
throw exception(TASK_IS_NOT_ACTIVITY);
}
return task;
}
/**
* 回退流程节点时校验源头节点和目标节点的关系
*
* @param sourceKey 当前任务节点Key
* @param targetKey 目标任务节点key
* @param processDefinitionId 当前流程定义ID
* @return 目标元素
*/
private FlowElement validateRollbackProcessDefinition(String sourceKey, String targetKey, String processDefinitionId) {
// 获取流程模型信息
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
// 获取当前任务节点元素
FlowElement source = ModelUtils.getFlowElementById(bpmnModel, sourceKey);
// 获取跳转的节点元素
FlowElement target = ModelUtils.getFlowElementById(bpmnModel, targetKey);
if (null == target) {
throw exception(TASK_TARGET_NODE_NOT_EXISTS);
}
// 从当前节点向前扫描判断当前节点与目标节点是否属于串行若目标节点是在并行网关上或非同一路线上不可跳转
boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
if (!isSequential) {
throw exception(TASK_SOURCE_TARGET_ERROR);
}
return target;
}
/**
* 处理回退逻辑
*
* @param task 当前回退的任务
* @param targetElement 需要回退到的目标任务
* @param reqVO 前端参数封装
*/
public void handlerRollback(Task task, FlowElement targetElement, BpmTaskRollbackReqVO reqVO) {
// 获取所有正常进行的任务节点 Key这些任务不能直接使用需要找出其中需要撤回的任务
List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
List<String> runTaskKeyList = convertList(runTaskList, Task::getTaskDefinitionKey);
// 通过 targetElement 的出口连线结合 runTaskList 比对获取需要撤回的任务 key
List<UserTask> rollbackUserTaskList = ModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
// 需退回任务 key
List<String> rollbackTaskDefinitionKeyList = convertList(rollbackUserTaskList, UserTask::getId);
// 通过 key runTaskList 中的key对比拿到任务ID设置设置回退意见
List<String> currentTaskIds = new ArrayList<>();
rollbackTaskDefinitionKeyList.forEach(currentId -> runTaskList.forEach(runTask -> {
if (currentId.equals(runTask.getTaskDefinitionKey())) {
currentTaskIds.add(runTask.getId());
}
}));
if (CollUtil.isEmpty(currentTaskIds)) {
throw exception(TASK_ROLLBACK_FAIL);
}
// 设置回退意见
for (String currentTaskId : currentTaskIds) {
taskService.addComment(currentTaskId, task.getProcessInstanceId(), BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason());
}
// 1 1 1 情况currentIds 当前要跳转的节点列表(1或多)targetKey 跳转到的节点(1)
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(rollbackTaskDefinitionKeyList, reqVO.getTargetDefinitionKey())
.changeState();
}
} }