diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java index d677170a..e63e82ab 100644 --- a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java @@ -16,8 +16,8 @@ public enum FlowComment { REJECT("3", "驳回意见"), DELEGATE("4", "委派意见"), ASSIGN("5", "转办意见"), - STOP("6", "终止流程"); - + STOP("6", "终止流程"), + REVOKE("7", "撤回"); /** * 类型 */ diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FlowComment.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FlowComment.java new file mode 100644 index 00000000..2b0d4927 --- /dev/null +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FlowComment.java @@ -0,0 +1,44 @@ +package com.ruoyi.flowable.domain.enums; + +/** + * 流程意见类型 + * + * @author Xuan xuan + * @date 2021/4/19 + */ +public enum FlowComment { + + /** + * 说明 + */ + NORMAL("1", "正常"), + REBACK("2", "退回"), + REJECT("3", "驳回"), + DELEGATE("4", "委派"), + TRANSFER("5", "转办"), + STOP("6", "终止"), + REVOKE("7", "撤回"); + + /** + * 类型 + */ + private final String type; + + /** + * 说明 + */ + private final String remark; + + FlowComment(String type, String remark) { + this.type = type; + this.remark = remark; + } + + public String getType() { + return type; + } + + public String getRemark() { + return remark; + } +} diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FormType.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FormType.java new file mode 100644 index 00000000..55c24add --- /dev/null +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/FormType.java @@ -0,0 +1,33 @@ +package com.ruoyi.flowable.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author KonBAI + * @createTime 2022/6/28 9:51 + */ +@Getter +@AllArgsConstructor +public enum FormType { + + /** + * 流程表单 + */ + PROCESS(0), + + /** + * 外置表单 + */ + EXTERNAL(1), + + /** + * 节点独立表单 + */ + INDEPENDENT(2); + + /** + * 表单类型 + */ + private final Integer type; +} diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/ProcessStatus.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/ProcessStatus.java new file mode 100644 index 00000000..f97b63fa --- /dev/null +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/enums/ProcessStatus.java @@ -0,0 +1,44 @@ +package com.ruoyi.flowable.domain.enums; + +import com.ruoyi.common.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author konbai + * @since 2023/3/9 00:45 + */ +@Getter +@AllArgsConstructor +public enum ProcessStatus { + + /** + * 进行中(审批中) + */ + RUNNING("running"), + /** + * 已终止 + */ + TERMINATED("terminated"), + /** + * 已完成 + */ + COMPLETED("completed"), + /** + * 已取消 + */ + CANCELED("canceled"); + + private final String status; + + public static ProcessStatus getProcessStatus(String str) { + if (StringUtils.isNotBlank(str)) { + for (ProcessStatus value : values()) { + if (StringUtils.equalsIgnoreCase(str, value.getStatus())) { + return value; + } + } + } + return null; + } +} diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/ModelUtils.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/ModelUtils.java new file mode 100644 index 00000000..8f87beb9 --- /dev/null +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/ModelUtils.java @@ -0,0 +1,373 @@ +package com.ruoyi.flowable.flow; + +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.*; + +/** + * @author KonBAI + * @createTime 2022/3/26 19:04 + */ +public class ModelUtils { + + private static final BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter(); + + /** + * xml转bpmnModel对象 + * + * @param xml xml + * @return bpmnModel对象 + */ + public static BpmnModel getBpmnModel(String xml) { + return bpmnXMLConverter.convertToBpmnModel(new StringStreamSource(xml), false, false); + } + + /** + * bpmnModel转xml字符串 + * + * @deprecated 存在会丢失 bpmn 连线问题 + * @param bpmnModel bpmnModel对象 + * @return xml字符串 + */ + @Deprecated + public static String getBpmnXmlStr(BpmnModel bpmnModel) { + return StrUtil.utf8Str(getBpmnXml(bpmnModel)); + } + + /** + * bpmnModel转xml对象 + * + * @deprecated 存在丢失 bpmn 连线问题 + * @param bpmnModel bpmnModel对象 + * @return xml + */ + @Deprecated + public static byte[] getBpmnXml(BpmnModel bpmnModel) { + return bpmnXMLConverter.convertToXML(bpmnModel); + } + + /** + * 根据节点,获取入口连线 + * + * @param source 起始节点 + * @return 入口连线列表 + */ + public static List getElementIncomingFlows(FlowElement source) { + List sequenceFlows = new ArrayList<>(); + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getIncomingFlows(); + } + return sequenceFlows; + } + + + /** + * 根据节点,获取出口连线 + * + * @param source 起始节点 + * @return 出口连线列表 + */ + public static List getElementOutgoingFlows(FlowElement source) { + List sequenceFlows = new ArrayList<>(); + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getOutgoingFlows(); + } + return sequenceFlows; + } + + /** + * 获取开始节点 + * + * @param model bpmnModel对象 + * @return 开始节点(未找到开始节点,返回null) + */ + public static StartEvent getStartEvent(BpmnModel model) { + Process process = model.getMainProcess(); + FlowElement startElement = process.getInitialFlowElement(); + if (startElement instanceof StartEvent) { + return (StartEvent) startElement; + } + return getStartEvent(process.getFlowElements()); + } + + /** + * 获取开始节点 + * + * @param flowElements 流程元素集合 + * @return 开始节点(未找到开始节点,返回null) + */ + public static StartEvent getStartEvent(Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + return (StartEvent) flowElement; + } + } + return null; + } + + /** + * 获取结束节点 + * + * @param model bpmnModel对象 + * @return 结束节点(未找到开始节点,返回null) + */ + public static EndEvent getEndEvent(BpmnModel model) { + Process process = model.getMainProcess(); + return getEndEvent(process.getFlowElements()); + } + + /** + * 获取结束节点 + * + * @param flowElements 流程元素集合 + * @return 结束节点(未找到开始节点,返回null) + */ + public static EndEvent getEndEvent(Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof EndEvent) { + return (EndEvent) flowElement; + } + } + return null; + } + + public static UserTask getUserTaskByKey(BpmnModel model, String taskKey) { + Process process = model.getMainProcess(); + FlowElement flowElement = process.getFlowElement(taskKey); + if (flowElement instanceof UserTask) { + return (UserTask) flowElement; + } + return null; + } + + /** + * 获取流程元素信息 + * + * @param model bpmnModel对象 + * @param flowElementId 元素ID + * @return 元素信息 + */ + public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { + Process process = model.getMainProcess(); + return process.getFlowElement(flowElementId); + } + + /** + * 获取元素表单Key(限开始节点和用户节点可用) + * + * @param flowElement 元素 + * @return 表单Key + */ + public static String getFormKey(FlowElement flowElement) { + if (flowElement != null) { + if (flowElement instanceof StartEvent) { + return ((StartEvent) flowElement).getFormKey(); + } else if (flowElement instanceof UserTask) { + return ((UserTask) flowElement).getFormKey(); + } + } + return null; + } + + /** + * 获取开始节点属性值 + * @param model bpmnModel对象 + * @param name 属性名 + * @return 属性值 + */ + public static String getStartEventAttributeValue(BpmnModel model, String name) { + StartEvent startEvent = getStartEvent(model); + return getElementAttributeValue(startEvent, name); + } + + /** + * 获取结束节点属性值 + * @param model bpmnModel对象 + * @param name 属性名 + * @return 属性值 + */ + public static String getEndEventAttributeValue(BpmnModel model, String name) { + EndEvent endEvent = getEndEvent(model); + return getElementAttributeValue(endEvent, name); + } + + /** + * 获取用户任务节点属性值 + * @param model bpmnModel对象 + * @param taskKey 任务Key + * @param name 属性名 + * @return 属性值 + */ + public static String getUserTaskAttributeValue(BpmnModel model, String taskKey, String name) { + UserTask userTask = getUserTaskByKey(model, taskKey); + return getElementAttributeValue(userTask, name); + } + + /** + * 获取元素属性值 + * @param baseElement 流程元素 + * @param name 属性名 + * @return 属性值 + */ + public static String getElementAttributeValue(BaseElement baseElement, String name) { + if (baseElement != null) { + List attributes = baseElement.getAttributes().get(name); + if (attributes != null && !attributes.isEmpty()) { + attributes.iterator().next().getValue(); + Iterator attrIterator = attributes.iterator(); + if(attrIterator.hasNext()) { + ExtensionAttribute attribute = attrIterator.next(); + return attribute.getValue(); + } + } + } + return null; + } + + public static boolean isMultiInstance(BpmnModel model, String taskKey) { + UserTask userTask = getUserTaskByKey(model, taskKey); + if (ObjectUtil.isNotNull(userTask)) { + return userTask.hasMultiInstanceLoopCharacteristics(); + } + return false; + } + + /** + * 获取所有用户任务节点 + * + * @param model bpmnModel对象 + * @return 用户任务节点列表 + */ + public static Collection getAllUserTaskEvent(BpmnModel model) { + Process process = model.getMainProcess(); + Collection flowElements = process.getFlowElements(); + return getAllUserTaskEvent(flowElements, null); + } + + /** + * 获取所有用户任务节点 + * @param flowElements 流程元素集合 + * @param allElements 所有流程元素集合 + * @return 用户任务节点列表 + */ + public static Collection getAllUserTaskEvent(Collection flowElements, Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof UserTask) { + allElements.add((UserTask) flowElement); + } + if (flowElement instanceof SubProcess) { + // 继续深入子流程,进一步获取子流程 + allElements = getAllUserTaskEvent(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + /** + * 查找起始节点下一个用户任务列表列表 + * @param source 起始节点 + * @return 结果 + */ + public static List findNextUserTasks(FlowElement source) { + return findNextUserTasks(source, null, null); + } + + /** + * 查找起始节点下一个用户任务列表列表 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 用户任务列表 + * @return 结果 + */ + public static List findNextUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>()); + userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>()); + // 获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (!sequenceFlows.isEmpty()) { + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement(); + if (targetFlowElement instanceof UserTask) { + // 若节点为用户任务,加入到结果列表中 + userTaskList.add((UserTask) targetFlowElement); + } else { + // 若节点非用户任务,继续递归查找下一个节点 + findNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList); + } + } + } + return userTaskList; + } + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * @param source 起始节点 + * @param target 目标节点 + * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 + * @return 结果 + */ + public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set visitedElements) { + visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; + if (source instanceof StartEvent && isInEventSubprocess(source)) { + return false; + } + + // 根据类型,获取入口连线 + List 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; + } + + 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; + } +} diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java index 9ce25386..e570a694 100644 --- a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java @@ -1,6 +1,8 @@ package com.ruoyi.flowable.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; @@ -25,6 +27,7 @@ import com.ruoyi.flowable.factory.FlowServiceFactory; import com.ruoyi.flowable.flow.CustomProcessDiagramGenerator; import com.ruoyi.flowable.flow.FindNextNodeUtil; import com.ruoyi.flowable.flow.FlowableUtils; +import com.ruoyi.flowable.flow.ModelUtils; import com.ruoyi.flowable.service.IFlowTaskService; import com.ruoyi.flowable.service.ISysDeployFormService; import com.ruoyi.flowable.service.ISysFormService; @@ -631,61 +634,71 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask } /** - * 撤回流程 目前存在错误 + * 撤回流程 * - * @param flowTaskVo - * @return + * @param flowTaskVo 请求实体参数 */ @Override + @Transactional() public AjaxResult revokeProcess(FlowTaskVo flowTaskVo) { - Task task = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).singleResult(); - if (task == null) { - throw new CustomException("流程未启动或已执行完成,无法撤回"); + String procInsId = flowTaskVo.getInstanceId(); + String taskId = flowTaskVo.getTaskId(); + // 校验流程是否结束 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(procInsId) + .active() + .singleResult(); + if(ObjectUtil.isNull(processInstance)) { + throw new RuntimeException("流程已结束或已挂起,无法执行撤回操作"); } + // 获取待撤回任务实例 + HistoricTaskInstance currTaskIns = historyService.createHistoricTaskInstanceQuery() + .taskId(taskId) + .taskAssignee(flowTaskVo.getUserId()) + .singleResult(); + if (ObjectUtil.isNull(currTaskIns)) { + throw new RuntimeException("当前任务不存在,无法执行撤回操作"); + } + // 获取 bpmn 模型 + BpmnModel bpmnModel = repositoryService.getBpmnModel(currTaskIns.getProcessDefinitionId()); + UserTask currUserTask = ModelUtils.getUserTaskByKey(bpmnModel, currTaskIns.getTaskDefinitionKey()); + // 查找下一级用户任务列表 + List nextUserTaskList = ModelUtils.findNextUserTasks(currUserTask); + List nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).collect(Collectors.toList()); - SysUser loginUser = SecurityUtils.getLoginUser().getUser(); - List htiList = historyService.createHistoricTaskInstanceQuery() - .processInstanceId(task.getProcessInstanceId()) - .orderByTaskCreateTime() - .asc() + // 获取当前节点之后已完成的流程历史节点 + List finishedTaskInsList = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(procInsId) + .taskCreatedAfter(currTaskIns.getEndTime()) + .finished() .list(); - String myTaskId = null; - HistoricTaskInstance myTask = null; - for (HistoricTaskInstance hti : htiList) { - if (loginUser.getUserId().toString().equals(hti.getAssignee())) { - myTaskId = hti.getId(); - myTask = hti; - break; + for (HistoricTaskInstance finishedTaskInstance : finishedTaskInsList) { + // 检查已完成流程历史节点是否存在下一级中 + if (CollUtil.contains(nextUserTaskKeys, finishedTaskInstance.getTaskDefinitionKey())) { + throw new RuntimeException("下一流程已处理,无法执行撤回操作"); } } - if (null == myTaskId) { - throw new CustomException("该任务非当前用户提交,无法撤回"); - } - - String processDefinitionId = myTask.getProcessDefinitionId(); - BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); - - //变量 -// Map variables = runtimeService.getVariableInstances(currentTask.getExecutionId()); - String myActivityId = null; - List haiList = historyService.createHistoricActivityInstanceQuery() - .executionId(myTask.getExecutionId()).finished().list(); - for (HistoricActivityInstance hai : haiList) { - if (myTaskId.equals(hai.getTaskId())) { - myActivityId = hai.getActivityId(); - break; + // 获取所有激活的任务节点,找到需要撤回的任务 + List activateTaskList = taskService.createTaskQuery().processInstanceId(procInsId).list(); + List revokeExecutionIds = new ArrayList<>(); + for (Task task : activateTaskList) { + // 检查激活的任务节点是否存在下一级中,如果存在,则加入到需要撤回的节点 + if (CollUtil.contains(nextUserTaskKeys, task.getTaskDefinitionKey())) { + // 添加撤回审批信息 + taskService.setAssignee(task.getId(), flowTaskVo.getUserId()); + taskService.addComment(task.getId(), task.getProcessInstanceId(), FlowComment.REVOKE.getType(), flowTaskVo.getAssignee() + "撤回流程审批"); + revokeExecutionIds.add(task.getExecutionId()); } } - FlowNode myFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(myActivityId); - - Execution execution = runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult(); - String activityId = execution.getActivityId(); - FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId); - - //记录原活动方向 - List oriSequenceFlows = new ArrayList<>(flowNode.getOutgoingFlows()); - - + try { + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(procInsId) + .moveExecutionsToSingleActivityId(revokeExecutionIds, currTaskIns.getTaskDefinitionKey()).changeState(); + } catch (FlowableObjectNotFoundException e) { + throw new RuntimeException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new RuntimeException("执行撤回操作失败"); + } return AjaxResult.success(); } diff --git a/ruoyi-ui/src/views/flowable/task/finished/index.vue b/ruoyi-ui/src/views/flowable/task/finished/index.vue index 7ec93258..0cbabe43 100644 --- a/ruoyi-ui/src/views/flowable/task/finished/index.vue +++ b/ruoyi-ui/src/views/flowable/task/finished/index.vue @@ -75,7 +75,7 @@ - + - - + + + + @@ -282,7 +287,11 @@ export default { /** 撤回任务 */ handleRevoke(row){ const params = { - instanceId: row.procInsId + procInsId: row.procInsId, + instanceId: row.procInsId, + taskId:row.taskId, + userId:row.startUserId, + assignee:row.startUserName } this.$confirm('是否确认撤回当前任务流程?', '提示', { confirmButtonText: '确定', diff --git a/ruoyi-ui/src/views/flowable/task/myProcess/index.vue b/ruoyi-ui/src/views/flowable/task/myProcess/index.vue index c86b75b6..728228d3 100644 --- a/ruoyi-ui/src/views/flowable/task/myProcess/index.vue +++ b/ruoyi-ui/src/views/flowable/task/myProcess/index.vue @@ -90,7 +90,7 @@ - +