Browse Source

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

cuicui 3 years ago
parent
commit
222732f7e0
18 changed files with 676 additions and 2 deletions
  1. 9 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
  2. 6 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  3. 13 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
  4. 17 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/LeaveFormKeyTest.java
  5. 205 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/java/cn/iocoder/yudao/module/bpm/MultiInstancesTest.java
  6. 152 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.bpmn
  7. BIN
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.png
  8. 44 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn
  9. 42 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn
  10. BIN
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.png
  11. 35 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn
  12. 35 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn
  13. 33 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn
  14. 33 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn
  15. 35 0
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn
  16. BIN
      yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.png
  17. 7 0
      yudao-ui-admin/src/api/bpm/task.js
  18. 10 2
      yudao-ui-admin/src/views/bpm/processInstance/detail.vue

+ 9 - 0
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<Boolean> backTask(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) {
+        //先硬编码到 回退到第一个审批节点
+        String destinationTaskDefKey = "task01";
+        taskService.backTask(reqVO.getId(),destinationTaskDefKey);
+        return success(true);
+    }
 }

+ 6 - 0
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);
 
     /**
      * 将流程任务分配给指定用户

+ 13 - 0
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) {
         // 校验任务存在

+ 17 - 0
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();
+
+    }
+
     /**
      * 领导驳回后申请人取消申请
      */

+ 205 - 0
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<String, Object> variables = new HashMap<String, Object>();
+        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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<Task> 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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<String, Object> taskVariables = new HashMap<String, Object>();
+            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<String, Object> variables = new HashMap<String, Object>();
+        List<String> 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<String, Object> taskVariables = new HashMap<String, Object>();
+            taskVariables.put("approved", "false");
+            taskService.complete(task.getId(), taskVariables);
+        }
+
+        Task task = taskService.createTaskQuery().taskAssignee("henryyan").singleResult();
+        assertNotNull(task);
+        assertEquals("调整申请", task.getName());
+    }
+}

+ 152 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.bpmn

