unit Api.ServiceImpl;

interface

uses
  XData.Server.Module, XData.Service.Common, Api.Database, Data.DB,
  System.SysUtils, System.Generics.Collections, XData.Sys.Exceptions, System.StrUtils,
  System.Hash, System.Classes, Common.Logging, System.JSON, Api.Service, VCL.Forms;


type
  [ServiceImplementation]
  TApiService = class(TInterfacedObject, IApiService)
  strict private
  ApiDB: TApiDatabaseModule;
  private
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    function GetComplaintMemos(const CfsId: string): TJSONObject;
  public
    function GetBadgeCounts: TJSONObject;
    function GetComplaintList: TJSONObject;
    function GetUnitList: TJSONObject;
    function GetComplaintMap: TJSONObject;
    function GetUnitMap: TJSONObject;
    function GetComplaintDetails(const ComplaintId: string): TJSONObject;
    function GetComplaintArchiveDetails(const ComplaintId: string): TJSONObject;
    function GetComplaintHistory(const ComplaintId: string): TJSONObject;
    function GetComplaintContacts(const ComplaintId: string): TJSONObject;
    function GetComplaintWarnings(const ComplaintId: string): TJSONObject;
    function GetUnitDetails(const UnitId: string): TJSONObject;
    function GetUnitLogs(const UnitId: string): TJSONObject;
  end;

implementation

uses
  uLibrary;

procedure TApiService.AfterConstruction;
begin
  inherited;
  ApiDB := TApiDatabaseModule.Create(nil);
  Logger.Log(3, 'ApiDatabaseModule created');
end;

procedure TApiService.BeforeDestruction;
begin
  ApiDB.Free;
  inherited;
  Logger.Log(3, 'ApiDatabaseModule destroyed');
end;


function TApiService.GetBadgeCounts: TJSONObject;
begin
  Logger.Log(3, '---TApiService.GetBadgeCounts initiated');

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

  try
    with ApiDB.uqBadgeCounts do
    begin
      Open;
      try
        Result.AddPair('BadgeComplaints', TJSONNumber.Create(FieldByName('COMPLAINTS').AsInteger));
        Result.AddPair('BadgeUnits', TJSONNumber.Create(FieldByName('UNITS').AsInteger));
      finally
        Close;
      end;
    end;
  except
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetBadgeCounts End (error): ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load badge counts');
    end;
  end;

  Logger.Log(3, '---TApiService.GetBadgeCounts End');
end;

function TApiService.GetComplaintMap: TJSONObject;
var
  data: TJSONArray;
  emitted: Integer;
  item: TJSONObject;
  UnitsByComplaintMap: TObjectDictionary<string,TJSONArray>;
  complaintId: string;
  unitArray: TJSONArray;
  unitStatus: string;
  latestUpdate: TDateTime;
  unitObj: TJSONObject;
begin
  Logger.Log(3,'---TApiService.GetComplaintMap initiated');

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

  data:=TJSONArray.Create;
  UnitsByComplaintMap:=TObjectDictionary<string,TJSONArray>.Create([doOwnsValues]);

  try
    ApiDB.uqMapComplaintUnitsList.Open;
    while not ApiDB.uqMapComplaintUnitsList.Eof do
    begin
      complaintId:=ApiDB.uqMapComplaintUnitsListCOMPLAINTID.AsString;
      if not UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
      begin
        unitArray:=TJSONArray.Create;
        UnitsByComplaintMap.Add(complaintId,unitArray);
      end;

      unitStatus:='Dispatched';
      if not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull then
        unitStatus:='Cleared'
      else if not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull then
        unitStatus:='On Scene'
      else if not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull then
        unitStatus:='Enroute';

      latestUpdate:=0;
      if not ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.IsNull then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime;

      unitObj:=TJSONObject.Create;
      unitObj.AddPair('Unit',ApiDB.uqMapComplaintUnitsListUNITNAME.AsString);
      unitObj.AddPair('Status',unitStatus);
      if latestUpdate<>0 then
        unitObj.AddPair('Updated',FormatDateTime('yyyy-mm-dd hh:nn:ss',latestUpdate))
      else
        unitObj.AddPair('Updated','');

      unitArray.AddElement(unitObj);
      ApiDB.uqMapComplaintUnitsList.Next;
    end;

    try
      emitted:=0;
      ApiDB.uqMapComplaints.Open;
      while not ApiDB.uqMapComplaints.Eof do
      begin
        item:=TJSONObject.Create;

        item.AddPair('ComplaintId',ApiDB.uqMapComplaintsCOMPLAINTID.AsString);
        item.AddPair('DispatchDistrict', ApiDB.uqMapComplaintsDISPATCHDISTRICT.AsString);
        item.AddPair('DispatchCodeDesc',ApiDB.uqMapComplaintsDISPATCH_CODE_DESC.AsString);
        item.AddPair('DispatchCodeCategory',ApiDB.uqMapComplaintsDISPATCHCODECATEGORY.AsString);
        item.AddPair('Priority',ApiDB.uqMapComplaintsPRIORITY.AsString);
        item.AddPair('priorityKey',ApiDB.uqMapComplaintspriorityKey.AsString);
        item.AddPair('pngName',ApiDB.uqMapComplaintspngName.AsString);
        item.AddPair('Address',ApiDB.uqMapComplaintsADDRESS.AsString);
        item.AddPair('Business',ApiDB.uqMapComplaintsBUSINESS.AsString);

        complaintId:=ApiDB.uqMapComplaintsCOMPLAINTID.AsString;
        if UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
          item.AddPair('Units',TJSONArray(unitArray.Clone))
        else
          item.AddPair('Units',TJSONArray.Create);

        item.AddPair('Lat',TJSONNumber.Create(ApiDB.uqMapComplaintsLAT.AsFloat));
        item.AddPair('Lng',TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat));

        data.AddElement(item);
        Inc(emitted);
        ApiDB.uqMapComplaints.Next;
      end;

      Result.AddPair('count',TJSONNumber.Create(data.Count));
      Result.AddPair('returned',TJSONNumber.Create(emitted));
      Result.AddPair('data',data);
      Logger.Log(3,'---TApiService.GetComplaintMap End (returned='+emitted.ToString+')');
    except
      on E: Exception do
      begin
        FreeAndNil(data);
        Logger.Log(2,'GetComplaintMap error: '+E.Message);
        raise EXDataHttpException.Create(500,'Failed to load complaint map');
      end;
    end;
  finally
    UnitsByComplaintMap.Free;
  end;
end;


function TApiService.GetUnitMap: TJSONObject;
var
  data: TJSONArray;
  item: TJSONObject;
  unitStatus: string;
  updateTimeText: string;
begin
  Logger.Log(4, '---TApiService.GetUnitMap initiated');

  // Note: GetUnitMap is AVL-anchored (shows all AVL units).
  // Note: DIS_UNITID is null when the unit is not dispatch-active; client should disable/hide Details in that case.
  // Note: To restrict map to dispatch-active only, change uqMapUnits join to DIS_UNIT_ACTIVE from LEFT JOIN to INNER JOIN.

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

  data := TJSONArray.Create;
  try
    with ApiDB.uqMapUnits do
    begin
      Open;
      try
        First;
        while not Eof do
        begin
          if (not ApiDB.uqMapUnitsGPS_LATITUDE.IsNull) and (not ApiDB.uqMapUnitsGPS_LONGITUDE.IsNull) then
          begin
            item := TJSONObject.Create;

            item.AddPair('UnitId', ApiDB.uqMapUnitsUNITID.AsString);
            item.AddPair('UnitName', ApiDB.uqMapUnitsUNITNAME.AsString);
            item.AddPair('District', ApiDB.uqMapUnitsUNIT_DISTRICT.AsString);

            item.AddPair('Lat', TJSONNumber.Create(ApiDB.uqMapUnitsGPS_LATITUDE.AsFloat));
            item.AddPair('Lng', TJSONNumber.Create(ApiDB.uqMapUnitsGPS_LONGITUDE.AsFloat));

            item.AddPair('CallType', ApiDB.uqMapUnitsCALL_TYPE.AsString);
            item.AddPair('Priority', ApiDB.uqMapUnitsPRIORITY.AsString);

            unitStatus := ApiDB.uqMapUnitsUNIT_STATUS_DESC.AsString;
            if Trim(unitStatus) = '' then
              unitStatus := 'Available';
            item.AddPair('Status', unitStatus);

            updateTimeText := '';
            if not ApiDB.uqMapUnitsUPDATE_TIME.IsNull then
              updateTimeText := FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqMapUnitsUPDATE_TIME.AsDateTime);
            item.AddPair('UpdateTime', updateTimeText);

            item.AddPair('Officer1Lname', ApiDB.uqMapUnitsOFFICER1_LNAME.AsString);
            item.AddPair('Officer1Fname', ApiDB.uqMapUnitsOFFICER1_FNAME.AsString);
            item.AddPair('Officer1Empnum', ApiDB.uqMapUnitsOFFICER1_EMPNUM.AsString);

            item.AddPair('Officer2Lname', ApiDB.uqMapUnitsOFFICER2_LNAME.AsString);
            item.AddPair('Officer2Fname', ApiDB.uqMapUnitsOFFICER2_FNAME.AsString);
            item.AddPair('Officer2Empnum', ApiDB.uqMapUnitsOFFICER2_EMPNUM.AsString);

            item.AddPair('CanShowDetails', TJSONBool.Create(not ApiDB.uqMapUnitsDIS_UNITID.IsNull));

            data.AddElement(item);
          end;

          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(2, '---TApiService.GetUnitMap error');
    raise EXDataHttpException.Create(500, 'Failed to load unit map');
  end;

  Logger.Log(4, '---TApiService.GetUnitMap End');
end;




function TApiService.GetComplaintList: TJSONObject;
var
  data: TJSONArray;
  lastDistrict: string;
begin
  Logger.Log(3, '---TApiService.GetComplaintList initiated');

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

  data := TJSONArray.Create;
  try
    lastDistrict := '';
    with ApiDB.uqComplaintList do
    begin
      Open;
      (FieldByName('DATEREPORTED') as TDateTimeField).DisplayFormat := 'yyyy-mm-dd hh:nn:ss';
      First;
      while not Eof do
      begin
        var status: string;
        if not FieldByName('DATECLEARED').IsNull then
          status := 'Cleared'
        else if not FieldByName('DATEARRIVED').IsNull then
          status := 'AtScene'
        else if not FieldByName('DATEDISPATCHED').IsNull then
          status := 'Dispatched'
        else
          status := 'Pending';

        var item := TJSONObject.Create;

        var curDistrict := ApiDB.uqComplaintListDISPATCHDISTRICT.AsString;
        if not SameText(curDistrict, lastDistrict) then
          item.AddPair('DistrictHeader', curDistrict);
        lastDistrict := curDistrict;

        var districtSector := ApiDB.uqComplaintListDISTRICT_DESC.AsString + ApiDB.uqComplaintListSECTOR_DESC.AsString;
        item.AddPair('DistrictSector', districtSector);

        var colorVal := ApiDB.uqComplaintListPRIORITY_COLOR.AsInteger;
        item.AddPair('PriorityColor', '#' + IntToHex(colorVal and $FFFFFF, 6));

        if (colorVal and $FFFFFF) = $0000FF then
          item.AddPair('PriorityTextColor', '#FFFFFF')
        else
          item.AddPair('PriorityTextColor', '#000000');

        item.AddPair('ComplaintId', ApiDB.uqComplaintListCOMPLAINTID.AsString);
        item.AddPair('Complaint', ApiDB.uqComplaintListcomplaintNumber.AsString);
        item.AddPair('Agency', ApiDB.uqComplaintListAGENCY.AsString);
        item.AddPair('Priority', ApiDB.uqComplaintListPRIORITY.AsString);
        item.AddPair('DispatchCodeDesc', ApiDB.uqComplaintListDISPATCH_CODE_DESC.AsString);
        item.AddPair('Address', ApiDB.uqComplaintListADDRESS.AsString);
        item.AddPair('Business', ApiDB.uqComplaintListBUSINESS.AsString);
        item.AddPair('CFSId', ApiDB.uqComplaintListCFSID.AsString);
        item.AddPair('Status', status);
        item.AddPair('DispatchDistrict', curDistrict);
        item.AddPair('DateReported', ApiDB.uqComplaintListDATEREPORTED.AsString);

        data.AddElement(item);
        Next;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(2, '---TApiService.GetComplaintList End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load complaints list');
  end;

  Logger.Log(3, '---TApiService.GetComplaintList End');
