asciisd / zoho-v3

Laravel Zoho API V3 Package
MIT License
16 stars 16 forks source link

Enhancement Request: Support for Pagination in getRecords Method and Implementation of Get Related Records API Call #31

Open maxabba opened 2 months ago

maxabba commented 2 months ago

I am working on a project involving a data service server for a mobile app. I am using the Zoho API via the Asciisd\Zoho package, version 1.2.10, within a Laravel 10 environment and PHP 8.1 (Sail).

Issue 1: Limitation in searchRecordsByCriteria Method

I am currently encountering a limitation with the searchRecordsByCriteria method, which restricts the results to 2000 records as per Zoho API documentation. To circumvent this, I attempted to use the getRecords method to leverage pagination with the next_page_token parameter. However, the next_page_token parameter is not present in the response of the getRecords method.

Code Example:

use Asciisd\Zoho\Facades\ZohoManager;

$records = ZohoManager::useModule('Leads')->searchRecordsByCriteria('(City:equals:NY) and (State:equals:Alden)',11,200);
//$records count is 0 because on page 11 on perPage 200 the call exeded the api limit of 2000
$first_record = $records[0];

Problem Description:

The searchRecordsByCriteria method is limited to 2000 records. When using the getRecords method, the expected next_page_token for pagination is missing in the response, preventing me from retrieving subsequent pages of data.

Issue 2: Missing Method for Get Related Records

I also need to use the Get Related Records API call as described in the Zoho API documentation. However, there is no method available in the current library that facilitates this API call.

Problem Description:

There is no callable method in the library for the Get Related Records API.

Environment:

Library Version: 1.2.10 Framework: Laravel 10 PHP Version: 8.1 Development Environment: Sail

Priority:

No immediate priority. Request:

Can you please add support for these features? Specifically:

Ensure the next_page_token parameter is included in the response of the getRecords method. Implement a method to facilitate the Get Related Records API call.

maxabba commented 2 months ago

I have fixed both of my problem editing ManagesRecords->handleRecordResponse() adding

$actionInfo = $actionWrapper->getInfo();

                    $info = [];
                    if ($actionInfo instanceof Info) {
                        $info[] = $actionInfo;
                    }

this is helpfull for handle all parameter from the api response, for example the needed next_page_token to have more then 2000 records at call pagination with getRecords() method passing it

and for the second problem adding

if ($actionHandler instanceof ResponseWrapper or $actionHandler instanceof RelatedResponseWrapper)

the instanceof RelatedResponseWrapper use another SDK class

use com\zoho\crm\api\relatedrecords\ResponseWrapper as RelatedResponseWrapper; to perform the get related records, at the bottom the new function to get them

here the full function

private function handleRecordResponse(?APIResponse $response): array
  {
      if ($response) {
          if ($response->isExpected()) {
              $actionHandler = $response->getObject();

              if ($actionHandler instanceof ResponseWrapper or $actionHandler instanceof RelatedResponseWrapper) {
                  $actionWrapper = $actionHandler;
                  $actionInfo = $actionWrapper->getInfo();

                  $info = [];
                  if ($actionInfo instanceof Info) {
                      $info[] = $actionInfo;
                  }

                  $actionResponse = $actionWrapper->getData();
                  $result = [];
                  foreach ($actionResponse as $response) {
                      if ($response instanceof Record) {
                          $result['records'][] = $response;
                      } else {
                          if ($response instanceof APIException) {
                              $this->handleRecordException($response);
                          }
                      }
                  }
                  $result['info'] = $info;
                  return $result;
              } else {
                  if ($actionHandler instanceof APIException) {
                      $this->handleRecordException($actionHandler);
                  }
              }
          }
      }

      return [];
  }
 public function getRelatedRecords($recordId, $relatedListAPIName, array $fields = ['id'], $page = 1, $perPage = 200, $sortBy = 'id', $sortOrder = 'desc'): array
  {
      $recordOperations = new RelatedRecordsOperations($relatedListAPIName ,$this->module_api_name);
      $paramInstance = new ParameterMap();

      $paramInstance->add(GetRecordsParam::page(), $page);
      $paramInstance->add(GetRecordsParam::perPage(), $perPage);
      $paramInstance->add(GetRecordsParam::sortBy(), $sortBy);
      $paramInstance->add(GetRecordsParam::sortOrder(), $sortOrder);
      $paramInstance->add(GetRecordsParam::fields(), implode(',', $fields));

      return $this->handleRecordResponse(
          $recordOperations->getRelatedRecords($recordId, $paramInstance)
      );
  }

here an example to how to use both

$response = ZohoManager::make('ISF')->searchRecordsByCriteria('Email:equals:'.$email);
$records = $response['records'][0];
$info = $response['info'];
$records2 = ZohoManager::make('ISF')->getRelatedRecords($records->getKeyValue('id'), 'Aree_ISF5');

now calling all the method of ZohoManager trail ManagesRecords the response has 'records' and 'info'

Following the Zoho Rest Api for related records: {api-domain}/crm/{version}/{module_api_name}/{record_id}/{related_list_api_name}

the getRelatedRecords now use the make module_api_name, $records->getKeyValue('id') for the id and the last argument as the related_list_api_name (be sure to use the name under related list, you can see using the dropdown menu in the api menu of zoho) All the other argument that can be passed to getRecords are included


$paramInstance->add(GetRecordsParam::page(), $page);
$paramInstance->add(GetRecordsParam::perPage(), $perPage);
$paramInstance->add(GetRecordsParam::sortBy(), $sortBy);
$paramInstance->add(GetRecordsParam::sortOrder(), $sortOrder);
$paramInstance->add(GetRecordsParam::fields(), implode(',', $fields));