@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
+	xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath"
+	targetNamespace="http://www.activiti.org/test">
+	<process id="leave-countersign" name="请假流程-会签" isExecutable="true">
+		<documentation>请假流程演示-会签</documentation>
+		<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId">
+			<extensionElements>
+				<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
+				<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
+				<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
+				<activiti:formProperty id="users" name="审批参与人" type="users" required="true"></activiti:formProperty>
+				<activiti:formProperty id="validScript" type="javascript" default="alert('表单已经加载完毕');"></activiti:formProperty>
+			</extensionElements>
+		</startEvent>
+		<userTask id="countersign" name="[部门/人事]联合会签" activiti:assignee="${user}">
+			<extensionElements>
+				<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="approved" name="审批意见" type="enum" required="true">
+					<activiti:value id="true" name="同意"></activiti:value>
+					<activiti:value id="false" name="拒绝"></activiti:value>
+				</activiti:formProperty>
+				<activiti:taskListener event="complete" delegateExpression="${leaveCounterSignCompleteListener}"></activiti:taskListener>
+			</extensionElements>
+			<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="users"
+				activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
+		</userTask>
+		<userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}">
+			<extensionElements>
+				<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
+				<activiti:formProperty id="reportBackDate" name="销假日期" type="date" default="${endDate}" datePattern="yyyy-MM-dd"
+					required="true"></activiti:formProperty>
+			</extensionElements>
+		</userTask>
+		<endEvent id="endevent1" name="End"></endEvent>
+		<sequenceFlow id="flow2" sourceRef="startevent1" targetRef="countersign">
+			<extensionElements>
+				<activiti:executionListener event="take" expression="${execution.setVariable('approvedCounter', 0)}"></activiti:executionListener>
+			</extensionElements>
+		</sequenceFlow>
+		<sequenceFlow id="flow8" name="销假" sourceRef="reportBack" targetRef="endevent1">
+			<extensionElements>
+				<activiti:executionListener event="take" expression="${execution.setVariable('result', 'ok')}"></activiti:executionListener>
+			</extensionElements>
+		</sequenceFlow>
+		<sequenceFlow id="flow13" name="全部通过" sourceRef="exclusivegateway1" targetRef="reportBack">
+			<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvedCounter == users.size()}]]></conditionExpression>
+		</sequenceFlow>
+		<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
+		<sequenceFlow id="flow14" sourceRef="countersign" targetRef="exclusivegateway1"></sequenceFlow>
+		<userTask id="modifyApply" name="调整申请" activiti:assignee="${applyUserId}">
+			<extensionElements>
+				<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
+				<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
+				<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
+				<activiti:formProperty id="reApply" name="重新申请" type="enum" required="true">
+					<activiti:value id="true" name="重新申请"></activiti:value>
+					<activiti:value id="false" name="取消申请"></activiti:value>
+				</activiti:formProperty>
+			</extensionElements>
+		</userTask>
+		<sequenceFlow id="flow15" name="部分通过" sourceRef="exclusivegateway1" targetRef="modifyApply">
+			<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvedCounter < users.size()}]]></conditionExpression>
+		</sequenceFlow>
+		<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
+		<sequenceFlow id="flow16" sourceRef="modifyApply" targetRef="exclusivegateway2"></sequenceFlow>
+		<sequenceFlow id="flow17" sourceRef="exclusivegateway2" targetRef="endevent1">
+			<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>
+		</sequenceFlow>
+		<sequenceFlow id="flow18" name="重新申请" sourceRef="exclusivegateway2" targetRef="countersign">
+			<extensionElements>
+				<activiti:executionListener event="take" expression="${execution.setVariable('approvedCounter', 0)}"></activiti:executionListener>
+			</extensionElements>
+			<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>
+		</sequenceFlow>
+	</process>
+	<bpmndi:BPMNDiagram id="BPMNDiagram_leave-countersign">
+		<bpmndi:BPMNPlane bpmnElement="leave-countersign" id="BPMNPlane_leave-countersign">
+			<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+				<omgdc:Bounds height="35.0" width="35.0" x="10.0" y="30.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="reportBack" id="BPMNShape_reportBack">
+				<omgdc:Bounds height="55.0" width="105.0" x="340.0" y="20.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+				<omgdc:Bounds height="35.0" width="35.0" x="375.0" y="203.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="countersign" id="BPMNShape_countersign">
+				<omgdc:Bounds height="55.0" width="105.0" x="90.0" y="20.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
+				<omgdc:Bounds height="40.0" width="40.0" x="220.0" y="27.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">
+				<omgdc:Bounds height="55.0" width="105.0" x="188.0" y="110.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
+				<omgdc:Bounds height="40.0" width="40.0" x="220.0" y="200.0"></omgdc:Bounds>
+			</bpmndi:BPMNShape>
+			<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+				<omgdi:waypoint x="45.0" y="47.0"></omgdi:waypoint>
+				<omgdi:waypoint x="90.0" y="47.0"></omgdi:waypoint>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
+				<omgdi:waypoint x="392.0" y="75.0"></omgdi:waypoint>
+				<omgdi:waypoint x="392.0" y="203.0"></omgdi:waypoint>
+				<bpmndi:BPMNLabel>
+					<omgdc:Bounds height="11.0" width="100.0" x="-22.0" y="-17.0"></omgdc:Bounds>
+				</bpmndi:BPMNLabel>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
+				<omgdi:waypoint x="260.0" y="47.0"></omgdi:waypoint>
+				<omgdi:waypoint x="340.0" y="47.0"></omgdi:waypoint>
+				<bpmndi:BPMNLabel>
+					<omgdc:Bounds height="11.0" width="100.0" x="-29.0" y="-17.0"></omgdc:Bounds>
+				</bpmndi:BPMNLabel>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14">
+				<omgdi:waypoint x="195.0" y="47.0"></omgdi:waypoint>
+				<omgdi:waypoint x="220.0" y="47.0"></omgdi:waypoint>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow15" id="BPMNEdge_flow15">
+				<omgdi:waypoint x="240.0" y="67.0"></omgdi:waypoint>
+				<omgdi:waypoint x="240.0" y="110.0"></omgdi:waypoint>
+				<bpmndi:BPMNLabel>
+					<omgdc:Bounds height="11.0" width="100.0" x="10.0" y="0.0"></omgdc:Bounds>
+				</bpmndi:BPMNLabel>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow16" id="BPMNEdge_flow16">
+				<omgdi:waypoint x="240.0" y="165.0"></omgdi:waypoint>
+				<omgdi:waypoint x="240.0" y="200.0"></omgdi:waypoint>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow17" id="BPMNEdge_flow17">
+				<omgdi:waypoint x="260.0" y="220.0"></omgdi:waypoint>
+				<omgdi:waypoint x="375.0" y="220.0"></omgdi:waypoint>
+			</bpmndi:BPMNEdge>
+			<bpmndi:BPMNEdge bpmnElement="flow18" id="BPMNEdge_flow18">
+				<omgdi:waypoint x="220.0" y="220.0"></omgdi:waypoint>
+				<omgdi:waypoint x="142.0" y="220.0"></omgdi:waypoint>
+				<omgdi:waypoint x="142.0" y="75.0"></omgdi:waypoint>
+				<bpmndi:BPMNLabel>
+					<omgdc:Bounds height="11.0" width="100.0" x="9.0" y="14.0"></omgdc:Bounds>
+				</bpmndi:BPMNLabel>
+			</bpmndi:BPMNEdge>
+		</bpmndi:BPMNPlane>
+	</bpmndi:BPMNDiagram>
+</definitions>

