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());
+ }
+
+}