end;


function TApiService.GetUnitList: TJSONObject;
var
  data: TJSONArray;
  lastDistrict: string;
begin
  Logger.Log(3, '---TApiService.GetUnitList initiated');

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

  data := TJSONArray.Create;
  try
    lastDistrict := '';
    with ApiDB.uqUnitList do
    begin
      Open;
      First;
      while not Eof do
      begin
        var item := TJSONObject.Create;


        // Group header: show once when district changes
        var curDistrict := ApiDB.uqUnitListDISTRICT_DESC.AsString;
        var header := IfThen(curDistrict <> '', curDistrict + ' District', '');
        if (header <> '') and not SameText(header, lastDistrict) then
          item.AddPair('DistrictHeader', header);
        lastDistrict := header;


        // Core unit identity
        item.AddPair('UnitId', ApiDB.uqUnitListUNITID.AsString);
        item.AddPair('UnitName', ApiDB.uqUnitListUNITNAME.AsString);
        item.AddPair('CarNumberDesc', ApiDB.uqUnitListCARNUMBER_DESC.AsString);
        item.AddPair('District', curDistrict);
        item.AddPair('Sector', ApiDB.uqUnitListSECTOR_DESC.AsString);
        item.AddPair('CallType', ApiDB.uqUnitListCALL_TYPE.AsString);


        // Current assignment (if any)
        item.AddPair('Location', ApiDB.uqUnitListLOCATION.AsString);
        item.AddPair('Complaint', ApiDB.uqUnitListCOMPLAINT.AsString);

        // Status: default to "Available" when no active CFS row
       var statusDesc := ApiDB.uqUnitListUNIT_STATUS_DESC.AsString;
       if statusDesc = '' then
         statusDesc := 'Available';
       item.AddPair('Status', statusDesc);

        // Officers (LAST, FIRST [MI])
        var o1 := Trim(ApiDB.uqUnitListOFFICER1_LAST_NAME.AsString);
        var f1 := Trim(ApiDB.uqUnitListOFFICER1_FIRST_NAME.AsString);
        var m1 := Trim(ApiDB.uqUnitListOFFICER1_MI.AsString);
        if o1 <> '' then
        begin
          if f1 <> '' then o1 := o1 + ', ' + f1;
          if m1 <> '' then o1 := o1 + ' ' + m1;
          item.AddPair('Officer1', o1);
        end;

        var o2 := Trim(ApiDB.uqUnitListOFFICER2_LAST_NAME.AsString);
        var f2 := Trim(ApiDB.uqUnitListOFFICER2_FIRST_NAME.AsString);
        var m2 := Trim(ApiDB.uqUnitListOFFICER2_MI.AsString);
        if o2 <> '' then
        begin
          if f2 <> '' then o2 := o2 + ', ' + f2;
          if m2 <> '' then o2 := o2 + ' ' + m2;
          item.AddPair('Officer2', o2);
        end;

        data.AddElement(item);
        Next;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(3, '---TApiService.GetUnitList End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load units list');
  end;

  Logger.Log(3, '---TApiService.GetUnitList End');
end;


function TApiService.GetComplaintDetails(const ComplaintId: string): TJSONObject;
var
  obj: TJSONObject;