BIN
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/leave-countersign.png


+ 44 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceFixedNumbers" name="testMultiInstanceFixedNumbers" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <serviceTask id="servicetask1" name="Service Task" activiti:expression="${counter + 1}" activiti:resultVariableName="counter">
+      <multiInstanceLoopCharacteristics isSequential="false">
+        <loopCardinality>${loop}</loopCardinality>
+      </multiInstanceLoopCharacteristics>
+    </serviceTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="receivetask1" targetRef="endevent1"></sequenceFlow>
+    <receiveTask id="receivetask1" name="Receive Task"></receiveTask>
+    <sequenceFlow id="flow3" sourceRef="servicetask1" targetRef="receivetask1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceFixedNumbers">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceFixedNumbers" id="BPMNPlane_testMultiInstanceFixedNumbers">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="50.0" y="50.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="116.0" y="40.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="410.0" y="50.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="receivetask1" id="BPMNShape_receivetask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="260.0" y="40.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="85.0" y="67.0"></omgdi:waypoint>
+        <omgdi:waypoint x="116.0" y="67.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="365.0" y="67.0"></omgdi:waypoint>
+        <omgdi:waypoint x="410.0" y="67.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
+        <omgdi:waypoint x="221.0" y="67.0"></omgdi:waypoint>
+        <omgdi:waypoint x="260.0" y="67.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

+ 42 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task" activiti:assignee="${user}">
+      <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
+    <userTask id="usertask2" name="User Task2"></userTask>
+    <sequenceFlow id="flow3" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="340.0" y="190.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
+        <omgdc:Bounds height="55.0" width="105.0" x="170.0" y="180.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
+        <omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="275.0" y="207.0"></omgdi:waypoint>
+        <omgdi:waypoint x="340.0" y="207.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
+        <omgdi:waypoint x="222.0" y="125.0"></omgdi:waypoint>
+        <omgdi:waypoint x="222.0" y="180.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

BIN
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.exception.png


+ 35 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task">
+      <multiInstanceLoopCharacteristics isSequential="false">
+        <loopCardinality>3</loopCardinality>
+      </multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="330.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
+        <omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="275.0" y="97.0"></omgdi:waypoint>
+        <omgdi:waypoint x="330.0" y="97.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

+ 35 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task">
+      <multiInstanceLoopCharacteristics isSequential="true">
+        <loopCardinality>3</loopCardinality>
+      </multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="330.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
+        <omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="275.0" y="97.0"></omgdi:waypoint>
+        <omgdi:waypoint x="330.0" y="97.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

+ 33 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task" activiti:assignee="${user}">
+      <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

+ 33 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task" activiti:assignee="${user}">
+      <multiInstanceLoopCharacteristics isSequential="true" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

+ 35 - 0
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
+  <process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
+    <startEvent id="startevent1" name="Start"></startEvent>
+    <userTask id="usertask1" name="User Task" activiti:assignee="${user}">
+      <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user">
+        <completionCondition>${nrOfCompletedInstances / nrOfInstances &gt;= rate}</completionCondition>
+      </multiInstanceLoopCharacteristics>
+    </userTask>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
+    <endEvent id="endevent1" name="End"></endEvent>
+    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
+    <bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
+        <omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
+        <omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
+        <omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

BIN
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/test/resources/diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.png


+ 7 - 0
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({

+ 10 - 2
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(); // 获得最新详情
+      });
     }
   }
 };