unit Api.ServiceImpl;

interface

uses
  XData.Server.Module, XData.Service.Common,
  System.Variants, System.DateUtils, Uni,
  Api.Service, Api.Database, Common.Logging,
  System.SysUtils, JSON, System.IniFiles, DBAccess;

type
  [ServiceImplementation]
  TApiService = class(TInterfacedObject, IApiService)
  strict private
    apiDB: TApiDatabase;
  private
    function BuildTaskNumber: string;
    function BuildTaskTitle(const taskNumber, projectName, subject: string): string;
    function FindAssignedOptionId(const taskId, name: string): string;
    function FindAssignedOptionName(const taskId, name: string): string;
    function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
    function BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
    function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
    function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
    function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
    procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
    function DeleteTaskRow(const taskId, taskItemId: Integer): Boolean;
    function FindProjectId(const taskId: string): string;
    function FindApplicationId(const taskId, name: string): Integer;
    function FindApplicationName(const taskId, name: string): string;
    function BuildApplicationOptionsResponse(const taskId: string): TTaskApplicationOptionsResponse;
    function AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
    function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
    function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
    function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
    function FindReportedOptionId(const taskId, name: string): string;
    function FindReportedOptionName(const taskId, name: string): string;
    function BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
    function AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
    function RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
    function DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    function GetTaskItems(taskId: string): TTaskItemsResponse;
  end;

implementation

procedure TApiService.AfterConstruction;
begin
  inherited;
  apiDB := TApiDatabase.Create(nil);
  Logger.Log(4, 'ApiService.AfterConstruction - apiDB created');
end;

procedure TApiService.BeforeDestruction;
begin
  Logger.Log(4, 'ApiService.BeforeDestruction - freeing apiDB');
  apiDB.Free;
  inherited;
end;


function TApiService.GetTaskItems(taskId: string): TTaskItemsResponse;
var
  taskHeader: TTaskHeader;
  taskUserOption: TTaskUserOption;
  taskItemCode: TTaskItemCode;
  taskApplicationOption: TTaskApplicationOption;
  item: TTaskItem;
begin
  Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));

  Result := TTaskItemsResponse.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  try
    apiDB.uqTaskHeader.Close;
    apiDB.uqTaskHeader.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqTaskHeader.Open;

    if apiDB.uqTaskHeader.IsEmpty then
    begin
      Logger.Log(2, Format('ApiService.GetTaskItems - no task header found for TASK_ID="%s"', [taskId]));
      Result.count := 0;
      Exit;
    end;

    taskHeader := TTaskHeader.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskHeader);

    taskHeader.taskId := apiDB.uqTaskHeaderTASK_ID.AsString;
    taskHeader.parentId := apiDB.uqTaskHeaderPARENT_ID.AsString;
    taskHeader.taskNumber := BuildTaskNumber;
    taskHeader.projectName := apiDB.uqTaskHeaderPROJECT_NAME.AsString;
    taskHeader.subject := apiDB.uqTaskHeaderSUBJECT.AsString;
    taskHeader.title := BuildTaskTitle(
      taskHeader.taskNumber,
      taskHeader.projectName,
      taskHeader.subject
    );

    Result.task := taskHeader;

    apiDB.uqProjectApplications.Close;
    apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqProjectApplications.Open;

    while not apiDB.uqProjectApplications.Eof do
    begin
      taskApplicationOption := TTaskApplicationOption.Create;
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskApplicationOption);

      taskApplicationOption.name := apiDB.uqProjectApplicationsNAME.AsString;
      Result.applicationOptions.Add(taskApplicationOption);

      apiDB.uqProjectApplications.Next;
    end;

    apiDB.uqProjectReportedUsers.Close;
    apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqProjectReportedUsers.Open;

    while not apiDB.uqProjectReportedUsers.Eof do
    begin
      taskUserOption := TTaskUserOption.Create;
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);

      taskUserOption.name := apiDB.uqProjectReportedUsersNAME.AsString;
      Result.reportedByOptions.Add(taskUserOption);

      apiDB.uqProjectReportedUsers.Next;
    end;

    apiDB.uqTaskAssignedUsers.Close;
    apiDB.uqTaskAssignedUsers.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqTaskAssignedUsers.Open;

    while not apiDB.uqTaskAssignedUsers.Eof do
    begin
      taskUserOption := TTaskUserOption.Create;
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);

      taskUserOption.name := apiDB.uqTaskAssignedUsersNAME.AsString;
      Result.assignedToOptions.Add(taskUserOption);

      apiDB.uqTaskAssignedUsers.Next;
    end;

    apiDB.uqTaskItemCodes.Close;
    apiDB.uqTaskItemCodes.Open;

    while not apiDB.uqTaskItemCodes.Eof do
    begin
      taskItemCode := TTaskItemCode.Create;
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskItemCode);

      taskItemCode.code := apiDB.uqTaskItemCodesCODE.AsString;
      taskItemCode.codeDesc := apiDB.uqTaskItemCodesCODE_DESC.AsString;
      taskItemCode.codeType := apiDB.uqTaskItemCodesCODE_TYPE.AsString;

      Result.statusOptions.Add(taskItemCode);

      apiDB.uqTaskItemCodes.Next;
    end;

    apiDB.uqTaskItems.Close;
    apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqTaskItems.Open;

    while not apiDB.uqTaskItems.Eof do
    begin
      item := TTaskItem.Create;
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);

      item.taskItemId := apiDB.uqTaskItemsTASK_ITEM_ID.AsInteger;
      item.taskId := apiDB.uqTaskItemsTASK_ID.AsString;
      item.itemNum := apiDB.uqTaskItemsITEM_NUM.AsInteger;
      item.application := apiDB.uqTaskItemsAPPLICATION.AsString;
      item.version := apiDB.uqTaskItemsAPP_VERSION.AsString;

      if apiDB.uqTaskItemsTASK_DATE.IsNull then
        item.taskDate := 0
      else
        item.taskDate := apiDB.uqTaskItemsTASK_DATE.AsDateTime;

      item.reportedBy := apiDB.uqTaskItemsREPORTED_BY.AsString;
      item.assignedTo := apiDB.uqTaskItemsASSIGNED_TO.AsString;
      item.status := apiDB.uqTaskItemsSTATUS.AsString;

      if apiDB.uqTaskItemsSTATUS_DATE.IsNull then
        item.statusDate := 0
      else
        item.statusDate := apiDB.uqTaskItemsSTATUS_DATE.AsDateTime;

      item.fixedVersion := apiDB.uqTaskItemsFIXED_VERSION.AsString;
      item.formSection := apiDB.uqTaskItemsFORM_SECTION.AsString;
      item.issue := apiDB.uqTaskItemsISSUE.AsString;
      item.notes := apiDB.uqTaskItemsNOTES.AsString;

      Result.items.Add(item);

      apiDB.uqTaskItems.Next;
    end;

    Result.count := Result.items.Count;
    Logger.Log(4, Format('ApiService.GetTaskItems - returned %d item(s)', [Result.count]));
  except
    on E: Exception do
    begin
      Logger.Log(2, 'ApiService.GetTaskItems - ERROR: ' + E.Message);
      raise;
    end;
  end;
end;


function TApiService.AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
var
  newItemNum: Integer;
  maxItemNum: Integer;
begin
  Logger.Log(4, Format('ApiService.AddTaskRow - TASK_ID="%s" INSERT_AFTER_ITEM_NUM="%d"', [taskId, insertAfterItemNum]));

  apiDB.uqGetTaskMaxItemNum.Close;
  apiDB.uqGetTaskMaxItemNum.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqGetTaskMaxItemNum.Open;
  maxItemNum := apiDB.uqGetTaskMaxItemNum.FieldByName('MAX_ITEM_NUM').AsInteger;

  if insertAfterItemNum < 0 then
    insertAfterItemNum := 0;

  if insertAfterItemNum > maxItemNum then
    insertAfterItemNum := maxItemNum;

  if insertAfterItemNum = 0 then
    newItemNum := maxItemNum + 1
  else
    newItemNum := insertAfterItemNum + 1;

  apiDB.uqAddTaskRow.Connection.StartTransaction;
  try
    if insertAfterItemNum > 0 then
    begin
      apiDB.uqShiftTaskRowsForInsert.Close;
      apiDB.uqShiftTaskRowsForInsert.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqShiftTaskRowsForInsert.ParamByName('INSERT_AFTER_ITEM_NUM').AsInteger := insertAfterItemNum;
      apiDB.uqShiftTaskRowsForInsert.ExecSQL;
    end;

    apiDB.uqAddTaskRow.Close;
    apiDB.uqAddTaskRow.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqAddTaskRow.ParamByName('ITEM_NUM').AsInteger := newItemNum;
    apiDB.uqAddTaskRow.ExecSQL;

    apiDB.uqAddTaskRow.Connection.Commit;

    Result := True;
    Logger.Log(4, Format('ApiService.AddTaskRow - OK TASK_ID="%s" ITEM_NUM="%d"', [taskId, newItemNum]));
  except
    on E: Exception do
    begin
      if apiDB.uqAddTaskRow.Connection.InTransaction then
        apiDB.uqAddTaskRow.Connection.Rollback;

      Logger.Log(2, 'ApiService.AddTaskRow - ERROR: ' + E.Message);
      raise;
    end;
  end;
end;


procedure TApiService.MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
var
  oldItemNum: Integer;
  maxItemNum: Integer;
  finalItemNum: Integer;
  tempItemNum: Integer;
