Activiti7 回退與會簽


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM