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..796ddf46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,40 @@
### Added
+- Added `update` and `download` methods to `Services\Catalog\Product\Service\Product` for
+ `catalog.product.update` / `catalog.product.download`,
+ see [catalog.product.* methods](https://apidocs.bitrix24.com/api-reference/catalog/product/index.html) ([#527](https://github.com/bitrix24/b24phpsdk/issues/527))
+- Added service `Services\Catalog\Product\ProductService\Service\ProductService` with support for
+ `catalog.product.service.*` methods,
+ see [catalog.product.service.* methods](https://apidocs.bitrix24.com/api-reference/catalog/product/service/index.html) ([#527](https://github.com/bitrix24/b24phpsdk/issues/527)):
+ - `add` creates a new service
+ - `update` updates an existing service
+ - `get` gets information about the service by its identifier
+ - `list` gets the list of services by filter
+ - `delete` deletes a service
+ - `fieldsByFilter` returns service field descriptions by iblock filter (`catalog.product.service.getFieldsByFilter`)
+ - `download` downloads a service file
+- Added service `Services\Catalog\Product\Sku\Service\Sku` with support for `catalog.product.sku.*`
+ methods,
+ see [catalog.product.sku.* methods](https://apidocs.bitrix24.com/api-reference/catalog/product/sku/index.html) ([#527](https://github.com/bitrix24/b24phpsdk/issues/527)):
+ - `add` creates a new parent (SKU) product
+ - `update` updates an existing parent product
+ - `get` gets information about the parent product by its identifier
+ - `list` gets the list of parent products by filter
+ - `delete` deletes a parent product
+ - `fieldsByFilter` returns parent product field descriptions by iblock filter (`catalog.product.sku.getFieldsByFilter`)
+ - `download` downloads a parent product file
+- Added service `Services\Catalog\Product\Offer\Service\Offer` with support for
+ `catalog.product.offer.*` methods,
+ see [catalog.product.offer.* methods](https://apidocs.bitrix24.com/api-reference/catalog/product/offer/index.html) ([#527](https://github.com/bitrix24/b24phpsdk/issues/527)):
+ - `add` creates a new product variation (offer)
+ - `update` updates an existing product variation
+ - `get` gets information about the product variation by its identifier
+ - `list` gets the list of product variations by filter
+ - `delete` deletes a product variation
+ - `fieldsByFilter` returns product variation field descriptions by iblock filter (`catalog.product.offer.getFieldsByFilter`)
+ - `download` downloads a product variation file
+
- 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
@@ -156,6 +190,11 @@
All classes under `Bitrix24\SDK\Legacy\` are marked `@deprecated` and will be removed
once v3 reaches feature parity with v1.
+### Fixed
+
+- Fixed `Services\Catalog\Catalog\Result\CatalogsResult::getCatalogs()` returning `ProductItemResult`
+ instances instead of `CatalogItemResult` ([#527](https://github.com/bitrix24/b24phpsdk/issues/527))
+
## 3.0.0 - 2026.01.01
### Added
diff --git a/Makefile b/Makefile
index c2fc4a9a..c3d2488a 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,12 @@ help:
@echo "test-integration-landing-role - run Landing Role integration tests"
@echo "test-integration-landing-repowidget - run Landing RepoWidget integration tests"
@echo "test-integration-scope-landing-template - run Landing Template integration tests"
+ @echo "test-integration-scope-catalog - run Catalog scope integration tests"
+ @echo "test-integration-catalog-catalog - run Catalog Catalog integration tests"
+ @echo "test-integration-catalog-product - run Catalog Product integration tests"
+ @echo "test-integration-catalog-product-service - run Catalog ProductService integration tests"
+ @echo "test-integration-catalog-product-sku - run Catalog Product Sku integration tests"
+ @echo "test-integration-catalog-product-offer - run Catalog Product Offer integration tests"
.PHONY: docker-init
@@ -492,6 +498,30 @@ 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-scope-catalog
+test-integration-scope-catalog:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_catalog
+
+.PHONY: test-integration-catalog-catalog
+test-integration-catalog-catalog:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_catalog
+
+.PHONY: test-integration-catalog-product
+test-integration-catalog-product:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_product
+
+.PHONY: test-integration-catalog-product-service
+test-integration-catalog-product-service:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_product_service
+
+.PHONY: test-integration-catalog-product-sku
+test-integration-catalog-product-sku:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_product_sku
+
+.PHONY: test-integration-catalog-product-offer
+test-integration-catalog-product-offer:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_product_offer
+
# work dev environment
.PHONY: php-dev-server-up
php-dev-server-up:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d0187ae5..f51c2705 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -262,6 +262,27 @@
./tests/Integration/Services/Landing/RepoWidget/
+
+ ./tests/Integration/Services/Catalog/
+
+
+ ./tests/Integration/Services/Catalog/Catalog/Service/CatalogTest.php
+
+
+ ./tests/Integration/Services/Catalog/Product/Service/ProductTest.php
+
+
+ ./tests/Integration/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php
+ ./tests/Integration/Services/Catalog/Product/ProductService/Result/ProductServiceItemResultTest.php
+
+
+ ./tests/Integration/Services/Catalog/Product/Sku/Service/SkuTest.php
+ ./tests/Integration/Services/Catalog/Product/Sku/Result/SkuItemResultTest.php
+
+
+ ./tests/Integration/Services/Catalog/Product/Offer/Service/OfferTest.php
+ ./tests/Integration/Services/Catalog/Product/Offer/Result/OfferItemResultTest.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..1fb78b88 100644
--- a/src/Services/Catalog/Catalog/Result/CatalogsResult.php
+++ b/src/Services/Catalog/Catalog/Result/CatalogsResult.php
@@ -15,21 +15,20 @@
use Bitrix24\SDK\Core\Exceptions\BaseException;
use Bitrix24\SDK\Core\Result\AbstractResult;
-use Bitrix24\SDK\Services\Catalog\Product\Result\ProductItemResult;
class CatalogsResult extends AbstractResult
{
/**
- * @return ProductItemResult[]
+ * @return CatalogItemResult[]
* @throws BaseException
*/
public function getCatalogs(): array
{
$res = [];
- foreach ($this->getCoreResponse()->getResponseData()->getResult()['catalogs'] as $product) {
- $res[] = new ProductItemResult($product);
+ foreach ($this->getCoreResponse()->getResponseData()->getResult()['catalogs'] as $catalog) {
+ $res[] = new CatalogItemResult($catalog);
}
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..73be0c8d 100644
--- a/src/Services/Catalog/CatalogServiceBuilder.php
+++ b/src/Services/Catalog/CatalogServiceBuilder.php
@@ -17,9 +17,22 @@
use Bitrix24\SDK\Core\Credentials\Scope;
use Bitrix24\SDK\Services\AbstractServiceBuilder;
use Bitrix24\SDK\Services\Catalog;
+
#[ApiServiceBuilderMetadata(new Scope(['catalog']))]
class CatalogServiceBuilder extends AbstractServiceBuilder
{
+ public function catalog(): Catalog\Catalog\Service\Catalog
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Catalog\Catalog\Service\Catalog(
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
public function product(): Catalog\Product\Service\Product
{
if (!isset($this->serviceCache[__METHOD__])) {
@@ -33,10 +46,34 @@ public function product(): Catalog\Product\Service\Product
return $this->serviceCache[__METHOD__];
}
- public function catalog(): Catalog\Catalog\Service\Catalog
+ public function productService(): Catalog\Product\ProductService\Service\ProductService
{
if (!isset($this->serviceCache[__METHOD__])) {
- $this->serviceCache[__METHOD__] = new Catalog\Catalog\Service\Catalog(
+ $this->serviceCache[__METHOD__] = new Catalog\Product\ProductService\Service\ProductService(
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function productSku(): Catalog\Product\Sku\Service\Sku
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Catalog\Product\Sku\Service\Sku(
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function productOffer(): Catalog\Product\Offer\Service\Offer
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Catalog\Product\Offer\Service\Offer(
$this->core,
$this->log
);
@@ -44,4 +81,4 @@ public function catalog(): Catalog\Catalog\Service\Catalog
return $this->serviceCache[__METHOD__];
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/Common/ProductType.php b/src/Services/Catalog/Common/ProductType.php
index 0bf8ef09..6b3b8428 100644
--- a/src/Services/Catalog/Common/ProductType.php
+++ b/src/Services/Catalog/Common/ProductType.php
@@ -20,4 +20,6 @@ enum ProductType: int
case SKU = 3;
case productOffer = 4;
case genericOffer = 5;
-}
\ No newline at end of file
+ case skuMaster = 6;
+ case service = 7;
+}
diff --git a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php
index e2929d73..762af872 100644
--- a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php
+++ b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php
@@ -39,15 +39,21 @@ public function __construct(array $data, ?Currency $currency = null)
* @return bool|CarbonImmutable|int|mixed|null
*/
+ #[\Override]
public function __get($offset)
{
switch ($offset) {
case 'active':
case 'available':
case 'bundle':
+ case 'vatIncluded':
+ case 'withoutOrder':
+ case 'subscribe':
+ case 'quantityTrace':
return $this->data[$offset] === 'Y';
case 'barcodeMulti':
case 'canBuyZero':
+ case 'recurSchemeType':
if ($this->data[$offset] !== null) {
return $this->data[$offset] === 'Y';
}
@@ -67,8 +73,7 @@ public function __get($offset)
case 'id':
case 'modifiedBy':
case 'sort':
- case 'height':
- case 'length':
+ case 'vatId':
if ($this->data[$offset] !== '' && $this->data[$offset] !== null) {
return (int)$this->data[$offset];
}
@@ -78,7 +83,7 @@ public function __get($offset)
case 'dateActiveTo':
case 'dateCreate':
case 'timestampX':
- if ($this->data[$offset] !== '') {
+ if ($this->data[$offset] !== '' && $this->data[$offset] !== null) {
return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]);
}
@@ -109,4 +114,4 @@ protected function getKeyWithUserfieldByFieldName(string $fieldName)
return $this->$fieldName;
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/Product/Offer/Result/OfferItemResult.php b/src/Services/Catalog/Product/Offer/Result/OfferItemResult.php
new file mode 100644
index 00000000..7d58d2ab
--- /dev/null
+++ b/src/Services/Catalog/Product/Offer/Result/OfferItemResult.php
@@ -0,0 +1,68 @@
+
+ *
+ * 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\Product\Offer\Result;
+
+use Bitrix24\SDK\Services\Catalog\Common\ProductType;
+use Bitrix24\SDK\Services\Catalog\Common\Result\AbstractCatalogItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read bool $active
+ * @property-read bool $available
+ * @property-read ?bool $barcodeMulti
+ * @property-read bool $bundle
+ * @property-read ?bool $canBuyZero
+ * @property-read string $code
+ * @property-read int $createdBy
+ * @property-read CarbonImmutable|null $dateActiveFrom
+ * @property-read CarbonImmutable|null $dateActiveTo
+ * @property-read CarbonImmutable $dateCreate
+ * @property-read array|null $detailPicture
+ * @property-read string $detailText
+ * @property-read string $detailTextType
+ * @property-read ?string $height
+ * @property-read int $id
+ * @property-read int $iblockId
+ * @property-read ?array $iblockSection
+ * @property-read ?int $iblockSectionId
+ * @property-read ?string $length
+ * @property-read ?int $measure
+ * @property-read int $modifiedBy
+ * @property-read string $name
+ * @property-read ?array $parentId
+ * @property-read array|null $previewPicture
+ * @property-read string $previewText
+ * @property-read string $previewTextType
+ * @property-read ?string $purchasingCurrency
+ * @property-read ?string $purchasingPrice
+ * @property-read ?string $quantity
+ * @property-read ?string $quantityReserved
+ * @property-read bool $quantityTrace
+ * @property-read ?int $recurSchemeLength
+ * @property-read ?bool $recurSchemeType
+ * @property-read int $sort
+ * @property-read bool $subscribe
+ * @property-read CarbonImmutable $timestampX
+ * @property-read ?int $trialPriceId
+ * @property-read ProductType $type
+ * @property-read ?int $vatId
+ * @property-read bool $vatIncluded
+ * @property-read ?string $weight
+ * @property-read ?string $width
+ * @property-read bool $withoutOrder
+ * @property-read string $xmlId
+ */
+class OfferItemResult extends AbstractCatalogItem
+{
+}
diff --git a/src/Services/Catalog/Product/Offer/Result/OfferResult.php b/src/Services/Catalog/Product/Offer/Result/OfferResult.php
new file mode 100644
index 00000000..2e7a7455
--- /dev/null
+++ b/src/Services/Catalog/Product/Offer/Result/OfferResult.php
@@ -0,0 +1,24 @@
+
+ *
+ * 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\Product\Offer\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class OfferResult extends AbstractResult
+{
+ public function offer(): OfferItemResult
+ {
+ return new OfferItemResult($this->getCoreResponse()->getResponseData()->getResult()['offer']);
+ }
+}
diff --git a/src/Services/Catalog/Product/Offer/Result/OffersResult.php b/src/Services/Catalog/Product/Offer/Result/OffersResult.php
new file mode 100644
index 00000000..4e657953
--- /dev/null
+++ b/src/Services/Catalog/Product/Offer/Result/OffersResult.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\Product\Offer\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class OffersResult extends AbstractResult
+{
+ /**
+ * @return OfferItemResult[]
+ * @throws BaseException
+ */
+ public function getOffers(): array
+ {
+ $res = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult()['offers'] as $offer) {
+ $res[] = new OfferItemResult($offer);
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/Catalog/Product/Offer/Service/Offer.php b/src/Services/Catalog/Product/Offer/Service/Offer.php
new file mode 100644
index 00000000..b1feb835
--- /dev/null
+++ b/src/Services/Catalog/Product/Offer/Service/Offer.php
@@ -0,0 +1,171 @@
+
+ *
+ * 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\Product\Offer\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\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Result\OfferResult;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Result\OffersResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['catalog']))]
+class Offer extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * The method adds a product variation (offer) to the commercial catalog.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-add.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-add.html',
+ 'The method adds a product variation (offer) to the commercial catalog.'
+ )]
+ public function add(array $fields): OfferResult
+ {
+ return new OfferResult($this->core->call('catalog.product.offer.add', ['fields' => $fields]));
+ }
+
+ /**
+ * The method updates a product variation (offer) in the commercial catalog by its identifier.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-update.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-update.html',
+ 'The method updates a product variation (offer) in the commercial catalog by its identifier.'
+ )]
+ public function update(int $offerId, array $fields): OfferResult
+ {
+ return new OfferResult($this->core->call('catalog.product.offer.update', [
+ 'id' => $offerId,
+ 'fields' => $fields,
+ ]));
+ }
+
+ /**
+ * The method gets field values of a product variation (offer) by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-get.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.get',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-get.html',
+ 'The method gets field values of a product variation (offer) by ID.'
+ )]
+ public function get(int $offerId): OfferResult
+ {
+ return new OfferResult($this->core->call('catalog.product.offer.get', ['id' => $offerId]));
+ }
+
+ /**
+ * The method gets a list of product variations (offers) by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-list.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.list',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-list.html',
+ 'The method gets a list of product variations (offers) by filter.'
+ )]
+ public function list(array $select, array $filter, array $order = []): OffersResult
+ {
+ return new OffersResult($this->core->call('catalog.product.offer.list', [
+ 'select' => $select,
+ 'filter' => $filter,
+ 'order' => $order,
+ ]));
+ }
+
+ /**
+ * The method deletes a product variation (offer) by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-delete.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-delete.html',
+ 'The method deletes a product variation (offer) by ID.'
+ )]
+ public function delete(int $offerId): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.product.offer.delete', ['id' => $offerId]));
+ }
+
+ /**
+ * The method returns product variation (offer) fields by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-get-fields-by-filter.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.getFieldsByFilter',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-get-fields-by-filter.html',
+ 'The method returns product variation (offer) fields by filter.'
+ )]
+ public function fieldsByFilter(int $iblockId): FieldsResult
+ {
+ return new FieldsResult($this->core->call('catalog.product.offer.getFieldsByFilter', [
+ 'filter' => ['iblockId' => $iblockId],
+ ]));
+ }
+
+ /**
+ * The method downloads product variation (offer) files by the given parameters.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-download.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.offer.download',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/offer/catalog-product-offer-download.html',
+ 'The method downloads product variation (offer) files by the given parameters.'
+ )]
+ public function download(int $fileId, int $productId, string $fieldName): Response
+ {
+ return $this->core->call('catalog.product.offer.download', [
+ 'fields' => [
+ 'fileId' => $fileId,
+ 'productId' => $productId,
+ 'fieldName' => $fieldName,
+ ],
+ ]);
+ }
+}
diff --git a/src/Services/Catalog/Product/ProductService/Result/ProductServiceItemResult.php b/src/Services/Catalog/Product/ProductService/Result/ProductServiceItemResult.php
new file mode 100644
index 00000000..e33bd76b
--- /dev/null
+++ b/src/Services/Catalog/Product/ProductService/Result/ProductServiceItemResult.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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\Product\ProductService\Result;
+
+use Bitrix24\SDK\Services\Catalog\Common\ProductType;
+use Bitrix24\SDK\Services\Catalog\Common\Result\AbstractCatalogItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read bool $active
+ * @property-read bool $available
+ * @property-read bool $bundle
+ * @property-read string $code
+ * @property-read int $createdBy
+ * @property-read CarbonImmutable|null $dateActiveFrom
+ * @property-read CarbonImmutable|null $dateActiveTo
+ * @property-read CarbonImmutable $dateCreate
+ * @property-read array|null $detailPicture
+ * @property-read string $detailText
+ * @property-read string $detailTextType
+ * @property-read int $id
+ * @property-read int $iblockId
+ * @property-read ?array $iblockSection
+ * @property-read ?int $iblockSectionId
+ * @property-read ?int $measure
+ * @property-read int $modifiedBy
+ * @property-read string $name
+ * @property-read array|null $previewPicture
+ * @property-read string $previewText
+ * @property-read string $previewTextType
+ * @property-read int $sort
+ * @property-read CarbonImmutable $timestampX
+ * @property-read ProductType $type
+ * @property-read ?int $vatId
+ * @property-read bool $vatIncluded
+ * @property-read string $xmlId
+ */
+class ProductServiceItemResult extends AbstractCatalogItem
+{
+}
diff --git a/src/Services/Catalog/Product/ProductService/Result/ProductServiceResult.php b/src/Services/Catalog/Product/ProductService/Result/ProductServiceResult.php
new file mode 100644
index 00000000..caf43c27
--- /dev/null
+++ b/src/Services/Catalog/Product/ProductService/Result/ProductServiceResult.php
@@ -0,0 +1,24 @@
+
+ *
+ * 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\Product\ProductService\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class ProductServiceResult extends AbstractResult
+{
+ public function productService(): ProductServiceItemResult
+ {
+ return new ProductServiceItemResult($this->getCoreResponse()->getResponseData()->getResult()['service']);
+ }
+}
diff --git a/src/Services/Catalog/Product/ProductService/Result/ProductServicesResult.php b/src/Services/Catalog/Product/ProductService/Result/ProductServicesResult.php
new file mode 100644
index 00000000..7ce9b208
--- /dev/null
+++ b/src/Services/Catalog/Product/ProductService/Result/ProductServicesResult.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\Product\ProductService\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class ProductServicesResult extends AbstractResult
+{
+ /**
+ * @return ProductServiceItemResult[]
+ * @throws BaseException
+ */
+ public function getProductServices(): array
+ {
+ $res = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult()['services'] as $service) {
+ $res[] = new ProductServiceItemResult($service);
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/Catalog/Product/ProductService/Service/ProductService.php b/src/Services/Catalog/Product/ProductService/Service/ProductService.php
new file mode 100644
index 00000000..662a7836
--- /dev/null
+++ b/src/Services/Catalog/Product/ProductService/Service/ProductService.php
@@ -0,0 +1,171 @@
+
+ *
+ * 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\Product\ProductService\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\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Result\ProductServiceResult;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Result\ProductServicesResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['catalog']))]
+class ProductService extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * The method adds a service to the commercial catalog.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-add.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-add.html',
+ 'The method adds a service to the commercial catalog.'
+ )]
+ public function add(array $fields): ProductServiceResult
+ {
+ return new ProductServiceResult($this->core->call('catalog.product.service.add', ['fields' => $fields]));
+ }
+
+ /**
+ * The method updates a service in the commercial catalog by its identifier.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-update.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-update.html',
+ 'The method updates a service in the commercial catalog by its identifier.'
+ )]
+ public function update(int $serviceId, array $fields): ProductServiceResult
+ {
+ return new ProductServiceResult($this->core->call('catalog.product.service.update', [
+ 'id' => $serviceId,
+ 'fields' => $fields,
+ ]));
+ }
+
+ /**
+ * The method gets field values of a commercial catalog service by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-get.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.get',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-get.html',
+ 'The method gets field values of a commercial catalog service by ID.'
+ )]
+ public function get(int $serviceId): ProductServiceResult
+ {
+ return new ProductServiceResult($this->core->call('catalog.product.service.get', ['id' => $serviceId]));
+ }
+
+ /**
+ * The method gets a list of commercial catalog services by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-list.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.list',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-list.html',
+ 'The method gets a list of commercial catalog services by filter.'
+ )]
+ public function list(array $select, array $filter, array $order = []): ProductServicesResult
+ {
+ return new ProductServicesResult($this->core->call('catalog.product.service.list', [
+ 'select' => $select,
+ 'filter' => $filter,
+ 'order' => $order,
+ ]));
+ }
+
+ /**
+ * The method deletes a commercial catalog service by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-delete.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-delete.html',
+ 'The method deletes a commercial catalog service by ID.'
+ )]
+ public function delete(int $serviceId): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.product.service.delete', ['id' => $serviceId]));
+ }
+
+ /**
+ * The method returns commercial catalog service fields by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-get-fields-by-filter.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.getFieldsByFilter',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-get-fields-by-filter.html',
+ 'The method returns commercial catalog service fields by filter.'
+ )]
+ public function fieldsByFilter(int $iblockId): FieldsResult
+ {
+ return new FieldsResult($this->core->call('catalog.product.service.getFieldsByFilter', [
+ 'filter' => ['iblockId' => $iblockId],
+ ]));
+ }
+
+ /**
+ * The method downloads commercial catalog service files by the given parameters.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-download.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.service.download',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/service/catalog-product-service-download.html',
+ 'The method downloads commercial catalog service files by the given parameters.'
+ )]
+ public function download(int $fileId, int $productId, string $fieldName): Response
+ {
+ return $this->core->call('catalog.product.service.download', [
+ 'fields' => [
+ 'fileId' => $fileId,
+ 'productId' => $productId,
+ 'fieldName' => $fieldName,
+ ],
+ ]);
+ }
+}
diff --git a/src/Services/Catalog/Product/Result/ProductItemResult.php b/src/Services/Catalog/Product/Result/ProductItemResult.php
index c576d9b7..d8e6fa29 100644
--- a/src/Services/Catalog/Product/Result/ProductItemResult.php
+++ b/src/Services/Catalog/Product/Result/ProductItemResult.php
@@ -37,8 +37,8 @@
* @property-read int $iblockId
* @property-read int $iblockSectionId
* @property-read int $modifiedBy
- * @property-read ?int $height
- * @property-read ?int $length
+ * @property-read ?string $height
+ * @property-read ?string $length
* @property-read mixed $measure
* @property-read string $name
* @property-read array|null $previewPicture
@@ -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..a0df5172 100644
--- a/src/Services/Catalog/Product/Service/Product.php
+++ b/src/Services/Catalog/Product/Service/Product.php
@@ -19,13 +19,13 @@
use Bitrix24\SDK\Core\Credentials\Scope;
use Bitrix24\SDK\Core\Exceptions\BaseException;
use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core\Response\Response;
use Bitrix24\SDK\Core\Result\DeletedItemResult;
use Bitrix24\SDK\Core\Result\FieldsResult;
use Bitrix24\SDK\Services\AbstractService;
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,21 +35,20 @@ public function __construct(
public Batch $batch,
CoreInterface $core,
LoggerInterface $logger
- )
- {
+ ) {
parent::__construct($core, $logger);
}
/**
* The method gets field value of commercial catalog product by ID.
*
- * @see https://training.bitrix24.com/rest_help/catalog/product/catalog_product_get.php
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-get.html
* @throws TransportException
* @throws BaseException
*/
#[ApiEndpointMetadata(
'catalog.product.get',
- 'https://training.bitrix24.com/rest_help/catalog/product/catalog_product_get.php',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-get.html',
'The method gets field value of commercial catalog product by ID.'
)]
public function get(int $productId): ProductResult
@@ -60,18 +59,20 @@ public function get(int $productId): ProductResult
/**
* The method adds a commercial catalog product.
*
- * @see https://training.bitrix24.com/rest_help/catalog/product/catalog_product_add.php
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-add.html
* @throws BaseException
* @throws TransportException
*/
#[ApiEndpointMetadata(
'catalog.product.add',
- 'https://training.bitrix24.com/rest_help/catalog/product/catalog_product_add.php',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-add.html',
'The method adds a commercial catalog product.'
)]
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
]
));
@@ -80,13 +81,13 @@ public function add(array $productFields): ProductResult
/**
* The method deletes commercial catalog product.
*
- * @see https://training.bitrix24.com/rest_help/catalog/product/catalog_product_delete.php
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-delete.html
* @throws BaseException
* @throws TransportException
*/
#[ApiEndpointMetadata(
'catalog.product.delete',
- 'https://training.bitrix24.com/rest_help/catalog/product/catalog_product_delete.php',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-delete.html',
'The method deletes commercial catalog product by ID'
)]
public function delete(int $productId): DeletedItemResult
@@ -97,13 +98,13 @@ public function delete(int $productId): DeletedItemResult
/**
* The method gets list of commercial catalog products by filter.
*
- * @see https://training.bitrix24.com/rest_help/catalog/product/catalog_product_list.php
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-list.html
* @throws TransportException
* @throws BaseException
*/
#[ApiEndpointMetadata(
'catalog.product.list',
- 'https://training.bitrix24.com/rest_help/catalog/product/catalog_product_list.php',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-list.html',
'The method gets list of commercial catalog products by filter.'
)]
public function list(array $select, array $filter, array $order, int $start): ProductsResult
@@ -118,14 +119,14 @@ public function list(array $select, array $filter, array $order, int $start): Pr
/**
* The method returns commercial catalog product fields by filter.
- * @see https://training.bitrix24.com/rest_help/catalog/product/catalog_product_getfieldsbyfilter.php
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-get-fields-by-filter.html
*
* @throws BaseException
* @throws TransportException
*/
#[ApiEndpointMetadata(
'catalog.product.getFieldsByFilter',
- 'https://training.bitrix24.com/rest_help/catalog/product/catalog_product_getfieldsbyfilter.php',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-get-fields-by-filter.html',
'The method returns commercial catalog product fields by filter.'
)]
public function fieldsByFilter(int $iblockId, ProductType $productType, ?array $additionalFilter = null): FieldsResult
@@ -140,4 +141,47 @@ 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
+
+ /**
+ * The method updates commercial catalog product by ID with the given fields.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-update.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-update.html',
+ 'The method updates commercial catalog product by ID with the given fields.'
+ )]
+ public function update(int $productId, array $productFields): ProductResult
+ {
+ return new ProductResult($this->core->call('catalog.product.update', [
+ 'id' => $productId,
+ 'fields' => $productFields,
+ ]));
+ }
+
+ /**
+ * The method downloads commercial catalog product files by the given parameters.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-download.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.download',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/catalog-product-download.html',
+ 'The method downloads commercial catalog product files by the given parameters.'
+ )]
+ public function download(int $fileId, int $productId, string $fieldName): Response
+ {
+ return $this->core->call('catalog.product.download', [
+ 'fields' => [
+ 'fileId' => $fileId,
+ 'productId' => $productId,
+ 'fieldName' => $fieldName,
+ ],
+ ]);
+ }
+}
diff --git a/src/Services/Catalog/Product/Sku/Result/SkuItemResult.php b/src/Services/Catalog/Product/Sku/Result/SkuItemResult.php
new file mode 100644
index 00000000..337e5b8a
--- /dev/null
+++ b/src/Services/Catalog/Product/Sku/Result/SkuItemResult.php
@@ -0,0 +1,60 @@
+
+ *
+ * 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\Product\Sku\Result;
+
+use Bitrix24\SDK\Services\Catalog\Common\ProductType;
+use Bitrix24\SDK\Services\Catalog\Common\Result\AbstractCatalogItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read bool $active
+ * @property-read bool $available
+ * @property-read bool $bundle
+ * @property-read ?bool $canBuyZero
+ * @property-read string $code
+ * @property-read int $createdBy
+ * @property-read CarbonImmutable|null $dateActiveFrom
+ * @property-read CarbonImmutable|null $dateActiveTo
+ * @property-read CarbonImmutable $dateCreate
+ * @property-read array|null $detailPicture
+ * @property-read string $detailText
+ * @property-read string $detailTextType
+ * @property-read ?string $height
+ * @property-read int $id
+ * @property-read int $iblockId
+ * @property-read ?array $iblockSection
+ * @property-read ?int $iblockSectionId
+ * @property-read ?string $length
+ * @property-read ?int $measure
+ * @property-read int $modifiedBy
+ * @property-read string $name
+ * @property-read array|null $previewPicture
+ * @property-read string $previewText
+ * @property-read string $previewTextType
+ * @property-read ?string $purchasingCurrency
+ * @property-read ?string $purchasingPrice
+ * @property-read ?string $quantity
+ * @property-read int $sort
+ * @property-read bool $subscribe
+ * @property-read CarbonImmutable $timestampX
+ * @property-read ProductType $type
+ * @property-read ?int $vatId
+ * @property-read bool $vatIncluded
+ * @property-read ?string $weight
+ * @property-read ?string $width
+ * @property-read string $xmlId
+ */
+class SkuItemResult extends AbstractCatalogItem
+{
+}
diff --git a/src/Services/Catalog/Product/Sku/Result/SkuResult.php b/src/Services/Catalog/Product/Sku/Result/SkuResult.php
new file mode 100644
index 00000000..5257292e
--- /dev/null
+++ b/src/Services/Catalog/Product/Sku/Result/SkuResult.php
@@ -0,0 +1,24 @@
+
+ *
+ * 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\Product\Sku\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class SkuResult extends AbstractResult
+{
+ public function sku(): SkuItemResult
+ {
+ return new SkuItemResult($this->getCoreResponse()->getResponseData()->getResult()['sku']);
+ }
+}
diff --git a/src/Services/Catalog/Product/Sku/Result/SkusResult.php b/src/Services/Catalog/Product/Sku/Result/SkusResult.php
new file mode 100644
index 00000000..2176fc6f
--- /dev/null
+++ b/src/Services/Catalog/Product/Sku/Result/SkusResult.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\Product\Sku\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class SkusResult extends AbstractResult
+{
+ /**
+ * @return SkuItemResult[]
+ * @throws BaseException
+ */
+ public function getSkus(): array
+ {
+ $res = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult()['units'] as $unit) {
+ $res[] = new SkuItemResult($unit);
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/Catalog/Product/Sku/Service/Sku.php b/src/Services/Catalog/Product/Sku/Service/Sku.php
new file mode 100644
index 00000000..09f80d4d
--- /dev/null
+++ b/src/Services/Catalog/Product/Sku/Service/Sku.php
@@ -0,0 +1,171 @@
+
+ *
+ * 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\Product\Sku\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\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Result\SkuResult;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Result\SkusResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['catalog']))]
+class Sku extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * The method adds a parent (SKU) product to the commercial catalog.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-add.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-add.html',
+ 'The method adds a parent (SKU) product to the commercial catalog.'
+ )]
+ public function add(array $fields): SkuResult
+ {
+ return new SkuResult($this->core->call('catalog.product.sku.add', ['fields' => $fields]));
+ }
+
+ /**
+ * The method updates a parent (SKU) product in the commercial catalog by its identifier.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-update.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-update.html',
+ 'The method updates a parent (SKU) product in the commercial catalog by its identifier.'
+ )]
+ public function update(int $skuId, array $fields): SkuResult
+ {
+ return new SkuResult($this->core->call('catalog.product.sku.update', [
+ 'id' => $skuId,
+ 'fields' => $fields,
+ ]));
+ }
+
+ /**
+ * The method gets field values of a parent (SKU) product by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-get.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.get',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-get.html',
+ 'The method gets field values of a parent (SKU) product by ID.'
+ )]
+ public function get(int $skuId): SkuResult
+ {
+ return new SkuResult($this->core->call('catalog.product.sku.get', ['id' => $skuId]));
+ }
+
+ /**
+ * The method gets a list of parent (SKU) products by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-list.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.list',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-list.html',
+ 'The method gets a list of parent (SKU) products by filter.'
+ )]
+ public function list(array $select, array $filter, array $order = []): SkusResult
+ {
+ return new SkusResult($this->core->call('catalog.product.sku.list', [
+ 'select' => $select,
+ 'filter' => $filter,
+ 'order' => $order,
+ ]));
+ }
+
+ /**
+ * The method deletes a parent (SKU) product by ID.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-delete.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-delete.html',
+ 'The method deletes a parent (SKU) product by ID.'
+ )]
+ public function delete(int $skuId): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.product.sku.delete', ['id' => $skuId]));
+ }
+
+ /**
+ * The method returns parent (SKU) product fields by filter.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-get-fields-by-filter.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.getFieldsByFilter',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-get-fields-by-filter.html',
+ 'The method returns parent (SKU) product fields by filter.'
+ )]
+ public function fieldsByFilter(int $iblockId): FieldsResult
+ {
+ return new FieldsResult($this->core->call('catalog.product.sku.getFieldsByFilter', [
+ 'filter' => ['iblockId' => $iblockId],
+ ]));
+ }
+
+ /**
+ * The method downloads parent (SKU) product files by the given parameters.
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-download.html
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.product.sku.download',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/product/sku/catalog-product-sku-download.html',
+ 'The method downloads parent (SKU) product files by the given parameters.'
+ )]
+ public function download(int $fileId, int $productId, string $fieldName): Response
+ {
+ return $this->core->call('catalog.product.sku.download', [
+ 'fields' => [
+ 'fileId' => $fileId,
+ 'productId' => $productId,
+ 'fieldName' => $fieldName,
+ ],
+ ]);
+ }
+}
diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php
index dd1d32b7..4a47e163 100644
--- a/tests/CustomAssertions/CustomBitrix24Assertions.php
+++ b/tests/CustomAssertions/CustomBitrix24Assertions.php
@@ -19,18 +19,19 @@
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\Common\ProductType;
use Carbon\CarbonImmutable;
use MoneyPHP\Percentage\Percentage;
use Typhoon\Reflection\TyphoonReflector;
-use function Typhoon\Type\stringify;
use Money\Currency;
+use function Typhoon\Type\stringify;
+
trait CustomBitrix24Assertions
{
/**
* @param array $fieldCodesFromApi
* @param class-string $resultItemClassName
- * @return void
*/
protected function assertBitrix24AllResultItemFieldsAnnotated(
array $fieldCodesFromApi,
@@ -39,16 +40,17 @@ protected function assertBitrix24AllResultItemFieldsAnnotated(
sort($fieldCodesFromApi);
// parse keys from phpdoc annotation
- $props = TyphoonReflector::build()->reflectClass($resultItemClassName)->properties();
+ $collection = TyphoonReflector::build()->reflectClass($resultItemClassName)->properties();
$propsFromAnnotations = [];
- foreach ($props as $meta) {
+ foreach ($collection as $meta) {
if ($meta->isAnnotated() && !$meta->isNative()) {
$propsFromAnnotations[] = $meta->id->name;
}
}
+
sort($propsFromAnnotations);
- if (count($fieldCodesFromApi) >= $propsFromAnnotations) {
+ if (count($fieldCodesFromApi) >= count($propsFromAnnotations)) {
$this->assertEquals(
$fieldCodesFromApi,
$propsFromAnnotations,
@@ -76,9 +78,9 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
string $resultItemClassName
): void {
// parse keys from phpdoc annotation
- $props = TyphoonReflector::build()->reflectClass($resultItemClassName)->properties();
+ $collection = TyphoonReflector::build()->reflectClass($resultItemClassName)->properties();
$propsFromAnnotations = [];
- foreach ($props as $meta) {
+ foreach ($collection as $meta) {
if ($meta->isAnnotated() && !$meta->isNative()) {
$propsFromAnnotations[$meta->id->name] = stringify($meta->type());
}
@@ -105,6 +107,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
// if field code contains currency
if (str_contains($fieldCode, 'CURRENCY_ID')) {
$this->assertTrue(
@@ -120,6 +123,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
if (str_contains($fieldCode, 'EDIT_FORM_LABEL') ||
str_contains($fieldCode, 'LIST_COLUMN_LABEL') ||
str_contains($fieldCode, 'LIST_FILTER_LABEL')
@@ -156,6 +160,21 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
case 'integer':
case 'int':
case 'mail_message':
+ if ($fieldCode === 'type') {
+ $this->assertTrue(
+ str_contains($propsFromAnnotations[$fieldCode], ProductType::class),
+ 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'],
+ ProductType::class
+ )
+ );
+ break;
+ }
+
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'int'),
sprintf(
@@ -183,6 +202,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
if (str_contains(mb_strtoupper($fieldCode), 'QUANTITY')) {
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'string'),
@@ -197,7 +217,12 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
- if (str_contains(mb_strtoupper($fieldCode), 'WEIGHT')) {
+
+ if (str_contains(mb_strtoupper($fieldCode), 'WEIGHT')
+ || str_contains(mb_strtoupper($fieldCode), 'HEIGHT')
+ || str_contains(mb_strtoupper($fieldCode), 'LENGTH')
+ || str_contains(mb_strtoupper($fieldCode), 'WIDTH')
+ ) {
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'string'),
sprintf(
@@ -211,6 +236,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
if (str_contains(mb_strtoupper($fieldCode), 'RATE')) {
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], Percentage::class),
@@ -225,8 +251,9 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
$this->assertTrue(
- str_contains($propsFromAnnotations[$fieldCode], 'Money\Money'),
+ str_contains($propsFromAnnotations[$fieldCode], \Money\Money::class),
sprintf(
'class «%s» field «%s» has invalid type phpdoc annotation «%s», field type from bitrix24 is «%s», expected sdk-type «%s»',
$resultItemClassName,
@@ -280,6 +307,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
break;
}
+
if (str_contains($fieldCode, 'durationType')
|| str_contains($fieldCode, 'mark')
|| str_contains($fieldCode, 'TYPE')
@@ -298,6 +326,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
break;
}
+
if (str_contains($fieldCode, 'priority')
|| str_contains($fieldCode, 'status')
) {
@@ -315,6 +344,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
break;
}
+
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'bool'),
sprintf(
@@ -329,14 +359,15 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
break;
case 'file':
$this->assertTrue(
- str_contains($propsFromAnnotations[$fieldCode], 'File'),
+ str_contains($propsFromAnnotations[$fieldCode], 'File')
+ || str_contains($propsFromAnnotations[$fieldCode], 'array'),
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'],
- 'File|null'
+ 'File|null or array|null'
)
);
break;
@@ -365,6 +396,7 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'int'),
sprintf(
@@ -462,6 +494,8 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
case 'array':
+ case 'list':
+ case 'productproperty':
case 'crm':
case 'crm_activity_binding':
case 'crm_activity_communication':
diff --git a/tests/Integration/Services/Catalog/Catalog/Service/CatalogTest.php b/tests/Integration/Services/Catalog/Catalog/Service/CatalogTest.php
index 47e03abb..f98f2987 100644
--- a/tests/Integration/Services/Catalog/Catalog/Service/CatalogTest.php
+++ b/tests/Integration/Services/Catalog/Catalog/Service/CatalogTest.php
@@ -57,8 +57,9 @@ public function testGet(): void
$this->assertEquals($catalog->id, $this->service->get($catalog->id)->catalog()->id);
}
+ #[\Override]
protected function setUp(): void
{
$this->service = Fabric::getServiceBuilder()->getCatalogScope()->catalog();
}
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/Services/Catalog/Product/Offer/Result/OfferItemResultTest.php b/tests/Integration/Services/Catalog/Product/Offer/Result/OfferItemResultTest.php
new file mode 100644
index 00000000..a00b5b6a
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/Offer/Result/OfferItemResultTest.php
@@ -0,0 +1,135 @@
+
+ *
+ * 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\Product\Offer\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Result\OfferItemResult;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Service\Offer;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Service\Sku;
+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(OfferItemResult::class)]
+class OfferItemResultTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Offer $offerService;
+
+ private Sku $skuService;
+
+ private int $skuId;
+
+ private int $offerId;
+
+ private int $offersCatalogIblockId;
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->offerService = $serviceBuilder->getCatalogScope()->productOffer();
+ $this->skuService = $serviceBuilder->getCatalogScope()->productSku();
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+
+ $productCatalog = null;
+ $offersCatalog = null;
+ foreach ($catalogService->list([], [], [], 1)->getCatalogs() as $catalogItemResult) {
+ if ($catalogItemResult->productIblockId === null) {
+ $productCatalog = $catalogItemResult;
+ } else {
+ $offersCatalog = $catalogItemResult;
+ }
+ }
+
+ $this->offersCatalogIblockId = $offersCatalog->iblockId;
+
+ $this->skuId = $this->skuService->add([
+ 'iblockId' => $productCatalog->iblockId,
+ 'name' => sprintf('test sku for offer annotations %s', time()),
+ ])->sku()->id;
+
+ $this->offerId = $this->offerService->add([
+ 'iblockId' => $offersCatalog->iblockId,
+ 'name' => sprintf('test offer annotations %s', time()),
+ 'parentId' => $this->skuId,
+ ])->offer()->id;
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function tearDown(): void
+ {
+ $this->offerService->delete($this->offerId);
+ $this->skuService->delete($this->skuId);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in OfferItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllFieldsAreAnnotated(): void
+ {
+ $rawItem = $this->offerService->get($this->offerId)->getCoreResponse()
+ ->getResponseData()->getResult()['offer'];
+
+ // dynamic catalog properties (propertyN) vary per portal and are intentionally not annotated
+ $fieldCodes = array_filter(
+ array_keys($rawItem),
+ static fn (string $fieldCode): bool => in_array(preg_match('/^property\d+$/', $fieldCode), [0, false], true)
+ );
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ $fieldCodes,
+ OfferItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in OfferItemResult have valid type casting in magic getters')]
+ public function testAllFieldsHasValidTypeCastingInMagicGetters(): void
+ {
+ // priceType and negativeAmountTrace are present only in fieldsByFilter response, not in item response, and are not annotated
+ // dynamic catalog properties (propertyN) vary per portal and are intentionally not annotated
+ $fieldsNotInItemResponse = ['priceType', 'negativeAmountTrace'];
+ $fields = array_filter(
+ $this->offerService->fieldsByFilter($this->offersCatalogIblockId)->getFieldsDescription()['offer'],
+ static fn (string $fieldCode): bool => !in_array($fieldCode, $fieldsNotInItemResponse, true) && in_array(preg_match('/^property\d+$/', $fieldCode), [0, false], true),
+ ARRAY_FILTER_USE_KEY
+ );
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fields,
+ OfferItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Product/Offer/Service/OfferTest.php b/tests/Integration/Services/Catalog/Product/Offer/Service/OfferTest.php
new file mode 100644
index 00000000..a99e5c51
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/Offer/Service/OfferTest.php
@@ -0,0 +1,95 @@
+
+ *
+ * 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\Product\Offer\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Service\Offer;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Service\Sku;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Offer::class)]
+class OfferTest extends TestCase
+{
+ private Offer $offerService;
+
+ private Sku $skuService;
+
+ private Catalog $catalogService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->offerService = Fabric::getServiceBuilder()->getCatalogScope()->productOffer();
+ $this->skuService = Fabric::getServiceBuilder()->getCatalogScope()->productSku();
+ $this->catalogService = Fabric::getServiceBuilder()->getCatalogScope()->catalog();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Offer::add, get, update, delete, list, fieldsByFilter')]
+ public function testAddGetUpdateDeleteListFieldsByFilter(): void
+ {
+ $catalogs = $this->catalogService->list([], [], [], 1)->getCatalogs();
+ $productCatalog = null;
+ $offersCatalog = null;
+ foreach ($catalogs as $catalog) {
+ if ($catalog->productIblockId === null) {
+ $productCatalog = $catalog;
+ } else {
+ $offersCatalog = $catalog;
+ }
+ }
+
+ $this->assertNotNull($productCatalog, 'products catalog not found');
+ $this->assertNotNull($offersCatalog, 'offers catalog not found');
+
+ $skuId = $this->skuService->add([
+ 'iblockId' => $productCatalog->iblockId,
+ 'name' => sprintf('test sku for offer %s', time()),
+ ])->sku()->id;
+
+ $offerResult = $this->offerService->add([
+ 'iblockId' => $offersCatalog->iblockId,
+ 'name' => sprintf('test offer %s', time()),
+ 'parentId' => $skuId,
+ ]);
+ $offerId = $offerResult->offer()->id;
+ $this->assertGreaterThan(0, $offerId);
+
+ $getResult = $this->offerService->get($offerId);
+ $this->assertEquals($offerId, $getResult->offer()->id);
+
+ $updated = $this->offerService->update($offerId, ['name' => 'updated offer name']);
+ $this->assertEquals('updated offer name', $updated->offer()->name);
+
+ $offersResult = $this->offerService->list(
+ ['id', 'iblockId'],
+ ['id' => $offerId, 'iblockId' => $offersCatalog->iblockId]
+ );
+ $this->assertCount(1, $offersResult->getOffers());
+
+ $fieldsResult = $this->offerService->fieldsByFilter($offersCatalog->iblockId);
+ $this->assertIsArray($fieldsResult->getFieldsDescription());
+
+ $this->assertTrue($this->offerService->delete($offerId)->isSuccess());
+ $this->assertTrue($this->skuService->delete($skuId)->isSuccess());
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Product/ProductService/Result/ProductServiceItemResultTest.php b/tests/Integration/Services/Catalog/Product/ProductService/Result/ProductServiceItemResultTest.php
new file mode 100644
index 00000000..81888685
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/ProductService/Result/ProductServiceItemResultTest.php
@@ -0,0 +1,110 @@
+
+ *
+ * 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\Product\ProductService\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Result\ProductServiceItemResult;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Service\ProductService;
+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(ProductServiceItemResult::class)]
+class ProductServiceItemResultTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private ProductService $productServiceScope;
+
+ private int $serviceId;
+
+ private int $iblockId;
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->productServiceScope = $serviceBuilder->getCatalogScope()->productService();
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+ $this->iblockId = $catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $this->serviceId = $this->productServiceScope->add([
+ 'iblockId' => $this->iblockId,
+ 'name' => sprintf('test service annotations %s', time()),
+ ])->productService()->id;
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function tearDown(): void
+ {
+ $this->productServiceScope->delete($this->serviceId);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in ProductServiceItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllFieldsAreAnnotated(): void
+ {
+ $rawItem = $this->productServiceScope->get($this->serviceId)->getCoreResponse()
+ ->getResponseData()->getResult()['service'];
+
+ // dynamic catalog properties (propertyN) vary per portal and are intentionally not annotated
+ $fieldCodes = array_filter(
+ array_keys($rawItem),
+ static fn (string $fieldCode): bool => in_array(preg_match('/^property\d+$/', $fieldCode), [0, false], true)
+ );
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ $fieldCodes,
+ ProductServiceItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in ProductServiceItemResult have valid type casting in magic getters')]
+ public function testAllFieldsHasValidTypeCastingInMagicGetters(): void
+ {
+ // priceType is present only in fieldsByFilter response, not in item response, and is not annotated
+ // dynamic catalog properties (propertyN) vary per portal and are intentionally not annotated
+ $fields = array_filter(
+ $this->productServiceScope->fieldsByFilter($this->iblockId)->getFieldsDescription()['service'],
+ static fn (string $fieldCode): bool => $fieldCode !== 'priceType' && in_array(preg_match('/^property\d+$/', $fieldCode), [0, false], true),
+ ARRAY_FILTER_USE_KEY
+ );
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fields,
+ ProductServiceItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php b/tests/Integration/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php
new file mode 100644
index 00000000..a4384975
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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\Product\ProductService\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Service\ProductService;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(ProductService::class)]
+class ProductServiceTest extends TestCase
+{
+ private ProductService $productServiceScope;
+
+ private Catalog $catalogService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->productServiceScope = Fabric::getServiceBuilder()->getCatalogScope()->productService();
+ $this->catalogService = Fabric::getServiceBuilder()->getCatalogScope()->catalog();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test ProductService::add, get, update, delete, list, fieldsByFilter')]
+ public function testAddGetUpdateDeleteListFieldsByFilter(): void
+ {
+ $iblockId = $this->catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $productServiceResult = $this->productServiceScope->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('test service %s', time()),
+ ]);
+ $serviceId = $productServiceResult->productService()->id;
+ $this->assertGreaterThan(0, $serviceId);
+
+ $getResult = $this->productServiceScope->get($serviceId);
+ $this->assertEquals($serviceId, $getResult->productService()->id);
+
+ $updated = $this->productServiceScope->update($serviceId, ['name' => 'updated service name']);
+ $this->assertEquals('updated service name', $updated->productService()->name);
+
+ $productServicesResult = $this->productServiceScope->list(
+ ['id', 'iblockId'],
+ ['id' => $serviceId, 'iblockId' => $iblockId]
+ );
+ $this->assertCount(1, $productServicesResult->getProductServices());
+
+ $fieldsResult = $this->productServiceScope->fieldsByFilter($iblockId);
+ $this->assertIsArray($fieldsResult->getFieldsDescription());
+
+ $this->assertTrue($this->productServiceScope->delete($serviceId)->isSuccess());
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Product/Service/ProductTest.php b/tests/Integration/Services/Catalog/Product/Service/ProductTest.php
index 3e514c0f..817baa61 100644
--- a/tests/Integration/Services/Catalog/Product/Service/ProductTest.php
+++ b/tests/Integration/Services/Catalog/Product/Service/ProductTest.php
@@ -123,7 +123,7 @@ public function testDelete(): void
* @throws TransportException If there is a transport exception thrown during the process of listing products.
*/
#[TestDox('test Product::list')]
- public function testList():void
+ public function testList(): void
{
$iblockId = $this->catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
$fields = [
@@ -150,9 +150,10 @@ public function testList():void
$this->assertCount(1, $productsResult->getProducts());
}
+ #[\Override]
protected function setUp(): void
{
$this->productService = Fabric::getServiceBuilder()->getCatalogScope()->product();
$this->catalogService = Fabric::getServiceBuilder()->getCatalogScope()->catalog();
}
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/Services/Catalog/Product/Sku/Result/SkuItemResultTest.php b/tests/Integration/Services/Catalog/Product/Sku/Result/SkuItemResultTest.php
new file mode 100644
index 00000000..e467252a
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/Sku/Result/SkuItemResultTest.php
@@ -0,0 +1,115 @@
+
+ *
+ * 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\Product\Sku\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Result\SkuItemResult;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Service\Sku;
+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(SkuItemResult::class)]
+class SkuItemResultTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private const FULL_SELECT = [
+ 'id', 'iblockId', 'name', 'active', 'available', 'bundle', 'canBuyZero', 'code',
+ 'createdBy', 'dateActiveFrom', 'dateActiveTo', 'dateCreate', 'detailPicture', 'detailText',
+ 'detailTextType', 'height', 'iblockSection', 'iblockSectionId', 'length', 'measure',
+ 'modifiedBy', 'previewPicture', 'previewText', 'previewTextType', 'purchasingCurrency',
+ 'purchasingPrice', 'quantity', 'sort', 'subscribe', 'timestampX', 'type', 'vatId',
+ 'vatIncluded', 'weight', 'width', 'xmlId',
+ ];
+
+ private Sku $skuService;
+
+ private int $skuId;
+
+ private int $iblockId;
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->skuService = $serviceBuilder->getCatalogScope()->productSku();
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+ $this->iblockId = $catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $this->skuId = $this->skuService->add([
+ 'iblockId' => $this->iblockId,
+ 'name' => sprintf('test sku annotations %s', time()),
+ ])->sku()->id;
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function tearDown(): void
+ {
+ $this->skuService->delete($this->skuId);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in SkuItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllFieldsAreAnnotated(): void
+ {
+ $rawItem = $this->skuService->list(
+ self::FULL_SELECT,
+ ['id' => $this->skuId, 'iblockId' => $this->iblockId]
+ )->getCoreResponse()->getResponseData()->getResult()['units'][0];
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItem),
+ SkuItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('all fields in SkuItemResult have valid type casting in magic getters')]
+ public function testAllFieldsHasValidTypeCastingInMagicGetters(): void
+ {
+ // priceType is present only in fieldsByFilter response, not in item response, and is not annotated
+ // dynamic catalog properties (propertyN) vary per portal and are intentionally not annotated
+ $fields = array_filter(
+ $this->skuService->fieldsByFilter($this->iblockId)->getFieldsDescription()['sku'],
+ static fn (string $fieldCode): bool => $fieldCode !== 'priceType' && in_array(preg_match('/^property\d+$/', $fieldCode), [0, false], true),
+ ARRAY_FILTER_USE_KEY
+ );
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fields,
+ SkuItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Product/Sku/Service/SkuTest.php b/tests/Integration/Services/Catalog/Product/Sku/Service/SkuTest.php
new file mode 100644
index 00000000..7f23622e
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Product/Sku/Service/SkuTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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\Product\Sku\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Catalog\Service\Catalog;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Service\Sku;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Sku::class)]
+class SkuTest extends TestCase
+{
+ private Sku $skuService;
+
+ private Catalog $catalogService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->skuService = Fabric::getServiceBuilder()->getCatalogScope()->productSku();
+ $this->catalogService = Fabric::getServiceBuilder()->getCatalogScope()->catalog();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Sku::add, get, update, delete, list, fieldsByFilter')]
+ public function testAddGetUpdateDeleteListFieldsByFilter(): void
+ {
+ $iblockId = $this->catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $skuResult = $this->skuService->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('test sku %s', time()),
+ ]);
+ $skuId = $skuResult->sku()->id;
+ $this->assertGreaterThan(0, $skuId);
+
+ $getResult = $this->skuService->get($skuId);
+ $this->assertEquals($skuId, $getResult->sku()->id);
+
+ $updated = $this->skuService->update($skuId, ['name' => 'updated sku name']);
+ $this->assertEquals('updated sku name', $updated->sku()->name);
+
+ $skusResult = $this->skuService->list(
+ ['id', 'iblockId'],
+ ['id' => $skuId, 'iblockId' => $iblockId]
+ );
+ $this->assertCount(1, $skusResult->getSkus());
+
+ $fieldsResult = $this->skuService->fieldsByFilter($iblockId);
+ $this->assertIsArray($fieldsResult->getFieldsDescription());
+
+ $this->assertTrue($this->skuService->delete($skuId)->isSuccess());
+ }
+}
diff --git a/tests/Unit/Services/Catalog/Product/Offer/Service/OfferTest.php b/tests/Unit/Services/Catalog/Product/Offer/Service/OfferTest.php
new file mode 100644
index 00000000..777bdd52
--- /dev/null
+++ b/tests/Unit/Services/Catalog/Product/Offer/Service/OfferTest.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\Unit\Services\Catalog\Product\Offer\Service;
+
+use Bitrix24\SDK\Core\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Result\OfferResult;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Result\OffersResult;
+use Bitrix24\SDK\Services\Catalog\Product\Offer\Service\Offer;
+use Bitrix24\SDK\Tests\Unit\Stubs\NullCore;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+#[CoversClass(Offer::class)]
+class OfferTest extends TestCase
+{
+ private Offer $service;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->service = new Offer(new NullCore(), new NullLogger());
+ }
+
+ #[Test]
+ public function testAddReturnsOfferResult(): void
+ {
+ $this->assertInstanceOf(
+ OfferResult::class,
+ $this->service->add(['iblockId' => 1, 'name' => 'test', 'parentId' => 1])
+ );
+ }
+
+ #[Test]
+ public function testUpdateReturnsOfferResult(): void
+ {
+ $this->assertInstanceOf(
+ OfferResult::class,
+ $this->service->update(1, ['name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testGetReturnsOfferResult(): void
+ {
+ $this->assertInstanceOf(
+ OfferResult::class,
+ $this->service->get(1)
+ );
+ }
+
+ #[Test]
+ public function testListReturnsOffersResult(): void
+ {
+ $this->assertInstanceOf(
+ OffersResult::class,
+ $this->service->list(['id', 'iblockId'], ['iblockId' => 1])
+ );
+ }
+
+ #[Test]
+ public function testDeleteReturnsDeletedItemResult(): void
+ {
+ $this->assertInstanceOf(
+ DeletedItemResult::class,
+ $this->service->delete(1)
+ );
+ }
+
+ #[Test]
+ public function testFieldsByFilterReturnsFieldsResult(): void
+ {
+ $this->assertInstanceOf(
+ FieldsResult::class,
+ $this->service->fieldsByFilter(1)
+ );
+ }
+
+ #[Test]
+ public function testDownloadReturnsResponse(): void
+ {
+ $this->assertInstanceOf(
+ Response::class,
+ $this->service->download(1, 1, 'detailPicture')
+ );
+ }
+}
diff --git a/tests/Unit/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php b/tests/Unit/Services/Catalog/Product/ProductService/Service/ProductServiceTest.php
new file mode 100644
index 00000000..cab6b9fd
--- /dev/null
+++ b/tests/Unit/Services/Catalog/Product/ProductService/Service/ProductServiceTest.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\Unit\Services\Catalog\Product\ProductService\Service;
+
+use Bitrix24\SDK\Core\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Result\ProductServiceResult;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Result\ProductServicesResult;
+use Bitrix24\SDK\Services\Catalog\Product\ProductService\Service\ProductService;
+use Bitrix24\SDK\Tests\Unit\Stubs\NullCore;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+#[CoversClass(ProductService::class)]
+class ProductServiceTest extends TestCase
+{
+ private ProductService $service;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->service = new ProductService(new NullCore(), new NullLogger());
+ }
+
+ #[Test]
+ public function testAddReturnsProductServiceResult(): void
+ {
+ $this->assertInstanceOf(
+ ProductServiceResult::class,
+ $this->service->add(['iblockId' => 1, 'name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testUpdateReturnsProductServiceResult(): void
+ {
+ $this->assertInstanceOf(
+ ProductServiceResult::class,
+ $this->service->update(1, ['name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testGetReturnsProductServiceResult(): void
+ {
+ $this->assertInstanceOf(
+ ProductServiceResult::class,
+ $this->service->get(1)
+ );
+ }
+
+ #[Test]
+ public function testListReturnsProductServicesResult(): void
+ {
+ $this->assertInstanceOf(
+ ProductServicesResult::class,
+ $this->service->list(['id', 'iblockId'], ['iblockId' => 1])
+ );
+ }
+
+ #[Test]
+ public function testDeleteReturnsDeletedItemResult(): void
+ {
+ $this->assertInstanceOf(
+ DeletedItemResult::class,
+ $this->service->delete(1)
+ );
+ }
+
+ #[Test]
+ public function testFieldsByFilterReturnsFieldsResult(): void
+ {
+ $this->assertInstanceOf(
+ FieldsResult::class,
+ $this->service->fieldsByFilter(1)
+ );
+ }
+
+ #[Test]
+ public function testDownloadReturnsResponse(): void
+ {
+ $this->assertInstanceOf(
+ Response::class,
+ $this->service->download(1, 1, 'detailPicture')
+ );
+ }
+}
diff --git a/tests/Unit/Services/Catalog/Product/Service/ProductTest.php b/tests/Unit/Services/Catalog/Product/Service/ProductTest.php
new file mode 100644
index 00000000..a4e51378
--- /dev/null
+++ b/tests/Unit/Services/Catalog/Product/Service/ProductTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * 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\Unit\Services\Catalog\Product\Service;
+
+use Bitrix24\SDK\Core\Response\Response;
+use Bitrix24\SDK\Services\Catalog\Product\Result\ProductResult;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Batch;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Product;
+use Bitrix24\SDK\Tests\Unit\Stubs\NullBatch;
+use Bitrix24\SDK\Tests\Unit\Stubs\NullCore;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+#[CoversClass(Product::class)]
+class ProductTest extends TestCase
+{
+ private Product $service;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->service = new Product(new Batch(new NullBatch(), new NullLogger()), new NullCore(), new NullLogger());
+ }
+
+ #[Test]
+ public function testUpdateReturnsProductResult(): void
+ {
+ $this->assertInstanceOf(
+ ProductResult::class,
+ $this->service->update(1, ['name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testDownloadReturnsResponse(): void
+ {
+ $this->assertInstanceOf(
+ Response::class,
+ $this->service->download(1, 1, 'detailPicture')
+ );
+ }
+}
diff --git a/tests/Unit/Services/Catalog/Product/Sku/Service/SkuTest.php b/tests/Unit/Services/Catalog/Product/Sku/Service/SkuTest.php
new file mode 100644
index 00000000..ff635e9c
--- /dev/null
+++ b/tests/Unit/Services/Catalog/Product/Sku/Service/SkuTest.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\Unit\Services\Catalog\Product\Sku\Service;
+
+use Bitrix24\SDK\Core\Response\Response;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Result\SkuResult;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Result\SkusResult;
+use Bitrix24\SDK\Services\Catalog\Product\Sku\Service\Sku;
+use Bitrix24\SDK\Tests\Unit\Stubs\NullCore;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+#[CoversClass(Sku::class)]
+class SkuTest extends TestCase
+{
+ private Sku $service;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->service = new Sku(new NullCore(), new NullLogger());
+ }
+
+ #[Test]
+ public function testAddReturnsSkuResult(): void
+ {
+ $this->assertInstanceOf(
+ SkuResult::class,
+ $this->service->add(['iblockId' => 1, 'name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testUpdateReturnsSkuResult(): void
+ {
+ $this->assertInstanceOf(
+ SkuResult::class,
+ $this->service->update(1, ['name' => 'test'])
+ );
+ }
+
+ #[Test]
+ public function testGetReturnsSkuResult(): void
+ {
+ $this->assertInstanceOf(
+ SkuResult::class,
+ $this->service->get(1)
+ );
+ }
+
+ #[Test]
+ public function testListReturnsSkusResult(): void
+ {
+ $this->assertInstanceOf(
+ SkusResult::class,
+ $this->service->list(['id', 'iblockId'], ['iblockId' => 1])
+ );
+ }
+
+ #[Test]
+ public function testDeleteReturnsDeletedItemResult(): void
+ {
+ $this->assertInstanceOf(
+ DeletedItemResult::class,
+ $this->service->delete(1)
+ );
+ }
+
+ #[Test]
+ public function testFieldsByFilterReturnsFieldsResult(): void
+ {
+ $this->assertInstanceOf(
+ FieldsResult::class,
+ $this->service->fieldsByFilter(1)
+ );
+ }
+
+ #[Test]
+ public function testDownloadReturnsResponse(): void
+ {
+ $this->assertInstanceOf(
+ Response::class,
+ $this->service->download(1, 1, 'detailPicture')
+ );
+ }
+}