begin
  logger.log(4, 'MoveTaskRow start TASK_ID=' + IntToStr(taskId) +
    ' TASK_ITEM_ID=' + IntToStr(taskItemId) +
    ' NEW_ITEM_NUM=' + IntToStr(newItemNum));

  apiDB.uqGetTaskRowPosition.Close;
  apiDB.uqGetTaskRowPosition.ParamByName('TASK_ID').AsInteger := taskId;
  apiDB.uqGetTaskRowPosition.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
  apiDB.uqGetTaskRowPosition.Open;

  if apiDB.uqGetTaskRowPosition.IsEmpty then
  begin
    logger.log(2, 'MoveTaskRow row not found TASK_ID=' + IntToStr(taskId) +
      ' TASK_ITEM_ID=' + IntToStr(taskItemId));
    raise Exception.Create('Task row not found.');
  end;

  oldItemNum := apiDB.uqGetTaskRowPositionITEM_NUM.AsInteger;

  apiDB.uqGetTaskMaxItemNum.Close;
  apiDB.uqGetTaskMaxItemNum.ParamByName('TASK_ID').AsInteger := taskId;
  apiDB.uqGetTaskMaxItemNum.Open;

  maxItemNum := apiDB.uqGetTaskMaxItemNum.FieldByName('MAX_ITEM_NUM').AsInteger;

  finalItemNum := newItemNum;

  if finalItemNum < 1 then
    finalItemNum := 1;

  if finalItemNum > maxItemNum then
    finalItemNum := maxItemNum;

  if finalItemNum = oldItemNum then
  begin
    logger.log(4, 'MoveTaskRow no move needed TASK_ID=' + IntToStr(taskId) +
      ' TASK_ITEM_ID=' + IntToStr(taskItemId) +
      ' ITEM_NUM=' + IntToStr(oldItemNum));
    Exit;
  end;

  tempItemNum := -1;

  apiDB.uqGetTaskRowPosition.Connection.StartTransaction;
  try
    apiDB.uqMoveTaskRowTemp.ParamByName('TEMP_ITEM_NUM').AsInteger := tempItemNum;
    apiDB.uqMoveTaskRowTemp.ParamByName('TASK_ID').AsInteger := taskId;
    apiDB.uqMoveTaskRowTemp.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
    apiDB.uqMoveTaskRowTemp.ExecSQL;

    if finalItemNum < oldItemNum then
    begin
      apiDB.uqShiftTaskRowsDown.ParamByName('TASK_ID').AsInteger := taskId;
      apiDB.uqShiftTaskRowsDown.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
      apiDB.uqShiftTaskRowsDown.ParamByName('OLD_ITEM_NUM').AsInteger := oldItemNum;
      apiDB.uqShiftTaskRowsDown.ExecSQL;
    end
    else
    begin
      apiDB.uqShiftTaskRowsUp.ParamByName('TASK_ID').AsInteger := taskId;
      apiDB.uqShiftTaskRowsUp.ParamByName('OLD_ITEM_NUM').AsInteger := oldItemNum;
      apiDB.uqShiftTaskRowsUp.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
      apiDB.uqShiftTaskRowsUp.ExecSQL;
    end;

    apiDB.uqSetTaskRowFinalPosition.ParamByName('TASK_ID').AsInteger := taskId;
    apiDB.uqSetTaskRowFinalPosition.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
    apiDB.uqSetTaskRowFinalPosition.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
    apiDB.uqSetTaskRowFinalPosition.ExecSQL;

    apiDB.uqGetTaskRowPosition.Connection.Commit;

    logger.log(4, 'MoveTaskRow success TASK_ID=' + IntToStr(taskId) +
      ' TASK_ITEM_ID=' + IntToStr(taskItemId) +
      ' OLD_ITEM_NUM=' + IntToStr(oldItemNum) +
      ' NEW_ITEM_NUM=' + IntToStr(finalItemNum));
  except
    on E: Exception do
    begin
      if apiDB.uqGetTaskRowPosition.Connection.InTransaction then
        apiDB.uqGetTaskRowPosition.Connection.Rollback;

      logger.log(2, 'MoveTaskRow failed TASK_ID=' + IntToStr(taskId) +
        ' TASK_ITEM_ID=' + IntToStr(taskItemId) +
        ' ERROR=' + E.Message);
      raise;
    end;
  end;
end;


function TApiService.SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
var
  uqSaveField: TUniQuery;
  columnName: string;
  d: TDateTime;

  function MapFieldNameToColumn(const AFieldName: string): string;
  begin
    Result := '';

    if SameText(AFieldName, 'application') then
      Result := 'APPLICATION'
    else if SameText(AFieldName, 'version') then
      Result := 'APP_VERSION'
    else if SameText(AFieldName, 'taskDate') then
      Result := 'TASK_DATE'
    else if SameText(AFieldName, 'reportedBy') then
      Result := 'REPORTED_BY'
    else if SameText(AFieldName, 'assignedTo') then
      Result := 'ASSIGNED_TO'
    else if SameText(AFieldName, 'status') then
      Result := 'STATUS'
    else if SameText(AFieldName, 'statusDate') then
      Result := 'STATUS_DATE'
    else if SameText(AFieldName, 'formSection') then
      Result := 'FORM_SECTION'
    else if SameText(AFieldName, 'issue') then
      Result := 'ISSUE'
    else if SameText(AFieldName, 'notes') then
      Result := 'NOTES'
    else if SameText(AFieldName, 'fixedVersion') then
      Result := 'FIXED_VERSION';
  end;

  function IsDateField(const AFieldName: string): Boolean;
  begin
    Result := SameText(AFieldName, 'taskDate') or SameText(AFieldName, 'statusDate');
  end;

  function ParseDateOrZero(const S: string; out ADate: TDateTime): Boolean;
  var
    y, m, dInt: Word;
  begin
    Result := False;
    ADate := 0;

    if Length(Trim(S)) <> 10 then
      Exit;

    y := StrToIntDef(Copy(S, 1, 4), 0);
    m := StrToIntDef(Copy(S, 6, 2), 0);
    dInt := StrToIntDef(Copy(S, 9, 2), 0);

    if (y > 0) and (m > 0) and (dInt > 0) then
    begin
      try
        ADate := EncodeDate(y, m, dInt);
        Result := True;
      except
        Result := False;
      end;
    end;
  end;

begin
  Result := False;

  if not Assigned(Item) then
    raise Exception.Create('SaveTaskItemField: Item is nil.');

  if Item.taskItemId <= 0 then
    raise Exception.Create('SaveTaskItemField: Invalid taskItemId.');

  columnName := MapFieldNameToColumn(Item.fieldName);
  if columnName = '' then
    raise Exception.Create('SaveTaskItemField: Invalid field name: ' + Item.fieldName);

  uqSaveField := TUniQuery.Create(nil);
  try
    uqSaveField.Connection := apiDB.ucETaskApi;
    uqSaveField.SQL.Text :=
      'update task_items ' +
      'set ' + columnName + ' = :' + columnName + ' ' +
      'where TASK_ITEM_ID = :TASK_ITEM_ID';

    uqSaveField.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;

    if IsDateField(Item.fieldName) then
    begin
      if ParseDateOrZero(Item.value, d) then
        uqSaveField.ParamByName(columnName).AsDateTime := d
      else
        uqSaveField.ParamByName(columnName).Clear;
    end
    else
    begin
      if Trim(Item.value) = '' then
        uqSaveField.ParamByName(columnName).Clear
      else
        uqSaveField.ParamByName(columnName).AsString := Item.value;
    end;

    uqSaveField.ExecSQL;
    Result := True;
  finally
    uqSaveField.Free;
  end;
end;


function TApiService.BuildTaskNumber: string;

  procedure AddPart(const value: string);
  var
    s: string;
  begin
    s := Trim(value);
    if s = '' then
      Exit;

    if Result = '' then
      Result := s
    else
      Result := Result + '.' + s;
  end;

begin
  Result := '';

  AddPart(apiDB.uqTaskHeaderTASK_NUM_1.AsString);
  AddPart(apiDB.uqTaskHeaderTASK_NUM_2.AsString);
  AddPart(apiDB.uqTaskHeaderTASK_NUM_3.AsString);
  AddPart(apiDB.uqTaskHeaderTASK_NUM_4.AsString);
  AddPart(apiDB.uqTaskHeaderTASK_NUM_5.AsString);
  AddPart(apiDB.uqTaskHeaderTASK_NUM_6.AsString);
end;


function TApiService.BuildTaskTitle(const taskNumber, projectName, subject: string): string;
begin
  Result := 'Task';

  if Trim(taskNumber) <> '' then
    Result := Result + ' - ' + Trim(taskNumber);

  if Trim(projectName) <> '' then
    Result := Result + ' | ' + Trim(projectName);

  if Trim(subject) <> '' then
    Result := Result + ' | ' + Trim(subject);
end;


function TApiService.DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
var
  oldItemNum: Integer;