begin
  Logger.Log(3,'---TApiService.GetComplaintDetails initiated: '+ComplaintId);
  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
  try
    with ApiDB.uqComplaintDetails do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        if Eof then raise EXDataHttpException.Create(404,'Complaint not found');

        obj := TJSONObject.Create;
        obj.AddPair('ComplaintId', ApiDB.uqComplaintDetailsCOMPLAINTID.AsString);
        obj.AddPair('CFSId', ApiDB.uqComplaintDetailsCFSID.AsString);
        obj.AddPair('Complaint', ApiDB.uqComplaintDetailsCOMPLAINT.AsString);
        obj.AddPair('Priority', ApiDB.uqComplaintDetailsPRIORITY.AsString);
        obj.AddPair('DispatchCode', ApiDB.uqComplaintDetailsDISPATCHCODE.AsString);
        obj.AddPair('DispatchCodeDesc', ApiDB.uqComplaintDetailsDISPATCH_CODE_DESC.AsString);
        obj.AddPair('DispatchDistrict', ApiDB.uqComplaintDetailsDISPATCHDISTRICT.AsString);
        obj.AddPair('Address', ApiDB.uqComplaintDetailsADDRESS.AsString);
        obj.AddPair('Business',ApiDB.uqComplaintDetailsBUSINESS.AsString);
        obj.AddPair('History', ApiDB.uqComplaintDetailsHISTORY.AsString);
        obj.AddPair('Contacts', ApiDB.uqComplaintDetailsCONTACTS.AsString);
        obj.AddPair('Warnings', ApiDB.uqComplaintDetailsWARNINGS.AsString);

        if ApiDB.uqComplaintDetailsDATEREPORTED.IsNull then
          obj.AddPair('DateReported', '')
        else
          obj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEREPORTED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATERECEIVED.IsNull then
          obj.AddPair('DateReceived', '')
        else
          obj.AddPair('DateReceived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERECEIVED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATEDISPATCHED.IsNull then
          obj.AddPair('DateDispatched', '')
        else
          obj.AddPair('DateDispatched', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEDISPATCHED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATERESPONDED.IsNull then
          obj.AddPair('DateResponded', '')
        else
          obj.AddPair('DateResponded', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERESPONDED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATEARRIVED.IsNull then
          obj.AddPair('DateArrived', '')
        else
          obj.AddPair('DateArrived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEARRIVED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATECLEARED.IsNull then
          obj.AddPair('DateCleared', '')
        else
          obj.AddPair('DateCleared', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATECLEARED.AsDateTime));

        Result.AddPair('data', obj);

      finally
        Close;
      end;
    end;
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(3,'---TApiService.GetComplaintDetails not found');
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(3,'---TApiService.GetComplaintDetails error: '+E.Message);
      raise EXDataHttpException.Create(500,'Failed to load complaint details');
    end;
  end;
  Logger.Log(3,'---TApiService.GetComplaintDetails End');
end;


function TApiService.GetComplaintArchiveDetails(const ComplaintId: string): TJSONObject;
var
  obj: TJSONObject;
begin
  Logger.Log(3,'---TApiService.GetComplaintArchiveDetails initiated: '+ComplaintId);
  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
  try
    with ApiDB.uqComplaintDetails do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        if Eof then raise EXDataHttpException.Create(404,'Complaint not found');

        if not Locate('STATUS', 1, []) then
          raise EXDataHttpException.Create(404,'Complaint archive not found');

        obj := TJSONObject.Create;
        TXDataOperationContext.Current.Handler.ManagedObjects.Add(obj);

        obj.AddPair('ComplaintId', ApiDB.uqComplaintDetailsCOMPLAINTID.AsString);
        obj.AddPair('CFSId', ApiDB.uqComplaintDetailsCFSID.AsString);
        obj.AddPair('Complaint', ApiDB.uqComplaintDetailsCOMPLAINT.AsString);
        obj.AddPair('Priority', ApiDB.uqComplaintDetailsPRIORITY.AsString);
        obj.AddPair('DispatchCode', ApiDB.uqComplaintDetailsDISPATCHCODE.AsString);
        obj.AddPair('DispatchCodeDesc', ApiDB.uqComplaintDetailsDISPATCH_CODE_DESC.AsString);
        obj.AddPair('DispatchDistrict', ApiDB.uqComplaintDetailsDISPATCHDISTRICT.AsString);
        obj.AddPair('Address', ApiDB.uqComplaintDetailsADDRESS.AsString);
        obj.AddPair('Business', ApiDB.uqComplaintDetailsBUSINESS.AsString);
        obj.AddPair('History', ApiDB.uqComplaintDetailsHISTORY.AsString);
        obj.AddPair('Contacts', ApiDB.uqComplaintDetailsCONTACTS.AsString);
        obj.AddPair('Warnings', ApiDB.uqComplaintDetailsWARNINGS.AsString);

        if ApiDB.uqComplaintDetailsDATEREPORTED.IsNull then
          obj.AddPair('DateReported', '')
        else
          obj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEREPORTED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATERECEIVED.IsNull then
          obj.AddPair('DateReceived', '')
        else
          obj.AddPair('DateReceived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERECEIVED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATEDISPATCHED.IsNull then
          obj.AddPair('DateDispatched', '')
        else
          obj.AddPair('DateDispatched', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEDISPATCHED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATERESPONDED.IsNull then
          obj.AddPair('DateResponded', '')
        else
          obj.AddPair('DateResponded', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERESPONDED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATEARRIVED.IsNull then
          obj.AddPair('DateArrived', '')
        else
          obj.AddPair('DateArrived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEARRIVED.AsDateTime));

        if ApiDB.uqComplaintDetailsDATECLEARED.IsNull then
          obj.AddPair('DateCleared', '')
        else
          obj.AddPair('DateCleared', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATECLEARED.AsDateTime));

        Result.AddPair('data', obj);
      finally
        Close;
      end;
    end;
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(3,'---TApiService.GetComplaintArchiveDetails not found');
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(3,'---TApiService.GetComplaintArchiveDetails error: '+E.Message);
      raise EXDataHttpException.Create(500,'Failed to load complaint archive details');
    end;
  end;
  Logger.Log(3,'---TApiService.GetComplaintArchiveDetails End');
end;


function TApiService.GetComplaintMemos(const CfsId: string): TJSONObject;
var
  data: TJSONArray;
  item: TJSONObject;
  ts: string;
begin
  Logger.Log(3, '---TApiService.GetComplaintMemos initiated: ' + CfsId);

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

  data := TJSONArray.Create;
  try
    with ApiDB.uqCFSMemos do
    begin
      ParamByName('CFSID').AsString := CfsId;
      Open;
      try
        First;
        while not Eof do
        begin
          item := TJSONObject.Create;

          item.AddPair('MemoId', ApiDB.uqCFSMemosMEMO_ID.AsString);
          item.AddPair('CFSId', ApiDB.uqCFSMemosCFSID.AsString);
          item.AddPair('MemoType', ApiDB.uqCFSMemosMEMO_TYPE.AsString);

          if ApiDB.uqCFSMemosTIMESTAMP.IsNull then
            ts := ''
          else
            ts := FormatDateTime('yyyy-mm-dd' + '  ' + 'hh:nn:ss', ApiDB.uqCFSMemosTIMESTAMP.AsDateTime);
          item.AddPair('Timestamp', ts);

          item.AddPair('BadgeNumber', ApiDB.uqCFSMemosBADGE_NUMBER.AsString);
          item.AddPair('Remarks', ApiDB.uqCFSMemosREMARKS.AsString);

          data.AddElement(item);
          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(3, '---TApiService.GetComplaintMemos End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load complaint memos');
  end;

  Logger.Log(3, '---TApiService.GetComplaintMemos End');
end;


function TApiService.GetComplaintHistory(const ComplaintId: string): TJSONObject;
var
  dataArr: TJSONArray;
  rowObj: TJSONObject;
  returnedCount: Integer;
begin
  Logger.Log(4, '---TApiService.GetComplaintHistory initiated: ' + ComplaintId);

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

  dataArr := TJSONArray.Create;
  Result.AddPair('data', dataArr);

  returnedCount := 0;

  try
    with ApiDB.uqComplaintHistory do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        while not Eof do
        begin
          if returnedCount >= 50 then
            Break;

          rowObj := TJSONObject.Create;
          dataArr.AddElement(rowObj);

          rowObj.AddPair('ComplaintId', ApiDB.uqComplaintHistoryCOMPLAINTID.AsString);
          rowObj.AddPair('Complaint', ApiDB.uqComplaintHistoryCOMPLAINT.AsString);
          rowObj.AddPair('Apartment', ApiDB.uqComplaintHistoryAPARTMENT.AsString);

          if ApiDB.uqComplaintHistoryDATEREPORTED.IsNull then
            rowObj.AddPair('DateReported', '')
          else
            rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd', ApiDB.uqComplaintHistoryDATEREPORTED.AsDateTime));

          rowObj.AddPair('DPriority', ApiDB.uqComplaintHistoryDPRIORITY.AsString);
          rowObj.AddPair('DCallType', ApiDB.uqComplaintHistoryDCALLTYPE.AsString);

          Inc(returnedCount);
          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(returnedCount));
    Result.AddPair('returned', TJSONNumber.Create(returnedCount));

    Logger.Log(3, '---TApiService.GetComplaintHistory End (returned=' + IntToStr(returnedCount) + ')');
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(2, '---TApiService.GetComplaintHistory http error: ' + E.Message);
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetComplaintHistory error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load complaint history');
    end;
  end;
end;

function TApiService.GetComplaintContacts(const ComplaintId: string): TJSONObject;
var
  dataArr: TJSONArray;
  rowObj: TJSONObject;
  returnedCount: Integer;
begin
  Logger.Log(4, '---TApiService.GetComplaintContacts initiated: ' + ComplaintId);

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

  dataArr := TJSONArray.Create;
  Result.AddPair('data', dataArr);

  returnedCount := 0;

  try
    with ApiDB.uqComplaintContacts do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        while not Eof do
        begin
          rowObj := TJSONObject.Create;
          dataArr.AddElement(rowObj);

          rowObj.AddPair('Name', FieldByName('NAME').AsString);
          rowObj.AddPair('Phone', FieldByName('PHONE').AsString);
          rowObj.AddPair('DContactType', FieldByName('DCONTACTTYPE').AsString);
          rowObj.AddPair('Remarks', FieldByName('REMARKS').AsString);

          Inc(returnedCount);
          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(returnedCount));
    Result.AddPair('returned', TJSONNumber.Create(returnedCount));

    Logger.Log(3, '---TApiService.GetComplaintContacts End (returned=' + IntToStr(returnedCount) + ')');
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(2, '---TApiService.GetComplaintContacts http error: ' + E.Message);
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetComplaintContacts error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load complaint contacts');
    end;
  end;
end;

function TApiService.GetComplaintWarnings(const ComplaintId: string): TJSONObject;
var
  dataArr: TJSONArray;
  rowObj: TJSONObject;
  returnedCount: Integer;
begin
  Logger.Log(3, '---TApiService.GetComplaintWarnings initiated: ' + ComplaintId);

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

  dataArr := TJSONArray.Create;
  Result.AddPair('data', dataArr);

  returnedCount := 0;

  try
    with ApiDB.uqComplaintWarnings do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        while not Eof do
        begin
          rowObj := TJSONObject.Create;
          dataArr.AddElement(rowObj);

          rowObj.AddPair('CodeDesc', FieldByName('CODE_DESC').AsString);
          rowObj.AddPair('Address', FieldByName('ADDRESS_TEXT').AsString);
          rowObj.AddPair('Notes', FieldByName('NOTES').AsString);

          Inc(returnedCount);
          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(returnedCount));
    Result.AddPair('returned', TJSONNumber.Create(returnedCount));

    Logger.Log(3, '---TApiService.GetComplaintWarnings End (returned=' + IntToStr(returnedCount) + ')');
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(2, '---TApiService.GetComplaintWarnings http error: ' + E.Message);
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetComplaintWarnings error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load complaint warnings');
    end;
  end;
end;

function TApiService.GetUnitLogs(const UnitId: string): TJSONObject;
var
  dataArr: TJSONArray;
  rowObj: TJSONObject;
  returnedCount: Integer;
  ts: string;
  complaintText: string;
begin
  Logger.Log(4, '---TApiService.GetUnitLogs initiated: ' + UnitId);

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

  dataArr := TJSONArray.Create;
  returnedCount := 0;

  try
    with ApiDB.uqUnitLogs do
    begin
      ParamByName('UNITID').AsString := UnitId;
      Open;
      try
        while not Eof do
        begin
          rowObj := TJSONObject.Create;
          dataArr.AddElement(rowObj);

          ts := '';
          if not ApiDB.uqUnitLogsLOG_TIME.IsNull then
            ts := FormatDateTime('yyyy-mm-dd' + '  ' + 'HH:nn:ss', ApiDB.uqUnitLogsLOG_TIME.AsDateTime);

          complaintText := Trim(ApiDB.uqUnitLogsCOMPLAINT_NUM.AsString);

          rowObj.AddPair('LogTime', ts);
          rowObj.AddPair('Complaint', complaintText);
          rowObj.AddPair('Log', ApiDB.uqUnitLogsLOG_TEXT.AsString);

          Inc(returnedCount);
          Next;
        end;
      finally
        Close;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(returnedCount));
    Result.AddPair('returned', TJSONNumber.Create(returnedCount));
    Result.AddPair('data', dataArr);

    Logger.Log(4, '---TApiService.GetUnitLogs End (returned=' + IntToStr(returnedCount) + ')');
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(2, '---TApiService.GetUnitLogs http error: ' + E.Message);
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetUnitLogs error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load unit logs');
    end;
  end;
end;



function TApiService.GetUnitDetails(const UnitId: string): TJSONObject;
var
  obj: TJSONObject;
  updateTimeText: string;
  unitStatus: string;
begin
  Logger.Log(4, '---TApiService.GetUnitDetails initiated: ' + UnitId);

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

  try
    with ApiDB.uqUnitDetails do
    begin
      ParamByName('UNITID').AsString := UnitId;
      Open;
      try
        if Eof then
          raise EXDataHttpException.Create(404, 'Unit not found');

        obj := TJSONObject.Create;

        obj.AddPair('UnitId', ApiDB.uqUnitDetailsUNITID.AsString);
        obj.AddPair('UnitName', ApiDB.uqUnitDetailsUNITNAME.AsString);
        obj.AddPair('CarNumberDesc', ApiDB.uqUnitDetailsCARNUMBER_DESC.AsString);
        obj.AddPair('District', ApiDB.uqUnitDetailsDISTRICT_DESC.AsString);
        obj.AddPair('Location', ApiDB.uqUnitDetailsLOCATION.AsString);

        unitStatus := Trim(ApiDB.uqUnitDetailsUNIT_STATUS_DESC.AsString);
        if unitStatus = '' then
          unitStatus := 'Available';
        obj.AddPair('Status', unitStatus);

        obj.AddPair('Officer1Lname', ApiDB.uqUnitDetailsOFFICER1_LNAME.AsString);
        obj.AddPair('Officer1Fname', ApiDB.uqUnitDetailsOFFICER1_FNAME.AsString);
        obj.AddPair('Officer1Empnum', ApiDB.uqUnitDetailsOFFICER1_EMPNUM.AsString);

        obj.AddPair('Officer2Lname', ApiDB.uqUnitDetailsOFFICER2_LNAME.AsString);
        obj.AddPair('Officer2Fname', ApiDB.uqUnitDetailsOFFICER2_FNAME.AsString);
        obj.AddPair('Officer2Empnum', ApiDB.uqUnitDetailsOFFICER2_EMPNUM.AsString);

        updateTimeText := '';
        if not ApiDB.uqUnitDetailsUPDATE_TIME.IsNull then
          updateTimeText := FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqUnitDetailsUPDATE_TIME.AsDateTime);
        obj.AddPair('UpdateTime', updateTimeText);

        Result.AddPair('data', obj);
      finally
        Close;
      end;
    end;
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(2, '---TApiService.GetUnitDetails http error: ' + E.Message);
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(2, '---TApiService.GetUnitDetails error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load unit details');
    end;
  end;

  Logger.Log(4, '---TApiService.GetUnitDetails End');
end;


initialization
  RegisterServiceType(TApiService);

end.

