All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pro.taskana.task.rest.TaskController Maven / Gradle / Ivy

The newest version!
package pro.taskana.task.rest;

import static java.util.function.Predicate.not;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.beans.ConstructorProperties;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.common.api.BaseQuery.SortDirection;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.rest.QueryPagingParameter;
import pro.taskana.common.rest.QuerySortBy;
import pro.taskana.common.rest.QuerySortParameter;
import pro.taskana.common.rest.RestEndpoints;
import pro.taskana.common.rest.util.QueryParamsValidator;
import pro.taskana.task.api.TaskCustomField;
import pro.taskana.task.api.TaskQuery;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
import pro.taskana.task.api.exceptions.InvalidCallbackStateException;
import pro.taskana.task.api.exceptions.InvalidOwnerException;
import pro.taskana.task.api.exceptions.InvalidTaskStateException;
import pro.taskana.task.api.exceptions.ObjectReferencePersistenceException;
import pro.taskana.task.api.exceptions.TaskAlreadyExistException;
import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.task.rest.assembler.BulkOperationResultsRepresentationModelAssembler;
import pro.taskana.task.rest.assembler.TaskRepresentationModelAssembler;
import pro.taskana.task.rest.assembler.TaskSummaryRepresentationModelAssembler;
import pro.taskana.task.rest.models.BulkOperationResultsRepresentationModel;
import pro.taskana.task.rest.models.IsReadRepresentationModel;
import pro.taskana.task.rest.models.TaskRepresentationModel;
import pro.taskana.task.rest.models.TaskSummaryCollectionRepresentationModel;
import pro.taskana.task.rest.models.TaskSummaryPagedRepresentationModel;
import pro.taskana.task.rest.models.TransferTaskRepresentationModel;
import pro.taskana.workbasket.api.exceptions.NotAuthorizedOnWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;

/** Controller for all {@link Task} related endpoints. */
@RestController
@EnableHypermediaSupport(type = HypermediaType.HAL)
public class TaskController {

  private final TaskService taskService;
  private final TaskRepresentationModelAssembler taskRepresentationModelAssembler;
  private final TaskSummaryRepresentationModelAssembler taskSummaryRepresentationModelAssembler;
  private final BulkOperationResultsRepresentationModelAssembler
      bulkOperationResultsRepresentationModelAssembler;

  @Autowired
  TaskController(
      TaskService taskService,
      TaskRepresentationModelAssembler taskRepresentationModelAssembler,
      TaskSummaryRepresentationModelAssembler taskSummaryRepresentationModelAssembler,
      BulkOperationResultsRepresentationModelAssembler
          bulkOperationResultsRepresentationModelAssembler) {
    this.taskService = taskService;
    this.taskRepresentationModelAssembler = taskRepresentationModelAssembler;
    this.taskSummaryRepresentationModelAssembler = taskSummaryRepresentationModelAssembler;
    this.bulkOperationResultsRepresentationModelAssembler =
        bulkOperationResultsRepresentationModelAssembler;
  }

  // region CREATE