begin
  Logger.Log(4, Format('ApiService.DeleteTaskRow - TASK_ID="%d" TASK_ITEM_ID="%d"', [taskId, taskItemId]));

  apiDB.uqGetTaskRowPosition.Close;
  apiDB.uqGetTaskRowPosition.ParamByName('TASK_ID').AsInteger := taskId;
  apiDB.uqGetTaskRowPosition.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
  apiDB.uqGetTaskRowPosition.Open;

  if apiDB.uqGetTaskRowPosition.IsEmpty then
  begin
    Logger.Log(2, Format('ApiService.DeleteTaskRow - row not found TASK_ID="%d" TASK_ITEM_ID="%d"', [taskId, taskItemId]));
    raise Exception.Create('Task row not found.');
  end;

  oldItemNum := apiDB.uqGetTaskRowPositionITEM_NUM.AsInteger;

  apiDB.uqGetTaskRowPosition.Connection.StartTransaction;
  try
    apiDB.uqDeleteTaskRow.ParamByName('TASK_ID').AsInteger := taskId;
    apiDB.uqDeleteTaskRow.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
    apiDB.uqDeleteTaskRow.ExecSQL;

    apiDB.uqShiftTaskRowsAfterDelete.ParamByName('TASK_ID').AsInteger := taskId;
    apiDB.uqShiftTaskRowsAfterDelete.ParamByName('OLD_ITEM_NUM').AsInteger := oldItemNum;
    apiDB.uqShiftTaskRowsAfterDelete.ExecSQL;

    apiDB.uqGetTaskRowPosition.Connection.Commit;

    Result := True;
    Logger.Log(4, Format('ApiService.DeleteTaskRow - OK TASK_ID="%d" TASK_ITEM_ID="%d" OLD_ITEM_NUM="%d"', [taskId, taskItemId, oldItemNum]));
  except
    on E: Exception do
    begin
      if apiDB.uqGetTaskRowPosition.Connection.InTransaction then
        apiDB.uqGetTaskRowPosition.Connection.Rollback;

      Logger.Log(2, Format('ApiService.DeleteTaskRow - ERROR TASK_ID="%d" TASK_ITEM_ID="%d" MSG="%s"', [taskId, taskItemId, E.Message]));
      raise;
    end;
  end;
end;


 function TApiService.FindAssignedOptionId(const taskId, name: string): string;
