Activiti7 回退與會籤

廢物大師兄發表於2021-07-19

1.  回退(駁回)

回退的思路就是動態更改節點的流向。先遇水搭橋,最後再過河拆橋。

具體操作如下:

  1. 取得當前節點的資訊
  2. 取得當前節點的上一個節點的資訊
  3. 儲存當前節點的流向
  4. 新建流向,由當前節點指向上一個節點
  5. 將當前節點的流向設定為上面新建的流向
  6. 當前節點完成任務
  7. 將當前節點的流向還原
  8. 取得之前上個節點的執行人
  9. 設定上個節點的assignee為之前的執行人

程式碼實現起來可能是這樣的: 

@Test
public void huitui() throws Exception {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery().processInstanceId("55001").singleResult();
    backProcess(task);
}

/**
 * 駁回 / 回退
 * 按照這種方法,可以回退至任意節點
 * @param task
 * @throws Exception
 */
public void backProcess(Task task) throws Exception {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    HistoryService historyService = processEngine.getHistoryService();
    RepositoryService repositoryService = processEngine.getRepositoryService();
    TaskService taskService = processEngine.getTaskService();

    String processInstanceId = task.getProcessInstanceId();

    //  獲取所有歷史任務(按建立時間降序)
    List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(processInstanceId)
            .orderByTaskCreateTime()
            .desc()
            .list();

    List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(processInstanceId).list();

    if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
        return;
    }

    //  當前任務
    HistoricTaskInstance currentTask = hisTaskList.get(0);
    //  前一個任務
    HistoricTaskInstance lastTask = hisTaskList.get(1);
    //  當前活動
    HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
    //  前一個活動
    HistoricActivityInstance lastActivity = hisActivityList.stream().filter(e -> lastTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);

    BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());

    //  獲取前一個活動節點
    FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivity.getActivityId());
    //  獲取當前活動節點
    FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId());

    //  臨時儲存當前活動的原始方向
    List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
    originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
    //  清理活動方向
    currentFlowNode.getOutgoingFlows().clear();

    //  建立新方向
    SequenceFlow newSequenceFlow = new SequenceFlow();
    newSequenceFlow.setId("newSequenceFlowId");
    newSequenceFlow.setSourceFlowElement(currentFlowNode);
    newSequenceFlow.setTargetFlowElement(lastFlowNode);
    List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
    newSequenceFlowList.add(newSequenceFlow);
    //  當前節點指向新的方向
    currentFlowNode.setOutgoingFlows(newSequenceFlowList);

    //  完成當前任務
    taskService.complete(task.getId());

    //  重新查詢當前任務
    Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
    if (null != nextTask) {
        taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
    }

    //  恢復原始方向
    currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}

以請假為例

<process id="holiday" name="holiday" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="填寫請假單" activiti:assignee="${assignee1}"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="部門經理審批" activiti:assignee="${assignee2}"></userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="人事審批" activiti:candidateUsers="tom,jerry"></userTask>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

假設現在已經到“人事審批”這個節點了,當前活動是usertask3

 

接下來,我們執行上面的程式碼,回退到上一個節點“部門經理審批”,於是

流程重新從“部門經理審批”節點開始往下走,當流程走完以後

證明,思路正確,寫法沒啥問題。但是,上面的程式碼可以簡化一下,如下:

/**
 * 跳到最開始的任務節點(直接打回)
 * @param task 當前任務
 */
public void jumpToStart(Task task) {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    HistoryService historyService = processEngine.getHistoryService();
    RepositoryService repositoryService = processEngine.getRepositoryService();
    TaskService taskService = processEngine.getTaskService();

    String processInstanceId = task.getProcessInstanceId();

    //  獲取所有歷史任務(按建立時間升序)
    List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(processInstanceId)
            .orderByTaskCreateTime()
            .asc()
            .list();

    if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
        return;
    }

    //  第一個任務
    HistoricTaskInstance startTask = hisTaskList.get(0);
    //  當前任務
    HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);

    BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());

    //  獲取第一個活動節點
    FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
    //  獲取當前活動節點
    FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());

    //  臨時儲存當前活動的原始方向
    List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
    originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
    //  清理活動方向
    currentFlowNode.getOutgoingFlows().clear();

    //  建立新方向
    SequenceFlow newSequenceFlow = new SequenceFlow();
    newSequenceFlow.setId("newSequenceFlowId");
    newSequenceFlow.setSourceFlowElement(currentFlowNode);
    newSequenceFlow.setTargetFlowElement(startFlowNode);
    List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
    newSequenceFlowList.add(newSequenceFlow);
    //  當前節點指向新的方向
    currentFlowNode.setOutgoingFlows(newSequenceFlowList);

    //  完成當前任務
    taskService.complete(task.getId());

    //  重新查詢當前任務
    Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
    if (null != nextTask) {
        taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
    }

    //  恢復原始方向
    currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}

 