  /**
   * This endpoint creates a persistent Task.
   *
   * @param taskRepresentationModel the Task which should be created.
   * @return the created Task
   * @throws WorkbasketNotFoundException if the referenced Workbasket does not exist
   * @throws ClassificationNotFoundException if the referenced Classification does not exist
   * @throws NotAuthorizedOnWorkbasketException if the current user is not authorized to append a
   *     Task to the referenced Workbasket
   * @throws TaskAlreadyExistException if the requested Task already exists.
   * @throws InvalidArgumentException if any input is semantically wrong.
   * @throws AttachmentPersistenceException if an Attachment with ID will be added multiple times
   *     without using the task-methods
   * @throws ObjectReferencePersistenceException if an ObjectReference with ID will be added
   *     multiple times without using the task-methods
   * @title Create a new Task
   */
  @Operation(
      summary = "Create a new Task",
      description = "This endpoint creates a persistent Task.",
      requestBody =
          @io.swagger.v3.oas.annotations.parameters.RequestBody(
              description = "the Task which should be created.",
              content =
                  @Content(
                      mediaType = MediaTypes.HAL_JSON_VALUE,
                      schema = @Schema(implementation = TaskRepresentationModel.class),
                      examples =
                          @ExampleObject(
                              value =
                                  "{"
                                      + "\"priority\" : 0,"
                                      + "\"manualPriority\" : -1,"
                                      + "\"classificationSummary\" : {"
                                      + "\"key\" : \"L11010\","
                                      + "\"priority\" : 0"
                                      + "},"
                                      + "\"workbasketSummary\" : {"
                                      + "\"workbasketId\" : "
                                      + "\"WBI:100000000000000000000000000000000004\","
                                      + "\"markedForDeletion\" : false"
                                      + "},"
                                      + "\"primaryObjRef\" : {"
                                      + "\"company\" : \"MyCompany1\","
                                      + "\"system\" : \"MySystem1\","
                                      + "\"systemInstance\" : \"MyInstance1\","
                                      + "\"type\" : \"MyType1\","
                                      + "\"value\" : \"00000001\""
                                      + "},"
                                      + "\"secondaryObjectReferences\" : [ {"
                                      + "\"company\" : \"company\","
                                      + "\"system\" : \"system\","
                                      + "\"systemInstance\" : \"systemInstance\","
                                      + "\"type\" : \"type\","
                                      + "\"value\" : \"value\""
                                      + "} ],"
                                      + "\"customAttributes\" : [ ],"
                                      + "\"callbackInfo\" : [ ],"
                                      + "\"attachments\" : [ ],"
                                      + "\"read\" : false,"
                                      + "\"transferred\" : false"
                                      + "}"))),
      responses = {
        @ApiResponse(
            responseCode = "201",
            description = "the created Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            })
      })
  @PostMapping(path = RestEndpoints.URL_TASKS)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity createTask(
      @RequestBody TaskRepresentationModel taskRepresentationModel)
      throws WorkbasketNotFoundException,
          ClassificationNotFoundException,
          TaskAlreadyExistException,
          InvalidArgumentException,
          AttachmentPersistenceException,
          ObjectReferencePersistenceException,
          NotAuthorizedOnWorkbasketException {

    if (!taskRepresentationModel.getAttachments().stream()
        .filter(att -> Objects.nonNull(att.getTaskId()))
        .filter(att -> !att.getTaskId().equals(taskRepresentationModel.getTaskId()))
        .toList()
        .isEmpty()) {
      throw new InvalidArgumentException(
          "An attachments' taskId must be empty or equal to the id of the task it belongs to");
    }

    Task fromResource = taskRepresentationModelAssembler.toEntityModel(taskRepresentationModel);
    Task createdTask = taskService.createTask(fromResource);

    return ResponseEntity.status(HttpStatus.CREATED)
        .body(taskRepresentationModelAssembler.toModel(createdTask));
  }

  // endregion

  // region READ

  /**
   * This endpoint retrieves a list of existing Tasks. Filters can be applied.
   *
   * @title Get a list of all Tasks
   * @param request the HTTP request
   * @param filterParameter the filter parameters
   * @param filterCustomFields the filter parameters regarding TaskCustomFields
   * @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields * @param
   * @param groupByParameter the group by parameters
   * @param sortParameter the sort parameters
   * @param pagingParameter the paging parameters
   * @return the Tasks with the given filter, sort and paging options.
   * @throws InvalidArgumentException if the query parameter "owner-is-null" has values
   */
  @Operation(
      summary = "Get a list of all Tasks",
      description = "This endpoint retrieves a list of existing Tasks. Filters can be applied.",
      parameters = {
        @Parameter(name = "por-type", example = "VNR"),
        @Parameter(name = "por-value", example = "22334455"),
        @Parameter(name = "sort-by", example = "NAME")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the Tasks with the given filter, sort and paging options.",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskSummaryPagedRepresentationModel.class))
            })
      })
  @GetMapping(path = RestEndpoints.URL_TASKS)
  @Transactional(readOnly = true, rollbackFor = Exception.class)
  public ResponseEntity getTasks(
      HttpServletRequest request,
      @ParameterObject TaskQueryFilterParameter filterParameter,
      @ParameterObject TaskQueryFilterCustomFields filterCustomFields,
      @ParameterObject TaskQueryFilterCustomIntFields filterCustomIntFields,
      @ParameterObject TaskQueryGroupByParameter groupByParameter,
      @ParameterObject TaskQuerySortParameter sortParameter,
      @ParameterObject QueryPagingParameter pagingParameter) {
    QueryParamsValidator.validateParams(
        request,
        TaskQueryFilterParameter.class,
        TaskQueryFilterCustomFields.class,
        TaskQueryFilterCustomIntFields.class,
        TaskQueryGroupByParameter.class,
        QuerySortParameter.class,
        QueryPagingParameter.class);

    if (QueryParamsValidator.hasQueryParameterValuesOrIsNotTrue(request, "owner-is-null")) {
      throw new InvalidArgumentException(
          "It is prohibited to use the param owner-is-null with values.");
    }

    TaskQuery query = taskService.createTaskQuery();

    filterParameter.apply(query);
    filterCustomFields.apply(query);
    filterCustomIntFields.apply(query);
    groupByParameter.apply(query);
    sortParameter.apply(query);

    List taskSummaries = pagingParameter.apply(query);

    TaskSummaryPagedRepresentationModel pagedModels =
        taskSummaryRepresentationModelAssembler.toPagedModel(
            taskSummaries, pagingParameter.getPageMetadata());
    return ResponseEntity.ok(pagedModels);
  }

  /**
   * This endpoint retrieves a specific Task.
   *
   * @param taskId the Id of the requested Task
   * @return the requested Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws NotAuthorizedOnWorkbasketException if the current user is not authorized to get the
   *     requested Task.
   * @title Get a single Task
   */
  @Operation(
      summary = "Get a single Task",
      description = "This endpoint retrieves a specific Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            required = true,
            description = "the Id of the requested Task",
            example = "TKI:100000000000000000000000000000000000")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the requested Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            })
      })
  @GetMapping(path = RestEndpoints.URL_TASKS_ID)
  @Transactional(readOnly = true, rollbackFor = Exception.class)
  public ResponseEntity getTask(@PathVariable("taskId") String taskId)
      throws TaskNotFoundException, NotAuthorizedOnWorkbasketException {
    Task task = taskService.getTask(taskId);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  // endregion

  // region UPDATE

  /**
   * This endpoint claims a Task if possible.
   *
   * @param taskId the Id of the Task which should be claimed
   * @param userName TODO: this is currently not used
   * @return the claimed Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the state of the requested Task is not READY.
   * @throws InvalidOwnerException if the Task is already claimed by someone else.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permissions for the
   *     requested Task.
   * @title Claim a Task
   */
  @Operation(
      summary = "Claim a Task",
      description = "This endpoint claims a Task if possible.",
      parameters = {
        @Parameter(
            name = "taskId",
            required = true,
            description = "the Id of the Task which should be claimed",
            example = "TKI:000000000000000000000000000000000003")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the claimed Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            })
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity claimTask(
      @PathVariable("taskId") String taskId, @RequestBody(required = false) String userName)
      throws TaskNotFoundException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException,
          InvalidTaskStateException {
    // TODO verify user
    Task updatedTask = taskService.claim(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint force claims a Task if possible even if it is already claimed by someone else.
   *
   * @param taskId the Id of the Task which should be force claimed
   * @param userName TODO: this is currently not used
   * @return the force claimed Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the state of Task with taskId is in an END_STATE.
   * @throws InvalidOwnerException cannot be thrown.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permissions for the
   *     requested Task.
   * @title Force claim a Task
   */
  @Operation(
      summary = "Force claim a Task",
      description =
          "This endpoint force claims a Task if possible even if it is already claimed by someone "
              + "else.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task which should be force claimed",
            required = true,
            example = "TKI:000000000000000000000000000000000003")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the force claimed Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            })
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceClaimTask(
      @PathVariable("taskId") String taskId, @RequestBody(required = false) String userName)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    // TODO verify user
    Task updatedTask = taskService.forceClaim(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint selects the first Task returned by the Task Query and claims it.
   *
   * @param filterParameter the filter parameters
   * @param filterCustomFields the filter parameters regarding TaskCustomFields
   * @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
   * @param sortParameter the sort parameters
   * @return the claimed Task or 404 if no Task is found
   * @throws InvalidOwnerException if the Task is already claimed by someone else
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Select and claim a Task
   */
  @Operation(
      summary = "Select and claim a Task",
      description =
          "This endpoint selects the first Task returned by the Task Query and claims it.",
      parameters = {@Parameter(name = "custom14", example = "abc")},
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the claimed Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            }),
        @ApiResponse(
            responseCode = "404",
            description = "if no Task is found",
            content = {@Content(schema = @Schema())})
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_SELECT_AND_CLAIM)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity selectAndClaimTask(
      @ParameterObject TaskQueryFilterParameter filterParameter,
      @ParameterObject TaskQueryFilterCustomFields filterCustomFields,
      @ParameterObject TaskQueryFilterCustomIntFields filterCustomIntFields,
      @ParameterObject TaskQuerySortParameter sortParameter)
      throws InvalidOwnerException, NotAuthorizedOnWorkbasketException {
    TaskQuery query = taskService.createTaskQuery();

    filterParameter.apply(query);
    filterCustomFields.apply(query);
    filterCustomIntFields.apply(query);
    sortParameter.apply(query);

    Optional selectedAndClaimedTask = taskService.selectAndClaim(query);

    return selectedAndClaimedTask
        .map(task -> ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task)))
        .orElseGet(() -> ResponseEntity.notFound().build());
  }

  /**
   * This endpoint cancels the claim of an existing Task if it was claimed by the current user
   * before.
   *
   * @param taskId the Id of the requested Task.
   * @param keepOwner flag whether or not to keep the owner despite the cancel claim
   * @return the unclaimed Task.
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the Task is already in an end state.
   * @throws InvalidOwnerException if the Task is claimed by a different user.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Cancel a claimed Task
   */
  @Operation(
      summary = "Cancel a claimed Task",
      description =
          "This endpoint cancels the claim of an existing Task if it was claimed by the current "
              + "user before.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the requested Task.",
            required = true,
            example = "TKI:000000000000000000000000000000000002"),
        @Parameter(
            name = "keepOwner",
            description = "flag whether or not to keep the owner despite the cancel claim")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the unclaimed Task",
            content = {
              @Content(
                  mediaType = MediaTypes.HAL_JSON_VALUE,
                  schema = @Schema(implementation = TaskRepresentationModel.class))
            })
      })
  @DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity cancelClaimTask(
      @PathVariable("taskId") String taskId,
      @RequestParam(value = "keepOwner", defaultValue = "false") boolean keepOwner)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task updatedTask = taskService.cancelClaim(taskId, keepOwner);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint force cancels the claim of an existing Task.
   *
   * @param taskId the Id of the requested Task.
   * @param keepOwner flag whether or not to keep the owner despite the cancel claim
   * @return the unclaimed Task.
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the Task is already in an end state.
   * @throws InvalidOwnerException if the Task is claimed by a different user.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Force cancel a claimed Task
   */
  @Operation(
      summary = "Force cancel a claimed Task",
      description = "This endpoint force cancels the claim of an existing Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the requested Task.",
            required = true,
            example = "TKI:000000000000000000000000000000000002"),
        @Parameter(
            name = "keepOwner",
            description = "flag whether or not to keep the owner despite the cancel claim.")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the unclaimed Task.",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceCancelClaimTask(
      @PathVariable("taskId") String taskId,
      @RequestParam(value = "keepOwner", defaultValue = "false") boolean keepOwner)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task updatedTask = taskService.forceCancelClaim(taskId, keepOwner);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint request a review on the specified Task.
   *
   * @param taskId taskId the id of the relevant Task
   * @return the Task after a review has been requested
   * @throws InvalidTaskStateException if the state of the Task with taskId is not CLAIMED
   * @throws TaskNotFoundException if the Task with taskId wasn't found
   * @throws InvalidOwnerException if the Task is claimed by another user
   * @throws NotAuthorizedOnWorkbasketException if the current user has no READ permissions for the
   *     Workbasket the Task is in
   * @title Request a review on a Task
   */
  @Operation(
      summary = "Request a review on a Task",
      description = "This endpoint requests a review on the specified Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the id of the relevant Task",
            required = true,
            example = "TKI:000000000000000000000000000000000032")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the Task after a review has been requested",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class))),
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_REQUEST_REVIEW)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity requestReview(
      @PathVariable("taskId") String taskId)
      throws InvalidTaskStateException,
          TaskNotFoundException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task task = taskService.requestReview(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  /**
   * This endpoint force request a review on the specified Task.
   *
   * @param taskId taskId the id of the relevant Task
   * @return the Task after a review has been requested
   * @throws InvalidTaskStateException if the state of the Task with taskId is not CLAIMED
   * @throws TaskNotFoundException if the Task with taskId wasn't found
   * @throws InvalidOwnerException cannot be thrown
   * @throws NotAuthorizedOnWorkbasketException if the current user has no READ permissions for the
   *     Workbasket the Task is in
   * @title Force request a review on a Task
   */
  @Operation(
      summary = "Force request a review on a Task",
      description = "This endpoint force requests a review on the specified Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the id of the relevant Task",
            required = true,
            example = "TKI:000000000000000000000000000000000101")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the Task after a review has been requested",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_REQUEST_REVIEW_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceRequestReview(
      @PathVariable("taskId") String taskId)
      throws InvalidTaskStateException,
          TaskNotFoundException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task task = taskService.forceRequestReview(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  /**
   * This endpoint request changes on the specified Task.
   *
   * @param taskId the id of the relevant Task
   * @return the Task after changes have been requested
   * @throws InvalidTaskStateException if the state of the Task with taskId is not IN_REVIEW
   * @throws TaskNotFoundException if the Task with taskId wasn't found
   * @throws InvalidOwnerException if the Task is claimed by another user
   * @throws NotAuthorizedOnWorkbasketException if the current user has no READ permissions for the
   *     Workbasket the Task is in
   * @title Request changes on a Task
   */
  @Operation(
      summary = "Request changes on a Task",
      description = "This endpoint requests changes on the specified Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the id of the relevant Task",
            required = true,
            example = "TKI:000000000000000000000000000000000136")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "Changes requested successfully",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class))),
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_REQUEST_CHANGES)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity requestChanges(
      @PathVariable("taskId") String taskId)
      throws InvalidTaskStateException,
          TaskNotFoundException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task task = taskService.requestChanges(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  /**
   * This endpoint force requests changes on a Task.
   *
   * @param taskId the Id of the Task on which a review should be requested
   * @return the change requested Task
   * @throws InvalidTaskStateException if the Task with taskId is in an end state
   * @throws TaskNotFoundException if the Task with taskId wasn't found
   * @throws InvalidOwnerException cannot be thrown
   * @throws NotAuthorizedOnWorkbasketException if the current user has no READ permissions for the
   *     Workbasket the Task is in
   * @title Force request changes on a Task
   */
  @Operation(
      summary = "Force request changes on a Task",
      description = "This endpoint force requests changes on the specified Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task on which a review should be requested",
            required = true,
            example = "TKI:000000000000000000000000000000000100")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the change requested Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class))),
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_REQUEST_CHANGES_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceRequestChanges(
      @PathVariable("taskId") String taskId)
      throws InvalidTaskStateException,
          TaskNotFoundException,
          InvalidOwnerException,
          NotAuthorizedOnWorkbasketException {
    Task task = taskService.forceRequestChanges(taskId);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  /**
   * This endpoint completes a Task.
   *
   * @param taskId Id of the requested Task to complete.
   * @return the completed Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidOwnerException if current user is not the owner of the Task or an administrator.
   * @throws InvalidTaskStateException if Task wasn't claimed previously.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Complete a Task
   */
  @Operation(
      summary = "Complete a Task",
      description = "This endpoint completes a Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "Id of the requested Task to complete.",
            example = "TKI:000000000000000000000000000000000003",
            required = true)
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the completed Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_COMPLETE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity completeTask(@PathVariable("taskId") String taskId)
      throws TaskNotFoundException,
          InvalidOwnerException,
          InvalidTaskStateException,
          NotAuthorizedOnWorkbasketException {

    Task updatedTask = taskService.completeTask(taskId);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint force completes a Task.
   *
   * @param taskId Id of the requested Task to force complete.
   * @return the force completed Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidOwnerException cannot be thrown.
   * @throws InvalidTaskStateException if the state of the Task with taskId is TERMINATED or
   *     CANCELED
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Force complete a Task
   */
  @Operation(
      summary = "Force complete a Task",
      description = "This endpoint force completes a Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "Id of the requested Task to force complete.",
            example = "TKI:000000000000000000000000000000000003",
            required = true)
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the force completed Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_COMPLETE_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceCompleteTask(
      @PathVariable("taskId") String taskId)
      throws TaskNotFoundException,
          InvalidOwnerException,
          InvalidTaskStateException,
          NotAuthorizedOnWorkbasketException {

    Task updatedTask = taskService.forceCompleteTask(taskId);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint cancels a Task. Cancellation marks a Task as obsolete. The actual work the Task
   * was referring to is no longer required
   *
   * @param taskId Id of the requested Task to cancel.
   * @return the cancelled Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the task is not in state READY or CLAIMED
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Cancel a Task
   */
  @Operation(
      summary = "Cancel a Task",
      description =
          "This endpoint cancels a Task. Cancellation marks a Task as obsolete. The actual work "
              + "the Task was referring to is no longer required",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "Id of the requested Task to cancel.",
            example = "TKI:000000000000000000000000000000000026",
            required = true)
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the cancelled Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_CANCEL)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity cancelTask(@PathVariable("taskId") String taskId)
      throws TaskNotFoundException, NotAuthorizedOnWorkbasketException, InvalidTaskStateException {

    Task cancelledTask = taskService.cancelTask(taskId);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(cancelledTask));
  }

  /**
   * This endpoint terminates a Task. Termination is an administrative action to complete a Task.
   *
   * @param taskId Id of the requested Task to terminate.
   * @return the terminated Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException if the task is not in state READY or CLAIMED
   * @throws NotAuthorizedException if the current user isn't an administrator (ADMIN/TASKADMIN)
   * @throws NotAuthorizedOnWorkbasketException if the current user has not correct permissions
   * @title Terminate a Task
   */
  @Operation(
      summary = "Terminate a Task",
      description =
          "This endpoint terminates a Task. Termination is an administrative action to complete a "
              + "Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "Id of the requested Task to terminate.",
            required = true,
            example = "TKI:000000000000000000000000000000000000")
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the terminated Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_TERMINATE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity terminateTask(
      @PathVariable("taskId") String taskId)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          NotAuthorizedException,
          NotAuthorizedOnWorkbasketException {

    Task terminatedTask = taskService.terminateTask(taskId);

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(terminatedTask));
  }

  /**
   * This endpoint transfers a given Task to a given Workbasket, if possible.
   *
   * @title Transfer a Task to another Workbasket
   * @param taskId the Id of the Task which should be transferred
   * @param workbasketId the Id of the destination Workbasket
   * @param transferTaskRepresentationModel sets the transfer flag of the Task (default: true) and
   *     owner of the task
   * @return the successfully transferred Task.
   * @throws TaskNotFoundException if the requested Task does not exist
   * @throws WorkbasketNotFoundException if the requested Workbasket does not exist
   * @throws NotAuthorizedOnWorkbasketException if the current user has no authorization to transfer
   *     the Task.
   * @throws InvalidTaskStateException if the Task is in a state which does not allow transferring.
   */
  @Operation(
      summary = "Transfer a Task to another Workbasket",
      description = "This endpoint transfers a Task to a given Workbasket, if possible.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task which should be transferred",
            example = "TKI:000000000000000000000000000000000004",
            required = true),
        @Parameter(
            name = "workbasketId",
            description = "the Id of the destination Workbasket",
            example = "WBI:100000000000000000000000000000000001",
            required = true)
      },
      requestBody =
          @io.swagger.v3.oas.annotations.parameters.RequestBody(
              description = "sets the tansfer flag of the task (default: true)",
              content =
                  @Content(
                      schema = @Schema(implementation = TaskRepresentationModel.class),
                      examples = @ExampleObject(value = "{\"setTransferFlag\": false}"))),
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the successfully transferred Task.",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_TRANSFER_WORKBASKET_ID)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity transferTask(
      @PathVariable("taskId") String taskId,
      @PathVariable("workbasketId") String workbasketId,
      @RequestBody(required = false)
          TransferTaskRepresentationModel transferTaskRepresentationModel)
      throws TaskNotFoundException,
          WorkbasketNotFoundException,
          NotAuthorizedOnWorkbasketException,
          InvalidTaskStateException {
    Task updatedTask;
    if (transferTaskRepresentationModel == null) {
      updatedTask = taskService.transfer(taskId, workbasketId);
    } else {
      updatedTask =
          taskService.transferWithOwner(
              taskId,
              workbasketId,
              transferTaskRepresentationModel.getOwner(),
              transferTaskRepresentationModel.getSetTransferFlag());
    }

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  /**
   * This endpoint transfers a list of Tasks listed in the body to a given Workbasket, if possible.
   * Tasks that can be transfered without throwing an exception get transferred independent of other
   * Tasks. If the transfer of a Task throws an exception, then the Task will remain in the old
   * Workbasket.
   *
   * @title Transfer Tasks to another Workbasket
   * @param workbasketId the Id of the destination Workbasket
   * @param transferTaskRepresentationModel JSON formatted request body containing the TaskIds,
   *     owner and setTransferFlag of tasks to be transferred; owner and setTransferFlag are
   *     optional, while the TaskIds are mandatory
   * @return the taskIds and corresponding ErrorCode of tasks failed to be transferred
   * @throws WorkbasketNotFoundException if the requested Workbasket does not exist
   * @throws NotAuthorizedOnWorkbasketException if the current user has no authorization to transfer
   *     the Task
   */
  @PostMapping(path = RestEndpoints.URL_TRANSFER_WORKBASKET_ID)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity transferTasks(
      @PathVariable("workbasketId") String workbasketId,
      @RequestBody TransferTaskRepresentationModel transferTaskRepresentationModel)
      throws NotAuthorizedOnWorkbasketException, WorkbasketNotFoundException {
    List taskIds = transferTaskRepresentationModel.getTaskIds();
    BulkOperationResults result =
        taskService.transferTasksWithOwner(
            workbasketId,
            taskIds,
            transferTaskRepresentationModel.getOwner(),
            transferTaskRepresentationModel.getSetTransferFlag());

    BulkOperationResultsRepresentationModel repModel =
        bulkOperationResultsRepresentationModelAssembler.toModel(result);

    return ResponseEntity.ok(repModel);
  }

  /**
   * This endpoint updates a requested Task.
   *
   * @param taskId the Id of the Task which should be updated
   * @param taskRepresentationModel the new Task for the requested id.
   * @return the updated Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws ClassificationNotFoundException if the updated Classification does not exist.
   * @throws InvalidArgumentException if any semantically invalid parameter was provided
   * @throws ConcurrencyException if the Task has been updated by a different process in the
   *     meantime
   * @throws NotAuthorizedOnWorkbasketException if the current user is not authorized.
   * @throws AttachmentPersistenceException if the modified Task contains two attachments with the
   *     same id.
   * @throws ObjectReferencePersistenceException if the modified Task contains two object references
   *     with the same id.
   * @throws InvalidTaskStateException if an attempt is made to change the owner of the Task and the
   *     Task is not in state READY.
   * @title Update a Task
   */
  @Operation(
      summary = "Update a Task",
      description = "This endpoint updates a requested Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task which should be updated",
            example = "TKI:000000000000000000000000000000000003",
            required = true)
      },
      requestBody =
          @io.swagger.v3.oas.annotations.parameters.RequestBody(
              description = "the new Task for the requested id.",
              content =
                  @Content(
                      schema = @Schema(implementation = TaskRepresentationModel.class),
                      examples =
                          @ExampleObject(
                              value =
                                  "{\n"
                                      + "  \"taskId\": "
                                      + "\"TKI:000000000000000000000000000000000003\",\n"
                                      + "  \"externalId\": "
                                      + "\"ETI:000000000000000000000000000000000003\",\n"
                                      + "  \"created\": \"2018-02-01T12:00:00.000Z\",\n"
                                      + "  \"modified\": \"2018-02-01T12:00:00.000Z\",\n"
                                      + "  \"planned\": \"2024-05-27T15:27:56.595Z\",\n"
                                      + "  \"received\": \"2024-05-29T15:27:56.595Z\",\n"
                                      + "  \"due\": \"2024-05-29T15:27:56.595Z\",\n"
                                      + "  \"name\": \"Widerruf\",\n"
                                      + "  \"creator\": \"creator_user_id\",\n"
                                      + "  \"description\": \"new description\",\n"
                                      + "  \"priority\": 2,\n"
                                      + "  \"manualPriority\": -1,\n"
                                      + "  \"state\": \"READY\",\n"
                                      + "  \"classificationSummary\": {\n"
                                      + "    \"classificationId\": "
                                      + "\"CLI:100000000000000000000000000000000003\",\n"
                                      + "    \"key\": \"L1050\",\n"
                                      + "    \"applicationEntryPoint\": \"\",\n"
                                      + "    \"category\": \"EXTERNAL\",\n"
                                      + "    \"domain\": \"DOMAIN_A\",\n"
                                      + "    \"name\": \"Widerruf\",\n"
                                      + "    \"parentId\": \"\",\n"
                                      + "    \"parentKey\": \"\",\n"
                                      + "    \"priority\": 1,\n"
                                      + "    \"serviceLevel\": \"P13D\",\n"
                                      + "    \"type\": \"TASK\",\n"
                                      + "    \"custom1\": \"VNR,RVNR,KOLVNR\",\n"
                                      + "    \"custom2\": \"\",\n"
                                      + "    \"custom3\": \"\",\n"
                                      + "    \"custom4\": \"\",\n"
                                      + "    \"custom5\": \"\",\n"
                                      + "    \"custom6\": \"\",\n"
                                      + "    \"custom7\": \"\",\n"
                                      + "    \"custom8\": \"\"\n"
                                      + "  },\n"
                                      + "  \"workbasketSummary\": {\n"
                                      + "    \"workbasketId\": "
                                      + "\"WBI:100000000000000000000000000000000001\",\n"
                                      + "    \"key\": \"GPK_KSC\",\n"
                                      + "    \"name\": \"Gruppenpostkorb KSC\",\n"
                                      + "    \"domain\": \"DOMAIN_A\",\n"
                                      + "    \"type\": \"GROUP\",\n"
                                      + "    \"description\": \"Gruppenpostkorb KSC\",\n"
                                      + "    \"owner\": \"teamlead-1\",\n"
                                      + "    \"custom1\": \"ABCQVW\",\n"
                                      + "    \"custom2\": \"\",\n"
                                      + "    \"custom3\": \"xyz4\",\n"
                                      + "    \"custom4\": \"\",\n"
                                      + "    \"custom5\": \"\",\n"
                                      + "    \"custom6\": \"\",\n"
                                      + "    \"custom7\": \"\",\n"
                                      + "    \"custom8\": \"\",\n"
                                      + "    \"orgLevel1\": \"\",\n"
                                      + "    \"orgLevel2\": \"\",\n"
                                      + "    \"orgLevel3\": \"\",\n"
                                      + "    \"orgLevel4\": \"\",\n"
                                      + "    \"markedForDeletion\": false\n"
                                      + "  },\n"
                                      + "  \"businessProcessId\": \"PI_0000000000003\",\n"
                                      + "  \"parentBusinessProcessId\": "
                                      + "\"DOC_0000000000000000003\",\n"
                                      + "  \"primaryObjRef\": {\n"
                                      + "    \"company\": \"00\",\n"
                                      + "    \"system\": \"PASystem\",\n"
                                      + "    \"systemInstance\": \"00\",\n"
                                      + "    \"type\": \"VNR\",\n"
                                      + "    \"value\": \"11223344\"\n"
                                      + "  },\n"
                                      + "  \"custom1\": \"efg\",\n"
                                      + "  \"custom14\": \"abc\",\n"
                                      + "  \"customInt1\": 1,\n"
                                      + "  \"customInt2\": 2,\n"
                                      + "  \"customInt3\": 3,\n"
                                      + "  \"customInt4\": 4,\n"
                                      + "  \"customInt5\": 5,\n"
                                      + "  \"customInt6\": 6,\n"
                                      + "  \"customInt7\": 7,\n"
                                      + "  \"customInt8\": 8,\n"
                                      + "  \"secondaryObjectReferences\": [],\n"
                                      + "  \"customAttributes\": [],\n"
                                      + "  \"callbackInfo\": [],\n"
                                      + "  \"attachments\": [],\n"
                                      + "  \"read\": false,\n"
                                      + "  \"transferred\": false\n"
                                      + "}"))),
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the updated Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PutMapping(path = RestEndpoints.URL_TASKS_ID)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity updateTask(
      @PathVariable("taskId") String taskId,
      @RequestBody TaskRepresentationModel taskRepresentationModel)
      throws TaskNotFoundException,
          ClassificationNotFoundException,
          InvalidArgumentException,
          ConcurrencyException,
          NotAuthorizedOnWorkbasketException,
          AttachmentPersistenceException,
          InvalidTaskStateException,
          ObjectReferencePersistenceException {
    if (!taskId.equals(taskRepresentationModel.getTaskId())) {
      throw new InvalidArgumentException(
          String.format(
              "TaskId ('%s') is not identical with the taskId of to "
                  + "object in the payload which should be updated. ID=('%s')",
              taskId, taskRepresentationModel.getTaskId()));
    }

    if (!taskRepresentationModel.getAttachments().stream()
        .filter(att -> Objects.nonNull(att.getTaskId()))
        .filter(att -> !att.getTaskId().equals(taskRepresentationModel.getTaskId()))
        .toList()
        .isEmpty()) {
      throw new InvalidArgumentException(
          "An attachments' taskId must be empty or equal to the id of the task it belongs to");
    }

    Task task = taskRepresentationModelAssembler.toEntityModel(taskRepresentationModel);
    task = taskService.updateTask(task);
    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
  }

  /**
   * This endpoint sets the 'isRead' property of a Task.
   *
   * @param taskId Id of the requested Task to set read or unread.
   * @param isRead if true, the Task property isRead is set to true, else it's set to false
   * @return the updated Task
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws NotAuthorizedOnWorkbasketException if the current user has no read permission for the
   *     Workbasket the Task is in
   * @title Set a Task read or unread
   */
  @Operation(
      summary = "Set a Task read or unread",
      description = "This endpoint sets the 'isRead' property of a Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "Id of the requested Task to set read or unread.",
            example = "TKI:000000000000000000000000000000000025",
            required = true)
      },
      requestBody =
          @io.swagger.v3.oas.annotations.parameters.RequestBody(
              description =
                  "if true, the Task property isRead is set to true, else it's set to false",
              content =
                  @Content(
                      schema = @Schema(implementation = IsReadRepresentationModel.class),
                      examples = @ExampleObject(value = "{\"is-read\": true}"))),
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the updated Task",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema = @Schema(implementation = TaskRepresentationModel.class)))
      })
  @PostMapping(path = RestEndpoints.URL_TASKS_ID_SET_READ)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity setTaskRead(
      @PathVariable("taskId") String taskId, @RequestBody IsReadRepresentationModel isRead)
      throws TaskNotFoundException, NotAuthorizedOnWorkbasketException {

    Task updatedTask = taskService.setTaskRead(taskId, isRead.getIsRead());

    return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
  }

  // endregion

  // region DELETE

  /**
   * This endpoint deletes a Task.
   *
   * @title Delete a Task
   * @param taskId the Id of the Task which should be deleted.
   * @return the deleted Task.
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException If the Task is not in an END_STATE
   * @throws NotAuthorizedException if the current user isn't an administrator (ADMIN)
   * @throws NotAuthorizedOnWorkbasketException if the current user has not correct permissions
   * @throws InvalidCallbackStateException some comment
   */
  @Operation(
      summary = "Delete a Task",
      description = "This endpoint deletes a Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task which should be deleted.",
            required = true,
            example = "TKI:000000000000000000000000000000000039")
      },
      responses = {@ApiResponse(responseCode = "204", content = @Content(schema = @Schema()))})
  @DeleteMapping(path = RestEndpoints.URL_TASKS_ID)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity deleteTask(@PathVariable("taskId") String taskId)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          NotAuthorizedException,
          NotAuthorizedOnWorkbasketException,
          InvalidCallbackStateException {
    taskService.deleteTask(taskId);

    return ResponseEntity.noContent().build();
  }

  /**
   * This endpoint force deletes a Task even if it's not completed.
   *
   * @title Force delete a Task
   * @param taskId the Id of the Task which should be force deleted.
   * @return the force deleted Task.
   * @throws TaskNotFoundException if the requested Task does not exist.
   * @throws InvalidTaskStateException If the Task is not TERMINATED or CANCELLED and the Callback
   *     state of the Task is CALLBACK_PROCESSING_REQUIRED
   * @throws NotAuthorizedException if the current user isn't an administrator (ADMIN) Task.
   * @throws NotAuthorizedOnWorkbasketException if the current user has not correct
   * @throws InvalidCallbackStateException some comment
   */
  @Operation(
      summary = "Force delete a Task",
      description = "This endpoint force deletes a Task.",
      parameters = {
        @Parameter(
            name = "taskId",
            description = "the Id of the Task which should be force deleted.",
            example = "TKI:000000000000000000000000000000000005",
            required = true)
      },
      responses = {@ApiResponse(responseCode = "204", content = @Content(schema = @Schema()))})
  @DeleteMapping(path = RestEndpoints.URL_TASKS_ID_FORCE)
  @Transactional(rollbackFor = Exception.class)
  public ResponseEntity forceDeleteTask(
      @PathVariable("taskId") String taskId)
      throws TaskNotFoundException,
          InvalidTaskStateException,
          NotAuthorizedException,
          NotAuthorizedOnWorkbasketException,
          InvalidCallbackStateException {
    taskService.forceDeleteTask(taskId);

    return ResponseEntity.noContent().build();
  }

  /**
   * This endpoint deletes an aggregation of Tasks and returns the deleted Tasks. Filters can be
   * applied.
   *
   * @title Delete multiple Tasks
   * @param filterParameter the filter parameters
   * @param filterCustomFields the filter parameters regarding TaskCustomFields
   * @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
   * @return the deleted task summaries
   * @throws InvalidArgumentException TODO: this is never thrown
   * @throws NotAuthorizedException if the current user is not authorized to delete the requested
   *     Tasks.
   */
  @Operation(
      summary = "Delete multiple Tasks",
      description =
          "This endpoint deletes an aggregation of Tasks and returns the deleted Tasks. Filters "
              + "can be applied.",
      parameters = {
        @Parameter(
            name = "task-id",
            examples = {
              @ExampleObject(value = "TKI:000000000000000000000000000000000036"),
              @ExampleObject(value = "TKI:000000000000000000000000000000000037"),
              @ExampleObject(value = "TKI:000000000000000000000000000000000038")
            })
      },
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "the deleted task summaries",
            content =
                @Content(
                    mediaType = MediaTypes.HAL_JSON_VALUE,
                    schema =
                        @Schema(implementation = TaskSummaryCollectionRepresentationModel.class)))
      })
  @DeleteMapping(path = RestEndpoints.URL_TASKS)
  @Transactional(readOnly = true, rollbackFor = Exception.class)
  public ResponseEntity deleteTasks(
      @ParameterObject TaskQueryFilterParameter filterParameter,
      @ParameterObject TaskQueryFilterCustomFields filterCustomFields,
      @ParameterObject TaskQueryFilterCustomIntFields filterCustomIntFields)
      throws InvalidArgumentException, NotAuthorizedException {
    TaskQuery query = taskService.createTaskQuery();
    filterParameter.apply(query);
    filterCustomFields.apply(query);
    filterCustomIntFields.apply(query);

    List taskSummaries = query.list();

    List taskIdsToDelete = taskSummaries.stream().map(TaskSummary::getId).toList();

    BulkOperationResults result =
        taskService.deleteTasks(taskIdsToDelete);

    Set failedIds = new HashSet<>(result.getFailedIds());
    List successfullyDeletedTaskSummaries =
        taskSummaries.stream().filter(not(summary -> failedIds.contains(summary.getId()))).toList();

    return ResponseEntity.ok(
        taskSummaryRepresentationModelAssembler.toTaskanaCollectionModel(
            successfullyDeletedTaskSummaries));
  }

  // endregion

  // region TaskQuery

  public enum TaskQuerySortBy implements QuerySortBy {
    CLASSIFICATION_KEY(TaskQuery::orderByClassificationKey),
    CLASSIFICATION_NAME(TaskQuery::orderByClassificationName),
    POR_TYPE(TaskQuery::orderByPrimaryObjectReferenceType),
    POR_VALUE(TaskQuery::orderByPrimaryObjectReferenceValue),
    POR_COMPANY(TaskQuery::orderByPrimaryObjectReferenceCompany),
    POR_SYSTEM(TaskQuery::orderByPrimaryObjectReferenceSystem),
    POR_SYSTEM_INSTANCE(TaskQuery::orderByPrimaryObjectReferenceSystemInstance),
    STATE(TaskQuery::orderByState),
    NAME(TaskQuery::orderByName),
    DUE(TaskQuery::orderByDue),
    PLANNED(TaskQuery::orderByPlanned),
    RECEIVED(TaskQuery::orderByReceived),
    PRIORITY(TaskQuery::orderByPriority),
    CREATED(TaskQuery::orderByCreated),
    CLAIMED(TaskQuery::orderByClaimed),
    DOMAIN(TaskQuery::orderByDomain),
    TASK_ID(TaskQuery::orderByTaskId),
    MODIFIED(TaskQuery::orderByModified),
    CREATOR(TaskQuery::orderByCreator),
    NOTE(TaskQuery::orderByNote),
    OWNER(TaskQuery::orderByOwner),
    OWNER_LONG_NAME(TaskQuery::orderByOwnerLongName),
    BUSINESS_PROCESS_ID(TaskQuery::orderByBusinessProcessId),
    PARENT_BUSINESS_PROCESS_ID(TaskQuery::orderByParentBusinessProcessId),
    WORKBASKET_KEY(TaskQuery::orderByWorkbasketKey),
    CUSTOM_1((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_1, sort)),
    CUSTOM_2((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_2, sort)),
    CUSTOM_3((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_3, sort)),
    CUSTOM_4((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_4, sort)),
    CUSTOM_5((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_5, sort)),
    CUSTOM_6((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_6, sort)),
    CUSTOM_7((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_7, sort)),
    CUSTOM_8((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_8, sort)),
    CUSTOM_9((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_9, sort)),
    CUSTOM_10((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_10, sort)),
    CUSTOM_11((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_11, sort)),
    CUSTOM_12((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_12, sort)),
    CUSTOM_13((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_13, sort)),
    CUSTOM_14((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_14, sort)),
    CUSTOM_15((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_15, sort)),
    CUSTOM_16((query, sort) -> query.orderByCustomAttribute(TaskCustomField.CUSTOM_16, sort)),
    WORKBASKET_ID(TaskQuery::orderByWorkbasketId),
    WORKBASKET_NAME(TaskQuery::orderByWorkbasketName),
    ATTACHMENT_CLASSIFICATION_KEY(TaskQuery::orderByAttachmentClassificationKey),
    ATTACHMENT_CLASSIFICATION_NAME(TaskQuery::orderByAttachmentClassificationName),
    ATTACHMENT_CLASSIFICATION_ID(TaskQuery::orderByAttachmentClassificationId),
    ATTACHMENT_CHANNEL(TaskQuery::orderByAttachmentChannel),
    ATTACHMENT_REFERENCE(TaskQuery::orderByAttachmentReference),
    ATTACHMENT_RECEIVED(TaskQuery::orderByAttachmentReceived),
    COMPLETED(TaskQuery::orderByCompleted);

    private final BiConsumer consumer;

    TaskQuerySortBy(BiConsumer consumer) {
      this.consumer = consumer;
    }

    @Override
    public void applySortByForQuery(TaskQuery query, SortDirection sortDirection) {
      consumer.accept(query, sortDirection);
    }
  }

  // Unfortunately this class is necessary, since spring can not inject the generic 'sort-by'
  // parameter from the super class.
  public static class TaskQuerySortParameter
      extends QuerySortParameter {

    @ConstructorProperties({"sort-by", "order"})
    public TaskQuerySortParameter(List sortBy, List order)
        throws InvalidArgumentException {
      super(sortBy, order);
    }

    // this getter is necessary for the documentation!
    @Override
    public List getSortBy() {
      return super.getSortBy();
    }
  }

  // endregion

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy