diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java index eb3fab98c26..b9a70578419 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java @@ -247,7 +247,7 @@ public void deleteChildEntity(CommandContext commandContext, DelegatePlanItemIns CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); cmmnEngineConfiguration.getCmmnHistoryManager().recordCaseInstanceEnd( caseInstance, CaseInstanceState.TERMINATED, cmmnEngineConfiguration.getClock().getCurrentTime()); - caseInstanceEntityManager.delete(caseInstance.getId(), cascade, null); + caseInstanceEntityManager.delete(caseInstance.getId(), cascade, "cmmn-state-transition-delete-case"); } // This is not the regular termination through the agenda, but the historic plan item state still needs to be correct. diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java index a2669fa121c..8895e317634 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java @@ -642,6 +642,69 @@ public void testTerminateCaseInstanceWithNestedCaseTasks() { } } + @Test + @CmmnDeployment(resources = { + "org/flowable/cmmn/test/runtime/CaseTaskTest.caseTaskWithChildHumanTask.cmmn", + "org/flowable/cmmn/test/runtime/oneHumanTaskCase.cmmn" + }) + public void testTerminateCaseInstanceSetsDeleteReasonOnChildCaseTask() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("caseTaskWithChildHumanTask").start(); + Task childTask = cmmnTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); + assertThat(childTask.getScopeId()).isNotEqualTo(caseInstance.getId()); + + cmmnRuntimeService.terminateCaseInstance(caseInstance.getId()); + + assertThat(cmmnTaskService.createTaskQuery().count()).isZero(); + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, cmmnEngineConfiguration)) { + HistoricTaskInstance historicChildTask = cmmnHistoryService.createHistoricTaskInstanceQuery().taskId(childTask.getId()).singleResult(); + assertThat(historicChildTask.getEndTime()).isNotNull(); + assertThat(historicChildTask.getDeleteReason()).isEqualTo("cmmn-state-transition-delete-case"); + } + } + + @Test + @CmmnDeployment(resources = { + "org/flowable/cmmn/test/runtime/CaseTaskTest.caseTaskWithChildHumanTask.cmmn", + "org/flowable/cmmn/test/runtime/oneHumanTaskCase.cmmn" + }) + public void testDeleteCaseInstanceSetsDeleteReasonOnChildCaseTask() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("caseTaskWithChildHumanTask").start(); + Task childTask = cmmnTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); + assertThat(childTask.getScopeId()).isNotEqualTo(caseInstance.getId()); + + cmmnRuntimeService.deleteCaseInstance(caseInstance.getId()); + + assertThat(cmmnTaskService.createTaskQuery().count()).isZero(); + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, cmmnEngineConfiguration)) { + HistoricTaskInstance historicChildTask = cmmnHistoryService.createHistoricTaskInstanceQuery().taskId(childTask.getId()).singleResult(); + assertThat(historicChildTask.getEndTime()).isNotNull(); + assertThat(historicChildTask.getDeleteReason()).isEqualTo("cmmn-state-transition-delete-case"); + } + } + + @Test + @CmmnDeployment(resources = { + "org/flowable/cmmn/test/runtime/CaseTaskTest.testThreeLevelRootCase.cmmn", + "org/flowable/cmmn/test/runtime/CaseTaskTest.testThreeLevelLevel1Case.cmmn", + "org/flowable/cmmn/test/runtime/oneHumanTaskCase.cmmn" + }) + public void testTerminateCaseInstanceSetsDeleteReasonOnGrandchildCaseTask() { + CaseInstance rootCase = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("rootCase").start(); + String level1CaseId = (String) rootCase.getCaseVariables().get("caseIdVariable"); + String oneHumanTaskCaseId = (String) cmmnRuntimeService.getVariable(level1CaseId, "caseIdVariable"); + Task grandchildTask = cmmnTaskService.createTaskQuery().caseInstanceIdWithChildren(rootCase.getId()).singleResult(); + assertThat(grandchildTask.getScopeId()).isEqualTo(oneHumanTaskCaseId); + + cmmnRuntimeService.terminateCaseInstance(rootCase.getId()); + + assertThat(cmmnTaskService.createTaskQuery().count()).isZero(); + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, cmmnEngineConfiguration)) { + HistoricTaskInstance historicGrandchildTask = cmmnHistoryService.createHistoricTaskInstanceQuery().taskId(grandchildTask.getId()).singleResult(); + assertThat(historicGrandchildTask.getEndTime()).isNotNull(); + assertThat(historicGrandchildTask.getDeleteReason()).isEqualTo("cmmn-state-transition-delete-case"); + } + } + @Test @CmmnDeployment public void testFallbackToDefaultTenant() { diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.caseTaskWithChildHumanTask.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.caseTaskWithChildHumanTask.cmmn new file mode 100644 index 00000000000..bff14f006f9 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.caseTaskWithChildHumanTask.cmmn @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/history/DeleteReason.java b/modules/flowable-engine/src/main/java/org/flowable/engine/history/DeleteReason.java index 91cf1d46c4f..c9e4da4e03f 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/history/DeleteReason.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/history/DeleteReason.java @@ -29,4 +29,6 @@ public interface DeleteReason { String TRANSACTION_CANCELED = "transaction canceled"; + String ADHOC_SUBPROCESS_COMPLETED = "adhoc subprocess completed"; + } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateConditionalEventsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateConditionalEventsOperation.java index ec681df378d..54fe2e5ede1 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateConditionalEventsOperation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateConditionalEventsOperation.java @@ -25,6 +25,7 @@ import org.flowable.bpmn.model.SubProcess; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.debug.ExecutionTreeUtil; +import org.flowable.engine.history.DeleteReason; import org.flowable.engine.impl.delegate.ActivityBehavior; import org.flowable.engine.impl.delegate.TriggerableActivityBehavior; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; @@ -99,7 +100,8 @@ protected void evaluateEventSubProcesses(List eventSubProcesses if (conditionIsTrue) { ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext); if (startEvent.isInterrupting()) { - executionEntityManager.deleteChildExecutions(parentExecution, null, true); + executionEntityManager.deleteChildExecutions(parentExecution, null, null, + DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + startEvent.getId() + ")", true, startEvent); } ExecutionEntity eventSubProcessExecution = executionEntityManager.createChildExecution(parentExecution); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/TakeOutgoingSequenceFlowsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/TakeOutgoingSequenceFlowsOperation.java index ffd5a989919..a5f23488472 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/TakeOutgoingSequenceFlowsOperation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/TakeOutgoingSequenceFlowsOperation.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.flowable.bpmn.model.Activity; @@ -36,6 +37,7 @@ import org.flowable.common.engine.api.delegate.BusinessError; import org.flowable.engine.delegate.ExecutionListener; import org.flowable.engine.delegate.event.impl.FlowableEventBuilder; +import org.flowable.engine.history.DeleteReason; import org.flowable.engine.impl.Condition; import org.flowable.engine.impl.bpmn.helper.ErrorPropagation; import org.flowable.engine.impl.bpmn.helper.SkipExpressionUtil; @@ -301,6 +303,13 @@ protected void handleAdhocSubProcess(FlowNode flowNode) { } } + if (completeAdhocSubProcess && adhocSubProcess.isCancelRemainingInstances()) { + // The remaining activities are cancelled; otherwise EndExecutionOperation deletes them with a null delete reason. + // Done before leaving, so the executions of the outgoing sequence flows are kept. + CommandContextUtil.getExecutionEntityManager(commandContext).deleteChildExecutions(execution.getParent(), + Collections.singletonList(execution.getId()), null, DeleteReason.ADHOC_SUBPROCESS_COMPLETED, true, flowNode); + } + if (flowNode.getOutgoingFlows().size() > 0) { leaveFlowNode(flowNode); } else { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/conditional/ConditionalEventSubprocessTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/conditional/ConditionalEventSubprocessTest.java index f8a9e0a0003..0f61e9d93ef 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/conditional/ConditionalEventSubprocessTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/conditional/ConditionalEventSubprocessTest.java @@ -19,6 +19,7 @@ import java.util.Collections; import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.engine.history.DeleteReason; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.impl.test.PluggableFlowableTestCase; @@ -26,6 +27,7 @@ import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.Deployment; import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; import org.junit.jupiter.api.Test; /** @@ -142,12 +144,18 @@ public void testNonInterruptingNestedSubProcess() { public void testInterruptingNestedSubProcess() { ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("process"); assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(3); + Task interruptedTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); runtimeService.evaluateConditionalEvents(processInstance.getId(), Collections.singletonMap("myVar", "test")); assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(5); assertThat(taskService.createTaskQuery().count()).isEqualTo(1); + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + assertThat(historyService.createHistoricTaskInstanceQuery().taskId(interruptedTask.getId()).singleResult().getDeleteReason()) + .isEqualTo(DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(eventProcessStart)"); + } + // now let's complete the task in the event subprocess Task task = taskService.createTaskQuery().taskDefinitionKey("eventSubProcessTask").singleResult(); taskService.complete(task.getId()); @@ -208,6 +216,30 @@ public void testSimpleInterruptingEventSubProcess() { assertProcessEnded(processInstance.getId()); } + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/event/conditional/ConditionalEventSubprocessTest.testSimpleInterruptingEventSubProcess.bpmn20.xml") + public void testInterruptingEventSubProcessSetsDeleteReason() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("simpleConditionalEventSubProcess"); + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskDefinitionKey("task").singleResult(); + + runtimeService.evaluateConditionalEvents(processInstance.getId(), Collections.singletonMap("testVar", true)); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).list()) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("eventSubProcessTask1"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, processEngineConfiguration)) { + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstance.getId()).activityId("task").singleResult() + .getDeleteReason()).isEqualTo(DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(eventSubProcessConditionalStart)"); + } + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + HistoricTaskInstance historicTask = historyService.createHistoricTaskInstanceQuery().taskId(task.getId()).singleResult(); + assertThat(historicTask.getEndTime()).isNotNull(); + assertThat(historicTask.getDeleteReason()).isEqualTo(DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(eventSubProcessConditionalStart)"); + } + } + @Test @Deployment public void testGroovyCondition() { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.java index 2e35d112e5e..8057e48ee1f 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.java @@ -16,18 +16,27 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.flowable.bpmn.model.FlowNode; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; +import org.flowable.engine.history.DeleteReason; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.runtime.callback.ProcessInstanceState; import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.Deployment; +import org.flowable.engine.test.api.event.TestFlowableActivityEventListener; +import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.junit.jupiter.api.Test; @@ -194,6 +203,126 @@ public void testParallelAdhocSubProcess() { assertThat(runtimeService.createProcessInstanceQuery().processInstanceId(pi.getId()).singleResult()).isNull(); } + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testParallelAdhocSubProcess.bpmn20.xml") + public void testCompletionConditionSetsDeleteReasonOnRemainingTasks() { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("simpleSubProcess", Collections.singletonMap("completed", false)); + Execution execution = runtimeService.createExecutionQuery().activityId("adhocSubProcess").singleResult(); + + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "subProcessTask"); + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "subProcessTask2"); + Task subProcessTask = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask").singleResult(); + Task subProcessTask2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask2").singleResult(); + + taskService.complete(subProcessTask.getId(), Collections.singletonMap("completed", true)); + + assertThat(taskService.createTaskQuery().processInstanceId(pi.getId()).list()) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("afterTask"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, processEngineConfiguration)) { + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(pi.getId()) + .activityId("subProcessTask").singleResult().getDeleteReason()).isNull(); + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(pi.getId()) + .activityId("subProcessTask2").singleResult().getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + HistoricTaskInstance completedTask = historyService.createHistoricTaskInstanceQuery().taskId(subProcessTask.getId()).singleResult(); + assertThat(completedTask.getDeleteReason()).isNull(); + + HistoricTaskInstance cancelledTask = historyService.createHistoricTaskInstanceQuery().taskId(subProcessTask2.getId()).singleResult(); + assertThat(cancelledTask.getEndTime()).isNotNull(); + assertThat(cancelledTask.getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + } + + @Test + @Deployment(resources = { + "org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithRemainingCallActivity.bpmn20.xml", + "org/flowable/engine/test/api/oneTaskProcess.bpmn20.xml" + }) + public void testCompletionConditionWithRemainingCallActivity() { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("simpleSubProcess", Collections.singletonMap("completed", false)); + Execution execution = runtimeService.createExecutionQuery().activityId("adhocSubProcess").singleResult(); + + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "subProcessTask"); + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "callActivity"); + Task subProcessTask = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask").singleResult(); + ProcessInstance childProcessInstance = runtimeService.createProcessInstanceQuery().superProcessInstanceId(pi.getId()).singleResult(); + Task childTask = taskService.createTaskQuery().processInstanceId(childProcessInstance.getId()).singleResult(); + + taskService.complete(subProcessTask.getId(), Collections.singletonMap("completed", true)); + + assertThat(runtimeService.createProcessInstanceQuery().processInstanceId(childProcessInstance.getId()).count()).isZero(); + assertThat(taskService.createTaskQuery().processInstanceId(pi.getId()).list()) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("afterTask"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, processEngineConfiguration)) { + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(pi.getId()) + .activityId("callActivity").singleResult().getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + + // The remaining activities are cancelled, so the child process instance is ended as cancelled + HistoricProcessInstance historicChildProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(childProcessInstance.getId()).singleResult(); + assertThat(historicChildProcessInstance.getEndTime()).isNotNull(); + assertThat(historicChildProcessInstance.getState()).isEqualTo(ProcessInstanceState.CANCELLED); + assertThat(historicChildProcessInstance.getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + HistoricTaskInstance historicChildTask = historyService.createHistoricTaskInstanceQuery().taskId(childTask.getId()).singleResult(); + assertThat(historicChildTask.getEndTime()).isNotNull(); + assertThat(historicChildTask.getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + } + + @Test + @Deployment + public void testCompletionConditionWithMultipleOutgoingFlows() { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("simpleSubProcess", Collections.singletonMap("completed", false)); + Execution execution = runtimeService.createExecutionQuery().activityId("adhocSubProcess").singleResult(); + + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "subProcessTask"); + runtimeService.executeActivityInAdhocSubProcess(execution.getId(), "subProcessTask2"); + Task subProcessTask = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask").singleResult(); + Task subProcessTask2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask2").singleResult(); + + TestFlowableActivityEventListener listener = new TestFlowableActivityEventListener(true); + runtimeService.addEventListener(listener, FlowableEngineEventType.ACTIVITY_CANCELLED); + try { + taskService.complete(subProcessTask.getId(), Collections.singletonMap("completed", true)); + } finally { + runtimeService.removeEventListener(listener); + } + + assertThat(listener.getEventsReceived()) + .singleElement() + .isInstanceOfSatisfying(FlowableActivityCancelledEvent.class, event -> { + assertThat(event.getActivityId()).isEqualTo("subProcessTask2"); + assertThat(((FlowNode) event.getCause()).getId()).isEqualTo("subProcessTask"); + }); + + assertThat(taskService.createTaskQuery().processInstanceId(pi.getId()).list()) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("afterTask"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, processEngineConfiguration)) { + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(pi.getId()).activityType("sequenceFlow").list()) + .extracting(HistoricActivityInstance::getActivityId) + .containsExactlyInAnyOrder("flow1", "subflow1", "subflow2", "flow2"); + assertThat(historyService.createHistoricActivityInstanceQuery().processInstanceId(pi.getId()) + .activityId("subProcessTask2").singleResult().getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + HistoricTaskInstance cancelledTask = historyService.createHistoricTaskInstanceQuery().taskId(subProcessTask2.getId()).singleResult(); + assertThat(cancelledTask.getEndTime()).isNotNull(); + assertThat(cancelledTask.getDeleteReason()).isEqualTo(DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + } + @Test @Deployment public void testSequentialAdhocSubProcess() { @@ -322,8 +451,9 @@ public void testParallelFlowsInAdhocSubProcess() { assertThat(subProcessTask2.getName()).isEqualTo("Task2 in subprocess"); taskService.complete(subProcessTask2.getId()); - subProcessTask2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("sequentialTask2").singleResult(); - assertThat(subProcessTask2.getName()).isEqualTo("The next task2"); + org.flowable.task.api.Task sequentialTask2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("sequentialTask2").singleResult(); + assertThat(sequentialTask2.getName()).isEqualTo("The next task2"); + org.flowable.task.api.Task subProcessTask3 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("subProcessTask3").singleResult(); List tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list(); assertThat(tasks).hasSize(3); @@ -335,6 +465,12 @@ public void testParallelFlowsInAdhocSubProcess() { org.flowable.task.api.Task afterTask = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult(); assertThat(afterTask.getName()).isEqualTo("After task"); + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, processEngineConfiguration)) { + assertThat(historyService.createHistoricTaskInstanceQuery().taskIds(List.of(sequentialTask2.getId(), subProcessTask3.getId())).list()) + .extracting(HistoricTaskInstance::getDeleteReason) + .containsExactly(DeleteReason.ADHOC_SUBPROCESS_COMPLETED, DeleteReason.ADHOC_SUBPROCESS_COMPLETED); + } + taskService.complete(afterTask.getId()); assertThat(runtimeService.createProcessInstanceQuery().processInstanceId(pi.getId()).singleResult()).isNull(); diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithMultipleOutgoingFlows.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithMultipleOutgoingFlows.bpmn20.xml new file mode 100644 index 00000000000..16f59c82927 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithMultipleOutgoingFlows.bpmn20.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + ${completed} + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithRemainingCallActivity.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithRemainingCallActivity.bpmn20.xml new file mode 100644 index 00000000000..1ec43846b0d --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/subprocess/adhoc/AdhocSubProcessTest.testCompletionConditionWithRemainingCallActivity.bpmn20.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + ${completed} + + + + + + + + + + + + + +