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..fae56948
--- /dev/null
+++ b/src/Services/Catalog/RoundingRule/Result/RoundingRuleItemResult.php
@@ -0,0 +1,44 @@
+
+ *
+ * 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
+{
+ /**
+ * @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/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/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(
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;
+ }
+}