maybe usefull

maxabba commented 2 months ago

Another solution is to extend the library with another trait called ManagesRelatedRecords to have all the related future implementation separated by the ManagesRegords as follow


<?php
namespace Asciisd\Zoho\Concerns;

use com\zoho\crm\api\modules\APIException;
use com\zoho\crm\api\ParameterMap;
use com\zoho\crm\api\record\GetRecordsParam;
use com\zoho\crm\api\record\Record;
use com\zoho\crm\api\record\Info;
use com\zoho\crm\api\relatedrecords\RelatedRecordsOperations;
use com\zoho\crm\api\relatedrecords\ResponseWrapper;
use com\zoho\crm\api\util\APIResponse;

trait ManagesRelatedRecords
{

    public function getRelatedRecords($recordId, $relatedListAPIName, array $fields = ['id'], $page = 1, $perPage = 200, $sortBy = 'id', $sortOrder = 'desc'): array
    {
        $recordOperations = new RelatedRecordsOperations($relatedListAPIName, $this->module_api_name);
        $paramInstance = new ParameterMap();

        $paramInstance->add(GetRecordsParam::page(), $page);
        $paramInstance->add(GetRecordsParam::perPage(), $perPage);
        $paramInstance->add(GetRecordsParam::sortBy(), $sortBy);
        $paramInstance->add(GetRecordsParam::sortOrder(), $sortOrder);
        $paramInstance->add(GetRecordsParam::fields(), implode(',', $fields));

        return $this->handleRelatedRecordResponse(
            $recordOperations->getRelatedRecords($recordId, $paramInstance)
        );
    }

    //TODO implements other methods by SDK

    private function handleRelatedRecordResponse(?APIResponse $response): array
    {
        if ($response) {
            if ($response->isExpected()) {
                $actionHandler = $response->getObject();

                if ($actionHandler instanceof ResponseWrapper) {
                    $actionWrapper = $actionHandler;
                    $actionInfo = $actionWrapper->getInfo();

                    $info = [];
                    if ($actionInfo instanceof Info) {
                        $info[] = $actionInfo;
                    }

                    $actionResponse = $actionWrapper->getData();
                    $result = [];
                    foreach ($actionResponse as $response) {
                        if ($response instanceof Record) {
                            $result['records'][] = $response;
                        } else {
                            if ($response instanceof APIException) {
                                $this->handleRelatedRecordException($response);
                            }
                        }
                    }
                    $result['info'] = $info;
                    return $result;
                } else {
                    if ($actionHandler instanceof APIException) {
                        $this->handleRelatedRecordException($actionHandler);
                    }
                }
            }
        }

        return [];
    }

    private function handleRelatedRecordException(APIException $exception): void
    {
        $message = $exception->getMessage();
        $details = json_encode($exception->getDetails());
        $status = $exception->getStatus()->getValue();

        logger()->error("Zoho SDK API | ManagesRecords | handleRecordResponse | Status: $status | Message: $message | Details : $details");
    }

}

And add this trait to class ZohoManager as follow

    use CriteriaBuilder;
    use ManagesModules;
    use ManagesRecords;
    **use ManagesRelatedRecords;**
    use ManagesTags;
    use ManagesActions;
    use ManagesBulkActions;

    protected string $module_api_name;

    public function __construct($module_api_name = 'Leads')
    {
        try {
            $this->module_api_name = $module_api_name;
            Zoho::initialize();
        } catch (SDKException $e) {
            logger()->error($e->getMessage());
        }
    }

    public static function useModule(string $module_name = 'Leads'): static
    {
        return new static($module_name);
    }

    public static function make(string $module_name = 'Leads'): static
    {
        return static::useModule($module_name);
    }

}
maxabba commented 2 months ago

To use in correct way the next_page_token we have to extend the parameters in getRecords as follow:

  public function getRecords(array $fields = ['id'], $page = 1, $perPage = 200, $sortBy = 'id', $sortOrder = 'desc', $page_token = null): array
  {
      $recordOperations = new RecordOperations($this->module_api_name);
      $paramInstance = new ParameterMap();
      if (!$page_token) {
          $paramInstance->add(GetRecordsParam::page(), $page);
      }
      $paramInstance->add(GetRecordsParam::perPage(), $perPage);
      $paramInstance->add(GetRecordsParam::sortBy(), $sortBy);
      $paramInstance->add(GetRecordsParam::sortOrder(), $sortOrder);
      if ($page_token) {
          $paramInstance->add(GetRecordsParam::pageToken(), $page_token);
      }
      $paramInstance->add(GetRecordsParam::fields(), implode(',', $fields));

      return $this->handleRecordResponse(
          $recordOperations->getRecords($paramInstance)
      );
  }

And here an example of how to use

public function test_getRecord()
{
    $page = 1;
    $perPage = 200;
    $totalFetched = 0;
    $page_token = null;
    do {
        $zohoContacts = ZohoManager::make('Contacts')
            ->getRecords(['id'], $page, $perPage, 'id', 'asc', $page_token);

            $info = $zohoContacts['info'][0];
            $page_token = $info->getNextPageToken();

        $fetchedCount = count($zohoContacts['records']);
        $totalFetched += $fetchedCount;

        foreach ($zohoContacts['records'] as $zohoContact) {
            $id_zoho = $zohoContact->getKeyValue('id');
        }

        $page++;

    } while ($fetchedCount == $perPage);
}