begin
  Result := '';

  apiDB.uqTaskAssignedUsers.Close;
  apiDB.uqTaskAssignedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqTaskAssignedUsers.Open;

  while not apiDB.uqTaskAssignedUsers.Eof do
  begin
    if SameText(Trim(apiDB.uqTaskAssignedUsersNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqTaskAssignedUsersTASK_ITEM_USER_ID.AsString;
      Exit;
    end;

    apiDB.uqTaskAssignedUsers.Next;
  end;
end;

function TApiService.FindAssignedOptionName(const taskId, name: string): string;
begin
  Result := '';

  apiDB.uqTaskAssignedUsers.Close;
  apiDB.uqTaskAssignedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqTaskAssignedUsers.Open;

  while not apiDB.uqTaskAssignedUsers.Eof do
  begin
    if SameText(Trim(apiDB.uqTaskAssignedUsersNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqTaskAssignedUsersNAME.AsString;
      Exit;
    end;

    apiDB.uqTaskAssignedUsers.Next;
  end;
end;


function TApiService.AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
  newName: string;
  existingName: string;
  newGuid: TGuid;
begin
  newName := Trim(name);
  if newName = '' then
    raise Exception.Create('Assigned name cannot be blank.');

  existingName := FindAssignedOptionName(taskId, newName);
  if existingName = '' then
  begin
    CreateGUID(newGuid);

    apiDB.uqAssignedInsert.Connection.StartTransaction;
    try
      apiDB.uqAssignedInsert.Close;
      apiDB.uqAssignedInsert.ParamByName('TASK_ITEM_USER_ID').AsString := GuidToString(newGuid);
      apiDB.uqAssignedInsert.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqAssignedInsert.ParamByName('NAME').AsString := newName;
      apiDB.uqAssignedInsert.ExecSQL;

      apiDB.uqAssignedInsert.Connection.Commit;
    except
      on E: Exception do
      begin
        if apiDB.uqAssignedInsert.Connection.InTransaction then
          apiDB.uqAssignedInsert.Connection.Rollback;
        raise;
      end;
    end;
  end;

  Result := BuildAssignedOptionsResponse(taskId);
end;


function TApiService.RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
var
  oldAssignedId: string;
  existingAssignedId: string;
  trimmedOldName: string;
  trimmedNewName: string;
begin
  trimmedOldName := Trim(oldName);
  trimmedNewName := Trim(newName);

  if trimmedOldName = '' then
    raise Exception.Create('Old assigned name cannot be blank.');

  if trimmedNewName = '' then
    raise Exception.Create('New assigned name cannot be blank.');

  oldAssignedId := FindAssignedOptionId(taskId, trimmedOldName);
  if oldAssignedId = '' then
    raise Exception.Create('Assigned name not found.');

  existingAssignedId := FindAssignedOptionId(taskId, trimmedNewName);

  apiDB.uqAssignedRename.Connection.StartTransaction;
  try
    apiDB.uqRenameAssignedTo.Close;
    apiDB.uqRenameAssignedTo.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqRenameAssignedTo.ParamByName('OLD_NAME').AsString := trimmedOldName;
    apiDB.uqRenameAssignedTo.ParamByName('NEW_NAME').AsString := trimmedNewName;
    apiDB.uqRenameAssignedTo.ExecSQL;

    if (existingAssignedId <> '') and (not SameText(existingAssignedId, oldAssignedId)) then
    begin
      apiDB.uqAssignedDelete.Close;
      apiDB.uqAssignedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := oldAssignedId;
      apiDB.uqAssignedDelete.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqAssignedDelete.ExecSQL;
    end
    else
    begin
      apiDB.uqAssignedRename.Close;
      apiDB.uqAssignedRename.ParamByName('TASK_ITEM_USER_ID').AsString := oldAssignedId;
      apiDB.uqAssignedRename.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqAssignedRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
      apiDB.uqAssignedRename.ExecSQL;
    end;

    apiDB.uqAssignedRename.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqAssignedRename.Connection.InTransaction then
        apiDB.uqAssignedRename.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildAssignedOptionsResponse(taskId);
end;


function TApiService.DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
  assignedId: string;
  assignedName: string;
begin
  assignedId := FindAssignedOptionId(taskId, name);
  assignedName := FindAssignedOptionName(taskId, name);

  if assignedId = '' then
    raise Exception.Create('Assigned name not found.');

  apiDB.uqAssignedDelete.Connection.StartTransaction;
  try
    apiDB.uqBlankAssignedTo.Close;
    apiDB.uqBlankAssignedTo.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqBlankAssignedTo.ParamByName('NAME').AsString := assignedName;
    apiDB.uqBlankAssignedTo.ExecSQL;

    apiDB.uqAssignedDelete.Close;
    apiDB.uqAssignedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := assignedId;
    apiDB.uqAssignedDelete.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqAssignedDelete.ExecSQL;

    apiDB.uqAssignedDelete.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqAssignedDelete.Connection.InTransaction then
        apiDB.uqAssignedDelete.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildAssignedOptionsResponse(taskId);
end;


function TApiService.BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
var
  taskUserOption: TTaskUserOption;
begin
  Result := TTaskUserOptionsResponse.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  apiDB.uqTaskAssignedUsers.Close;
  apiDB.uqTaskAssignedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqTaskAssignedUsers.Open;

  while not apiDB.uqTaskAssignedUsers.Eof do
  begin
    taskUserOption := TTaskUserOption.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);

    taskUserOption.name := apiDB.uqTaskAssignedUsersNAME.AsString;
    Result.assignedToOptions.Add(taskUserOption);

    apiDB.uqTaskAssignedUsers.Next;
  end;
end;


function TApiService.FindProjectId(const taskId: string): string;
begin
  Result := '';

  apiDB.uqTaskHeader.Close;
  apiDB.uqTaskHeader.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqTaskHeader.Open;

  if not apiDB.uqTaskHeader.IsEmpty then
    Result := apiDB.uqTaskHeaderPROJECT_ID.AsString;
end;


function TApiService.FindApplicationId(const taskId, name: string): Integer;
begin
  Result := 0;

  apiDB.uqProjectApplications.Close;
  apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectApplications.Open;

  while not apiDB.uqProjectApplications.Eof do
  begin
    if SameText(Trim(apiDB.uqProjectApplicationsNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqProjectApplicationsTASK_ITEM_APPLICATION_ID.AsInteger;
      Exit;
    end;

    apiDB.uqProjectApplications.Next;
  end;
end;

function TApiService.FindApplicationName(const taskId, name: string): string;
begin
  Result := '';

  apiDB.uqProjectApplications.Close;
  apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectApplications.Open;

  while not apiDB.uqProjectApplications.Eof do
  begin
    if SameText(Trim(apiDB.uqProjectApplicationsNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqProjectApplicationsNAME.AsString;
      Exit;
    end;

    apiDB.uqProjectApplications.Next;
  end;
end;


function TApiService.BuildApplicationOptionsResponse(const taskId: string): TTaskApplicationOptionsResponse;
var
  taskApplicationOption: TTaskApplicationOption;
begin
  Result := TTaskApplicationOptionsResponse.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  apiDB.uqProjectApplications.Close;
  apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectApplications.Open;

  while not apiDB.uqProjectApplications.Eof do
  begin
    taskApplicationOption := TTaskApplicationOption.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskApplicationOption);

    taskApplicationOption.name := apiDB.uqProjectApplicationsNAME.AsString;
    Result.applicationOptions.Add(taskApplicationOption);

    apiDB.uqProjectApplications.Next;
  end;
end;


function TApiService.AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
var
  projectId: string;
  newName: string;
  existingName: string;
begin
  newName := Trim(name);
  if newName = '' then
    raise Exception.Create('Application name cannot be blank.');

  projectId := FindProjectId(taskId);
  if projectId = '' then
    raise Exception.Create('Project not found for task.');

  existingName := FindApplicationName(taskId, newName);
  if existingName = '' then
  begin
    apiDB.uqApplicationInsert.Connection.StartTransaction;
    try
      apiDB.uqApplicationInsert.Close;
      apiDB.uqApplicationInsert.ParamByName('PROJECT_ID').AsString := projectId;
      apiDB.uqApplicationInsert.ParamByName('NAME').AsString := newName;
      apiDB.uqApplicationInsert.ExecSQL;

      apiDB.uqApplicationInsert.Connection.Commit;
    except
      on E: Exception do
      begin
        if apiDB.uqApplicationInsert.Connection.InTransaction then
          apiDB.uqApplicationInsert.Connection.Rollback;
        raise;
      end;
    end;
  end;

  Result := BuildApplicationOptionsResponse(taskId);
end;


 function TApiService.RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
var
  projectId: string;
  oldApplicationId: Integer;
  existingApplicationId: Integer;
  trimmedOldName: string;
  trimmedNewName: string;
begin
  trimmedOldName := Trim(oldName);
  trimmedNewName := Trim(newName);

  if trimmedOldName = '' then
    raise Exception.Create('Old application name cannot be blank.');

  if trimmedNewName = '' then
    raise Exception.Create('New application name cannot be blank.');

  projectId := FindProjectId(taskId);
  if projectId = '' then
    raise Exception.Create('Project not found for task.');

  oldApplicationId := FindApplicationId(taskId, trimmedOldName);
  if oldApplicationId = 0 then
    raise Exception.Create('Application name not found.');

  existingApplicationId := FindApplicationId(taskId, trimmedNewName);

  apiDB.uqApplicationRename.Connection.StartTransaction;
  try
    apiDB.uqRenameTaskItemApplication.Close;
    apiDB.uqRenameTaskItemApplication.ParamByName('PROJECT_ID').AsString := projectId;
    apiDB.uqRenameTaskItemApplication.ParamByName('OLD_NAME').AsString := trimmedOldName;
    apiDB.uqRenameTaskItemApplication.ParamByName('NEW_NAME').AsString := trimmedNewName;
    apiDB.uqRenameTaskItemApplication.ExecSQL;

    if (existingApplicationId <> 0) and (existingApplicationId <> oldApplicationId) then
    begin
      apiDB.uqApplicationDelete.Close;
      apiDB.uqApplicationDelete.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := oldApplicationId;
      apiDB.uqApplicationDelete.ParamByName('PROJECT_ID').AsString := projectId;
      apiDB.uqApplicationDelete.ExecSQL;
    end
    else
    begin
      apiDB.uqApplicationRename.Close;
      apiDB.uqApplicationRename.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := oldApplicationId;
      apiDB.uqApplicationRename.ParamByName('PROJECT_ID').AsString := projectId;
      apiDB.uqApplicationRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
      apiDB.uqApplicationRename.ExecSQL;
    end;

    apiDB.uqApplicationRename.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqApplicationRename.Connection.InTransaction then
        apiDB.uqApplicationRename.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildApplicationOptionsResponse(taskId);
end;


function TApiService.DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
var
  projectId: string;
  applicationId: Integer;
  applicationName: string;
begin
  projectId := FindProjectId(taskId);
  if projectId = '' then
    raise Exception.Create('Project not found for task.');

  applicationId := FindApplicationId(taskId, name);
  applicationName := FindApplicationName(taskId, name);

  if applicationId = 0 then
    raise Exception.Create('Application name not found.');

  apiDB.uqApplicationDelete.Connection.StartTransaction;
  try
    apiDB.uqBlankTaskItemApplication.Close;
    apiDB.uqBlankTaskItemApplication.ParamByName('PROJECT_ID').AsString := projectId;
    apiDB.uqBlankTaskItemApplication.ParamByName('NAME').AsString := applicationName;
    apiDB.uqBlankTaskItemApplication.ExecSQL;

    apiDB.uqApplicationDelete.Close;
    apiDB.uqApplicationDelete.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := applicationId;
    apiDB.uqApplicationDelete.ParamByName('PROJECT_ID').AsString := projectId;
    apiDB.uqApplicationDelete.ExecSQL;

    apiDB.uqApplicationDelete.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqApplicationDelete.Connection.InTransaction then
        apiDB.uqApplicationDelete.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildApplicationOptionsResponse(taskId);
end;

function TApiService.FindReportedOptionId(const taskId, name: string): string;
begin
  Result := '';

  apiDB.uqProjectReportedUsers.Close;
  apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectReportedUsers.Open;

  while not apiDB.uqProjectReportedUsers.Eof do
  begin
    if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqProjectReportedUsersTASK_ITEM_USER_ID.AsString;
      Exit;
    end;

    apiDB.uqProjectReportedUsers.Next;
  end;
end;

function TApiService.FindReportedOptionName(const taskId, name: string): string;
begin
  Result := '';

  apiDB.uqProjectReportedUsers.Close;
  apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectReportedUsers.Open;

  while not apiDB.uqProjectReportedUsers.Eof do
  begin
    if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
    begin
      Result := apiDB.uqProjectReportedUsersNAME.AsString;
      Exit;
    end;

    apiDB.uqProjectReportedUsers.Next;
  end;
end;

function TApiService.BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
var
  taskUserOption: TTaskUserOption;
begin
  Result := TTaskUserOptionsResponse.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  apiDB.uqProjectReportedUsers.Close;
  apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
  apiDB.uqProjectReportedUsers.Open;

  while not apiDB.uqProjectReportedUsers.Eof do
  begin
    taskUserOption := TTaskUserOption.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);

    taskUserOption.name := apiDB.uqProjectReportedUsersNAME.AsString;
    Result.reportedByOptions.Add(taskUserOption);

    apiDB.uqProjectReportedUsers.Next;
  end;
end;

function TApiService.AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
  newName: string;
  existingName: string;
  newGuid: TGuid;
begin
  newName := Trim(name);
  if newName = '' then
    raise Exception.Create('Reported name cannot be blank.');

  existingName := FindReportedOptionName(taskId, newName);
  if existingName = '' then
  begin
    CreateGUID(newGuid);

    apiDB.uqReportedInsert.Connection.StartTransaction;
    try
      apiDB.uqReportedInsert.Close;
      apiDB.uqReportedInsert.ParamByName('TASK_ITEM_USER_ID').AsString := GuidToString(newGuid);
      apiDB.uqReportedInsert.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqReportedInsert.ParamByName('NAME').AsString := newName;
      apiDB.uqReportedInsert.ExecSQL;

      apiDB.uqReportedInsert.Connection.Commit;
    except
      on E: Exception do
      begin
        if apiDB.uqReportedInsert.Connection.InTransaction then
          apiDB.uqReportedInsert.Connection.Rollback;
        raise;
      end;
    end;
  end;

  Result := BuildReportedOptionsResponse(taskId);
end;

function TApiService.RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
var
  oldReportedId: string;
  existingReportedId: string;
  trimmedOldName: string;
  trimmedNewName: string;
begin
  trimmedOldName := Trim(oldName);
  trimmedNewName := Trim(newName);

  if trimmedOldName = '' then
    raise Exception.Create('Old reported name cannot be blank.');

  if trimmedNewName = '' then
    raise Exception.Create('New reported name cannot be blank.');

  oldReportedId := FindReportedOptionId(taskId, trimmedOldName);
  if oldReportedId = '' then
    raise Exception.Create('Reported name not found.');

  existingReportedId := FindReportedOptionId(taskId, trimmedNewName);

  apiDB.uqReportedRename.Connection.StartTransaction;
  try
    apiDB.uqRenameTaskItemReportedBy.Close;
    apiDB.uqRenameTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqRenameTaskItemReportedBy.ParamByName('OLD_NAME').AsString := trimmedOldName;
    apiDB.uqRenameTaskItemReportedBy.ParamByName('NEW_NAME').AsString := trimmedNewName;
    apiDB.uqRenameTaskItemReportedBy.ExecSQL;

    if (existingReportedId <> '') and (not SameText(existingReportedId, oldReportedId)) then
    begin
      apiDB.uqReportedDelete.Close;
      apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
      apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqReportedDelete.ExecSQL;
    end
    else
    begin
      apiDB.uqReportedRename.Close;
      apiDB.uqReportedRename.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
      apiDB.uqReportedRename.ParamByName('TASK_ID').AsString := taskId;
      apiDB.uqReportedRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
      apiDB.uqReportedRename.ExecSQL;
    end;

    apiDB.uqReportedRename.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqReportedRename.Connection.InTransaction then
        apiDB.uqReportedRename.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildReportedOptionsResponse(taskId);
end;

function TApiService.DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
  reportedId: string;
  reportedName: string;
begin
  reportedId := FindReportedOptionId(taskId, name);
  reportedName := FindReportedOptionName(taskId, name);

  if reportedId = '' then
    raise Exception.Create('Reported name not found.');

  apiDB.uqReportedDelete.Connection.StartTransaction;
  try
    apiDB.uqBlankTaskItemReportedBy.Close;
    apiDB.uqBlankTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqBlankTaskItemReportedBy.ParamByName('NAME').AsString := reportedName;
    apiDB.uqBlankTaskItemReportedBy.ExecSQL;

    apiDB.uqReportedDelete.Close;
    apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := reportedId;
    apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
    apiDB.uqReportedDelete.ExecSQL;

    apiDB.uqReportedDelete.Connection.Commit;
  except
    on E: Exception do
    begin
      if apiDB.uqReportedDelete.Connection.InTransaction then
        apiDB.uqReportedDelete.Connection.Rollback;
      raise;
    end;
  end;

  Result := BuildReportedOptionsResponse(taskId);
end;



initialization
  RegisterServiceType(TypeInfo(IApiService));
  RegisterServiceType(TApiService);

end.

