From 0707bc585859beb6da1e8c9b2d0b74e14a0a63e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Thu, 13 Aug 2026 14:54:00 +0400 Subject: [PATCH 1/2] Add catalog.roundingRule methods --- .php-cs-fixer.php | 1 + CHANGELOG.md | 8 + Makefile | 4 + phpunit.xml.dist | 3 + .../Catalog/Result/CatalogItemResult.php | 2 +- .../Catalog/Catalog/Result/CatalogResult.php | 2 +- .../Catalog/Catalog/Result/CatalogsResult.php | 2 +- .../Catalog/Catalog/Service/Catalog.php | 2 +- .../Catalog/CatalogServiceBuilder.php | 19 ++- src/Services/Catalog/Common/ProductType.php | 2 +- .../Common/Result/AbstractCatalogItem.php | 2 +- .../Product/Result/ProductItemResult.php | 2 +- .../Catalog/Product/Result/ProductResult.php | 2 +- .../Catalog/Product/Result/ProductsResult.php | 2 +- .../Catalog/Product/Service/Batch.php | 6 +- .../Catalog/Product/Service/Product.php | 10 +- src/Services/Catalog/RoundingRule/Batch.php | 108 +++++++++++++ .../Result/RoundingRuleAddedBatchResult.php | 33 ++++ .../Result/RoundingRuleFieldsResult.php | 29 ++++ .../Result/RoundingRuleItemResult.php | 32 ++++ .../Result/RoundingRuleResult.php | 28 ++++ .../Result/RoundingRuleUpdatedBatchResult.php | 33 ++++ .../Result/RoundingRulesResult.php | 34 ++++ .../Catalog/RoundingRule/Service/Batch.php | 103 ++++++++++++ .../RoundingRule/Service/RoundingRule.php | 149 ++++++++++++++++++ .../RoundingRule/Service/BatchTest.php | 79 ++++++++++ .../RoundingRule/Service/RoundingRuleTest.php | 137 ++++++++++++++++ .../RoundingRule/Service/RoundingRuleTest.php | 99 ++++++++++++ 28 files changed, 915 insertions(+), 18 deletions(-) create mode 100644 src/Services/Catalog/RoundingRule/Batch.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRuleAddedBatchResult.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRuleFieldsResult.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRuleResult.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRuleUpdatedBatchResult.php create mode 100644 src/Services/Catalog/RoundingRule/Result/RoundingRulesResult.php create mode 100644 src/Services/Catalog/RoundingRule/Service/Batch.php create mode 100644 src/Services/Catalog/RoundingRule/Service/RoundingRule.php create mode 100644 tests/Integration/Services/Catalog/RoundingRule/Service/BatchTest.php create mode 100644 tests/Integration/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php create mode 100644 tests/Unit/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php 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..3e2a2268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ ### Added +- Added service `Services\Catalog\RoundingRule` with support methods, + see [catalog.roundingRule.* methods](https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/index.html) ([#573](https://github.com/bitrix24/b24phpsdk/issues/573)): + - `add` creates a new price rounding rule, with batch calls support + - `update` updates an existing price rounding rule, with batch calls support + - `list` gets the list of price rounding rules + - `delete` deletes a price rounding rule, with batch calls support + - `get` gets information about a price rounding rule by its identifier + - `getFields` returns the description of price rounding rule 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..843a5656 100644 --- a/Makefile +++ b/Makefile @@ -492,6 +492,10 @@ 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-rounding-rule +test-integration-catalog-rounding-rule: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_rounding_rule + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d0187ae5..b4e704e2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -262,6 +262,9 @@ ./tests/Integration/Services/Landing/RepoWidget/ + + ./tests/Integration/Services/Catalog/RoundingRule/ + 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..85197646 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 roundingRule(): Catalog\RoundingRule\Service\RoundingRule + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Catalog\RoundingRule\Service\RoundingRule( + new Catalog\RoundingRule\Service\Batch( + new Catalog\RoundingRule\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/RoundingRule/Batch.php b/src/Services/Catalog/RoundingRule/Batch.php new file mode 100644 index 00000000..516b40a4 --- /dev/null +++ b/src/Services/Catalog/RoundingRule/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\RoundingRule; + +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.roundingRule.* REST methods: + * - delete uses lowercase 'id' instead of 'ID' + * + * @see https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-delete.html + * @see https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-list.html + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Determines the ID key — lowercase 'id' for catalog rounding rule + */ + #[\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 rounding rule id «%s» at position %s, rounding rule 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 rounding rule items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete rounding rule 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/RoundingRule/Result/RoundingRuleAddedBatchResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleAddedBatchResult.php new file mode 100644 index 00000000..ab5c94ca --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleAddedBatchResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Response\DTO\ResponseData; + +class RoundingRuleAddedBatchResult +{ + public function __construct(private readonly ResponseData $responseData) + { + } + + public function getResponseData(): ResponseData + { + return $this->responseData; + } + + public function roundingRule(): RoundingRuleItemResult + { + return new RoundingRuleItemResult($this->responseData->getResult()['roundingRule']); + } +} diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRuleFieldsResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleFieldsResult.php new file mode 100644 index 00000000..4196d56c --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleFieldsResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RoundingRuleFieldsResult extends AbstractResult +{ + /** + * @return array> + * @throws BaseException + */ + public function getFieldsDescription(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['roundingRule']; + } +} diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php new file mode 100644 index 00000000..004d289b --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read int $catalogGroupId + * @property-read float $price + * @property-read int $roundType + * @property-read float $roundPrecision + * @property-read int|null $createdBy + * @property-read int|null $modifiedBy + * @property-read CarbonImmutable|null $dateCreate + * @property-read CarbonImmutable|null $dateModify + */ +class RoundingRuleItemResult extends AbstractItem +{ +} diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRuleResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleResult.php new file mode 100644 index 00000000..41556957 --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RoundingRuleResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function roundingRule(): RoundingRuleItemResult + { + return new RoundingRuleItemResult($this->getCoreResponse()->getResponseData()->getResult()['roundingRule']); + } +} diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRuleUpdatedBatchResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleUpdatedBatchResult.php new file mode 100644 index 00000000..e9862046 --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleUpdatedBatchResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Response\DTO\ResponseData; + +class RoundingRuleUpdatedBatchResult +{ + public function __construct(private readonly ResponseData $responseData) + { + } + + public function getResponseData(): ResponseData + { + return $this->responseData; + } + + public function roundingRule(): RoundingRuleItemResult + { + return new RoundingRuleItemResult($this->responseData->getResult()['roundingRule']); + } +} diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRulesResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRulesResult.php new file mode 100644 index 00000000..78b7d092 --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRulesResult.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\RoundingRule\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RoundingRulesResult extends AbstractResult +{ + /** + * @return RoundingRuleItemResult[] + * @throws BaseException + */ + public function getRoundingRules(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_map( + static fn (array $item): RoundingRuleItemResult => new RoundingRuleItemResult($item), + $result['roundingRules'] ?? [] + ); + } +} diff --git a/src/Services/Catalog/RoundingRule/Service/Batch.php b/src/Services/Catalog/RoundingRule/Service/Batch.php new file mode 100644 index 00000000..30d311fa --- /dev/null +++ b/src/Services/Catalog/RoundingRule/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\RoundingRule\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\RoundingRule; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRuleAddedBatchResult; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRuleUpdatedBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['catalog']))] +class Batch +{ + public function __construct(protected RoundingRule\Batch $batch, protected LoggerInterface $log) + { + } + + /** + * Batch adding price rounding rules + * + * @param array $roundingRules + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.roundingRule.add', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-add.html', + 'Batch adding price rounding rules' + )] + public function add(array $roundingRules): Generator + { + $items = []; + foreach ($roundingRules as $roundingRule) { + $items[] = ['fields' => $roundingRule]; + } + + foreach ($this->batch->addEntityItems('catalog.roundingRule.add', $items) as $key => $item) { + yield $key => new RoundingRuleAddedBatchResult($item); + } + } + + /** + * Batch delete price rounding rules + * + * @param int[] $roundingRuleId + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.roundingRule.delete', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-delete.html', + 'Batch delete price rounding rules' + )] + public function delete(array $roundingRuleId): Generator + { + foreach ($this->batch->deleteEntityItems('catalog.roundingRule.delete', $roundingRuleId) as $key => $item) { + yield $key => new DeletedItemBatchResult($item); + } + } + + /** + * Batch update price rounding rules + * + * @param array $roundingRules keyed by rounding rule id + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'catalog.roundingRule.update', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-update.html', + 'Batch update price rounding rules' + )] + public function update(array $roundingRules): Generator + { + $items = []; + foreach ($roundingRules as $id => $roundingRule) { + $items[$id] = ['fields' => $roundingRule]; + } + + foreach ($this->batch->updateEntityItems('catalog.roundingRule.update', $items) as $key => $item) { + yield $key => new RoundingRuleUpdatedBatchResult($item); + } + } +} diff --git a/src/Services/Catalog/RoundingRule/Service/RoundingRule.php b/src/Services/Catalog/RoundingRule/Service/RoundingRule.php new file mode 100644 index 00000000..28466ea3 --- /dev/null +++ b/src/Services/Catalog/RoundingRule/Service/RoundingRule.php @@ -0,0 +1,149 @@ + + * + * 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\RoundingRule\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\RoundingRule\Result\RoundingRuleFieldsResult; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRuleResult; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRulesResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['catalog']))] +class RoundingRule extends AbstractService +{ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Adds a new price rounding rule + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.add', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-add.html', + 'Adds a new price rounding rule' + )] + public function add(array $fields): RoundingRuleResult + { + return new RoundingRuleResult($this->core->call('catalog.roundingRule.add', ['fields' => $fields])); + } + + /** + * Updates a price rounding rule by its identifier + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.update', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-update.html', + 'Updates a price rounding rule by its identifier' + )] + public function update(int $id, array $fields): RoundingRuleResult + { + return new RoundingRuleResult($this->core->call('catalog.roundingRule.update', ['id' => $id, 'fields' => $fields])); + } + + /** + * Returns price rounding rule information by identifier + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.get', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-get.html', + 'Returns price rounding rule information by identifier' + )] + public function get(int $id): RoundingRuleResult + { + return new RoundingRuleResult($this->core->call('catalog.roundingRule.get', ['id' => $id])); + } + + /** + * Returns a list of price rounding rules by filter + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-list.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.list', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-list.html', + 'Returns a list of price rounding rules by filter' + )] + public function list(array $select = [], array $filter = [], array $order = []): RoundingRulesResult + { + return new RoundingRulesResult( + $this->core->call( + 'catalog.roundingRule.list', + ['select' => $select, 'filter' => $filter, 'order' => $order] + ) + ); + } + + /** + * Deletes a price rounding rule by identifier + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.delete', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-delete.html', + 'Deletes a price rounding rule by identifier' + )] + public function delete(int $id): DeletedItemResult + { + return new DeletedItemResult($this->core->call('catalog.roundingRule.delete', ['id' => $id])); + } + + /** + * Returns the fields of a price rounding rule + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-get-fields.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.roundingRule.getFields', + 'https://apidocs.bitrix24.com/api-reference/catalog/rounding-rule/catalog-rounding-rule-get-fields.html', + 'Returns the fields of a price rounding rule' + )] + public function getFields(): RoundingRuleFieldsResult + { + return new RoundingRuleFieldsResult($this->core->call('catalog.roundingRule.getFields')); + } +} diff --git a/tests/Integration/Services/Catalog/RoundingRule/Service/BatchTest.php b/tests/Integration/Services/Catalog/RoundingRule/Service/BatchTest.php new file mode 100644 index 00000000..f8980e16 --- /dev/null +++ b/tests/Integration/Services/Catalog/RoundingRule/Service/BatchTest.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\RoundingRule\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Service\Batch; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Service\RoundingRule; +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 +{ + // default «Base» price type identifier, always present on a Bitrix24 portal + private const BASE_CATALOG_GROUP_ID = 1; + + private RoundingRule $roundingRuleService; + + private int $catalogGroupId; + + #[\Override] + protected function setUp(): void + { + $this->roundingRuleService = Fabric::getServiceBuilder()->getCatalogScope()->roundingRule(); + $this->catalogGroupId = self::BASE_CATALOG_GROUP_ID; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test Batch::add, Batch::update, Batch::delete')] + public function testAddUpdateDelete(): void + { + $addedIds = []; + foreach ($this->roundingRuleService->batch->add([ + ['catalogGroupId' => $this->catalogGroupId, 'price' => 1000, 'roundType' => 4, 'roundPrecision' => 100], + ]) as $addedItemResult) { + $addedIds[] = $addedItemResult->roundingRule()->id; + } + + $this->assertCount(1, $addedIds); + + $updatePayload = []; + foreach ($addedIds as $addedId) { + $updatePayload[$addedId] = ['catalogGroupId' => $this->catalogGroupId, 'price' => 1500, 'roundType' => 2, 'roundPrecision' => 10]; + } + + $updatedCount = 0; + foreach ($this->roundingRuleService->batch->update($updatePayload) as $updatedItemResult) { + $this->assertSame(2, $updatedItemResult->roundingRule()->roundType); + $updatedCount++; + } + + $this->assertSame(1, $updatedCount); + + $deletedCount = 0; + foreach ($this->roundingRuleService->batch->delete($addedIds) as $deletedItemResult) { + $this->assertTrue($deletedItemResult->isSuccess()); + $deletedCount++; + } + + $this->assertSame(1, $deletedCount); + } +} diff --git a/tests/Integration/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php b/tests/Integration/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php new file mode 100644 index 00000000..8ecd6f9c --- /dev/null +++ b/tests/Integration/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php @@ -0,0 +1,137 @@ + + * + * 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\RoundingRule\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRuleItemResult; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Service\RoundingRule; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RoundingRule::class)] +class RoundingRuleTest extends TestCase +{ + use CustomBitrix24Assertions; + + // default «Base» price type identifier, always present on a Bitrix24 portal + private const BASE_CATALOG_GROUP_ID = 1; + + private RoundingRule $roundingRuleService; + + private int $catalogGroupId; + + #[\Override] + protected function setUp(): void + { + $this->roundingRuleService = Fabric::getServiceBuilder()->getCatalogScope()->roundingRule(); + $this->catalogGroupId = self::BASE_CATALOG_GROUP_ID; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test RoundingRule::add, RoundingRule::get, RoundingRule::delete')] + public function testAddGetDelete(): void + { + $roundingRuleResult = $this->roundingRuleService->add([ + 'catalogGroupId' => $this->catalogGroupId, + 'price' => 1000, + 'roundType' => 4, + 'roundPrecision' => 100, + ]); + $roundingRuleId = $roundingRuleResult->roundingRule()->id; + $this->assertSame($this->catalogGroupId, $roundingRuleResult->roundingRule()->catalogGroupId); + $this->assertSame(4, $roundingRuleResult->roundingRule()->roundType); + + $getResult = $this->roundingRuleService->get($roundingRuleId); + $this->assertSame($roundingRuleId, $getResult->roundingRule()->id); + + $this->assertTrue($this->roundingRuleService->delete($roundingRuleId)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test RoundingRule::update')] + public function testUpdate(): void + { + $roundingRuleId = $this->roundingRuleService->add([ + 'catalogGroupId' => $this->catalogGroupId, + 'price' => 1000, + 'roundType' => 4, + 'roundPrecision' => 100, + ])->roundingRule()->id; + + $roundingRuleResult = $this->roundingRuleService->update($roundingRuleId, [ + 'catalogGroupId' => $this->catalogGroupId, + 'price' => 1500, + 'roundType' => 2, + 'roundPrecision' => 10, + ]); + $this->assertSame(2, $roundingRuleResult->roundingRule()->roundType); + $this->assertSame(10.0, $roundingRuleResult->roundingRule()->roundPrecision); + + $this->roundingRuleService->delete($roundingRuleId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test RoundingRule::list')] + public function testList(): void + { + $roundingRuleId = $this->roundingRuleService->add([ + 'catalogGroupId' => $this->catalogGroupId, + 'price' => 1000, + 'roundType' => 4, + 'roundPrecision' => 100, + ])->roundingRule()->id; + + $roundingRulesResult = $this->roundingRuleService->list([], ['id' => $roundingRuleId]); + $this->assertCount(1, $roundingRulesResult->getRoundingRules()); + + $this->roundingRuleService->delete($roundingRuleId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[TestDox('test RoundingRule::getFields')] + public function testGetFields(): void + { + $this->assertIsArray($this->roundingRuleService->getFields()->getFieldsDescription()); + } + + #[TestDox('all fields in RoundingRuleItemResult are annotated in phpdoc')] + public function testAllFieldsAnnotated(): void + { + $fields = $this->roundingRuleService->getFields()->getFieldsDescription(); + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($fields), RoundingRuleItemResult::class); + } + + #[TestDox('all fields in RoundingRuleItemResult have valid type annotation')] + public function testAllFieldsHasValidTypeAnnotation(): void + { + $fields = $this->roundingRuleService->getFields()->getFieldsDescription(); + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation($fields, RoundingRuleItemResult::class); + } +} diff --git a/tests/Unit/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php b/tests/Unit/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php new file mode 100644 index 00000000..f8161f2c --- /dev/null +++ b/tests/Unit/Services/Catalog/RoundingRule/Service/RoundingRuleTest.php @@ -0,0 +1,99 @@ +mockCore('catalog.roundingRule.add', [ + 'fields' => ['catalogGroupId' => 1, 'price' => 1000.0, 'roundType' => 4, 'roundPrecision' => 100.0], + ]); + + self::assertInstanceOf( + RoundingRuleResult::class, + $this->makeService($core)->add(['catalogGroupId' => 1, 'price' => 1000.0, 'roundType' => 4, 'roundPrecision' => 100.0]) + ); + } + + public function testUpdateBuildsParameters(): void + { + $core = $this->mockCore('catalog.roundingRule.update', [ + 'id' => 2, + 'fields' => ['catalogGroupId' => 1, 'price' => 1500.0, 'roundType' => 2, 'roundPrecision' => 10.0], + ]); + + self::assertInstanceOf( + RoundingRuleResult::class, + $this->makeService($core)->update(2, ['catalogGroupId' => 1, 'price' => 1500.0, 'roundType' => 2, 'roundPrecision' => 10.0]) + ); + } + + public function testGetBuildsParameters(): void + { + $core = $this->mockCore('catalog.roundingRule.get', ['id' => 1]); + + self::assertInstanceOf(RoundingRuleResult::class, $this->makeService($core)->get(1)); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('catalog.roundingRule.list', [ + 'select' => ['id', 'price'], + 'filter' => ['modifiedBy' => 1], + 'order' => ['id' => 'ASC'], + ]); + + self::assertInstanceOf( + RoundingRulesResult::class, + $this->makeService($core)->list(['id', 'price'], ['modifiedBy' => 1], ['id' => 'ASC']) + ); + } + + public function testDeleteBuildsParameters(): void + { + $core = $this->mockCore('catalog.roundingRule.delete', ['id' => 2]); + + self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->delete(2)); + } + + public function testGetFieldsBuildsParameters(): void + { + $core = $this->mockCore('catalog.roundingRule.getFields', []); + + self::assertInstanceOf(RoundingRuleFieldsResult::class, $this->makeService($core)->getFields()); + } + + private function makeService(CoreInterface $core): RoundingRule + { + return new RoundingRule(new Batch(new RoundingRuleBatch($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; + } +} From a47e0267ef72a2eda26ba53538deb98a96207db0 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Thu, 13 Aug 2026 15:26:13 +0400 Subject: [PATCH 2/2] Fix on test results --- .../Result/RoundingRuleItemResult.php | 12 ++++++++++++ .../CustomBitrix24Assertions.php | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php b/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php index 004d289b..fae56948 100644 --- a/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php +++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php @@ -29,4 +29,16 @@ */ class RoundingRuleItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + return match ($offset) { + 'price', 'roundPrecision' => $this->data[$offset] !== null ? (float)$this->data[$offset] : null, + default => parent::__get($offset), + }; + } } diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php index dd1d32b7..c134d60d 100644 --- a/tests/CustomAssertions/CustomBitrix24Assertions.php +++ b/tests/CustomAssertions/CustomBitrix24Assertions.php @@ -19,6 +19,7 @@ use Bitrix24\SDK\Services\CRM\Activity\ActivityPriority; use Bitrix24\SDK\Services\CRM\Activity\ActivityStatus; use Bitrix24\SDK\Services\CRM\Activity\ActivityType; +use Bitrix24\SDK\Services\Catalog\RoundingRule\Result\RoundingRuleItemResult; use Carbon\CarbonImmutable; use MoneyPHP\Percentage\Percentage; use Typhoon\Reflection\TyphoonReflector; @@ -225,6 +226,21 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( ); break; } + // catalog.roundingRule fields are numeric values without a currency in the api response, not money amounts + if ($resultItemClassName === RoundingRuleItemResult::class && ($fieldCode === 'price' || $fieldCode === 'roundPrecision')) { + $this->assertTrue( + str_contains($propsFromAnnotations[$fieldCode], 'float'), + sprintf( + 'class «%s» field «%s» has invalid type phpdoc annotation «%s», field type from bitrix24 is «%s», expected sdk-type «%s»', + $resultItemClassName, + $fieldCode, + $propsFromAnnotations[$fieldCode], + $fieldData['type'], + 'float' + ) + ); + break; + } $this->assertTrue( str_contains($propsFromAnnotations[$fieldCode], 'Money\Money'), sprintf(