2.  會籤

多個人同時處理一個任務,這種任務我們稱之為會籤任務 。Activiti實現會籤是基於多例項任務,將節點設定成多例項,主要通過在UserTask節點的屬性上配置。

會籤的種類:

  • 按數量通過: 達到一定數量的通過表決後,會籤通過。
  • 按比例通過: 達到一定比例的通過表決後,會籤通過。
  • 一票否決: 只要有一個表決時否定的,會籤通過。
  • 一票通過: 只要有一個表決通過的,會籤通過。

每個例項有以下變數:

  • nrOfInstances: 例項總數
  • nrOfActiveInstances: 當前啟用的(未完成的)例項總數。 如果序列執行,則改值永遠是1

  • nrOfCompletedInstances: 已完成的例項總數

條件${nrOfInstances == nrOfCompletedInstances}表示所有人員審批完成後會簽結束。

條件${ nrOfCompletedInstances == 1}表示一個人完成審批,該會籤就結束。

其他條件依次類推,同時這裡也可以寫自己新增的流程變數。

相關文件如下:

 

下面舉個例子:

<process id="countersign" name="countersign" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="申請" activiti:assignee="zhangsan"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="會籤審批" activiti:assignee="${approver}">
        <multiInstanceLoopCharacteristics isSequential="false" 
            activiti:collection="${approverList}" activiti:elementVariable="approver">
            <completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
        </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="備案" activiti:assignee="tianqi"></userTask>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

編寫程式碼:

//  部署流程定義
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("diagram/countersign.bpmn")
                .name("會籤示例")
                .key("countersign")
                .deploy();

//  啟動流程例項
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<>();
variables.put("approverList", Arrays.asList("lisi","wangwu","zhaoliu"));
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("countersign", variables);

//  完成任務
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("107501").taskAssignee("zhaoliu").singleResult();
if (null != task) {
    taskService.complete(task.getId());
}

流程啟動後,首先是zhangsan審批

 

當zhangsan完成自己的任務後,進入會籤環節,於是我們看到當前有3個啟用的任務

當lisi完成任務以後,當前任務剩下2個

當wangwu和zhaoliu都完成任務了以後,會籤任務完成,進入下一個環節

 

剛才的例子中沒有考慮到審批不通過的情況,接下來我們完善一下,考慮下面的流程

<process id="countersign" name="countersign" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="申請" activiti:assignee="zhangsan"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="會籤審批" activiti:assignee="${approver}">
        <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${approverList}" activiti:elementVariable="approver">
            <completionCondition>${nrOfCompletedInstances / nrOfInstances == 1 || pass == false}</completionCondition>
        </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="備案" activiti:assignee="tianqi"></userTask>
    <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
    <sequenceFlow id="flow5" sourceRef="usertask2" targetRef="exclusivegateway1"></sequenceFlow>
    <sequenceFlow id="flow6" name="通過" sourceRef="exclusivegateway1" targetRef="usertask3">
        <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == true}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow7" name="拒絕" sourceRef="exclusivegateway1" targetRef="usertask1">
        <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == false}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow8" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

在會籤審批完成任務時就要加上流程變數pass了

RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();

Task task = taskService.createTaskQuery().processInstanceId("152501").taskAssignee("lisi").singleResult();
if (null != task) {
    Map<String, Object> variables = new HashMap<>();
    variables.put("pass", true);
//            variables.put("pass", false);
    taskService.complete(task.getId(), variables);

    runtimeService.getVariable(task.getExecutionId(), "nrOfCompletedInstances");
}

zhaoliu審批的時候pass傳的false,於是流程又走到zhangsan那裡,流程重新又走了一遍才全部完成

 

關於回退和會籤就先講到這裡 

3.  參考

https://www.activiti.org/userguide/index.html#bpmnMultiInstance

http://zpycloud.com/archives/1755 

https://blog.csdn.net/zjsdrs/article/details/89917206

相關文章