From 624739a1115ac2c4464fbe434ac6cf981e400bca Mon Sep 17 00:00:00 2001 From: Vadim Soluyanov Date: Tue, 29 Jul 2025 16:49:25 +0400 Subject: [PATCH 1/5] add classes and tests --- src/Services/CRM/CRMServiceBuilder.php | 17 ++ src/Services/CRM/CallList/Batch.php | 81 +++++++ .../CallList/Result/CallListItemResult.php | 31 +++ .../Result/CallListItemsItemResult.php | 27 +++ .../CallList/Result/CallListItemsResult.php | 40 +++ .../CRM/CallList/Result/CallListResult.php | 25 ++ .../Result/CallListStatusItemResult.php | 28 +++ .../Result/CallListStatusesResult.php | 40 +++ .../CRM/CallList/Result/CallListsResult.php | 40 +++ src/Services/CRM/CallList/Service/Batch.php | 101 ++++++++ .../CRM/CallList/Service/CallList.php | 228 ++++++++++++++++++ .../CRM/CallList/Service/BatchTest.php | 163 +++++++++++++ .../CRM/CallList/Service/CallListTest.php | 134 ++++++++++ 13 files changed, 955 insertions(+) create mode 100644 src/Services/CRM/CallList/Batch.php create mode 100644 src/Services/CRM/CallList/Result/CallListItemResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListItemsItemResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListItemsResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListStatusItemResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListStatusesResult.php create mode 100644 src/Services/CRM/CallList/Result/CallListsResult.php create mode 100644 src/Services/CRM/CallList/Service/Batch.php create mode 100644 src/Services/CRM/CallList/Service/CallList.php create mode 100644 tests/Integration/Services/CRM/CallList/Service/BatchTest.php create mode 100644 tests/Integration/Services/CRM/CallList/Service/CallListTest.php diff --git a/src/Services/CRM/CRMServiceBuilder.php b/src/Services/CRM/CRMServiceBuilder.php index 854c28e0..73d41d0c 100644 --- a/src/Services/CRM/CRMServiceBuilder.php +++ b/src/Services/CRM/CRMServiceBuilder.php @@ -528,4 +528,21 @@ 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__]; + } } diff --git a/src/Services/CRM/CallList/Batch.php b/src/Services/CRM/CallList/Batch.php new file mode 100644 index 00000000..516c747f --- /dev/null +++ b/src/Services/CRM/CallList/Batch.php @@ -0,0 +1,81 @@ + + * + * 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'); + } + + +} 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..93bd7a9b --- /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()); + } +} \ No newline at end of file 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..18f9b3d9 --- /dev/null +++ b/src/Services/CRM/CallList/Service/Batch.php @@ -0,0 +1,101 @@ + + * + * 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\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'] + * @param int|null $limit + * + * @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 + { + $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..eb931089 --- /dev/null +++ b/src/Services/CRM/CallList/Service/CallList.php @@ -0,0 +1,228 @@ + + * + * 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 Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['crm']))] +class CallList extends AbstractService +{ + public Batch $batch; + + /** + * CallList constructor. + * + * @param Batch $batch + * @param CoreInterface $core + * @param LoggerInterface $log + */ + public function __construct(Batch $batch, CoreInterface $core, LoggerInterface $log) + { + parent::__construct($core, $log); + $this->batch = $batch; + } + + /** + * Add new calllist + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-add.html + * + * @return AddedItemResult + * @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 + { + return new AddedItemResult( + $this->core->call( + 'crm.calllist.add', + [ + 'ENTITY_TYPE' => $entityType, + 'ENTITIES' => $entities, + 'WEBFORM_ID' => $webformId, + ] + ) + ); + } + + /** + * Returns a calllist by the id. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-get.html + * + * @param int $id + * + * @return CallListResult + * @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) + * + * @return CallListsResult + * @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 + { + 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 + * + * @return UpdatedItemResult + * @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 + { + return new UpdatedItemResult( + $this->core->call( + 'crm.calllist.update', + [ + 'LIST_ID' => $listId, + 'ENTITY_TYPE' => $entityType, + 'ENTITIES' => $entities, + 'WEBFORM_ID' => $webformId, + ] + ) + ); + } + + /** + * Count calllists by filter + * + * @param array{ + * ID?: int, + * ENTITY_TYPE_ID?: int, + * WEBFORM_ID?: int + * CREATED_BY_ID?: int + * } $filter + * + * @return int + * @throws BaseException + * @throws TransportException + */ + public function countByFilter(array $filter = []): int + { + return $this->list([], $filter, ['ID'], 1)->getCoreResponse()->getResponseData()->getPagination()->getTotal(); + } + + /** + * Get list of calllist statuses. + * + * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-statuslist.html + * + * @return CallListStatusesResult + * @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 CallListsResult( + $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 + * + * @return CallListItemsResult + * @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..d377358f --- /dev/null +++ b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php @@ -0,0 +1,163 @@ + + * + * 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\Department\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Department\Service\Department; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\TestCase; + +/** + * Class BatchTest + * + * @package Bitrix24\SDK\Tests\Integration\Services\Department\Service + */ +#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Department\Service\Batch::class)] +class BatchTest extends TestCase +{ + protected Department $departmentService; + + protected int $rootDepartmentId = 0; + + + protected function setUp(): void + { + $this->departmentService = Fabric::getServiceBuilder()->getDepartmentScope()->department(); + $dep = $this->departmentService->get(['PARENT' => 0])->getDepartments()[0]; + + $this->rootDepartmentId = intval($dep->ID); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch get departments')] + public function testBatchGet(): void + { + $depId = $this->departmentService->add('Test depart', $this->rootDepartmentId)->getId(); + $cnt = 0; + foreach ($this->departmentService->batch->get(['ID' => $depId]) as $item) { + $cnt++; + } + + self::assertGreaterThanOrEqual(1, $cnt); + + $this->departmentService->delete($depId); + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch add department')] + public function testBatchAdd(): void + { + $items = []; + for ($i = 1; $i < 60; $i++) { + $items[] = [ + 'NAME' => 'Dep-' . $i, + 'PARENT' => $this->rootDepartmentId + ]; + } + + $cnt = 0; + $depId = []; + foreach ($this->departmentService->batch->add($items) as $item) { + $cnt++; + $depId[] = $item->getId(); + } + + self::assertEquals(count($items), $cnt); + + $cnt = 0; + foreach ($this->departmentService->batch->delete($depId) as $cnt => $deleteResult) { + $cnt++; + } + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch delete departments')] + public function testBatchDelete(): void + { + $items = []; + for ($i = 1; $i < 60; $i++) { + $items[] = [ + 'NAME' => 'Dep-' . $i, + 'PARENT' => $this->rootDepartmentId + ]; + } + + $cnt = 0; + $depId = []; + foreach ($this->departmentService->batch->add($items) as $item) { + $cnt++; + $depId[] = $item->getId(); + } + + $cnt = 0; + foreach ($this->departmentService->batch->delete($depId) as $cnt => $deleteResult) { + $cnt++; + } + + self::assertEquals(count($items), $cnt); + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + #[\PHPUnit\Framework\Attributes\TestDox('Batch update departments')] + public function testBatchUpdate(): void + { + $items = []; + for ($i = 1; $i < 60; $i++) { + $items[] = [ + 'NAME' => 'Dep-' . $i, + 'PARENT' => $this->rootDepartmentId + ]; + } + + $cnt = 0; + $depIds = []; + foreach ($this->departmentService->batch->add($items) as $item) { + $cnt++; + $depIds[] = $item->getId(); + } + + $updates = []; + foreach ($depIds as $depId) { + $updates[$depId] = [ + 'NAME' => 'Updated '.$depId, + ]; + } + + $cnt = 0; + foreach ($this->departmentService->batch->update($updates) as $cnt => $updateResult) { + $cnt++; + self::assertTrue($updateResult->isSuccess()); + } + + self::assertEquals(count($updates), $cnt); + + $cnt = 0; + foreach ($this->departmentService->batch->delete($depIds) as $cnt => $deleteResult) { + $cnt++; + } + + self::assertEquals(count($items), $cnt); + } + +} 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..414e0fee --- /dev/null +++ b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php @@ -0,0 +1,134 @@ + + * + * 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(); + $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) { + // + } + } + + public function testAllSystemFieldsAnnotated(): void + { + $propListFromApi = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($this->callListService->fields()->getFieldsDescription())); + $this->assertBitrix24AllResultItemFieldsAnnotated($propListFromApi, CallListItemResult::class); + } + + /* + ignore because the result has code => name pairs only + public function testAllSystemFieldsHasValidTypeAnnotation():void + { + $allFields = $this->callListService->fields()->getFieldsDescription(); + $systemFieldsCodes = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($allFields)); + $systemFields = array_filter($allFields, static fn($code): bool => in_array($code, $systemFieldsCodes, true), ARRAY_FILTER_USE_KEY); + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $systemFields, + CallListItemResult::class); + } + */ + + /** + * @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 testUpdate(): void + { + $listId = $this->callListService->add('CONTACT', [ $this->contactIds[0] ])->getId(); + + self::assertTrue($this->callListService->update($listId, 'CONTACT', $this->contactIds)->isSuccess()); + } + + /** + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + * @throws \Bitrix24\SDK\Core\Exceptions\TransportException + */ + public function testCountByFilter(): void + { + $before = $this->callListService->countByFilter(); + $listId = $this->callListService->add('CONTACT', [ $this->contactIds[0] ])->getId(); + $after = $this->callListService->countByFilter(); + $this->assertEquals($before + 1, $after); + } +} From da4e90731d4c195011853f220cdcce81e2ecc209 Mon Sep 17 00:00:00 2001 From: Vadim Soluyanov Date: Wed, 30 Jul 2025 11:19:13 +0400 Subject: [PATCH 2/5] add test classes --- .php-cs-fixer.php | 1 + Makefile | 4 + phpstan.neon.dist | 1 + phpunit.xml.dist | 3 + rector.php | 2 + .../CRM/CallList/Service/BatchTest.php | 159 ++++++++---------- .../CRM/CallList/Service/CallListTest.php | 20 --- 7 files changed, 83 insertions(+), 107 deletions(-) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index ef49ce2f..a2629428 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/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/Makefile b/Makefile index c2f32b5c..833a6dc7 100644 --- a/Makefile +++ b/Makefile @@ -238,6 +238,10 @@ integration_tests_lead_productrows: .PHONY: integration_tests_crm_quote 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 # work dev environment .PHONY: php-dev-server-up diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 87d2a095..9fa96224 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 bootstrapFiles: - tests/bootstrap.php parallel: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6c5c2c80..253265bd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -85,6 +85,9 @@ ./tests/Integration/Services/CRM/Quote/ + + ./tests/Integration/Services/CRM/CallList/ + diff --git a/rector.php b/rector.php index 58bb6267..99b41baf 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__ . '/tests/Unit/', ]) ->withCache(cacheDirectory: __DIR__ . '.cache/rector') diff --git a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php index d377358f..6715f2cd 100644 --- a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php +++ b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php @@ -11,51 +11,52 @@ declare(strict_types=1); -namespace Bitrix24\SDK\Tests\Integration\Services\Department\Service; +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\Department\Service\Department; +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\Department\Service + * @package Bitrix24\SDK\Tests\Integration\Services\CRM\CallList\Service */ -#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Department\Service\Batch::class)] +#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\CallList\Service\Batch::class)] class BatchTest extends TestCase { - protected Department $departmentService; - - protected int $rootDepartmentId = 0; + protected CallList $callListService; protected function setUp(): void { - $this->departmentService = Fabric::getServiceBuilder()->getDepartmentScope()->department(); - $dep = $this->departmentService->get(['PARENT' => 0])->getDepartments()[0]; - - $this->rootDepartmentId = intval($dep->ID); + $this->callListService = Fabric::getServiceBuilder()->getCRMScope()->callList(); } /** * @throws BaseException * @throws TransportException */ - #[\PHPUnit\Framework\Attributes\TestDox('Batch get departments')] - public function testBatchGet(): void + #[\PHPUnit\Framework\Attributes\TestDox('Batch get call lists')] + public function testBatchList(): void { - $depId = $this->departmentService->add('Test depart', $this->rootDepartmentId)->getId(); + $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->departmentService->batch->get(['ID' => $depId]) as $item) { + foreach ($this->callListService->batch->list() as $item) { $cnt++; } - self::assertGreaterThanOrEqual(1, $cnt); - - $this->departmentService->delete($depId); + self::assertGreaterThanOrEqual($callListNum, $cnt); + + $this->deleteContacts($allContactIds); } /** @@ -64,56 +65,25 @@ public function testBatchGet(): void #[\PHPUnit\Framework\Attributes\TestDox('Batch add department')] public function testBatchAdd(): void { - $items = []; - for ($i = 1; $i < 60; $i++) { - $items[] = [ - 'NAME' => 'Dep-' . $i, - 'PARENT' => $this->rootDepartmentId - ]; - } - - $cnt = 0; - $depId = []; - foreach ($this->departmentService->batch->add($items) as $item) { - $cnt++; - $depId[] = $item->getId(); - } - - self::assertEquals(count($items), $cnt); - - $cnt = 0; - foreach ($this->departmentService->batch->delete($depId) as $cnt => $deleteResult) { - $cnt++; - } - } - - /** - * @throws \Bitrix24\SDK\Core\Exceptions\BaseException - */ - #[\PHPUnit\Framework\Attributes\TestDox('Batch delete departments')] - public function testBatchDelete(): void - { - $items = []; - for ($i = 1; $i < 60; $i++) { - $items[] = [ - 'NAME' => 'Dep-' . $i, - 'PARENT' => $this->rootDepartmentId + $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; - $depId = []; - foreach ($this->departmentService->batch->add($items) as $item) { - $cnt++; - $depId[] = $item->getId(); - } - $cnt = 0; - foreach ($this->departmentService->batch->delete($depId) as $cnt => $deleteResult) { + foreach ($this->callListService->batch->add($callLists) as $item) { $cnt++; } - self::assertEquals(count($items), $cnt); + self::assertGreaterThanOrEqual($callListNum, $cnt); + + $this->deleteContacts($allContactIds); } /** @@ -122,42 +92,57 @@ public function testBatchDelete(): void #[\PHPUnit\Framework\Attributes\TestDox('Batch update departments')] public function testBatchUpdate(): void { - $items = []; - for ($i = 1; $i < 60; $i++) { - $items[] = [ - 'NAME' => 'Dep-' . $i, - 'PARENT' => $this->rootDepartmentId - ]; - } - - $cnt = 0; - $depIds = []; - foreach ($this->departmentService->batch->add($items) as $item) { - $cnt++; - $depIds[] = $item->getId(); + $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(); } - $updates = []; - foreach ($depIds as $depId) { - $updates[$depId] = [ - 'NAME' => 'Updated '.$depId, + foreach ($callListIds as $callListId) { + $contactIds = $this->addContacts(1); + $allContactIds = array_merge($allContactIds, $contactIds); + $callListUpdates[] = [ + 'ENTITY_TYPE' => 'CONTACT', + 'ENTITIES' => $contactIds ]; } - + $cnt = 0; - foreach ($this->departmentService->batch->update($updates) as $cnt => $updateResult) { + foreach ($this->callListService->batch->update($callListUpdates) as $updateResult) { $cnt++; self::assertTrue($updateResult->isSuccess()); } - self::assertEquals(count($updates), $cnt); + self::assertGreaterThanOrEqual($callListNum, $cnt); - $cnt = 0; - foreach ($this->departmentService->batch->delete($depIds) as $cnt => $deleteResult) { - $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 + { + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->delete($contactIds) as $item) { + // } - - self::assertEquals(count($items), $cnt); } } diff --git a/tests/Integration/Services/CRM/CallList/Service/CallListTest.php b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php index 414e0fee..ef326191 100644 --- a/tests/Integration/Services/CRM/CallList/Service/CallListTest.php +++ b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php @@ -66,26 +66,6 @@ protected function tearDown(): void } } - public function testAllSystemFieldsAnnotated(): void - { - $propListFromApi = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($this->callListService->fields()->getFieldsDescription())); - $this->assertBitrix24AllResultItemFieldsAnnotated($propListFromApi, CallListItemResult::class); - } - - /* - ignore because the result has code => name pairs only - public function testAllSystemFieldsHasValidTypeAnnotation():void - { - $allFields = $this->callListService->fields()->getFieldsDescription(); - $systemFieldsCodes = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($allFields)); - $systemFields = array_filter($allFields, static fn($code): bool => in_array($code, $systemFieldsCodes, true), ARRAY_FILTER_USE_KEY); - - $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( - $systemFields, - CallListItemResult::class); - } - */ - /** * @throws BaseException * @throws TransportException From 1499058f423e26ac08d54be96397e3fd4bd36700 Mon Sep 17 00:00:00 2001 From: Vadim Soluyanov Date: Thu, 31 Jul 2025 09:59:02 +0400 Subject: [PATCH 3/5] add tests: testStatusList, testGetItems --- src/Services/CRM/CallList/Batch.php | 255 ++++++++++++++++++ src/Services/CRM/CallList/Service/Batch.php | 4 + .../CRM/CallList/Service/CallList.php | 54 ++-- .../CRM/CallList/Service/BatchTest.php | 6 +- .../CRM/CallList/Service/CallListTest.php | 52 +++- 5 files changed, 327 insertions(+), 44 deletions(-) diff --git a/src/Services/CRM/CallList/Batch.php b/src/Services/CRM/CallList/Batch.php index 516c747f..717ad85c 100644 --- a/src/Services/CRM/CallList/Batch.php +++ b/src/Services/CRM/CallList/Batch.php @@ -77,5 +77,260 @@ public function updateEntityItems(string $apiMethod, array $entityItems): Genera $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/Service/Batch.php b/src/Services/CRM/CallList/Service/Batch.php index 18f9b3d9..82e44fdc 100644 --- a/src/Services/CRM/CallList/Service/Batch.php +++ b/src/Services/CRM/CallList/Service/Batch.php @@ -18,6 +18,7 @@ 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; @@ -43,6 +44,9 @@ class Batch extends AbstractBatchService )] 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', [ diff --git a/src/Services/CRM/CallList/Service/CallList.php b/src/Services/CRM/CallList/Service/CallList.php index eb931089..a1adca8d 100644 --- a/src/Services/CRM/CallList/Service/CallList.php +++ b/src/Services/CRM/CallList/Service/CallList.php @@ -61,14 +61,17 @@ public function __construct(Batch $batch, CoreInterface $core, LoggerInterface $ )] 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', - [ - 'ENTITY_TYPE' => $entityType, - 'ENTITIES' => $entities, - 'WEBFORM_ID' => $webformId, - ] + $params ) ); } @@ -115,6 +118,9 @@ public function get(int $id): CallListResult )] 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', @@ -144,37 +150,21 @@ public function list(array $order = [], array $filter = [], array $select = [], )] 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', - [ - 'LIST_ID' => $listId, - 'ENTITY_TYPE' => $entityType, - 'ENTITIES' => $entities, - 'WEBFORM_ID' => $webformId, - ] + $params ) ); } - - /** - * Count calllists by filter - * - * @param array{ - * ID?: int, - * ENTITY_TYPE_ID?: int, - * WEBFORM_ID?: int - * CREATED_BY_ID?: int - * } $filter - * - * @return int - * @throws BaseException - * @throws TransportException - */ - public function countByFilter(array $filter = []): int - { - return $this->list([], $filter, ['ID'], 1)->getCoreResponse()->getResponseData()->getPagination()->getTotal(); - } /** * Get list of calllist statuses. @@ -190,9 +180,9 @@ public function countByFilter(array $filter = []): int 'https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-statuslist.html', 'Get list of calllist statuses.' )] - public function statuslist(): CallListStatusesResult + public function statusList(): CallListStatusesResult { - return new CallListsResult( + return new CallListStatusesResult( $this->core->call( 'crm.calllist.statuslist', [] ) diff --git a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php index 6715f2cd..22dd84bf 100644 --- a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php +++ b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php @@ -106,6 +106,7 @@ public function testBatchUpdate(): void $contactIds = $this->addContacts(1); $allContactIds = array_merge($allContactIds, $contactIds); $callListUpdates[] = [ + 'LIST_ID' => $callListId, 'ENTITY_TYPE' => 'CONTACT', 'ENTITIES' => $contactIds ]; @@ -140,8 +141,11 @@ protected function addContacts(int $num): array 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 index ef326191..d0502031 100644 --- a/tests/Integration/Services/CRM/CallList/Service/CallListTest.php +++ b/tests/Integration/Services/CRM/CallList/Service/CallListTest.php @@ -50,6 +50,7 @@ class CallListTest extends TestCase protected function setUp(): void { $this->callListService = Fabric::getServiceBuilder()->getCRMScope()->callList(); + $this->contactIds = []; $contacts = [ ['NAME' => 'name-1'], ['NAME' => 'name-2'], @@ -88,27 +89,56 @@ public function testGet(): void $this->callListService->get($listId)->calllist()->ID ); } - + /** * @throws BaseException * @throws TransportException */ - public function testUpdate(): void + public function testList(): void { - $listId = $this->callListService->add('CONTACT', [ $this->contactIds[0] ])->getId(); - - self::assertTrue($this->callListService->update($listId, 'CONTACT', $this->contactIds)->isSuccess()); + $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 \Bitrix24\SDK\Core\Exceptions\BaseException - * @throws \Bitrix24\SDK\Core\Exceptions\TransportException + * @throws BaseException + * @throws TransportException */ - public function testCountByFilter(): void + public function testUpdate(): void { - $before = $this->callListService->countByFilter(); $listId = $this->callListService->add('CONTACT', [ $this->contactIds[0] ])->getId(); - $after = $this->callListService->countByFilter(); - $this->assertEquals($before + 1, $after); + + self::assertTrue($this->callListService->update($listId, 'CONTACT', $this->contactIds)->isSuccess()); } + } From 34205a05238cab6c3db7cdc3bcae16e21cb39b01 Mon Sep 17 00:00:00 2001 From: Vadim Soluyanov Date: Thu, 31 Jul 2025 11:34:24 +0400 Subject: [PATCH 4/5] run linters after branch update --- .../CRM/CallList/Result/CallListResult.php | 2 +- src/Services/CRM/CallList/Service/Batch.php | 4 +-- .../CRM/CallList/Service/CallList.php | 30 +++++++------------ .../CRM/CallList/Service/BatchTest.php | 9 ++++-- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/Services/CRM/CallList/Result/CallListResult.php b/src/Services/CRM/CallList/Result/CallListResult.php index 93bd7a9b..725365b3 100644 --- a/src/Services/CRM/CallList/Result/CallListResult.php +++ b/src/Services/CRM/CallList/Result/CallListResult.php @@ -22,4 +22,4 @@ public function calllist(): CallListItemResult { return new CallListItemResult($this->getCoreResponse()->getResponseData()->getResult()); } -} \ No newline at end of file +} diff --git a/src/Services/CRM/CallList/Service/Batch.php b/src/Services/CRM/CallList/Service/Batch.php index 82e44fdc..efa2f125 100644 --- a/src/Services/CRM/CallList/Service/Batch.php +++ b/src/Services/CRM/CallList/Service/Batch.php @@ -32,7 +32,6 @@ class Batch extends AbstractBatchService * @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|null $limit * * @return Generator * @throws BaseException @@ -47,6 +46,7 @@ public function list(array $order = [], array $filter = [], array $select = [], if ($select === []) { $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID']; } + $this->log->debug( 'list', [ @@ -83,7 +83,7 @@ public function add(array $calllists): Generator yield $key => new AddedItemBatchResult($item); } } - + /** * Batch update calllists * diff --git a/src/Services/CRM/CallList/Service/CallList.php b/src/Services/CRM/CallList/Service/CallList.php index a1adca8d..29829ed6 100644 --- a/src/Services/CRM/CallList/Service/CallList.php +++ b/src/Services/CRM/CallList/Service/CallList.php @@ -25,24 +25,19 @@ 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 { - public Batch $batch; - /** * CallList constructor. - * - * @param Batch $batch - * @param CoreInterface $core - * @param LoggerInterface $log */ - public function __construct(Batch $batch, CoreInterface $core, LoggerInterface $log) + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) { - parent::__construct($core, $log); - $this->batch = $batch; + parent::__construct($core, $logger); } /** @@ -50,7 +45,6 @@ public function __construct(Batch $batch, CoreInterface $core, LoggerInterface $ * * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-add.html * - * @return AddedItemResult * @throws BaseException * @throws TransportException */ @@ -68,6 +62,7 @@ public function add(string $entityType, array $entities, int $webformId = 0): Ad if ($webformId !== 0) { $params['WEBFORM_ID'] = $webformId; } + return new AddedItemResult( $this->core->call( 'crm.calllist.add', @@ -81,9 +76,7 @@ public function add(string $entityType, array $entities, int $webformId = 0): Ad * * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-get.html * - * @param int $id * - * @return CallListResult * @throws BaseException * @throws TransportException */ @@ -107,7 +100,6 @@ public function get(int $id): CallListResult * @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) * - * @return CallListsResult * @throws BaseException * @throws TransportException */ @@ -121,6 +113,7 @@ public function list(array $order = [], array $filter = [], array $select = [], if ($select === []) { $select = ['ID','ENTITY_TYPE_ID','WEBFORM_ID','DATE_CREATE','CREATED_BY_ID']; } + return new CallListsResult( $this->core->call( 'crm.calllist.list', @@ -139,7 +132,6 @@ public function list(array $order = [], array $filter = [], array $select = [], * * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-update.html * - * @return UpdatedItemResult * @throws BaseException * @throws TransportException */ @@ -158,6 +150,7 @@ public function update(int $listId, string $entityType, array $entities, int $we if ($webformId !== 0) { $params['WEBFORM_ID'] = $webformId; } + return new UpdatedItemResult( $this->core->call( 'crm.calllist.update', @@ -165,13 +158,12 @@ public function update(int $listId, string $entityType, array $entities, int $we ) ); } - + /** * Get list of calllist statuses. * * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-statuslist.html * - * @return CallListStatusesResult * @throws BaseException * @throws TransportException */ @@ -184,17 +176,17 @@ public function statusList(): CallListStatusesResult { return new CallListStatusesResult( $this->core->call( - 'crm.calllist.statuslist', [] + 'crm.calllist.statuslist', + [] ) ); } - + /** * Get list of calllist items. * * @link https://apidocs.bitrix24.com/api-reference/crm/call-list/crm-call-list-items-get.html * - * @return CallListItemsResult * @throws BaseException * @throws TransportException */ diff --git a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php index 22dd84bf..450b18e0 100644 --- a/tests/Integration/Services/CRM/CallList/Service/BatchTest.php +++ b/tests/Integration/Services/CRM/CallList/Service/BatchTest.php @@ -49,13 +49,14 @@ public function testBatchList(): void $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); } @@ -76,13 +77,14 @@ public function testBatchAdd(): void 'ENTITIES' => $contactIds ]; } + $cnt = 0; foreach ($this->callListService->batch->add($callLists) as $item) { $cnt++; } self::assertGreaterThanOrEqual($callListNum, $cnt); - + $this->deleteContacts($allContactIds); } @@ -132,10 +134,11 @@ protected function addContacts(int $num): array 'NAME' => 'Test contact #'.$i ]; } + foreach (Fabric::getServiceBuilder()->getCRMScope()->contact()->batch->add($contacts) as $item) { $contactIds[] = $item->getId(); } - + return $contactIds; } From 7d9306ec83a40588b646056b844e6d9ceaa66aee Mon Sep 17 00:00:00 2001 From: Vadim Soluyanov Date: Thu, 31 Jul 2025 13:29:03 +0400 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d7a8155..374908a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## UPCOMING 1.6.0 – 2025.09.01 +### 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 + + ## 1.5.0 – 2025.08.01 ### Added