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}
+
+
+
+
+
+
+
+
+
+
+
+
+
+