diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index ee2ef05e..681c1781 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -12,6 +12,7 @@ ->in(__DIR__ . '/src/Services/CRM/Quote/') ->in(__DIR__ . '/src/Services/CRM/Lead/') ->in(__DIR__ . '/src/Services/CRM/Currency/') + ->in(__DIR__ . '/src/Services/CRM/CallList/') ->in(__DIR__ . '/src/Services/CRM/Requisites/') ->in(__DIR__ . '/src/Services/CRM/Status/') ->in(__DIR__ . '/src/Services/CRM/Timeline/') diff --git a/CHANGELOG.md b/CHANGELOG.md index 63795699..75027d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ ### Added +- Added service `Services\CRM\CallList\Service\CallList` with support methods, + see [crm.entity.section.* methods](https://github.com/bitrix24/b24phpsdk/issues/208): + - `get` get information about the call list + - `list` get all call lists, with batch calls support + - `add` add a new call list, with batch calls support + - `update` update the call list, with batch calls support + - `statusList` get a list of the calllist statuses + - `getItems` get call list members - Added service `Services\Sale\PropertyVariant\Service\PropertyVariant` with support methods, see [sale.propertyvariant.* methods](https://github.com/bitrix24/b24phpsdk/issues/234): - `add` adds a variant of an order property diff --git a/Makefile b/Makefile index aade7d9b..6f3331a2 100644 --- a/Makefile +++ b/Makefile @@ -259,6 +259,10 @@ integration_tests_lead_productrows: integration_tests_crm_quote: docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_quote +.PHONY: integration_tests_crm_calllist +integration_tests_crm_calllist: + docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_calllist + .PHONY: integration_tests_crm_requisite integration_tests_crm_requisite: docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_requisite diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3bead767..c16db1fc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -26,6 +26,7 @@ parameters: - tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php - tests/Integration/Services/CRM/Lead/Service/LeadUserfieldTest.php - tests/Integration/Services/CRM/Currency + - tests/Integration/Services/CRM/CallList - tests/Integration/Services/CRM/Requisites - tests/Integration/Services/Task - tests/Integration/Services/Sale diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e41342c3..096b2097 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -97,6 +97,9 @@ ./tests/Integration/Services/CRM/Quote/ + + ./tests/Integration/Services/CRM/CallList/ + ./tests/Integration/Services/CRM/Requisites/Service/RequisiteLinkTest.php diff --git a/rector.php b/rector.php index 4b458e5f..6a2a022f 100644 --- a/rector.php +++ b/rector.php @@ -50,6 +50,8 @@ __DIR__ . '/tests/Integration/Services/CRM/Quote/Service', __DIR__ . '/src/Services/CRM/Currency', __DIR__ . '/tests/Integration/Services/CRM/Currency', + __DIR__ . '/src/Services/CRM/CallList', + __DIR__ . '/tests/Integration/Services/CRM/CallList', __DIR__ . '/src/Services/CRM/Requisites', __DIR__ . '/tests/Integration/Services/CRM/Requisites', __DIR__ . '/src/Services/CRM/Timeline', diff --git a/src/Services/CRM/CRMServiceBuilder.php b/src/Services/CRM/CRMServiceBuilder.php index d72be5ce..4f572db3 100644 --- a/src/Services/CRM/CRMServiceBuilder.php +++ b/src/Services/CRM/CRMServiceBuilder.php @@ -595,6 +595,23 @@ public function duplicate(): Duplicates\Service\Duplicate return $this->serviceCache[__METHOD__]; } + public function callList(): CallList\Service\CallList + { + if (!isset($this->serviceCache[__METHOD__])) { + $batch = new CallList\Batch( + $this->core, + $this->log + ); + $this->serviceCache[__METHOD__] = new CallList\Service\CallList( + new CallList\Service\Batch($batch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + public function status(): Status\Service\Status { if (!isset($this->serviceCache[__METHOD__])) { diff --git a/src/Services/CRM/CallList/Batch.php b/src/Services/CRM/CallList/Batch.php new file mode 100644 index 00000000..717ad85c --- /dev/null +++ b/src/Services/CRM/CallList/Batch.php @@ -0,0 +1,336 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Generator; + +/** + * Class Batch + * + * @package Bitrix24\SDK\Services\CRM\CallList + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Update calllist items with batch call + * + * @param array> $entityItems + * + * @return Generator|ResponseData[] + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + public function updateEntityItems(string $apiMethod, array $entityItems): Generator + { + $this->logger->debug( + 'updateEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItems, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItems as $entityItem) { + $cmdArguments = $entityItem; + $this->registerCommand($apiMethod, $cmdArguments); + } + + foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) { + yield $cnt => $updatedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('updateEntityItems.finish'); + } + + /** + * Get traversable list without count elements + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return \Generator + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + * @throws \Bitrix24\SDK\Core\Exceptions\TransportException + * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface + */ + public function getTraversableList( + string $apiMethod, + ?array $order = [], + ?array $filter = [], + ?array $select = [], + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + $apiMethod = strtolower($apiMethod); + $this->logger->debug( + 'getTraversableList.start', + [ + 'apiMethod' => $apiMethod, + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + 'additionalParameters' => $additionalParameters, + ] + ); + + // strategy.3 — ID filter, batch, no count, order + // — ✅ counting of the number of elements in the selection is disabled + // — ⚠️ The ID of elements in the selection is increasing, i.e. the results were sorted by ID + // — using batch + // — sequential execution of queries + // + // Optimization groundwork + // — limited use of parallel queries + // + // Queries are sent to the server sequentially with the "order" parameter: {"ID": "ASC"} (sorting in ascending ID). + // Since the results are sorted in ascending ID, they can be combined into batch queries with counting of the number of elements in each disabled. + // + // Filter formation order: + // + // took a filter with "direct" sorting and got the first ID + // took a filter with "reverse" sorting and got the last ID + // Since ID increases monotonically, then we assume that all pages are filled with elements uniformly, in fact there will be "holes" due to master-master replication and deleted elements. i.e. the resulting selections will not always contain exactly 50 elements. + // we form selections from ready-made filters and pack them into batch commands. + // if possible, batch queries are executed in parallel + + // we got the first id of the element in the selection by filter + // todo checked that this is a *.list command + // todo checked that there is an ID in the select, i.e. the developer understands that ID is used + // todo checked that sorting is set as "order": {"ID": "ASC"} i.e. the developer understands that the data will arrive in this order + // todo checked that if there is a limit, then it is >1 + // todo checked that there is no ID field in the filter, since we will work with it + + $params = [ + 'ORDER' => $order, + 'FILTER' => $filter, + 'SELECT' => $select, + 'start' => 0, + ]; + + // data structures for crm.items.* is little different =\ + $isCrmItemsInBatch = false; + if ($additionalParameters !== null) { + if (array_key_exists('entityTypeId', $additionalParameters)) { + $isCrmItemsInBatch = true; + } + + $params = array_merge($params, $additionalParameters); + } + + $keyId = $isCrmItemsInBatch ? 'id' : 'ID'; + $this->logger->debug('getTraversableList.getFirstPage', [ + 'apiMethod' => $apiMethod, + 'params' => $params, + ]); + $response = $this->core->call($apiMethod, $params); + $totalElementsCount = $response->getResponseData()->getPagination()->getTotal(); + $this->logger->debug('getTraversableList.totalElementsCount', [ + 'totalElementsCount' => $totalElementsCount, + ]); + // filtered elements count less than or equal one result page(50 elements) + $elementsCounter = 0; + if ($totalElementsCount <= self::MAX_ELEMENTS_IN_PAGE) { + foreach ($response->getResponseData()->getResult() as $listElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $listElement; + } + + $this->logger->debug('getTraversableList.finish'); + + return; + } + + // filtered elements count more than one result page(50 elements) + // return first page + $lastElementIdInFirstPage = null; + if ($isCrmItemsInBatch) { + foreach ($response->getResponseData()->getResult()['items'] as $listElement) { + ++$elementsCounter; + $lastElementIdInFirstPage = (int)$listElement[$keyId]; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $listElement; + } + } else { + foreach ($response->getResponseData()->getResult() as $listElement) { + ++$elementsCounter; + $lastElementIdInFirstPage = (int)$listElement[$keyId]; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $listElement; + } + } + + $this->clearCommands(); + if (!in_array($keyId, $select, true)) { + $select[] = $keyId; + } + + // getLastElementId in filtered result + // todo wait new api version + if ($apiMethod !== 'user.get') { + $defaultOrderKey = 'order'; + $orderKey = $apiMethod === 'entity.item.get' ? 'SORT' : $defaultOrderKey; + + $params = [ + $orderKey => $this->getReverseOrder($order), + 'FILTER' => $filter, + 'SELECT' => $select, + 'start' => 0, + ]; + } + + if ($additionalParameters !== null) { + $params = array_merge($params, $additionalParameters); + } + + $this->logger->debug('getTraversableList.getLastPage', [ + 'apiMethod' => $apiMethod, + 'params' => $params, + ]); + $lastResultPage = $this->core->call($apiMethod, $params); + if ($isCrmItemsInBatch) { + $lastElementId = (int)$lastResultPage->getResponseData()->getResult()['items'][0][$keyId]; + } else { + $lastElementId = (int)$lastResultPage->getResponseData()->getResult()[0][$keyId]; + } + + $this->logger->debug('getTraversableList.lastElementsId', [ + 'lastElementIdInFirstPage' => $lastElementIdInFirstPage, + 'lastElementIdInLastPage' => $lastElementId, + ]); + + + // reverse order if elements in batch ordered in DESC direction + if ($lastElementIdInFirstPage > $lastElementId) { + $tmp = $lastElementIdInFirstPage; + $lastElementIdInFirstPage = $lastElementId; + $lastElementId = $tmp; + } + + // register commands with updated filter + //more than one page in results - register list commands + ++$lastElementIdInFirstPage; + for ($startId = $lastElementIdInFirstPage; $startId <= $lastElementId; $startId += self::MAX_ELEMENTS_IN_PAGE) { + $this->logger->debug('registerCommand.item', [ + 'startId' => $startId, + 'lastElementId' => $lastElementId, + 'delta' => $lastElementId - $startId, + ]); + + $delta = $lastElementId - $startId; + $isLastPage = false; + if ($delta > self::MAX_ELEMENTS_IN_PAGE) { + // ignore + // - master–master replication with id + // - deleted elements + $lastElementIdInPage = $startId + self::MAX_ELEMENTS_IN_PAGE; + } else { + $lastElementIdInPage = $lastElementId; + $isLastPage = true; + } + + $params = [ + 'ORDER' => $order, + 'FILTER' => $this->updateFilterForBatch($keyId, $startId, $lastElementIdInPage, $isLastPage, $filter), + 'SELECT' => $select, + 'start' => -1, + ]; + + $this->registerCommand($apiMethod, $params); + } + + $this->logger->debug( + 'getTraversableList.commandsRegistered', + [ + 'commandsCount' => $this->commands->count(), + ] + ); + + // iterate batch queries, max: 50 results per 50 elements in each result + foreach ($this->getTraversable(true) as $queryCnt => $queryResultData) { + /** + * @var $queryResultData ResponseData + */ + $this->logger->debug( + 'getTraversableList.batchResultItem', + [ + 'batchCommandItemNumber' => $queryCnt, + 'nextItem' => $queryResultData->getPagination()->getNextItem(), + 'durationTime' => $queryResultData->getTime()->duration, + ] + ); + + // iterate items in batch query result + if ($isCrmItemsInBatch) { + foreach ($queryResultData->getResult()['items'] as $listElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $listElement; + } + } else { + foreach ($queryResultData->getResult() as $listElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $listElement; + } + } + } + + $this->logger->debug('getTraversableList.finish'); + } +} diff --git a/src/Services/CRM/CallList/Result/CallListItemResult.php b/src/Services/CRM/CallList/Result/CallListItemResult.php new file mode 100644 index 00000000..f07be5ac --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListItemResult.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem; +use Carbon\CarbonImmutable; + +/** + * Class CallListItemResult + * + * @property-read int $ID + * @property-read int $ENTITY_TYPE_ID + * @property-read string $ENTITY_TYPE + * @property-read CarbonImmutable $DATE_CREATE + * @property-read int $WEBFORM_ID + * @property-read int $CREATED_BY_ID + */ +class CallListItemResult extends AbstractCrmItem +{ +} diff --git a/src/Services/CRM/CallList/Result/CallListItemsItemResult.php b/src/Services/CRM/CallList/Result/CallListItemsItemResult.php new file mode 100644 index 00000000..43a7e68a --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListItemsItemResult.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem; + +/** + * Class CallListItemsItemResult + * + * @property-read int $ID + * @property-read string $STATUS + * @property-read int $ENTITY_TYPE + */ +class CallListItemsItemResult extends AbstractCrmItem +{ +} diff --git a/src/Services/CRM/CallList/Result/CallListItemsResult.php b/src/Services/CRM/CallList/Result/CallListItemsResult.php new file mode 100644 index 00000000..517a1582 --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListItemsResult.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class CallListItemsResult + * + * @package Bitrix24\SDK\Services\CRM\CallList\Result + */ +class CallListItemsResult extends AbstractResult +{ + /** + * @return \Bitrix24\SDK\Services\CRM\CallList\Result\CallListItemsItemResult[] + * @throws BaseException + */ + public function getItems(): array + { + $res = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $res[] = new CallListItemsItemResult($item); + } + + return $res; + } +} diff --git a/src/Services/CRM/CallList/Result/CallListResult.php b/src/Services/CRM/CallList/Result/CallListResult.php new file mode 100644 index 00000000..725365b3 --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListResult.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Core\Result\AbstractResult; + +class CallListResult extends AbstractResult +{ + public function calllist(): CallListItemResult + { + return new CallListItemResult($this->getCoreResponse()->getResponseData()->getResult()); + } +} diff --git a/src/Services/CRM/CallList/Result/CallListStatusItemResult.php b/src/Services/CRM/CallList/Result/CallListStatusItemResult.php new file mode 100644 index 00000000..402efbda --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListStatusItemResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem; + +/** + * Class CallListStatusItemResult + * + * @property-read int $ID + * @property-read string $NAME + * @property-read int $SORT + * @property-read string $STATUS_ID + */ +class CallListStatusItemResult extends AbstractCrmItem +{ +} diff --git a/src/Services/CRM/CallList/Result/CallListStatusesResult.php b/src/Services/CRM/CallList/Result/CallListStatusesResult.php new file mode 100644 index 00000000..d62b9190 --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListStatusesResult.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class CallListStatusesResult + * + * @package Bitrix24\SDK\Services\CRM\CallList\Result + */ +class CallListStatusesResult extends AbstractResult +{ + /** + * @return \Bitrix24\SDK\Services\CRM\CallList\Result\CallListStatusItemResult[] + * @throws BaseException + */ + public function getStatuses(): array + { + $res = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $res[] = new CallListStatusItemResult($item); + } + + return $res; + } +} diff --git a/src/Services/CRM/CallList/Result/CallListsResult.php b/src/Services/CRM/CallList/Result/CallListsResult.php new file mode 100644 index 00000000..74917492 --- /dev/null +++ b/src/Services/CRM/CallList/Result/CallListsResult.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class CallListsResult + * + * @package Bitrix24\SDK\Services\CRM\CallList\Result + */ +class CallListsResult extends AbstractResult +{ + /** + * @return \Bitrix24\SDK\Services\CRM\CallList\Result\CallListItemResult[] + * @throws BaseException + */ + public function getCallLists(): array + { + $res = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $res[] = new CallListItemResult($item); + } + + return $res; + } +} diff --git a/src/Services/CRM/CallList/Service/Batch.php b/src/Services/CRM/CallList/Service/Batch.php new file mode 100644 index 00000000..efa2f125 --- /dev/null +++ b/src/Services/CRM/CallList/Service/Batch.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; +use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult; +use Bitrix24\SDK\Services\AbstractBatchService; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListItemResult; +use Generator; + +#[ApiBatchServiceMetadata(new Scope(['crm']))] +class Batch extends AbstractBatchService +{ + /** + * batch calllist list method + * + * @param array $order - order of calllist items + * @param array $filter = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','CREATED_BY_ID'] + * @param array $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID'] + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'crm.calllist.list', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-list.html', + 'batch calllist list method' + )] + public function list(array $order = [], array $filter = [], array $select = [], ?int $limit = null): Generator + { + if ($select === []) { + $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID']; + } + + $this->log->debug( + 'list', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + ] + ); + foreach ($this->batch->getTraversableList('crm.calllist.list', $order, $filter, $select, $limit) as $key => $value) { + yield $key => new CallListItemResult($value); + } + } + + /** + * Batch adding calllist + * + * @param array $calllists + * + * @return Generator + */ + #[ApiBatchMethodMetadata( + 'crm.calllist.add', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-add.html', + 'Batch adding calllist' + )] + public function add(array $calllists): Generator + { + foreach ($this->batch->addEntityItems('crm.calllist.add', $calllists) as $key => $item) { + yield $key => new AddedItemBatchResult($item); + } + } + + /** + * Batch update calllists + * + * @param array $calllistItems + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'crm.calllist.update', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-update.html', + 'Update in batch mode a list of crm.calllists' + )] + public function update(array $calllistItems): Generator + { + foreach ($this->batch->updateEntityItems('crm.calllist.update', $calllistItems) as $key => $item) { + yield $key => new UpdatedItemBatchResult($item); + } + } +} diff --git a/src/Services/CRM/CallList/Service/CallList.php b/src/Services/CRM/CallList/Service/CallList.php new file mode 100644 index 00000000..29829ed6 --- /dev/null +++ b/src/Services/CRM/CallList/Service/CallList.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\CRM\CallList\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\AddedItemResult; +use Bitrix24\SDK\Core\Result\DeletedItemResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListResult; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListsResult; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListStatusesResult; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListItemsResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['crm']))] +class CallList extends AbstractService +{ + /** + * CallList constructor. + */ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Add new calllist + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.add', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-add.html', + 'Add new calllist' + )] + public function add(string $entityType, array $entities, int $webformId = 0): AddedItemResult + { + $params = [ + 'ENTITY_TYPE' => $entityType, + 'ENTITIES' => $entities, + ]; + if ($webformId !== 0) { + $params['WEBFORM_ID'] = $webformId; + } + + return new AddedItemResult( + $this->core->call( + 'crm.calllist.add', + $params + ) + ); + } + + /** + * Returns a calllist by the id. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-get.html + * + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.get', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-get.html', + 'Returns a calllist by the id.' + )] + public function get(int $id): CallListResult + { + return new CallListResult($this->core->call('crm.calllist.get', ['ID' => $id])); + } + + /** + * Get list of calllist items. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-list.html + * + * @param array $order - order of calllist items + * @param array $filter = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','CREATED_BY_ID'] + * @param array $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID'] + * @param int $startItem - entity number to start from (usually returned in 'next' field of previous 'crm.calllist.list' API call) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.list', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-list.html', + 'Get list of calllist items.' + )] + public function list(array $order = [], array $filter = [], array $select = [], int $startItem = 0): CallListsResult + { + if ($select === []) { + $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID']; + } + + return new CallListsResult( + $this->core->call( + 'crm.calllist.list', + [ + 'ORDER' => $order, + 'FILTER' => $filter, + 'SELECT' => $select, + 'start' => $startItem, + ] + ) + ); + } + + /** + * Updates the specified (existing) calllist. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.update', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-update.html', + 'Updates the specified (existing) calllist.' + )] + public function update(int $listId, string $entityType, array $entities, int $webformId = 0): UpdatedItemResult + { + $params = [ + 'LIST_ID' => $listId, + 'ENTITY_TYPE' => $entityType, + 'ENTITIES' => $entities, + ]; + if ($webformId !== 0) { + $params['WEBFORM_ID'] = $webformId; + } + + return new UpdatedItemResult( + $this->core->call( + 'crm.calllist.update', + $params + ) + ); + } + + /** + * Get list of calllist statuses. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-statuslist.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.statuslist', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-statuslist.html', + 'Get list of calllist statuses.' + )] + public function statusList(): CallListStatusesResult + { + return new CallListStatusesResult( + $this->core->call( + 'crm.calllist.statuslist', + [] + ) + ); + } + + /** + * Get list of calllist items. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-items-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'crm.calllist.items.get', + 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-items-get.html', + 'Get list of calllist items.' + )] + public function getItems(int $listId, array $filter = []): CallListItemsResult + { + return new CallListItemsResult( + $this->core->call( + 'crm.calllist.items.get', + [ + 'LIST_ID' => $listId, + 'FILTER' => $filter + ] + ) + ); + } +} diff --git a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php new file mode 100644 index 00000000..450b18e0 --- /dev/null +++ b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\CRM\CallList\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\CRM\CallList\Service\CallList; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\TestCase; + +/** + * Class BatchTest + * + * @package Bitrix24\SDK\Tests\Integration\Services\CRM\CallList\Service + */ +#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\CallList\Service\Batch::class)] +class BatchTest extends TestCase +{ + protected CallList $callListService; + + + protected function setUp(): void + { + $this->callListService = Fabric::getServiceBuilder()->getCRMScope()->callList(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch get call lists')] + public function testBatchList(): void + { + $callListNum = 60; + $allContactIds = []; + for ($i=0;$i<$callListNum;$i++) { + $contactIds = $this->addContacts(2); + $allContactIds = array_merge($allContactIds, $contactIds); + $this->callListService->add('CONTACT', $contactIds); + } + + $cnt = 0; + foreach ($this->callListService->batch->list() as $item) { + $cnt++; + } + + self::assertGreaterThanOrEqual($callListNum, $cnt); + + $this->deleteContacts($allContactIds); + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch add department')] + public function testBatchAdd(): void + { + $callListNum = 60; + $allContactIds = []; + $callLists = []; + for ($i=0;$i<$callListNum;$i++) { + $contactIds = $this->addContacts(2); + $allContactIds = array_merge($allContactIds, $contactIds); + $callLists[] = [ + 'ENTITY_TYPE' => 'CONTACT', + 'ENTITIES' => $contactIds + ]; + } + + $cnt = 0; + foreach ($this->callListService->batch->add($callLists) as $item) { + $cnt++; + } + + self::assertGreaterThanOrEqual($callListNum, $cnt); + + $this->deleteContacts($allContactIds); + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch update departments')] + public function testBatchUpdate(): void + { + $callListNum = 60; + $allContactIds = []; + $callListUpdates = []; + $callListIds = []; + for ($i=0;$i<$callListNum;$i++) { + $contactIds = $this->addContacts(2); + $allContactIds = array_merge($allContactIds, $contactIds); + $callListIds[] = $this->callListService->add('CONTACT', $contactIds)->getId(); + } + + foreach ($callListIds as $callListId) { + $contactIds = $this->addContacts(1); + $allContactIds = array_merge($allContactIds, $contactIds); + $callListUpdates[] = [ + 'LIST_ID' => $callListId, + 'ENTITY_TYPE' => 'CONTACT', + 'ENTITIES' => $contactIds + ]; + } + + $cnt = 0; + foreach ($this->callListService->batch->update($callListUpdates) as $updateResult) { + $cnt++; + self::assertTrue($updateResult->isSuccess()); + } + + self::assertGreaterThanOrEqual($callListNum, $cnt); + + $this->deleteContacts($allContactIds); + } + + protected function addContacts(int $num): array + { + $contactIds = []; + $contacts = []; + for ($i=1;$i<=$num;$i++) { + $contacts[] = [ + 'NAME' => 'Test contact #'.$i + ]; + } + + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->add($contacts) as $item) { + $contactIds[] = $item->getId(); + } + + return $contactIds; + } + + protected function deleteContacts(array $contactIds): void + { + echo "Contacts: \n"; + print_r($contactIds); + + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->delete($contactIds) as $item) { + self::assertTrue($item->isSuccess()); + } + } + +} diff --git a/tests/Integration/Services/CRM/CallList/Service/CallListTest.php b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php new file mode 100644 index 00000000..d0502031 --- /dev/null +++ b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\CRM\CallList\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core; +use Bitrix24\SDK\Services\CRM\CallList\Result\CallListItemResult; +use Bitrix24\SDK\Services\CRM\CallList\Service\CallList; +use Bitrix24\SDK\Services\CRM\Contact\Service\Contact; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; + +/** + * Class CallListTest + * + * @package Bitrix24\SDK\Tests\Integration\Services\CRM\CallList\Service + */ +#[CoversMethod(CallList::class,'add')] +#[CoversMethod(CallList::class,'get')] +#[CoversMethod(CallList::class,'list')] +#[CoversMethod(CallList::class,'update')] +#[CoversMethod(CallList::class,'countByFilter')] +#[CoversMethod(CallList::class,'statusList')] +#[CoversMethod(CallList::class,'getItems')] +#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\CallList\Service\CallList::class)] +class CallListTest extends TestCase +{ + use CustomBitrix24Assertions; + + protected CallList $callListService; + + protected array $contactIds = []; + + + protected function setUp(): void + { + $this->callListService = Fabric::getServiceBuilder()->getCRMScope()->callList(); + $this->contactIds = []; + $contacts = [ + ['NAME' => 'name-1'], + ['NAME' => 'name-2'], + ]; + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->add($contacts) as $item) { + $this->contactIds[] = $item->getId(); + } + } + + protected function tearDown(): void + { + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->delete($this->contactIds) as $item) { + // + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $listId = $this->callListService->add('CONTACT', $this->contactIds)->getId(); + self::assertGreaterThan(1, $listId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + $listId = $this->callListService->add('CONTACT', $this->contactIds)->getId(); + self::assertGreaterThan( + 1, + $this->callListService->get($listId)->calllist()->ID + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testList(): void + { + $listId = $this->callListService->add('CONTACT', $this->contactIds)->getId(); + self::assertGreaterThan( + 1, + $this->callListService->list([], ['ID' => $listId])->getCallLists()[0]->ID + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testStatusList(): void + { + $statuses = $this->callListService->statusList()->getStatuses(); + self::assertGreaterThan( + 1, + count($statuses) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGetItems(): void + { + $listId = $this->callListService->add('CONTACT', $this->contactIds)->getId(); + $items = $this->callListService->getItems($listId)->getItems(); + self::assertGreaterThan( + 1, + count($items) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testUpdate(): void + { + $listId = $this->callListService->add('CONTACT', [ $this->contactIds[0] ])->getId(); + + self::assertTrue($this->callListService->update($listId, 'CONTACT', $this->contactIds)->isSuccess()); + } + +}