diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 6e5393c3..15e131ad 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -29,6 +29,7 @@ ->in(__DIR__ . '/src/Services/SonetGroup/') ->in(__DIR__ . '/src/Services/IMOpenLines/') ->in(__DIR__ . '/src/Services/Landing/') + ->in(__DIR__ . '/src/Services/Catalog/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bc3b52..22468c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ ### Added +- Added service `Services\Catalog\Section` with support methods, + see [catalog.section.* methods](https://apidocs.bitrix24.com/api-reference/catalog/section/index.html) ([#583](https://github.com/bitrix24/b24phpsdk/issues/583)): + - `add` creates a new trade-catalog section, with batch calls support + - `update` updates an existing trade-catalog section, with batch calls support + - `get` gets a trade-catalog section by its identifier + - `list` gets the list of trade-catalog sections by filter + - `delete` deletes a trade-catalog section, with batch calls support + - `getFields` returns the description of trade-catalog section fields - Added service `Services\Landing\Site\Service\Site` with support methods, see [landing.site.* methods](https://github.com/bitrix24/b24phpsdk/issues/267): - `add` adds a site diff --git a/Makefile b/Makefile index c2fc4a9a..0e08c5b7 100644 --- a/Makefile +++ b/Makefile @@ -492,6 +492,13 @@ test-integration-landing-role: test-integration-landing-repowidget: docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_landing_repowidget +.PHONY: test-integration-catalog-section +test-integration-catalog-section: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_section +.PHONY: test-integration-catalog-section-annotations +test-integration-catalog-section-annotations: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_section_annotations + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d0187ae5..b7bd6749 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -262,6 +262,12 @@ ./tests/Integration/Services/Landing/RepoWidget/ + + ./tests/Integration/Services/Catalog/Section/Service/ + + + ./tests/Integration/Services/Catalog/Section/Result/SectionItemResultTest.php + diff --git a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php index e62fc7cb..189852bb 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php @@ -29,4 +29,4 @@ */ class CatalogItemResult extends AbstractCatalogItem { -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Result/CatalogResult.php b/src/Services/Catalog/Catalog/Result/CatalogResult.php index aa468aa8..c5222f11 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogResult.php @@ -21,4 +21,4 @@ public function catalog(): CatalogItemResult { return new CatalogItemResult($this->getCoreResponse()->getResponseData()->getResult()['catalog']); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Result/CatalogsResult.php b/src/Services/Catalog/Catalog/Result/CatalogsResult.php index add5f259..f721c3bb 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogsResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogsResult.php @@ -32,4 +32,4 @@ public function getCatalogs(): array return $res; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Service/Catalog.php b/src/Services/Catalog/Catalog/Service/Catalog.php index d9cb55fe..0da76ea3 100644 --- a/src/Services/Catalog/Catalog/Service/Catalog.php +++ b/src/Services/Catalog/Catalog/Service/Catalog.php @@ -82,4 +82,4 @@ public function fields(): FieldsResult { return new FieldsResult($this->core->call('catalog.catalog.getFields')); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/CatalogServiceBuilder.php b/src/Services/Catalog/CatalogServiceBuilder.php index 57c91b88..6724d597 100644 --- a/src/Services/Catalog/CatalogServiceBuilder.php +++ b/src/Services/Catalog/CatalogServiceBuilder.php @@ -17,6 +17,7 @@ use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Services\AbstractServiceBuilder; use Bitrix24\SDK\Services\Catalog; + #[ApiServiceBuilderMetadata(new Scope(['catalog']))] class CatalogServiceBuilder extends AbstractServiceBuilder { @@ -44,4 +45,20 @@ public function catalog(): Catalog\Catalog\Service\Catalog return $this->serviceCache[__METHOD__]; } -} \ No newline at end of file + + public function section(): Catalog\Section\Service\Section + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Catalog\Section\Service\Section( + new Catalog\Section\Service\Batch( + new Catalog\Section\Batch($this->core, $this->log), + $this->log + ), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/Catalog/Common/ProductType.php b/src/Services/Catalog/Common/ProductType.php index 0bf8ef09..ad568a47 100644 --- a/src/Services/Catalog/Common/ProductType.php +++ b/src/Services/Catalog/Common/ProductType.php @@ -20,4 +20,4 @@ enum ProductType: int case SKU = 3; case productOffer = 4; case genericOffer = 5; -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php index e2929d73..9761daed 100644 --- a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php +++ b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php @@ -109,4 +109,4 @@ protected function getKeyWithUserfieldByFieldName(string $fieldName) return $this->$fieldName; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductItemResult.php b/src/Services/Catalog/Product/Result/ProductItemResult.php index c576d9b7..58c80851 100644 --- a/src/Services/Catalog/Product/Result/ProductItemResult.php +++ b/src/Services/Catalog/Product/Result/ProductItemResult.php @@ -52,4 +52,4 @@ */ class ProductItemResult extends AbstractCatalogItem { -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductResult.php b/src/Services/Catalog/Product/Result/ProductResult.php index 2751fda8..fc83fa8c 100644 --- a/src/Services/Catalog/Product/Result/ProductResult.php +++ b/src/Services/Catalog/Product/Result/ProductResult.php @@ -26,4 +26,4 @@ public function product(): ProductItemResult return new ProductItemResult($this->getCoreResponse()->getResponseData()->getResult()['product']); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductsResult.php b/src/Services/Catalog/Product/Result/ProductsResult.php index aa644fa3..268d6d8f 100644 --- a/src/Services/Catalog/Product/Result/ProductsResult.php +++ b/src/Services/Catalog/Product/Result/ProductsResult.php @@ -31,4 +31,4 @@ public function getProducts(): array return $res; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Service/Batch.php b/src/Services/Catalog/Product/Service/Batch.php index 45dddcb7..42970546 100644 --- a/src/Services/Catalog/Product/Service/Batch.php +++ b/src/Services/Catalog/Product/Service/Batch.php @@ -25,7 +25,7 @@ { public function __construct( protected BatchOperationsInterface $batch, - protected LoggerInterface $log) - { + protected LoggerInterface $log + ) { } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Service/Product.php b/src/Services/Catalog/Product/Service/Product.php index 730fd7c9..d37d34f6 100644 --- a/src/Services/Catalog/Product/Service/Product.php +++ b/src/Services/Catalog/Product/Service/Product.php @@ -25,7 +25,6 @@ use Bitrix24\SDK\Services\Catalog\Common\ProductType; use Bitrix24\SDK\Services\Catalog\Product\Result\ProductResult; use Bitrix24\SDK\Services\Catalog\Product\Result\ProductsResult; - use Psr\Log\LoggerInterface; #[ApiServiceMetadata(new Scope(['catalog']))] @@ -35,8 +34,7 @@ public function __construct( public Batch $batch, CoreInterface $core, LoggerInterface $logger - ) - { + ) { parent::__construct($core, $logger); } @@ -71,7 +69,9 @@ public function get(int $productId): ProductResult )] public function add(array $productFields): ProductResult { - return new ProductResult($this->core->call('catalog.product.add', [ + return new ProductResult($this->core->call( + 'catalog.product.add', + [ 'fields' => $productFields ] )); @@ -140,4 +140,4 @@ public function fieldsByFilter(int $iblockId, ProductType $productType, ?array $ return new FieldsResult($this->core->call('catalog.product.getFieldsByFilter', ['filter' => $filter])); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Section/Batch.php b/src/Services/Catalog/Section/Batch.php new file mode 100644 index 00000000..b257245e --- /dev/null +++ b/src/Services/Catalog/Section/Batch.php @@ -0,0 +1,108 @@ + + * + * 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\Catalog\Section; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Generator; + +/** + * Class Batch + * + * Overrides base Batch to handle parameter naming differences in catalog.section.* REST methods: + * - delete uses lowercase 'id' instead of 'ID' + * + * @see https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-delete.html + * @see https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-list.html + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Determines the ID key — lowercase 'id' for catalog section + */ + #[\Override] + protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string + { + return 'id'; + } + + /** + * Delete entity items with batch call using lowercase 'id' parameter + * + * @param int[] $entityItemId + * @param array|null $additionalParameters + * + * @return Generator|ResponseData[] + * @throws BaseException + */ + #[\Override] + public function deleteEntityItems( + string $apiMethod, + array $entityItemId, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'deleteEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItemId, + 'additionalParameters' => $additionalParameters, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItemId as $cnt => $itemId) { + if (!is_int($itemId)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of section id «%s» at position %s, section id must be integer type', + gettype($itemId), + $itemId, + $cnt + ) + ); + } + + $this->registerCommand($apiMethod, ['id' => $itemId]); + } + + foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) { + yield $cnt => $deletedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch delete section items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete section items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('deleteEntityItems.finish'); + } +} diff --git a/src/Services/Catalog/Section/Result/SectionAddedBatchResult.php b/src/Services/Catalog/Section/Result/SectionAddedBatchResult.php new file mode 100644 index 00000000..7f7a5876 --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionAddedBatchResult.php @@ -0,0 +1,33 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Response\DTO\ResponseData; + +class SectionAddedBatchResult +{ + public function __construct(private readonly ResponseData $responseData) + { + } + + public function getResponseData(): ResponseData + { + return $this->responseData; + } + + public function section(): SectionItemResult + { + return new SectionItemResult($this->responseData->getResult()['section']); + } +} diff --git a/src/Services/Catalog/Section/Result/SectionFieldsResult.php b/src/Services/Catalog/Section/Result/SectionFieldsResult.php new file mode 100644 index 00000000..48b1f0ab --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionFieldsResult.php @@ -0,0 +1,29 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SectionFieldsResult extends AbstractResult +{ + /** + * @return array> + * @throws BaseException + */ + public function getFieldsDescription(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['section']; + } +} diff --git a/src/Services/Catalog/Section/Result/SectionItemResult.php b/src/Services/Catalog/Section/Result/SectionItemResult.php new file mode 100644 index 00000000..d1f88d65 --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionItemResult.php @@ -0,0 +1,32 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read int $iblockId + * @property-read int|null $iblockSectionId + * @property-read string $name + * @property-read string|null $xmlId + * @property-read string|null $code + * @property-read int|null $sort + * @property-read bool|null $active + * @property-read string|null $description + * @property-read string|null $descriptionType + */ +class SectionItemResult extends AbstractItem +{ +} diff --git a/src/Services/Catalog/Section/Result/SectionResult.php b/src/Services/Catalog/Section/Result/SectionResult.php new file mode 100644 index 00000000..0df7a328 --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionResult.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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SectionResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function section(): SectionItemResult + { + return new SectionItemResult($this->getCoreResponse()->getResponseData()->getResult()['section']); + } +} diff --git a/src/Services/Catalog/Section/Result/SectionUpdatedBatchResult.php b/src/Services/Catalog/Section/Result/SectionUpdatedBatchResult.php new file mode 100644 index 00000000..f381da3d --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionUpdatedBatchResult.php @@ -0,0 +1,33 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Response\DTO\ResponseData; + +class SectionUpdatedBatchResult +{ + public function __construct(private readonly ResponseData $responseData) + { + } + + public function getResponseData(): ResponseData + { + return $this->responseData; + } + + public function section(): SectionItemResult + { + return new SectionItemResult($this->responseData->getResult()['section']); + } +} diff --git a/src/Services/Catalog/Section/Result/SectionsResult.php b/src/Services/Catalog/Section/Result/SectionsResult.php new file mode 100644 index 00000000..d3290dc2 --- /dev/null +++ b/src/Services/Catalog/Section/Result/SectionsResult.php @@ -0,0 +1,34 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SectionsResult extends AbstractResult +{ + /** + * @return SectionItemResult[] + * @throws BaseException + */ + public function getSections(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_map( + static fn (array $item): SectionItemResult => new SectionItemResult($item), + $result['sections'] ?? [] + ); + } +} diff --git a/src/Services/Catalog/Section/Service/Batch.php b/src/Services/Catalog/Section/Service/Batch.php new file mode 100644 index 00000000..0f0f60c8 --- /dev/null +++ b/src/Services/Catalog/Section/Service/Batch.php @@ -0,0 +1,103 @@ + + * + * 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\Catalog\Section\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\DeletedItemBatchResult; +use Bitrix24\SDK\Services\Catalog\Section; +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionAddedBatchResult; +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionUpdatedBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['catalog']))] +class Batch +{ + public function __construct(protected Section\Batch $batch, protected LoggerInterface $log) + { + } + + /** + * Batch adding trade-catalog sections + * + * @param array $sections + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.section.add', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-add.html', + 'Batch adding trade-catalog sections' + )] + public function add(array $sections): Generator + { + $items = []; + foreach ($sections as $section) { + $items[] = ['fields' => $section]; + } + + foreach ($this->batch->addEntityItems('catalog.section.add', $items) as $key => $item) { + yield $key => new SectionAddedBatchResult($item); + } + } + + /** + * Batch delete trade-catalog sections + * + * @param int[] $sectionId + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.section.delete', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-delete.html', + 'Batch delete trade-catalog sections' + )] + public function delete(array $sectionId): Generator + { + foreach ($this->batch->deleteEntityItems('catalog.section.delete', $sectionId) as $key => $item) { + yield $key => new DeletedItemBatchResult($item); + } + } + + /** + * Batch update trade-catalog sections + * + * @param array $sections keyed by section id + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.section.update', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-update.html', + 'Batch update trade-catalog sections' + )] + public function update(array $sections): Generator + { + $items = []; + foreach ($sections as $id => $section) { + $items[$id] = ['fields' => $section]; + } + + foreach ($this->batch->updateEntityItems('catalog.section.update', $items) as $key => $item) { + yield $key => new SectionUpdatedBatchResult($item); + } + } +} diff --git a/src/Services/Catalog/Section/Service/Section.php b/src/Services/Catalog/Section/Service/Section.php new file mode 100644 index 00000000..f5d0a59e --- /dev/null +++ b/src/Services/Catalog/Section/Service/Section.php @@ -0,0 +1,152 @@ + + * + * 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\Catalog\Section\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\DeletedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionFieldsResult; +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionResult; +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionsResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['catalog']))] +class Section extends AbstractService +{ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Adds a new trade-catalog section + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.add', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-add.html', + 'Adds a new trade-catalog section' + )] + public function add(array $fields): SectionResult + { + return new SectionResult($this->core->call('catalog.section.add', ['fields' => $fields])); + } + + /** + * Updates a trade-catalog section by its identifier + * + * Note: despite the API documentation listing `iblockId` as optional on update, the live + * API rejects the call with "Required fields: iblockId" if it is omitted from $fields. + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.update', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-update.html', + 'Updates a trade-catalog section by its identifier' + )] + public function update(int $id, array $fields): SectionResult + { + return new SectionResult($this->core->call('catalog.section.update', ['id' => $id, 'fields' => $fields])); + } + + /** + * Returns a trade-catalog section by its identifier + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.get', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-get.html', + 'Returns a trade-catalog section by its identifier' + )] + public function get(int $id): SectionResult + { + return new SectionResult($this->core->call('catalog.section.get', ['id' => $id])); + } + + /** + * Returns a list of trade-catalog sections by filter + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-list.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.list', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-list.html', + 'Returns a list of trade-catalog sections by filter' + )] + public function list(array $select = [], array $filter = []): SectionsResult + { + return new SectionsResult( + $this->core->call( + 'catalog.section.list', + ['select' => $select, 'filter' => $filter] + ) + ); + } + + /** + * Deletes a trade-catalog section by identifier + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.delete', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-delete.html', + 'Deletes a trade-catalog section by identifier' + )] + public function delete(int $id): DeletedItemResult + { + return new DeletedItemResult($this->core->call('catalog.section.delete', ['id' => $id])); + } + + /** + * Returns the fields of a trade-catalog section + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-get-fields.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.section.getFields', + 'https://apidocs.bitrix24.com/api-reference/catalog/section/catalog-section-get-fields.html', + 'Returns the description of trade-catalog section fields' + )] + public function getFields(): SectionFieldsResult + { + return new SectionFieldsResult($this->core->call('catalog.section.getFields')); + } +} diff --git a/tests/Integration/Services/Catalog/Section/Result/SectionItemResultTest.php b/tests/Integration/Services/Catalog/Section/Result/SectionItemResultTest.php new file mode 100644 index 00000000..e6069cfa --- /dev/null +++ b/tests/Integration/Services/Catalog/Section/Result/SectionItemResultTest.php @@ -0,0 +1,79 @@ + + * + * 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\Catalog\Section\Result; + +use Bitrix24\SDK\Services\Catalog\Section\Result\SectionItemResult; +use Bitrix24\SDK\Services\Catalog\Section\Service\Section; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SectionItemResult::class)] +class SectionItemResultTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Section $sectionService; + + private int $sectionId; + + #[\Override] + protected function setUp(): void + { + $serviceBuilder = Fabric::getServiceBuilder(true); + $this->sectionService = $serviceBuilder->getCatalogScope()->section(); + $iblockId = $serviceBuilder->getCatalogScope()->catalog() + ->list([], [], [], 1)->getCatalogs()[0]->iblockId; + + $this->sectionId = $this->sectionService->add([ + 'name' => sprintf('test section annotations %s', time()), + 'iblockId' => $iblockId, + ])->section()->id; + } + + #[\Override] + protected function tearDown(): void + { + $this->sectionService->delete($this->sectionId); + } + + #[Test] + #[TestDox('all fields in SectionItemResult are annotated in phpdoc and match with raw api response')] + public function testAllFieldsAreAnnotated(): void + { + $rawItem = $this->sectionService->get($this->sectionId) + ->getCoreResponse()->getResponseData()->getResult()['section']; + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), SectionItemResult::class); + } + + #[Test] + #[TestDox('all fields in SectionItemResult have valid type annotation in phpdoc')] + public function testAllFieldsHasValidTypeAnnotation(): void + { + $allFields = $this->sectionService->getFields()->getFieldsDescription(); + foreach ($allFields as $field => $params) { + $newParams = []; + foreach ($params as $key => $value) { + $newParams[mb_strtolower((string)$key)] = $value; + } + + $allFields[$field] = $newParams; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation($allFields, SectionItemResult::class); + } +} diff --git a/tests/Integration/Services/Catalog/Section/Service/BatchTest.php b/tests/Integration/Services/Catalog/Section/Service/BatchTest.php new file mode 100644 index 00000000..0719b3fd --- /dev/null +++ b/tests/Integration/Services/Catalog/Section/Service/BatchTest.php @@ -0,0 +1,78 @@ + + * + * 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\Catalog\Section\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Catalog\Section\Service\Batch; +use Bitrix24\SDK\Services\Catalog\Section\Service\Section; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Batch::class)] +class BatchTest extends TestCase +{ + private Section $sectionService; + + private int $iblockId; + + #[\Override] + protected function setUp(): void + { + $serviceBuilder = Fabric::getServiceBuilder(true); + $this->sectionService = $serviceBuilder->getCatalogScope()->section(); + $this->iblockId = $serviceBuilder->getCatalogScope()->catalog() + ->list([], [], [], 1)->getCatalogs()[0]->iblockId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test Batch::add, Batch::update, Batch::delete')] + public function testAddUpdateDelete(): void + { + $addedIds = []; + foreach ($this->sectionService->batch->add([ + ['name' => sprintf('batch section %s', time()), 'iblockId' => $this->iblockId], + ]) as $addedItemResult) { + $addedIds[] = $addedItemResult->section()->id; + } + + $this->assertCount(1, $addedIds); + + $updatePayload = []; + foreach ($addedIds as $addedId) { + $updatePayload[$addedId] = ['name' => 'updated batch section', 'iblockId' => $this->iblockId]; + } + + $updatedCount = 0; + foreach ($this->sectionService->batch->update($updatePayload) as $updatedItemResult) { + $this->assertSame('updated batch section', $updatedItemResult->section()->name); + $updatedCount++; + } + + $this->assertSame(1, $updatedCount); + + $deletedCount = 0; + foreach ($this->sectionService->batch->delete($addedIds) as $deletedItemResult) { + $this->assertTrue($deletedItemResult->isSuccess()); + $deletedCount++; + } + + $this->assertSame(1, $deletedCount); + } +} diff --git a/tests/Integration/Services/Catalog/Section/Service/SectionTest.php b/tests/Integration/Services/Catalog/Section/Service/SectionTest.php new file mode 100644 index 00000000..bf29f3f5 --- /dev/null +++ b/tests/Integration/Services/Catalog/Section/Service/SectionTest.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\Tests\Integration\Services\Catalog\Section\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Catalog\Section\Service\Section; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Section::class)] +class SectionTest extends TestCase +{ + private Section $sectionService; + + private int $iblockId; + + /** + * @var int[] + */ + private array $createdSectionIds = []; + + #[\Override] + protected function setUp(): void + { + $serviceBuilder = Fabric::getServiceBuilder(true); + $this->sectionService = $serviceBuilder->getCatalogScope()->section(); + $this->iblockId = $serviceBuilder->getCatalogScope()->catalog() + ->list([], [], [], 1)->getCatalogs()[0]->iblockId; + } + + #[\Override] + protected function tearDown(): void + { + foreach ($this->createdSectionIds as $createdSectionId) { + try { + $this->sectionService->delete($createdSectionId); + } catch (\Throwable) { + // already removed, ignore + } + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test Section::add, Section::update, Section::get, Section::list, Section::delete')] + public function testAddUpdateGetListDelete(): void + { + $name = sprintf('test section %s', time()); + $sectionResult = $this->sectionService->add([ + 'name' => $name, + 'iblockId' => $this->iblockId, + ]); + $sectionId = $sectionResult->section()->id; + $this->createdSectionIds[] = $sectionId; + + $this->assertSame($name, $sectionResult->section()->name); + $this->assertSame($this->iblockId, $sectionResult->section()->iblockId); + + $updatedName = sprintf('updated test section %s', time()); + $updateResult = $this->sectionService->update($sectionId, ['name' => $updatedName, 'iblockId' => $this->iblockId]); + $this->assertSame($updatedName, $updateResult->section()->name); + + $getResult = $this->sectionService->get($sectionId); + $this->assertSame($updatedName, $getResult->section()->name); + + $sectionsResult = $this->sectionService->list([], ['iblockId' => $this->iblockId, 'id' => $sectionId]); + $this->assertCount(1, $sectionsResult->getSections()); + + $this->assertTrue($this->sectionService->delete($sectionId)->isSuccess()); + $this->createdSectionIds = array_diff($this->createdSectionIds, [$sectionId]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test Section::getFields')] + public function testGetFields(): void + { + $fieldsDescription = $this->sectionService->getFields()->getFieldsDescription(); + $this->assertIsArray($fieldsDescription); + $this->assertArrayHasKey('iblockId', $fieldsDescription); + $this->assertArrayHasKey('name', $fieldsDescription); + } +} diff --git a/tests/Unit/Services/Catalog/Section/Service/SectionTest.php b/tests/Unit/Services/Catalog/Section/Service/SectionTest.php new file mode 100644 index 00000000..cb3c1b1f --- /dev/null +++ b/tests/Unit/Services/Catalog/Section/Service/SectionTest.php @@ -0,0 +1,98 @@ +mockCore('catalog.section.add', [ + 'fields' => ['name' => 'Kids Toys', 'iblockId' => 14], + ]); + + self::assertInstanceOf( + SectionResult::class, + $this->makeService($core)->add(['name' => 'Kids Toys', 'iblockId' => 14]) + ); + } + + public function testUpdateBuildsParameters(): void + { + $core = $this->mockCore('catalog.section.update', [ + 'id' => 32, + 'fields' => ['name' => 'Updated name'], + ]); + + self::assertInstanceOf( + SectionResult::class, + $this->makeService($core)->update(32, ['name' => 'Updated name']) + ); + } + + public function testGetBuildsParameters(): void + { + $core = $this->mockCore('catalog.section.get', ['id' => 31]); + + self::assertInstanceOf(SectionResult::class, $this->makeService($core)->get(31)); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('catalog.section.list', [ + 'select' => ['id', 'name'], + 'filter' => ['iblockId' => 14], + ]); + + self::assertInstanceOf( + SectionsResult::class, + $this->makeService($core)->list(['id', 'name'], ['iblockId' => 14]) + ); + } + + public function testDeleteBuildsParameters(): void + { + $core = $this->mockCore('catalog.section.delete', ['id' => 31]); + + self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->delete(31)); + } + + public function testGetFieldsBuildsParameters(): void + { + $core = $this->mockCore('catalog.section.getFields', []); + + self::assertInstanceOf(SectionFieldsResult::class, $this->makeService($core)->getFields()); + } + + private function makeService(CoreInterface $core): Section + { + return new Section(new Batch(new SectionBatch($core, new NullLogger()), new NullLogger()), $core, new NullLogger()); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters) + ->willReturn($response); + + return $core; + } +}