diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java index 52b16297d..d3318c963 100644 --- a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java @@ -76,4 +76,13 @@ public class BpmTaskController { taskService.updateTaskAssignee(getLoginUserId(), reqVO); return success(true); } + @PutMapping("/back") + @ApiOperation(value = "回退") +// @PreAuthorize("@ss.hasPermission('bpm:task:back')") + public CommonResult backTask(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) { + //先硬编码到 回退到第一个审批节点 + String destinationTaskDefKey = "task01"; + taskService.backTask(reqVO.getId(),destinationTaskDefKey); + return success(true); + } } diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index e8aab6aa8..00a0a8662 100644 --- a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -76,6 +76,12 @@ public interface BpmTaskService { * @param reqVO 不通过请求 */ void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); + /** + * 回退任务 + * + * @param taskId 任务编号 + */ + void backTask(String taskId,String destinationTaskDefKey); /** * 将流程任务分配给指定用户 diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 5c004977c..0660576f0 100644 --- a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -17,6 +17,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; +import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; @@ -51,6 +52,8 @@ public class BpmTaskServiceImpl implements BpmTaskService{ @Resource private TaskService taskService; @Resource + private RuntimeService runtimeService; + @Resource private HistoryService historyService; @Resource @@ -203,6 +206,16 @@ public class BpmTaskServiceImpl implements BpmTaskService{ .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()).setComment(reqVO.getComment())); } + @Override + public void backTask(String taskId,String destinationTaskDefKey) { + Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult(); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(currentTask.getProcessInstanceId()) + .moveActivityIdTo(currentTask.getTaskDefinitionKey(), destinationTaskDefKey) + .changeState(); + } + @Override public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) { // 校验任务存在 diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/LeaveFormKeyTest.java b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/LeaveFormKeyTest.java index 90105af2e..da99fec3d 100644 --- a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/LeaveFormKeyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/LeaveFormKeyTest.java @@ -87,6 +87,23 @@ public class LeaveFormKeyTest extends AbstractOATest { } + /** + * 任意流程的跳转 + */ + @Test + public void taskJump(){ + // 当前任务 + String taskId="ddd"; + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + String assignee = "下一个自由跳转人"; + taskService.setAssignee(taskId,assignee); + // 自由跳转 + String taskDefKey="目标-任务名称"; + //moveActivityIdTo的两个参数,源任务key,目标任务key + runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(), taskDefKey).changeState(); + + } + /** * 领导驳回后申请人取消申请 */ diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/MultiInstancesTest.java b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/MultiInstancesTest.java new file mode 100644 index 000000000..2640e7456 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/MultiInstancesTest.java @@ -0,0 +1,205 @@ +package cn.iocoder.yudao.module.bpm; + +import org.flowable.engine.history.HistoricDetail; +import org.flowable.engine.history.HistoricFormProperty; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricVariableUpdate; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.task.api.Task; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author henryyan + * testMultiInstanceForUserTask 会签 + * cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmUserTaskActivityBehavior#handleAssignments(org.flowable.task.service.TaskService, java.lang.String, java.lang.String, java.util.List, java.util.List, org.flowable.task.service.impl.persistence.entity.TaskEntity, org.flowable.common.engine.impl.el.ExpressionManager, org.flowable.engine.delegate.DelegateExecution, org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl) + * 执行了两次,任务分配到了同一个人 + */ +public class MultiInstancesTest extends AbstractOATest { + + /** + * Java Service多实例(是否顺序结果一样) + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn"}) + public void testParallel() throws Exception { + Map variables = new HashMap(); + long loop = 3; + variables.put("loop", loop); + variables.put("counter", 0); // 计数器 + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceFixedNumbers", variables); + Object variable = runtimeService.getVariable(processInstance.getId(), "counter"); + assertEquals(loop, variable); + } + + /** + * 用户任务多实例--顺序 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn"}) + public void testForUserSequence() throws Exception { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask"); + long count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count(); + assertEquals(1, count); + + Task task = taskService.createTaskQuery().singleResult(); + taskService.complete(task.getId()); + count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count(); + assertEquals(1, count); + + task = taskService.createTaskQuery().singleResult(); + taskService.complete(task.getId()); + count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count(); + assertEquals(1, count); + + task = taskService.createTaskQuery().singleResult(); + taskService.complete(task.getId()); + count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count(); + assertEquals(0, count); + } + + /** + * 用户任务多实例--并行 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn"}) + public void testForUserNoSequential() throws Exception { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask"); + long count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count(); + assertEquals(3, count); + } + + /** + * 用户任务多实例,通过用户数量决定实例个数--并行 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn"}) + public void testForUserCreateByUsersNoSequential() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("user1", "user2", "user3"); + variables.put("users", users); + runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables); + for (String userId : users) { + assertEquals(1, taskService.createTaskQuery().taskAssignee(userId).count()); + } + } + + /** + * 用户任务多实例,通过用户数量决定实例个数--顺序 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn"}) + public void testForUserCreateByUsersSequential() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("user1", "user2", "user3"); + variables.put("users", users); + runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables); + for (String userId : users) { + Task task = taskService.createTaskQuery().taskAssignee(userId).singleResult(); + taskService.complete(task.getId()); + } + } + + /** + * 用户任务多实例,按照任务完成的百分比比率决定是否提前结束流程 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn"}) + public void testForUserCreateByUsersSequentialWithCompleteCondition() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("user1", "user2", "user3"); + variables.put("users", users); + variables.put("rate", 0.6d); + runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables); + + Task task = taskService.createTaskQuery().taskAssignee("user1").singleResult(); + taskService.complete(task.getId()); + + task = taskService.createTaskQuery().taskAssignee("user2").singleResult(); + taskService.complete(task.getId()); + + long count = historyService.createHistoricProcessInstanceQuery().finished().count(); + assertEquals(1, count); + + } + + /** + * 用户任务多实例,按照任务完成的百分比比率决定是否提前结束流程 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn"}) + public void testForUserCreateByUsersException() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("user1", "user2", "user3"); + variables.put("users", users); + runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables); + + Task task = taskService.createTaskQuery().taskAssignee("user1").singleResult(); + taskService.complete(task.getId()); + + task = taskService.createTaskQuery().taskAssignee("user2").singleResult(); + taskService.complete(task.getId()); + + task = taskService.createTaskQuery().taskAssignee("user3").singleResult(); + taskService.complete(task.getId()); + + List list = taskService.createTaskQuery().list(); + for (Task task2 : list) { + System.out.println("============" + task2.getName()); + } + + } + ///////////////////////////////////////////////// + /** + * 全部通过 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/leave-countersign.bpmn"}) + public void testAllApproved() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("groupLeader", "deptLeader", "hr"); + variables.put("users", users); + identityService.setAuthenticatedUserId("henryyan"); + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave-countersign", variables); + for (String user : users) { + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee(user).singleResult(); + Map taskVariables = new HashMap(); + taskVariables.put("approved", "true"); + taskService.complete(task.getId(), taskVariables); + } + + Task task = taskService.createTaskQuery().taskAssignee("henryyan").singleResult(); + assertNotNull(task); + assertEquals("销假", task.getName()); + } + + /** + * 部分通过 + */ + @Test + @Deployment(resources = {"diagrams/chapter9/leave-countersign.bpmn"}) + public void testNotAllApproved() throws Exception { + Map variables = new HashMap(); + List users = Arrays.asList("groupLeader", "deptLeader", "hr"); + variables.put("users", users); + identityService.setAuthenticatedUserId("henryyan"); + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave-countersign", variables); + for (String user : users) { + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee(user).singleResult(); + Map taskVariables = new HashMap(); + taskVariables.put("approved", "false"); + taskService.complete(task.getId(), taskVariables); + } + + Task task = taskService.createTaskQuery().taskAssignee("henryyan").singleResult(); + assertNotNull(task); + assertEquals("调整申请", task.getName()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.bpmn new file mode 100644 index 000000000..bdb56857d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.bpmn @@ -0,0 +1,152 @@ + + + + 请假流程演示-会签 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.png b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.png new file mode 100644 index 000000000..e2a6f2ca2 Binary files /dev/null and b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.png differ diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn new file mode 100644 index 000000000..998570919 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn @@ -0,0 +1,44 @@ + + + + + + + ${loop} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn new file mode 100644 index 000000000..05a87d939 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.png b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.png new file mode 100644 index 000000000..5ac829387 Binary files /dev/null and b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.png differ diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn new file mode 100644 index 000000000..9790144fe --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn @@ -0,0 +1,35 @@ + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn new file mode 100644 index 000000000..c7bc82b4f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn @@ -0,0 +1,35 @@ + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn new file mode 100644 index 000000000..ca117190c --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn new file mode 100644 index 000000000..5dbd39676 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn new file mode 100644 index 000000000..d8fc63a55 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn @@ -0,0 +1,35 @@ + + + + + + + ${nrOfCompletedInstances / nrOfInstances >= rate} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.png b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.png new file mode 100644 index 000000000..c20f451b8 Binary files /dev/null and b/yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.png differ diff --git a/yudao-ui-admin/src/api/bpm/task.js b/yudao-ui-admin/src/api/bpm/task.js index 177a7dfc9..aa32263f3 100644 --- a/yudao-ui-admin/src/api/bpm/task.js +++ b/yudao-ui-admin/src/api/bpm/task.js @@ -39,6 +39,13 @@ export function rejectTask(data) { data: data }) } +export function backTask(data) { + return request({ + url: '/bpm/task/back', + method: 'PUT', + data: data + }) +} export function updateTaskAssignee(data) { return request({ diff --git a/yudao-ui-admin/src/views/bpm/processInstance/detail.vue b/yudao-ui-admin/src/views/bpm/processInstance/detail.vue index f5d4992c2..6311ae0a5 100644 --- a/yudao-ui-admin/src/views/bpm/processInstance/detail.vue +++ b/yudao-ui-admin/src/views/bpm/processInstance/detail.vue @@ -109,7 +109,7 @@ import store from "@/store"; import {decodeFields} from "@/utils/formGenerator"; import Parser from '@/components/parser/Parser' import {createProcessInstance, getProcessInstance} from "@/api/bpm/processInstance"; -import {approveTask, getTaskListByProcessInstanceId, rejectTask, updateTaskAssignee} from "@/api/bpm/task"; +import {approveTask, getTaskListByProcessInstanceId, rejectTask, updateTaskAssignee,backTask} from "@/api/bpm/task"; import {getDate} from "@/utils/dateUtils"; import {listSimpleUsers} from "@/api/system/user"; import {getActivityList} from "@/api/bpm/activity"; @@ -406,7 +406,15 @@ export default { }, /** 处理审批退回的操作 */ handleBack(task) { - this.$modal.msgError("暂不支持【退回】功能!"); + const data = { + id: task.id, + assigneeUserId: 1 + } + // this.$modal.msgError("暂不支持【--退回】功能!"); + backTask(data).then(response => { + this.$modal.msgSuccess("回退成功!"); + this.getDetail(); // 获得最新详情 + }); } } };