diff --git a/history/taskana-simplehistory-provider/pom.xml b/history/taskana-simplehistory-provider/pom.xml
index 3beeba5a47..11a43f0913 100644
--- a/history/taskana-simplehistory-provider/pom.xml
+++ b/history/taskana-simplehistory-provider/pom.xml
@@ -37,6 +37,12 @@
+
+ pro.taskana
+ taskana-test-api
+ ${project.version}
+ test
+
pro.taskana
taskana-common-data
diff --git a/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java
new file mode 100644
index 0000000000..6c25a30048
--- /dev/null
+++ b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskRerouteAccTest.java
@@ -0,0 +1,244 @@
+package acceptance.events.task;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import acceptance.events.task.CreateHistoryEventOnTaskRerouteAccTest.TaskRoutingProviderForDomainA;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.apache.ibatis.session.SqlSessionManager;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import pro.taskana.classification.api.ClassificationService;
+import pro.taskana.classification.api.models.ClassificationSummary;
+import pro.taskana.common.api.TaskanaEngine;
+import pro.taskana.common.test.security.JaasExtension;
+import pro.taskana.common.test.security.WithAccessId;
+import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
+import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl;
+import pro.taskana.simplehistory.impl.TaskanaHistoryEngineImpl;
+import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
+import pro.taskana.spi.history.api.TaskanaHistory;
+import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
+import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
+import pro.taskana.spi.routing.api.TaskRoutingProvider;
+import pro.taskana.task.api.TaskService;
+import pro.taskana.task.api.models.Task;
+import pro.taskana.testapi.DefaultTestEntities;
+import pro.taskana.testapi.TaskanaInject;
+import pro.taskana.testapi.TaskanaIntegrationTest;
+import pro.taskana.testapi.WithServiceProvider;
+import pro.taskana.testapi.builder.TaskBuilder;
+import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder;
+import pro.taskana.workbasket.api.WorkbasketPermission;
+import pro.taskana.workbasket.api.WorkbasketService;
+import pro.taskana.workbasket.api.models.Workbasket;
+import pro.taskana.workbasket.api.models.WorkbasketSummary;
+
+@WithServiceProvider(
+ serviceProviderInterface = TaskRoutingProvider.class,
+ serviceProviders = TaskRoutingProviderForDomainA.class)
+@WithServiceProvider(
+ serviceProviderInterface = TaskanaHistory.class,
+ serviceProviders = SimpleHistoryServiceImpl.class)
+@TaskanaIntegrationTest
+@ExtendWith(JaasExtension.class)
+class CreateHistoryEventOnTaskRerouteAccTest {
+ @TaskanaInject TaskanaEngine taskanaEngine;
+ @TaskanaInject TaskService taskService;
+ @TaskanaInject WorkbasketService workbasketService;
+ @TaskanaInject ClassificationService classificationService;
+
+ ClassificationSummary classificationSummary;
+ WorkbasketSummary domainAWorkbasketSummary;
+ WorkbasketSummary domainBWorkbasketSummary;
+ Task task1;
+ Task task2;
+ Task task3;
+ Task task4;
+ SimpleHistoryServiceImpl historyService;
+ TaskanaHistoryEngineImpl taskanaHistoryEngine;
+
+ @WithAccessId(user = "admin")
+ @BeforeAll
+ void setup() throws Exception {
+ historyService = new SimpleHistoryServiceImpl();
+ taskanaHistoryEngine = TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngine);
+ historyService.initialize(taskanaEngine);
+ classificationSummary =
+ DefaultTestEntities.defaultTestClassification()
+ .buildAndStoreAsSummary(classificationService);
+ domainAWorkbasketSummary =
+ DefaultTestEntities.defaultTestWorkbasket()
+ .domain("DOMAIN_A")
+ .buildAndStoreAsSummary(workbasketService);
+ domainBWorkbasketSummary =
+ DefaultTestEntities.defaultTestWorkbasket()
+ .domain("DOMAIN_B")
+ .buildAndStoreAsSummary(workbasketService);
+
+ task1 =
+ TaskBuilder.newTask()
+ .classificationSummary(classificationSummary)
+ .workbasketSummary(domainAWorkbasketSummary)
+ .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build())
+ .buildAndStore(taskService);
+ task2 =
+ TaskBuilder.newTask()
+ .classificationSummary(classificationSummary)
+ .workbasketSummary(domainAWorkbasketSummary)
+ .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build())
+ .buildAndStore(taskService);
+ task3 =
+ TaskBuilder.newTask()
+ .classificationSummary(classificationSummary)
+ .workbasketSummary(domainBWorkbasketSummary)
+ .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build())
+ .buildAndStore(taskService);
+
+ task4 =
+ TaskBuilder.newTask()
+ .classificationSummary(classificationSummary)
+ .workbasketSummary(domainAWorkbasketSummary)
+ .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build())
+ .buildAndStore(taskService);
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(domainAWorkbasketSummary.getId())
+ .accessId("user-1-1")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.APPEND)
+ .buildAndStore(workbasketService);
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(domainBWorkbasketSummary.getId())
+ .accessId("user-1-1")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.APPEND)
+ .buildAndStore(workbasketService);
+ }
+
+ @WithAccessId(user = "admin")
+ @Test
+ void should_CreateRerouteHistoryEvent_When_TaskIsRerouted() throws Exception {
+ historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
+ TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper();
+ taskService.rerouteTask(task4.getId());
+
+ List events =
+ taskHistoryQueryMapper.queryHistoryEvents(
+ (TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task4.getId()));
+
+ assertThat(events).hasSize(1);
+ String eventType = events.get(0).getEventType();
+ assertThat(eventType).isEqualTo(TaskHistoryEventType.REROUTED.getName());
+ assertRerouteHistoryEvent(
+ events.get(0).getId(),
+ domainAWorkbasketSummary.getId(),
+ domainBWorkbasketSummary.getId(),
+ "admin");
+
+ historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
+ }
+
+ @WithAccessId(user = "admin")
+ @Test
+ void should_CreateRerouteHistoryEvent_When_MultipleTasksAreRerouted() throws Exception {
+ List taskIds = List.of(task1.getId(), task2.getId(), task3.getId());
+ historyService.deleteHistoryEventsByTaskIds(taskIds);
+ TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper();
+ taskService.rerouteTasks(taskIds);
+
+ List events =
+ taskHistoryQueryMapper.queryHistoryEvents(
+ (TaskHistoryQueryImpl)
+ historyService.createTaskHistoryQuery().taskIdIn(taskIds.toArray(new String[0])));
+
+ assertThat(events)
+ .extracting(TaskHistoryEvent::getTaskId)
+ .containsExactlyInAnyOrderElementsOf(taskIds);
+
+ for (TaskHistoryEvent event : events) {
+ if (event.getTaskId().equals(task1.getId())) {
+ assertRerouteHistoryEvent(
+ event.getId(),
+ domainAWorkbasketSummary.getId(),
+ domainBWorkbasketSummary.getId(),
+ "admin");
+ } else if (event.getTaskId().equals(task2.getId())) {
+ assertRerouteHistoryEvent(
+ event.getId(),
+ domainAWorkbasketSummary.getId(),
+ domainBWorkbasketSummary.getId(),
+ "admin");
+ } else {
+ assertRerouteHistoryEvent(
+ event.getId(),
+ domainBWorkbasketSummary.getId(),
+ domainAWorkbasketSummary.getId(),
+ "admin");
+ }
+ }
+ }
+
+ private TaskHistoryQueryMapper getHistoryQueryMapper()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field sessionManagerField = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager");
+ sessionManagerField.setAccessible(true);
+ SqlSessionManager sqlSessionManager =
+ (SqlSessionManager) sessionManagerField.get(taskanaHistoryEngine);
+
+ return sqlSessionManager.getMapper(TaskHistoryQueryMapper.class);
+ }
+
+ private void assertRerouteHistoryEvent(
+ String eventId, String expectedOldValue, String expectedNewValue, String expectedUser)
+ throws Exception {
+ TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId);
+ assertThat(event.getDetails()).isNotNull();
+ JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes");
+ assertThat(changes.length()).isPositive();
+ boolean foundField = false;
+ for (int i = 0; i < changes.length() && !foundField; i++) {
+ JSONObject change = changes.getJSONObject(i);
+ if (change.get("fieldName").equals("workbasketSummary")) {
+ foundField = true;
+ String oldWorkbasketStr = change.get("oldValue").toString();
+ String newWorkbasketStr = change.get("newValue").toString();
+ Workbasket oldWorkbasket = workbasketService.getWorkbasket(expectedOldValue);
+ assertThat(oldWorkbasketStr)
+ .isEqualTo(JSONObject.wrap(oldWorkbasket.asSummary()).toString());
+ Workbasket newWorkbasket = workbasketService.getWorkbasket(expectedNewValue);
+ assertThat(newWorkbasketStr)
+ .isEqualTo(JSONObject.wrap(newWorkbasket.asSummary()).toString());
+ }
+ }
+ assertThat(foundField).describedAs("changes do not contain field 'workbasketSummary'").isTrue();
+
+ assertThat(event.getId()).startsWith("THI:");
+ assertThat(event.getOldValue()).isEqualTo(expectedOldValue);
+ assertThat(event.getNewValue()).isEqualTo(expectedNewValue);
+ assertThat(event.getUserId()).isEqualTo(expectedUser);
+ assertThat(event.getEventType()).isEqualTo(TaskHistoryEventType.REROUTED.getName());
+ }
+
+ class TaskRoutingProviderForDomainA implements TaskRoutingProvider {
+
+ @Override
+ public void initialize(TaskanaEngine taskanaEngine) {}
+
+ @Override
+ public String determineWorkbasketId(Task task) {
+ if ("DOMAIN_A".equals(task.getDomain())) {
+ return domainBWorkbasketSummary.getId();
+ } else if ("DOMAIN_B".equals(task.getDomain())) {
+ return domainAWorkbasketSummary.getId();
+ }
+ return null;
+ }
+ }
+}
diff --git a/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java
new file mode 100644
index 0000000000..c9d27a6871
--- /dev/null
+++ b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskReroutingAccTest.java
@@ -0,0 +1,368 @@
+package acceptance.taskrouting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowableOfType;
+import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
+import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference;
+import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;
+
+import acceptance.taskrouting.TaskReroutingAccTest.CustomTaskRoutingProvider;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import pro.taskana.classification.api.ClassificationService;
+import pro.taskana.classification.api.models.ClassificationSummary;
+import pro.taskana.common.api.BulkOperationResults;
+import pro.taskana.common.api.TaskanaEngine;
+import pro.taskana.common.api.exceptions.TaskanaException;
+import pro.taskana.spi.routing.api.TaskRoutingProvider;
+import pro.taskana.task.api.TaskService;
+import pro.taskana.task.api.TaskState;
+import pro.taskana.task.api.exceptions.InvalidTaskStateException;
+import pro.taskana.task.api.exceptions.TaskNotFoundException;
+import pro.taskana.task.api.models.ObjectReference;
+import pro.taskana.task.api.models.Task;
+import pro.taskana.task.api.models.TaskSummary;
+import pro.taskana.testapi.TaskanaInject;
+import pro.taskana.testapi.TaskanaIntegrationTest;
+import pro.taskana.testapi.WithServiceProvider;
+import pro.taskana.testapi.builder.ObjectReferenceBuilder;
+import pro.taskana.testapi.builder.TaskBuilder;
+import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder;
+import pro.taskana.testapi.security.WithAccessId;
+import pro.taskana.workbasket.api.WorkbasketPermission;
+import pro.taskana.workbasket.api.WorkbasketService;
+import pro.taskana.workbasket.api.exceptions.NotAuthorizedOnWorkbasketException;
+import pro.taskana.workbasket.api.models.WorkbasketSummary;
+
+@WithServiceProvider(
+ serviceProviderInterface = TaskRoutingProvider.class,
+ serviceProviders = CustomTaskRoutingProvider.class)
+@TaskanaIntegrationTest
+class TaskReroutingAccTest {
+
+ @TaskanaInject TaskService taskService;
+ @TaskanaInject ClassificationService classificationService;
+ @TaskanaInject WorkbasketService workbasketService;
+
+ ClassificationSummary defaultClassificationSummary;
+ WorkbasketSummary defaultWorkbasketSummary;
+ WorkbasketSummary workbasketSummary1;
+ WorkbasketSummary workbasketSummary2;
+ WorkbasketSummary workbasketSummary3;
+ ObjectReference defaultObjectReference;
+
+ @WithAccessId(user = "businessadmin")
+ @BeforeAll
+ void setup() throws Exception {
+ defaultClassificationSummary =
+ defaultTestClassification().buildAndStoreAsSummary(classificationService);
+ defaultWorkbasketSummary = defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);
+ workbasketSummary1 =
+ defaultTestWorkbasket().key("key_1").buildAndStoreAsSummary(workbasketService);
+ workbasketSummary2 =
+ defaultTestWorkbasket().key("key_2").buildAndStoreAsSummary(workbasketService);
+ workbasketSummary3 =
+ defaultTestWorkbasket().key("key_3").buildAndStoreAsSummary(workbasketService);
+ defaultObjectReference = defaultTestObjectReference().build();
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(defaultWorkbasketSummary.getId())
+ .accessId("user-1-2")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.READTASKS)
+ .permission(WorkbasketPermission.EDITTASKS)
+ .permission(WorkbasketPermission.APPEND)
+ .permission(WorkbasketPermission.TRANSFER)
+ .buildAndStore(workbasketService);
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(workbasketSummary1.getId())
+ .accessId("user-1-2")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.READTASKS)
+ .permission(WorkbasketPermission.EDITTASKS)
+ .permission(WorkbasketPermission.APPEND)
+ .buildAndStore(workbasketService);
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(workbasketSummary2.getId())
+ .accessId("user-1-2")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.READTASKS)
+ .permission(WorkbasketPermission.EDITTASKS)
+ .buildAndStore(workbasketService);
+
+ WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
+ .workbasketId(workbasketSummary3.getId())
+ .accessId("user-1-2")
+ .permission(WorkbasketPermission.OPEN)
+ .permission(WorkbasketPermission.READ)
+ .permission(WorkbasketPermission.READTASKS)
+ .permission(WorkbasketPermission.EDITTASKS)
+ .permission(WorkbasketPermission.APPEND)
+ .buildAndStore(workbasketService);
+ }
+
+ @WithAccessId(user = "taskadmin")
+ @Test
+ void should_RerouteTask_When_PorValueIsChanged() throws Exception {
+ Task task = createDefaultTask().buildAndStore(taskService, "admin");
+ Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS);
+
+ task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1"));
+ taskService.updateTask(task);
+ Task reroutedTask = taskService.rerouteTask(task.getId());
+
+ assertTaskIsRerouted(before, reroutedTask.asSummary(), task.getState(), workbasketSummary1);
+ }
+
+ @WithAccessId(user = "user-1-2")
+ @Test
+ void should_NotRerouteTask_When_UserHasNoAppendPermissionToDestinationWb() throws Exception {
+ Task task = createDefaultTask().buildAndStore(taskService, "admin");
+
+ task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2"));
+ taskService.updateTask(task);
+
+ ThrowingCallable call = () -> taskService.rerouteTask(task.getId());
+ NotAuthorizedOnWorkbasketException e =
+ catchThrowableOfType(call, NotAuthorizedOnWorkbasketException.class);
+
+ assertThat(e.getWorkbasketId()).isEqualTo(workbasketSummary2.getId());
+ assertThat(e.getCurrentUserId()).isEqualTo("user-1-2");
+ assertThat(e.getRequiredPermissions()).containsExactlyInAnyOrder(WorkbasketPermission.APPEND);
+
+ Task readTask = taskService.getTask(task.getId());
+ assertThat(readTask.getWorkbasketSummary().getId())
+ .isEqualTo(task.getWorkbasketSummary().getId());
+ }
+
+ @WithAccessId(user = "user-1-2")
+ @Test
+ void should_NotRerouteTask_When_UserHasNoTransferPermissionToOriginWb() throws Exception {
+ Task task =
+ createDefaultTask()
+ .workbasketSummary(workbasketSummary1)
+ .buildAndStore(taskService, "admin");
+
+ task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2"));
+ taskService.updateTask(task);
+
+ ThrowingCallable call = () -> taskService.rerouteTask(task.getId());
+ NotAuthorizedOnWorkbasketException e =
+ catchThrowableOfType(call, NotAuthorizedOnWorkbasketException.class);
+
+ assertThat(e.getWorkbasketId()).isEqualTo(workbasketSummary1.getId());
+ assertThat(e.getCurrentUserId()).isEqualTo("user-1-2");
+ assertThat(e.getRequiredPermissions()).containsExactlyInAnyOrder(WorkbasketPermission.TRANSFER);
+
+ Task readTask = taskService.getTask(task.getId());
+ assertThat(readTask.getWorkbasketSummary().getId())
+ .isEqualTo(task.getWorkbasketSummary().getId());
+ }
+
+ @WithAccessId(user = "taskadmin")
+ @Test
+ void should_RerouteTasksToSameWb_When_PorValueIsChanged() throws Exception {
+ Task task1 = createDefaultTask().buildAndStore(taskService, "admin");
+ Task task2 = createDefaultTask().buildAndStore(taskService, "admin");
+ Task task3 = createDefaultTask().buildAndStore(taskService, "admin");
+ List tasks = Arrays.asList(task1, task2, task3);
+ Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS);
+
+ for (Task task : tasks) {
+ task.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1"));
+ taskService.updateTask(task);
+ }
+ List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList());
+
+ BulkOperationResults results = taskService.rerouteTasks(taskIds);
+ assertThat(results.containsErrors()).isFalse();
+ List reroutedTasks =
+ taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
+ assertThat(reroutedTasks).isNotEmpty();
+ for (int i = 0; i < reroutedTasks.size(); i++) {
+ assertTaskIsRerouted(
+ before, reroutedTasks.get(i), tasks.get(i).getState(), workbasketSummary1);
+ }
+ }
+
+ @WithAccessId(user = "taskadmin")
+ @Test
+ void should_RerouteTasksToMultipleWorkbaskets_When_PorValueIsChanged() throws Exception {
+ final Task task1 = createDefaultTask().buildAndStore(taskService, "admin");
+ final Task task2 = createDefaultTask().buildAndStore(taskService, "admin");
+ final Task task3 = createDefaultTask().buildAndStore(taskService, "admin");
+ Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS);
+
+ task1.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value1"));
+ taskService.updateTask(task1);
+ task2.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value2"));
+ taskService.updateTask(task2);
+ task3.setPrimaryObjRef(createObjectReference("company", null, null, "MyType1", "Value3"));
+ taskService.updateTask(task3);
+ List tasks = Arrays.asList(task1, task2, task3);
+ List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList());
+
+ BulkOperationResults results = taskService.rerouteTasks(taskIds);
+ assertThat(results.containsErrors()).isFalse();
+ List reroutedTasks =
+ taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
+ assertThat(reroutedTasks).isNotEmpty();
+ for (TaskSummary reroutedTask : reroutedTasks) {
+ if (reroutedTask.getId().equals(task1.getId())) {
+ assertTaskIsRerouted(before, reroutedTask, task1.getState(), workbasketSummary1);
+ } else if (reroutedTask.getId().equals(task2.getId())) {
+ assertTaskIsRerouted(before, reroutedTask, task2.getState(), workbasketSummary2);
+ } else {
+ assertTaskIsRerouted(before, reroutedTask, task3.getState(), workbasketSummary3);
+ }
+ }
+ }
+
+ @WithAccessId(user = "user-1-2")
+ @Test
+ void should_RerouteValidTasksEvenIfErrorsExist_When_PorValueIsChanged() throws Exception {
+ final Task taskToBeRerouted1 = createDefaultTask().buildAndStore(taskService, "admin");
+ final Task taskToBeRerouted3 = createDefaultTask().buildAndStore(taskService, "admin");
+ final Task taskNotNeededToReroute =
+ createDefaultTask()
+ .workbasketSummary(workbasketSummary1)
+ .buildAndStore(taskService, "admin");
+ final Task taskWithFinalState =
+ createDefaultTask().state(TaskState.COMPLETED).buildAndStore(taskService, "admin");
+ final Task taskWithNoTransferPerm =
+ createDefaultTask()
+ .workbasketSummary(workbasketSummary1)
+ .buildAndStore(taskService, "admin");
+ final Task taskWithNoAppendDestPerm = createDefaultTask().buildAndStore(taskService, "admin");
+ Instant before = Instant.now().truncatedTo(ChronoUnit.MILLIS);
+
+ taskToBeRerouted1.setPrimaryObjRef(
+ createObjectReference("company", null, null, "MyType1", "Value1"));
+ taskService.updateTask(taskToBeRerouted1);
+ taskToBeRerouted3.setPrimaryObjRef(
+ createObjectReference("company", null, null, "MyType1", "Value3"));
+ taskService.updateTask(taskToBeRerouted3);
+ taskNotNeededToReroute.setPrimaryObjRef("company", null, null, "MyType1", "Value1");
+ taskService.updateTask(taskNotNeededToReroute);
+ taskWithFinalState.setPrimaryObjRef("company", null, null, "MyType1", "Value1");
+ taskService.updateTask(taskWithFinalState);
+ taskWithNoTransferPerm.setPrimaryObjRef("company", null, null, "MyType1", "Value3");
+ taskService.updateTask(taskWithNoTransferPerm);
+ taskWithNoAppendDestPerm.setPrimaryObjRef("company", null, null, "MyType1", "Value2");
+ taskService.updateTask(taskWithNoAppendDestPerm);
+ List tasks =
+ Arrays.asList(
+ taskToBeRerouted1,
+ taskToBeRerouted3,
+ taskNotNeededToReroute,
+ taskWithFinalState,
+ taskWithNoTransferPerm,
+ taskWithNoAppendDestPerm);
+ List taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList());
+ taskIds.add("invalid-id");
+
+ BulkOperationResults results = taskService.rerouteTasks(taskIds);
+ final List reroutedTasks =
+ taskService.createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
+ assertThat(results.containsErrors()).isTrue();
+ assertThat(results.getErrorMap()).hasSize(4);
+ assertThat(results.getErrorForId("invalid-id")).isOfAnyClassIn(TaskNotFoundException.class);
+ assertThat(results.getErrorForId(taskWithFinalState.getId()))
+ .isOfAnyClassIn(InvalidTaskStateException.class);
+ assertThat(results.getErrorForId(taskWithNoTransferPerm.getId()))
+ .isOfAnyClassIn(NotAuthorizedOnWorkbasketException.class);
+ assertThat(results.getErrorForId(taskWithNoAppendDestPerm.getId()))
+ .isOfAnyClassIn(NotAuthorizedOnWorkbasketException.class);
+
+ for (TaskSummary reroutedTask : reroutedTasks) {
+ if (reroutedTask.getId().equals(taskToBeRerouted1.getId())) {
+ assertTaskIsRerouted(
+ before, reroutedTask, taskToBeRerouted1.getState(), workbasketSummary1);
+ } else if (reroutedTask.getId().equals(taskToBeRerouted3.getId())) {
+ assertTaskIsRerouted(
+ before, reroutedTask, taskToBeRerouted3.getState(), workbasketSummary3);
+ } else if (reroutedTask.getId().equals(taskNotNeededToReroute.getId())) {
+ assertThat(reroutedTask).isEqualTo(taskNotNeededToReroute.asSummary());
+ } else if (reroutedTask.getId().equals(taskWithFinalState.getId())) {
+ assertThat(reroutedTask).isEqualTo(taskWithFinalState.asSummary());
+ } else if (reroutedTask.getId().equals(taskWithNoTransferPerm.getId())) {
+ assertThat(reroutedTask).isEqualTo(taskWithNoTransferPerm.asSummary());
+ } else if (reroutedTask.getId().equals(taskWithNoAppendDestPerm.getId())) {
+ assertThat(reroutedTask).isEqualTo(taskWithNoAppendDestPerm.asSummary());
+ }
+ }
+ }
+
+ ObjectReference createObjectReference(
+ String company, String system, String systemInstance, String type, String value) {
+ return ObjectReferenceBuilder.newObjectReference()
+ .company(company)
+ .system(system)
+ .systemInstance(systemInstance)
+ .type(type)
+ .value(value)
+ .build();
+ }
+
+ private void assertTaskIsRerouted(
+ Instant before,
+ TaskSummary reroutedTask,
+ TaskState stateBeforeTransfer,
+ WorkbasketSummary wbAfterReroute) {
+ assertThat(reroutedTask).isNotNull();
+ assertThat(reroutedTask.isRead()).isFalse();
+ assertThat(reroutedTask.isTransferred()).isTrue();
+ assertThat(reroutedTask.getState()).isEqualTo(getStateAfterTransfer(stateBeforeTransfer));
+ assertThat(reroutedTask.getOwner()).isNull();
+ assertThat(reroutedTask.getWorkbasketSummary().getId()).isEqualTo(wbAfterReroute.getId());
+ assertThat(reroutedTask.getDomain()).isEqualTo(wbAfterReroute.getDomain());
+ assertThat(reroutedTask.getModified()).isAfterOrEqualTo(before);
+ }
+
+ private TaskBuilder createDefaultTask() {
+ return (TaskBuilder.newTask()
+ .workbasketSummary(defaultWorkbasketSummary)
+ .primaryObjRef(defaultObjectReference))
+ .classificationSummary(defaultClassificationSummary);
+ }
+
+ private TaskState getStateAfterTransfer(TaskState stateBeforeTransfer) {
+ if (stateBeforeTransfer.equals(TaskState.CLAIMED)) {
+ return TaskState.READY;
+ }
+ if (stateBeforeTransfer.equals(TaskState.IN_REVIEW)) {
+ return TaskState.READY_FOR_REVIEW;
+ } else {
+ return stateBeforeTransfer;
+ }
+ }
+
+ class CustomTaskRoutingProvider implements TaskRoutingProvider {
+
+ @Override
+ public void initialize(TaskanaEngine taskanaEngine) {}
+
+ @Override
+ public String determineWorkbasketId(Task task) {
+ if (task.getPrimaryObjRef().getValue().equals("Value1")) {
+ return workbasketSummary1.getId();
+ } else if (task.getPrimaryObjRef().getValue().equals("Value2")) {
+ return workbasketSummary2.getId();
+ } else if (task.getPrimaryObjRef().getValue().equals("Value3")) {
+ return workbasketSummary3.getId();
+ }
+ return null;
+ }
+ }
+}
diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
index 96576bb4a8..e5c94bb736 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
@@ -11,7 +11,8 @@ public enum TaskHistoryEventType {
CANCELLED("CANCELLED"),
TERMINATED("TERMINATED"),
TRANSFERRED("TRANSFERRED"),
- DELETED("DELETED");
+ DELETED("DELETED"),
+ REROUTED("REROUTED");
private String name;
diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java
new file mode 100644
index 0000000000..d787184556
--- /dev/null
+++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskReroutedEvent.java
@@ -0,0 +1,19 @@
+package pro.taskana.spi.history.api.events.task;
+
+import pro.taskana.task.api.models.TaskSummary;
+
+public class TaskReroutedEvent extends TaskHistoryEvent {
+
+ public TaskReroutedEvent(
+ String id,
+ TaskSummary task,
+ String oldWorkbasketId,
+ String newWorkbasketId,
+ String userId,
+ String details) {
+ super(id, task, userId, details);
+ eventType = TaskHistoryEventType.REROUTED.getName();
+ this.oldValue = oldWorkbasketId;
+ this.newValue = newWorkbasketId;
+ }
+}
diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java
index d83214238c..4446a8d439 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskService.java
@@ -1019,4 +1019,12 @@ ObjectReference newObjectReference(
* @return a {@linkplain TaskCommentQuery}
*/
TaskCommentQuery createTaskCommentQuery();
+
+ Task rerouteTask(String taskId)
+ throws NotAuthorizedOnWorkbasketException,
+ TaskNotFoundException,
+ WorkbasketNotFoundException,
+ InvalidTaskStateException;
+
+ BulkOperationResults rerouteTasks(List taskIds);
}
diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java b/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java
index 5ee606b9c7..908ecb1a32 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/task/api/models/TaskSummary.java
@@ -308,4 +308,6 @@ void addSecondaryObjectReference(
* @return a copy of this TaskSummary
*/
TaskSummary copy();
+
+ Task asTask();
}
diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java
index 2e25c5cd62..a674133f10 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskMapper.java
@@ -167,6 +167,47 @@ void setOwnerOfTasks(
void updateTransfered(
@Param("taskIds") Set taskIds, @Param("referencetask") TaskImpl referencetask);
+ @Update(
+ "")
+ void updateTransferMultipleWorkbaskets(
+ @Param("taskIds") Set taskIds,
+ @Param("referenceTasks") List referenceTasks);
+
@Update(
"