diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/workflow/WfTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/workflow/WfTaskController.java index 9f19665e..c29c1377 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/workflow/WfTaskController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/workflow/WfTaskController.java @@ -72,12 +72,12 @@ public class WfTaskController { } /** - * 驳回任务 + * 拒绝任务 */ @PostMapping(value = "/reject") @SaCheckPermission("workflow:process:approval") - public R taskReject(@RequestBody WfTaskBo bo) { - flowTaskService.taskReject(bo); + public R taskReject(@RequestBody WfTaskBo taskBo) { + flowTaskService.taskReject(taskBo); return R.ok(); } diff --git a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/utils/ModelUtils.java b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/utils/ModelUtils.java index ee1ca17a..f4bbca63 100644 --- a/ruoyi-flowable/src/main/java/com/ruoyi/flowable/utils/ModelUtils.java +++ b/ruoyi-flowable/src/main/java/com/ruoyi/flowable/utils/ModelUtils.java @@ -66,6 +66,32 @@ public class ModelUtils { 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; + } + /** * 获取所有用户任务节点 * diff --git a/ruoyi-system/src/main/java/com/ruoyi/workflow/service/IWfTaskService.java b/ruoyi-system/src/main/java/com/ruoyi/workflow/service/IWfTaskService.java index 3a1b8e60..e5534fdb 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/workflow/service/IWfTaskService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/workflow/service/IWfTaskService.java @@ -24,11 +24,11 @@ public interface IWfTaskService { void complete(WfTaskBo task); /** - * 驳回任务 + * 拒绝任务 * - * @param bo + * @param taskBo */ - void taskReject(WfTaskBo bo); + void taskReject(WfTaskBo taskBo); /** diff --git a/ruoyi-system/src/main/java/com/ruoyi/workflow/service/impl/WfTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/workflow/service/impl/WfTaskServiceImpl.java index b17f637b..fac417ca 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/workflow/service/impl/WfTaskServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/workflow/service/impl/WfTaskServiceImpl.java @@ -16,6 +16,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.utils.ModelUtils; import com.ruoyi.flowable.utils.TaskUtils; import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysUserService; @@ -103,132 +104,48 @@ public class WfTaskServiceImpl extends FlowServiceFactory implements IWfTaskServ } /** - * 驳回任务 + * 拒绝任务 * - * @param bo + * @param taskBo */ @Override - public void taskReject(WfTaskBo bo) { + @Transactional(rollbackFor = Exception.class) + public void taskReject(WfTaskBo taskBo) { // 当前任务 task - Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult(); + Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult(); if (ObjectUtil.isNull(task)) { throw new RuntimeException("获取任务信息异常!"); } if (task.isSuspended()) { throw new RuntimeException("任务处于挂起状态"); } + // 获取流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(taskBo.getProcInsId()) + .singleResult(); + if (processInstance == null) { + throw new RuntimeException("流程实例不存在,请确认!"); + } // 获取流程定义信息 - ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()) + .singleResult(); + + // 添加审批意见 + taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.REJECT.getType(), taskBo.getComment()); + // 获取所有节点信息 - Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); - // 获取全部节点列表,包含子节点 - Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); - // 获取当前任务节点元素 - FlowElement source = null; - if (allElements != null) { - for (FlowElement flowElement : allElements) { - // 类型为用户节点 - if (flowElement.getId().equals(task.getTaskDefinitionKey())) { - // 获取节点信息 - source = flowElement; - } - } - } - - // 目的获取所有跳转到的节点 targetIds - // 获取当前节点的所有父级用户任务节点 - // 深度优先算法思想:延边迭代深入 - List parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null); - if (parentUserTaskList == null || parentUserTaskList.size() == 0) { - throw new RuntimeException("当前节点为初始任务节点,不能驳回"); - } - // 获取活动 ID 即节点 Key - List parentUserTaskKeyList = new ArrayList<>(); - parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId())); - // 获取全部历史节点活动实例,即已经走过的节点历史,数据采用开始时间升序 - List historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list(); - // 数据清洗,将回滚导致的脏数据清洗掉 - List lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, historicTaskInstanceList); - // 此时历史任务实例为倒序,获取最后走的节点 - List targetIds = new ArrayList<>(); - // 循环结束标识,遇到当前目标节点的次数 - int number = 0; - StringBuilder parentHistoricTaskKey = new StringBuilder(); - for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) { - // 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过 - if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) { - continue; - } - parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey); - if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) { - number++; - } - // 在数据清洗后,历史节点就是唯一一条从起始到当前节点的历史记录,理论上每个点只会出现一次 - // 在流程中如果出现循环,那么每次循环中间的点也只会出现一次,再出现就是下次循环 - // number == 1,第一次遇到当前节点 - // number == 2,第二次遇到,代表最后一次的循环范围 - if (number == 2) { - break; - } - // 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点 - if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) { - targetIds.add(historicTaskInstanceKey); - } - } - - - // 目的获取所有需要被跳转的节点 currentIds - // 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路 - UserTask oneUserTask = parentUserTaskList.get(0); - // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 - List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); - List runTaskKeyList = new ArrayList<>(); - runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); - // 需驳回任务列表 - List currentIds = new ArrayList<>(); - // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 - List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, null); - currentUserTaskList.forEach(item -> currentIds.add(item.getId())); - - - // 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况 - if (targetIds.size() > 1 && currentIds.size() > 1) { - throw new RuntimeException("任务出现多对多情况,无法撤回"); - } - - // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 - List currentTaskIds = new ArrayList<>(); - currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { - if (currentId.equals(runTask.getTaskDefinitionKey())) { - currentTaskIds.add(runTask.getId()); - } - })); - // 设置驳回意见 - currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), FlowComment.REJECT.getType(), bo.getComment())); - - try { - // 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况 - if (targetIds.size() > 1) { - // 1 对 多任务跳转,currentIds 当前节点(1),targetIds 跳转到的节点(多) - runtimeService.createChangeActivityStateBuilder() - .processInstanceId(task.getProcessInstanceId()). - moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState(); - } - // 如果父级任务只有一个,因此当前任务可能为网关中的任务 - if (targetIds.size() == 1) { - // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetIds.get(0) 跳转到的节点(1) - runtimeService.createChangeActivityStateBuilder() - .processInstanceId(task.getProcessInstanceId()) - .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState(); - } - } catch (FlowableObjectNotFoundException e) { - throw new RuntimeException("未找到流程实例,流程可能已发生变化"); - } catch (FlowableException e) { - throw new RuntimeException("无法取消或开始活动"); - } - // 设置任务节点名称 - bo.setTaskName(task.getName()); - if (!copyService.makeCopy(bo)) { + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + EndEvent endEvent = ModelUtils.getEndEvent(bpmnModel); + // 终止流程 + List executions = runtimeService.createExecutionQuery().parentId(task.getProcessInstanceId()).list(); + List executionIds = executions.stream().map(Execution::getId).collect(Collectors.toList()); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveExecutionsToSingleActivityId(executionIds, endEvent.getId()) + .changeState(); + // 处理抄送用户 + if (!copyService.makeCopy(taskBo)) { throw new RuntimeException("抄送任务失败"); } } diff --git a/ruoyi-ui/src/api/workflow/todo.js b/ruoyi-ui/src/api/workflow/todo.js index 27387ced..fa698118 100644 --- a/ruoyi-ui/src/api/workflow/todo.js +++ b/ruoyi-ui/src/api/workflow/todo.js @@ -36,7 +36,7 @@ export function returnTask(data) { }) } -// 驳回任务 +// 拒绝任务 export function rejectTask(data) { return request({ url: '/workflow/task/reject', diff --git a/ruoyi-ui/src/views/workflow/work/detail.vue b/ruoyi-ui/src/views/workflow/work/detail.vue index 767475cc..b5bcd85e 100644 --- a/ruoyi-ui/src/views/workflow/work/detail.vue +++ b/ruoyi-ui/src/views/workflow/work/detail.vue @@ -52,7 +52,7 @@ 退回 - 驳回 + 拒绝 @@ -202,6 +202,7 @@ import { selectUser, deptTreeSelect } from '@/api/system/user' import ProcessViewer from '@/components/ProcessViewer' import '@riophae/vue-treeselect/dist/vue-treeselect.css' import Treeselect from '@riophae/vue-treeselect' +import { forceLogout } from '@/api/monitor/online'; export default { name: "Detail", @@ -496,11 +497,14 @@ export default { } }) }, - /** 驳回任务 */ + /** 拒绝任务 */ handleReject() { this.$refs["taskForm"].validate(valid => { if (valid) { - rejectTask(this.taskForm).then(res => { + const _this = this; + this.$modal.confirm('拒绝审批单流程会终止,是否继续?').then(function() { + return rejectTask(_this.taskForm); + }).then(res => { this.$modal.msgSuccess(res.msg); this.goBack(); });