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..42735db9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,26 @@
### Added
+- Added service `Services\Catalog\Document` with support methods,
+ see [catalog.document.* methods](https://apidocs.bitrix24.com/api-reference/catalog/document/index.html) ([#559](https://github.com/bitrix24/b24phpsdk/issues/559)):
+ - `add` creates a new warehouse accounting document, with batch calls support
+ - `update` updates an existing document, with batch calls support
+ - `list` gets the list of documents
+ - `delete` deletes a document, with batch calls support
+ - `deleteList` deletes a group of documents
+ - `conduct` conducts (activates) a document
+ - `conductList` conducts a group of documents
+ - `cancel` cancels conducting of a document
+ - `cancelList` cancels conducting of a group of documents
+ - `getFields` returns the description of document fields
+ - `modeStatus` checks whether warehouse accounting mode is enabled
+- Added service `Services\Catalog\DocumentElement` with support methods,
+ see [catalog.document.element.* methods](https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/index.html) ([#559](https://github.com/bitrix24/b24phpsdk/issues/559)):
+ - `add` adds a product line item to a warehouse accounting document, with batch calls support
+ - `update` updates a document line item, with batch calls support
+ - `list` gets the list of document line items
+ - `delete` deletes a document line item, with batch calls support
+ - `getFields` returns the description of document element 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..a0fcaf6f 100644
--- a/Makefile
+++ b/Makefile
@@ -492,6 +492,20 @@ 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-document
+test-integration-catalog-document:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_document
+.PHONY: test-integration-catalog-document-annotations
+test-integration-catalog-document-annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_document_annotations
+
+.PHONY: test-integration-catalog-document-element
+test-integration-catalog-document-element:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_document_element
+.PHONY: test-integration-catalog-document-element-annotations
+test-integration-catalog-document-element-annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_document_element_annotations
+
# work dev environment
.PHONY: php-dev-server-up
php-dev-server-up:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d0187ae5..97216df3 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -262,6 +262,18 @@
./tests/Integration/Services/Landing/RepoWidget/
+
+ ./tests/Integration/Services/Catalog/Document/Service/
+
+
+ ./tests/Integration/Services/Catalog/Document/Result/DocumentItemResultTest.php
+
+
+ ./tests/Integration/Services/Catalog/DocumentElement/Service/
+
+
+ ./tests/Integration/Services/Catalog/DocumentElement/Result/DocumentElementItemResultTest.php
+
diff --git a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php
index e62fc7cb..189852bb 100644
--- a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php
+++ b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php
@@ -29,4 +29,4 @@
*/
class CatalogItemResult extends AbstractCatalogItem
{
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/Catalog/Result/CatalogResult.php b/src/Services/Catalog/Catalog/Result/CatalogResult.php
index aa468aa8..c5222f11 100644
--- a/src/Services/Catalog/Catalog/Result/CatalogResult.php
+++ b/src/Services/Catalog/Catalog/Result/CatalogResult.php
@@ -21,4 +21,4 @@ public function catalog(): CatalogItemResult
{
return new CatalogItemResult($this->getCoreResponse()->getResponseData()->getResult()['catalog']);
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/Catalog/Result/CatalogsResult.php b/src/Services/Catalog/Catalog/Result/CatalogsResult.php
index add5f259..f721c3bb 100644
--- a/src/Services/Catalog/Catalog/Result/CatalogsResult.php
+++ b/src/Services/Catalog/Catalog/Result/CatalogsResult.php
@@ -32,4 +32,4 @@ public function getCatalogs(): array
return $res;
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/Catalog/Service/Catalog.php b/src/Services/Catalog/Catalog/Service/Catalog.php
index d9cb55fe..0da76ea3 100644
--- a/src/Services/Catalog/Catalog/Service/Catalog.php
+++ b/src/Services/Catalog/Catalog/Service/Catalog.php
@@ -82,4 +82,4 @@ public function fields(): FieldsResult
{
return new FieldsResult($this->core->call('catalog.catalog.getFields'));
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/Catalog/CatalogServiceBuilder.php b/src/Services/Catalog/CatalogServiceBuilder.php
index 57c91b88..f2d2ef4a 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,36 @@ public function catalog(): Catalog\Catalog\Service\Catalog
return $this->serviceCache[__METHOD__];
}
-}
\ No newline at end of file
+
+ public function document(): Catalog\Document\Service\Document
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Catalog\Document\Service\Document(
+ new Catalog\Document\Service\Batch(
+ new Catalog\Document\Batch($this->core, $this->log),
+ $this->log
+ ),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function documentElement(): Catalog\DocumentElement\Service\DocumentElement
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Catalog\DocumentElement\Service\DocumentElement(
+ new Catalog\DocumentElement\Service\Batch(
+ new Catalog\DocumentElement\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/Document/Batch.php b/src/Services/Catalog/Document/Batch.php
new file mode 100644
index 00000000..7eed45fe
--- /dev/null
+++ b/src/Services/Catalog/Document/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\Document;
+
+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.document.* REST methods:
+ * - delete uses lowercase 'id' instead of 'ID'
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete.html
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-list.html
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for catalog document
+ */
+ #[\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 document id «%s» at position %s, document 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 document items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete document 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/Document/Result/DocumentAddedBatchResult.php b/src/Services/Catalog/Document/Result/DocumentAddedBatchResult.php
new file mode 100644
index 00000000..ba4eb039
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentAddedBatchResult.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\Document\Result;
+
+use Bitrix24\SDK\Core\Response\DTO\ResponseData;
+
+class DocumentAddedBatchResult
+{
+ public function __construct(private readonly ResponseData $responseData)
+ {
+ }
+
+ public function getResponseData(): ResponseData
+ {
+ return $this->responseData;
+ }
+
+ public function document(): DocumentItemResult
+ {
+ return new DocumentItemResult($this->responseData->getResult()['document']);
+ }
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentFieldsResult.php b/src/Services/Catalog/Document/Result/DocumentFieldsResult.php
new file mode 100644
index 00000000..10b5bd72
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentFieldsResult.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\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentFieldsResult extends AbstractResult
+{
+ /**
+ * @return array>
+ * @throws BaseException
+ */
+ public function getFieldsDescription(): array
+ {
+ return $this->getCoreResponse()->getResponseData()->getResult()['document'];
+ }
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentItemResult.php b/src/Services/Catalog/Document/Result/DocumentItemResult.php
new file mode 100644
index 00000000..bc059691
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentItemResult.php
@@ -0,0 +1,41 @@
+
+ *
+ * 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\Document\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Carbon\CarbonImmutable;
+use Money\Money;
+
+/**
+ * @property-read int $id
+ * @property-read string $docType
+ * @property-read string $currency
+ * @property-read int $responsibleId
+ * @property-read string|null $siteId
+ * @property-read CarbonImmutable|null $dateDocument
+ * @property-read CarbonImmutable|null $dateCreate
+ * @property-read CarbonImmutable|null $dateModify
+ * @property-read CarbonImmutable|null $dateStatus
+ * @property-read string|null $title
+ * @property-read string|null $commentary
+ * @property-read Money|null $total
+ * @property-read string|null $docNumber
+ * @property-read int|null $createdBy
+ * @property-read int|null $modifiedBy
+ * @property-read string|null $status
+ * @property-read int|null $statusBy
+ */
+class DocumentItemResult extends AbstractItem
+{
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentModeStatusResult.php b/src/Services/Catalog/Document/Result/DocumentModeStatusResult.php
new file mode 100644
index 00000000..075c6740
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentModeStatusResult.php
@@ -0,0 +1,30 @@
+
+ *
+ * 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\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentModeStatusResult extends AbstractResult
+{
+ /**
+ * Returns true if warehouse accounting mode is enabled ('Y'), false otherwise ('N')
+ *
+ * @throws BaseException
+ */
+ public function isEnabled(): bool
+ {
+ return $this->getCoreResponse()->getResponseData()->getResult()[0] === 'Y';
+ }
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentResult.php b/src/Services/Catalog/Document/Result/DocumentResult.php
new file mode 100644
index 00000000..47141ce8
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentResult.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\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function document(): DocumentItemResult
+ {
+ return new DocumentItemResult($this->getCoreResponse()->getResponseData()->getResult()['document']);
+ }
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentUpdatedBatchResult.php b/src/Services/Catalog/Document/Result/DocumentUpdatedBatchResult.php
new file mode 100644
index 00000000..a5d11ff0
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentUpdatedBatchResult.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\Document\Result;
+
+use Bitrix24\SDK\Core\Response\DTO\ResponseData;
+
+class DocumentUpdatedBatchResult
+{
+ public function __construct(private readonly ResponseData $responseData)
+ {
+ }
+
+ public function getResponseData(): ResponseData
+ {
+ return $this->responseData;
+ }
+
+ public function document(): DocumentItemResult
+ {
+ return new DocumentItemResult($this->responseData->getResult()['document']);
+ }
+}
diff --git a/src/Services/Catalog/Document/Result/DocumentsResult.php b/src/Services/Catalog/Document/Result/DocumentsResult.php
new file mode 100644
index 00000000..fa578866
--- /dev/null
+++ b/src/Services/Catalog/Document/Result/DocumentsResult.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\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentsResult extends AbstractResult
+{
+ /**
+ * @return DocumentItemResult[]
+ * @throws BaseException
+ */
+ public function getDocuments(): array
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ return array_map(
+ static fn (array $item): DocumentItemResult => new DocumentItemResult($item),
+ $result['documents'] ?? []
+ );
+ }
+}
diff --git a/src/Services/Catalog/Document/Service/Batch.php b/src/Services/Catalog/Document/Service/Batch.php
new file mode 100644
index 00000000..38a54e46
--- /dev/null
+++ b/src/Services/Catalog/Document/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\Document\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\Document;
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentAddedBatchResult;
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentUpdatedBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['catalog']))]
+class Batch
+{
+ public function __construct(protected Document\Batch $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch adding warehouse accounting documents
+ *
+ * @param array $documents
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-add.html',
+ 'Batch adding warehouse accounting documents'
+ )]
+ public function add(array $documents): Generator
+ {
+ $items = [];
+ foreach ($documents as $document) {
+ $items[] = ['fields' => $document];
+ }
+
+ foreach ($this->batch->addEntityItems('catalog.document.add', $items) as $key => $item) {
+ yield $key => new DocumentAddedBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete warehouse accounting documents
+ *
+ * @param int[] $documentId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete.html',
+ 'Batch delete warehouse accounting documents'
+ )]
+ public function delete(array $documentId): Generator
+ {
+ foreach ($this->batch->deleteEntityItems('catalog.document.delete', $documentId) as $key => $item) {
+ yield $key => new DeletedItemBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update warehouse accounting documents
+ *
+ * @param array $documents keyed by document id
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-update.html',
+ 'Batch update warehouse accounting documents'
+ )]
+ public function update(array $documents): Generator
+ {
+ $items = [];
+ foreach ($documents as $id => $document) {
+ $items[$id] = ['fields' => $document];
+ }
+
+ foreach ($this->batch->updateEntityItems('catalog.document.update', $items) as $key => $item) {
+ yield $key => new DocumentUpdatedBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Catalog/Document/Service/Document.php b/src/Services/Catalog/Document/Service/Document.php
new file mode 100644
index 00000000..63b00738
--- /dev/null
+++ b/src/Services/Catalog/Document/Service/Document.php
@@ -0,0 +1,246 @@
+
+ *
+ * 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\Document\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\Document\Result\DocumentFieldsResult;
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentModeStatusResult;
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentResult;
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentsResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['catalog']))]
+class Document extends AbstractService
+{
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Adds a new warehouse accounting document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-add.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-add.html',
+ 'Adds a new warehouse accounting document'
+ )]
+ public function add(array $fields): DocumentResult
+ {
+ return new DocumentResult($this->core->call('catalog.document.add', ['fields' => $fields]));
+ }
+
+ /**
+ * Updates a warehouse accounting document by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-update.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-update.html',
+ 'Updates a warehouse accounting document by its identifier'
+ )]
+ public function update(int $id, array $fields): DocumentResult
+ {
+ return new DocumentResult($this->core->call('catalog.document.update', ['id' => $id, 'fields' => $fields]));
+ }
+
+ /**
+ * Returns a list of warehouse accounting documents by filter
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-list.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.list',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-list.html',
+ 'Returns a list of warehouse accounting documents by filter'
+ )]
+ public function list(array $select = [], array $filter = []): DocumentsResult
+ {
+ return new DocumentsResult(
+ $this->core->call(
+ 'catalog.document.list',
+ ['select' => $select, 'filter' => $filter]
+ )
+ );
+ }
+
+ /**
+ * Deletes a warehouse accounting document by identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete.html',
+ 'Deletes a warehouse accounting document by identifier'
+ )]
+ public function delete(int $id): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.delete', ['id' => $id]));
+ }
+
+ /**
+ * Deletes a group of warehouse accounting documents by identifiers
+ *
+ * @param int[] $documentIds
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete-list.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.deleteList',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-delete-list.html',
+ 'Deletes a group of warehouse accounting documents by identifiers'
+ )]
+ public function deleteList(array $documentIds): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.deleteList', ['documentIds' => $documentIds]));
+ }
+
+ /**
+ * Conducts a warehouse accounting document, updating stock balances
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-conduct.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.conduct',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-conduct.html',
+ 'Conducts a warehouse accounting document, updating stock balances'
+ )]
+ public function conduct(int $id): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.conduct', ['id' => $id]));
+ }
+
+ /**
+ * Conducts a group of warehouse accounting documents, updating stock balances
+ *
+ * @param int[] $documentIds
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-conduct-list.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.conductList',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-conduct-list.html',
+ 'Conducts a group of warehouse accounting documents, updating stock balances'
+ )]
+ public function conductList(array $documentIds): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.conductList', ['documentIds' => $documentIds]));
+ }
+
+ /**
+ * Cancels conducting of a warehouse accounting document, updating stock balances
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-cancel.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.cancel',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-cancel.html',
+ 'Cancels conducting of a warehouse accounting document, updating stock balances'
+ )]
+ public function cancel(int $id): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.cancel', ['id' => $id]));
+ }
+
+ /**
+ * Cancels conducting of a group of warehouse accounting documents, updating stock balances
+ *
+ * @param int[] $documentIds
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-cancel-list.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.cancelList',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-cancel-list.html',
+ 'Cancels conducting of a group of warehouse accounting documents, updating stock balances'
+ )]
+ public function cancelList(array $documentIds): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.cancelList', ['documentIds' => $documentIds]));
+ }
+
+ /**
+ * Returns the fields of a warehouse accounting document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-get-fields.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.getFields',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-get-fields.html',
+ 'Returns the fields of a warehouse accounting document'
+ )]
+ public function getFields(): DocumentFieldsResult
+ {
+ return new DocumentFieldsResult($this->core->call('catalog.document.getFields'));
+ }
+
+ /**
+ * Checks whether warehouse accounting mode is enabled
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-mode-status.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.mode.status',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/catalog-document-mode-status.html',
+ 'Checks whether warehouse accounting mode is enabled'
+ )]
+ public function modeStatus(): DocumentModeStatusResult
+ {
+ return new DocumentModeStatusResult($this->core->call('catalog.document.mode.status'));
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Batch.php b/src/Services/Catalog/DocumentElement/Batch.php
new file mode 100644
index 00000000..ad36f7a7
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/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\DocumentElement;
+
+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.document.element.* REST methods:
+ * - delete uses lowercase 'id' instead of 'ID'
+ *
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-delete.html
+ * @see https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-list.html
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for catalog document element
+ */
+ #[\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 document element id «%s» at position %s, document element 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 document element items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete document element 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/DocumentElement/Result/DocumentElementAddedBatchResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementAddedBatchResult.php
new file mode 100644
index 00000000..d48c331e
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementAddedBatchResult.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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Response\DTO\ResponseData;
+
+class DocumentElementAddedBatchResult
+{
+ public function __construct(private readonly ResponseData $responseData)
+ {
+ }
+
+ public function getResponseData(): ResponseData
+ {
+ return $this->responseData;
+ }
+
+ public function documentElement(): DocumentElementItemResult
+ {
+ return new DocumentElementItemResult($this->responseData->getResult()['documentElement']);
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Result/DocumentElementFieldsResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementFieldsResult.php
new file mode 100644
index 00000000..45644edd
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementFieldsResult.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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentElementFieldsResult extends AbstractResult
+{
+ /**
+ * @return array>
+ * @throws BaseException
+ */
+ public function getFieldsDescription(): array
+ {
+ return $this->getCoreResponse()->getResponseData()->getResult()['documentElement'];
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Result/DocumentElementItemResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementItemResult.php
new file mode 100644
index 00000000..b82733de
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementItemResult.php
@@ -0,0 +1,30 @@
+
+ *
+ * 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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Money\Money;
+
+/**
+ * @property-read int $id
+ * @property-read int $docId
+ * @property-read int $elementId
+ * @property-read int|null $storeFrom
+ * @property-read int|null $storeTo
+ * @property-read float $amount
+ * @property-read Money|null $purchasingPrice
+ */
+class DocumentElementItemResult extends AbstractItem
+{
+}
diff --git a/src/Services/Catalog/DocumentElement/Result/DocumentElementResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementResult.php
new file mode 100644
index 00000000..53d8bb78
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementResult.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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentElementResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function documentElement(): DocumentElementItemResult
+ {
+ return new DocumentElementItemResult($this->getCoreResponse()->getResponseData()->getResult()['documentElement']);
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Result/DocumentElementUpdatedBatchResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementUpdatedBatchResult.php
new file mode 100644
index 00000000..20b84944
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementUpdatedBatchResult.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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Response\DTO\ResponseData;
+
+class DocumentElementUpdatedBatchResult
+{
+ public function __construct(private readonly ResponseData $responseData)
+ {
+ }
+
+ public function getResponseData(): ResponseData
+ {
+ return $this->responseData;
+ }
+
+ public function documentElement(): DocumentElementItemResult
+ {
+ return new DocumentElementItemResult($this->responseData->getResult()['documentElement']);
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Result/DocumentElementsResult.php b/src/Services/Catalog/DocumentElement/Result/DocumentElementsResult.php
new file mode 100644
index 00000000..e78df0e8
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Result/DocumentElementsResult.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\DocumentElement\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentElementsResult extends AbstractResult
+{
+ /**
+ * @return DocumentElementItemResult[]
+ * @throws BaseException
+ */
+ public function getDocumentElements(): array
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ return array_map(
+ static fn (array $item): DocumentElementItemResult => new DocumentElementItemResult($item),
+ $result['documentElements'] ?? []
+ );
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Service/Batch.php b/src/Services/Catalog/DocumentElement/Service/Batch.php
new file mode 100644
index 00000000..1e6cc833
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/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\DocumentElement\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\DocumentElement;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Result\DocumentElementAddedBatchResult;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Result\DocumentElementUpdatedBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['catalog']))]
+class Batch
+{
+ public function __construct(protected DocumentElement\Batch $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch adding warehouse accounting document line items
+ *
+ * @param array $documentElements
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.element.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-add.html',
+ 'Batch adding warehouse accounting document line items'
+ )]
+ public function add(array $documentElements): Generator
+ {
+ $items = [];
+ foreach ($documentElements as $documentElement) {
+ $items[] = ['fields' => $documentElement];
+ }
+
+ foreach ($this->batch->addEntityItems('catalog.document.element.add', $items) as $key => $item) {
+ yield $key => new DocumentElementAddedBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete warehouse accounting document line items
+ *
+ * @param int[] $documentElementId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.element.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-delete.html',
+ 'Batch delete warehouse accounting document line items'
+ )]
+ public function delete(array $documentElementId): Generator
+ {
+ foreach ($this->batch->deleteEntityItems('catalog.document.element.delete', $documentElementId) as $key => $item) {
+ yield $key => new DeletedItemBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update warehouse accounting document line items
+ *
+ * @param array $documentElements keyed by document element id
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'catalog.document.element.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-update.html',
+ 'Batch update warehouse accounting document line items'
+ )]
+ public function update(array $documentElements): Generator
+ {
+ $items = [];
+ foreach ($documentElements as $id => $documentElement) {
+ $items[$id] = ['fields' => $documentElement];
+ }
+
+ foreach ($this->batch->updateEntityItems('catalog.document.element.update', $items) as $key => $item) {
+ yield $key => new DocumentElementUpdatedBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Catalog/DocumentElement/Service/DocumentElement.php b/src/Services/Catalog/DocumentElement/Service/DocumentElement.php
new file mode 100644
index 00000000..dfb673be
--- /dev/null
+++ b/src/Services/Catalog/DocumentElement/Service/DocumentElement.php
@@ -0,0 +1,133 @@
+
+ *
+ * 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\DocumentElement\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\DocumentElement\Result\DocumentElementFieldsResult;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Result\DocumentElementResult;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Result\DocumentElementsResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['catalog']))]
+class DocumentElement extends AbstractService
+{
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Adds a product line item to a warehouse accounting document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-add.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.element.add',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-add.html',
+ 'Adds a product line item to a warehouse accounting document'
+ )]
+ public function add(array $fields): DocumentElementResult
+ {
+ return new DocumentElementResult($this->core->call('catalog.document.element.add', ['fields' => $fields]));
+ }
+
+ /**
+ * Updates a warehouse accounting document line item by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-update.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.element.update',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-update.html',
+ 'Updates a warehouse accounting document line item by its identifier'
+ )]
+ public function update(int $id, array $fields): DocumentElementResult
+ {
+ return new DocumentElementResult(
+ $this->core->call('catalog.document.element.update', ['id' => $id, 'fields' => $fields])
+ );
+ }
+
+ /**
+ * Returns a list of warehouse accounting document line items by filter
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-list.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.element.list',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-list.html',
+ 'Returns a list of warehouse accounting document line items by filter'
+ )]
+ public function list(array $select = [], array $filter = [], array $order = []): DocumentElementsResult
+ {
+ return new DocumentElementsResult(
+ $this->core->call(
+ 'catalog.document.element.list',
+ ['select' => $select, 'filter' => $filter, 'order' => $order]
+ )
+ );
+ }
+
+ /**
+ * Deletes a warehouse accounting document line item by identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.element.delete',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-delete.html',
+ 'Deletes a warehouse accounting document line item by identifier'
+ )]
+ public function delete(int $id): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('catalog.document.element.delete', ['id' => $id]));
+ }
+
+ /**
+ * Returns the fields of a warehouse accounting document line item
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-get-fields.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'catalog.document.element.getFields',
+ 'https://apidocs.bitrix24.com/api-reference/catalog/document/document-element/catalog-document-element-get-fields.html',
+ 'Returns the fields of a warehouse accounting document line item'
+ )]
+ public function getFields(): DocumentElementFieldsResult
+ {
+ return new DocumentElementFieldsResult($this->core->call('catalog.document.element.getFields'));
+ }
+}
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/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php
index dd1d32b7..af8be28d 100644
--- a/tests/CustomAssertions/CustomBitrix24Assertions.php
+++ b/tests/CustomAssertions/CustomBitrix24Assertions.php
@@ -225,6 +225,21 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
}
+ // catalog.document.element field «amount» is a stock quantity, not a monetary amount
+ if ($fieldCode === 'amount') {
+ $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(
@@ -252,6 +267,22 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
);
break;
case 'char':
+ // catalog.document(.element) fields use «char» for plain strings, not Y/N flags
+ if (in_array($fieldCode, ['commentary', 'currency', 'docType', 'siteId', 'status'], true)) {
+ $this->assertTrue(
+ str_contains($propsFromAnnotations[$fieldCode], 'string'),
+ 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'],
+ 'string'
+ )
+ );
+ break;
+ }
+
$this->assertTrue(
str_contains($propsFromAnnotations[$fieldCode], 'bool'),
sprintf(
diff --git a/tests/Integration/Services/Catalog/Document/Result/DocumentItemResultTest.php b/tests/Integration/Services/Catalog/Document/Result/DocumentItemResultTest.php
new file mode 100644
index 00000000..f3117829
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Document/Result/DocumentItemResultTest.php
@@ -0,0 +1,70 @@
+
+ *
+ * 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\Document\Result;
+
+use Bitrix24\SDK\Services\Catalog\Document\Result\DocumentItemResult;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+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(DocumentItemResult::class)]
+class DocumentItemResultTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Document $documentService;
+
+ private int $documentId;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Fabric::getServiceBuilder(true)->getCatalogScope()->document();
+ $this->documentId = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('test document annotations %s', time()),
+ ])->document()->id;
+ }
+
+ #[\Override]
+ protected function tearDown(): void
+ {
+ $this->documentService->delete($this->documentId);
+ }
+
+ #[Test]
+ #[TestDox('all fields in DocumentItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllFieldsAreAnnotated(): void
+ {
+ $rawItem = $this->documentService->list([], ['id' => $this->documentId])
+ ->getCoreResponse()->getResponseData()->getResult()['documents'][0];
+ $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), DocumentItemResult::class);
+ }
+
+ #[Test]
+ #[TestDox('all fields in DocumentItemResult have valid type casting in magic getters')]
+ public function testAllFieldsHasValidTypeCastingInMagicGetters(): void
+ {
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $this->documentService->getFields()->getFieldsDescription(),
+ DocumentItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Document/Service/BatchTest.php b/tests/Integration/Services/Catalog/Document/Service/BatchTest.php
new file mode 100644
index 00000000..655e8084
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Document/Service/BatchTest.php
@@ -0,0 +1,73 @@
+
+ *
+ * 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\Document\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Batch;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Batch::class)]
+class BatchTest extends TestCase
+{
+ private Document $documentService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Fabric::getServiceBuilder(true)->getCatalogScope()->document();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Batch::add, Batch::update, Batch::delete')]
+ public function testAddUpdateDelete(): void
+ {
+ $addedIds = [];
+ foreach ($this->documentService->batch->add([
+ ['docType' => 'A', 'currency' => 'USD', 'responsibleId' => 1, 'title' => sprintf('batch document %s', time())],
+ ]) as $addedItemResult) {
+ $addedIds[] = $addedItemResult->document()->id;
+ }
+
+ $this->assertCount(1, $addedIds);
+
+ $updatePayload = [];
+ foreach ($addedIds as $addedId) {
+ $updatePayload[$addedId] = ['title' => 'updated batch document'];
+ }
+
+ $updatedCount = 0;
+ foreach ($this->documentService->batch->update($updatePayload) as $updatedItemResult) {
+ $this->assertSame('updated batch document', $updatedItemResult->document()->title);
+ $updatedCount++;
+ }
+
+ $this->assertSame(1, $updatedCount);
+
+ $deletedCount = 0;
+ foreach ($this->documentService->batch->delete($addedIds) as $deletedItemResult) {
+ $this->assertTrue($deletedItemResult->isSuccess());
+ $deletedCount++;
+ }
+
+ $this->assertSame(1, $deletedCount);
+ }
+}
diff --git a/tests/Integration/Services/Catalog/Document/Service/DocumentTest.php b/tests/Integration/Services/Catalog/Document/Service/DocumentTest.php
new file mode 100644
index 00000000..271ff3db
--- /dev/null
+++ b/tests/Integration/Services/Catalog/Document/Service/DocumentTest.php
@@ -0,0 +1,222 @@
+
+ *
+ * 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\Document\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Service\DocumentElement;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Product;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Document::class)]
+class DocumentTest extends TestCase
+{
+ private Document $documentService;
+
+ private DocumentElement $documentElementService;
+
+ private Product $productService;
+
+ /**
+ * @var int[]
+ */
+ private array $createdDocumentIds = [];
+
+ /**
+ * @var int[]
+ */
+ private array $createdProductIds = [];
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder(true);
+ $this->documentService = $serviceBuilder->getCatalogScope()->document();
+ $this->documentElementService = $serviceBuilder->getCatalogScope()->documentElement();
+ $this->productService = $serviceBuilder->getCatalogScope()->product();
+ }
+
+ #[\Override]
+ protected function tearDown(): void
+ {
+ foreach ($this->createdDocumentIds as $createdDocumentId) {
+ try {
+ $this->documentService->cancel($createdDocumentId);
+ } catch (\Throwable) {
+ // document may not be conducted, ignore
+ }
+
+ try {
+ $this->documentService->delete($createdDocumentId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+ }
+
+ foreach ($this->createdProductIds as $createdProductId) {
+ try {
+ $this->productService->delete($createdProductId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::add, Document::update, Document::list, Document::delete')]
+ public function testAddUpdateListDelete(): void
+ {
+ $title = sprintf('test document %s', time());
+ $documentResult = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => $title,
+ ]);
+ $documentId = $documentResult->document()->id;
+ $this->createdDocumentIds[] = $documentId;
+
+ $this->assertSame($title, $documentResult->document()->title);
+ $this->assertSame('A', $documentResult->document()->docType);
+
+ $updatedTitle = sprintf('updated test document %s', time());
+ $updateResult = $this->documentService->update($documentId, ['title' => $updatedTitle]);
+ $this->assertSame($updatedTitle, $updateResult->document()->title);
+
+ $documentsResult = $this->documentService->list([], ['id' => $documentId]);
+ $this->assertCount(1, $documentsResult->getDocuments());
+
+ $this->assertTrue($this->documentService->delete($documentId)->isSuccess());
+ $this->createdDocumentIds = array_diff($this->createdDocumentIds, [$documentId]);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::deleteList')]
+ public function testDeleteList(): void
+ {
+ $documentIds = [];
+ for ($i = 0; $i < 2; ++$i) {
+ $documentIds[] = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('test document deleteList %s-%s', time(), $i),
+ ])->document()->id;
+ }
+
+ $this->createdDocumentIds = array_merge($this->createdDocumentIds, $documentIds);
+
+ $this->assertTrue($this->documentService->deleteList($documentIds)->isSuccess());
+ $this->createdDocumentIds = array_values(array_diff($this->createdDocumentIds, $documentIds));
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::getFields')]
+ public function testGetFields(): void
+ {
+ $this->assertIsArray($this->documentService->getFields()->getFieldsDescription());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::modeStatus')]
+ public function testModeStatus(): void
+ {
+ $this->assertIsBool($this->documentService->modeStatus()->isEnabled());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::conduct, Document::cancel')]
+ public function testConductCancel(): void
+ {
+ $documentId = $this->createDocumentWithElement('test document conduct');
+
+ $this->assertTrue($this->documentService->conduct($documentId)->isSuccess());
+ $this->assertTrue($this->documentService->cancel($documentId)->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Document::conductList, Document::cancelList')]
+ public function testConductListCancelList(): void
+ {
+ $documentIds = [
+ $this->createDocumentWithElement('test document conductList 0'),
+ $this->createDocumentWithElement('test document conductList 1'),
+ ];
+
+ $this->assertTrue($this->documentService->conductList($documentIds)->isSuccess());
+ $this->assertTrue($this->documentService->cancelList($documentIds)->isSuccess());
+ }
+
+ /**
+ * Creates a stock-taking document ('S') with one line item so it can be conducted.
+ * docType 'S' is used because it does not require a supplier, unlike docType 'A' (goods receipt).
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function createDocumentWithElement(string $title): int
+ {
+ $iblockId = Fabric::getServiceBuilder(true)->getCatalogScope()->catalog()
+ ->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $stores = Fabric::getCore(true)->call('catalog.store.list', ['select' => ['id'], 'filter' => ['active' => 'Y']])
+ ->getResponseData()->getResult();
+ $storeId = $stores['stores'][0]['id'];
+
+ $productId = $this->productService->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('%s product %s', $title, time()),
+ ])->product()->id;
+ $this->createdProductIds[] = $productId;
+
+ $documentId = $this->documentService->add([
+ 'docType' => 'S',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('%s %s', $title, time()),
+ ])->document()->id;
+ $this->createdDocumentIds[] = $documentId;
+
+ $this->documentElementService->add([
+ 'docId' => $documentId,
+ 'elementId' => $productId,
+ 'storeTo' => $storeId,
+ 'amount' => 1,
+ ]);
+
+ return $documentId;
+ }
+}
diff --git a/tests/Integration/Services/Catalog/DocumentElement/Result/DocumentElementItemResultTest.php b/tests/Integration/Services/Catalog/DocumentElement/Result/DocumentElementItemResultTest.php
new file mode 100644
index 00000000..8b1364bc
--- /dev/null
+++ b/tests/Integration/Services/Catalog/DocumentElement/Result/DocumentElementItemResultTest.php
@@ -0,0 +1,107 @@
+
+ *
+ * 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\DocumentElement\Result;
+
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Result\DocumentElementItemResult;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Service\DocumentElement;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Product;
+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(DocumentElementItemResult::class)]
+class DocumentElementItemResultTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private DocumentElement $documentElementService;
+
+ private Document $documentService;
+
+ private Product $productService;
+
+ private int $documentId;
+
+ private int $productId;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->documentElementService = $serviceBuilder->getCatalogScope()->documentElement();
+ $this->documentService = $serviceBuilder->getCatalogScope()->document();
+ $this->productService = $serviceBuilder->getCatalogScope()->product();
+
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+ $iblockId = $catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $this->productId = $this->productService->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('test document element annotations product %s', time()),
+ ])->product()->id;
+
+ $this->documentId = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('test document element annotations %s', time()),
+ ])->document()->id;
+
+ $this->documentElementService->add([
+ 'docId' => $this->documentId,
+ 'elementId' => $this->productId,
+ 'storeTo' => 0,
+ 'amount' => 2,
+ ]);
+ }
+
+ #[\Override]
+ protected function tearDown(): void
+ {
+ try {
+ $this->documentService->delete($this->documentId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+
+ try {
+ $this->productService->delete($this->productId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+ }
+
+ #[Test]
+ #[TestDox('all fields in DocumentElementItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllFieldsAreAnnotated(): void
+ {
+ $rawItem = $this->documentElementService->list([], ['docId' => $this->documentId])
+ ->getCoreResponse()->getResponseData()->getResult()['documentElements'][0];
+ $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), DocumentElementItemResult::class);
+ }
+
+ #[Test]
+ #[TestDox('all fields in DocumentElementItemResult have valid type casting in magic getters')]
+ public function testAllFieldsHasValidTypeCastingInMagicGetters(): void
+ {
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $this->documentElementService->getFields()->getFieldsDescription(),
+ DocumentElementItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Catalog/DocumentElement/Service/BatchTest.php b/tests/Integration/Services/Catalog/DocumentElement/Service/BatchTest.php
new file mode 100644
index 00000000..f831d659
--- /dev/null
+++ b/tests/Integration/Services/Catalog/DocumentElement/Service/BatchTest.php
@@ -0,0 +1,121 @@
+
+ *
+ * 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\DocumentElement\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Service\Batch;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Service\DocumentElement;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Product;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Batch::class)]
+class BatchTest extends TestCase
+{
+ private DocumentElement $documentElementService;
+
+ private Document $documentService;
+
+ private Product $productService;
+
+ private int $documentId;
+
+ private int $productId;
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->documentElementService = $serviceBuilder->getCatalogScope()->documentElement();
+ $this->documentService = $serviceBuilder->getCatalogScope()->document();
+ $this->productService = $serviceBuilder->getCatalogScope()->product();
+
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+ $iblockId = $catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $this->productId = $this->productService->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('test batch document element product %s', time()),
+ ])->product()->id;
+
+ $this->documentId = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('test batch document for element %s', time()),
+ ])->document()->id;
+ }
+
+ #[\Override]
+ protected function tearDown(): void
+ {
+ try {
+ $this->documentService->delete($this->documentId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+
+ try {
+ $this->productService->delete($this->productId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test Batch::add, Batch::update, Batch::delete')]
+ public function testAddUpdateDelete(): void
+ {
+ $addedIds = [];
+ foreach ($this->documentElementService->batch->add([
+ ['docId' => $this->documentId, 'elementId' => $this->productId, 'storeTo' => 0, 'amount' => 3],
+ ]) as $addedItemResult) {
+ $addedIds[] = $addedItemResult->documentElement()->id;
+ }
+
+ $this->assertCount(1, $addedIds);
+
+ $updatePayload = [];
+ foreach ($addedIds as $addedId) {
+ $updatePayload[$addedId] = ['amount' => 6];
+ }
+
+ $updatedCount = 0;
+ foreach ($this->documentElementService->batch->update($updatePayload) as $updatedItemResult) {
+ $this->assertEqualsWithDelta(6.0, $updatedItemResult->documentElement()->amount, PHP_FLOAT_EPSILON);
+ $updatedCount++;
+ }
+
+ $this->assertSame(1, $updatedCount);
+
+ $deletedCount = 0;
+ foreach ($this->documentElementService->batch->delete($addedIds) as $deletedItemResult) {
+ $this->assertTrue($deletedItemResult->isSuccess());
+ $deletedCount++;
+ }
+
+ $this->assertSame(1, $deletedCount);
+ }
+}
diff --git a/tests/Integration/Services/Catalog/DocumentElement/Service/DocumentElementTest.php b/tests/Integration/Services/Catalog/DocumentElement/Service/DocumentElementTest.php
new file mode 100644
index 00000000..b04e15a9
--- /dev/null
+++ b/tests/Integration/Services/Catalog/DocumentElement/Service/DocumentElementTest.php
@@ -0,0 +1,120 @@
+
+ *
+ * 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\DocumentElement\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Catalog\Document\Service\Document;
+use Bitrix24\SDK\Services\Catalog\DocumentElement\Service\DocumentElement;
+use Bitrix24\SDK\Services\Catalog\Product\Service\Product;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(DocumentElement::class)]
+class DocumentElementTest extends TestCase
+{
+ private DocumentElement $documentElementService;
+
+ private Document $documentService;
+
+ private Product $productService;
+
+ private int $documentId;
+
+ private int $productId;
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $serviceBuilder = Fabric::getServiceBuilder();
+ $this->documentElementService = $serviceBuilder->getCatalogScope()->documentElement();
+ $this->documentService = $serviceBuilder->getCatalogScope()->document();
+ $this->productService = $serviceBuilder->getCatalogScope()->product();
+
+ $catalogService = $serviceBuilder->getCatalogScope()->catalog();
+ $iblockId = $catalogService->list([], [], [], 1)->getCatalogs()[0]->iblockId;
+
+ $this->productId = $this->productService->add([
+ 'iblockId' => $iblockId,
+ 'name' => sprintf('test document element product %s', time()),
+ ])->product()->id;
+
+ $this->documentId = $this->documentService->add([
+ 'docType' => 'A',
+ 'currency' => 'USD',
+ 'responsibleId' => 1,
+ 'title' => sprintf('test document for element %s', time()),
+ ])->document()->id;
+ }
+
+ #[\Override]
+ protected function tearDown(): void
+ {
+ try {
+ $this->documentService->delete($this->documentId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+
+ try {
+ $this->productService->delete($this->productId);
+ } catch (\Throwable) {
+ // already removed, ignore
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test DocumentElement::add, DocumentElement::update, DocumentElement::list, DocumentElement::delete')]
+ public function testAddUpdateListDelete(): void
+ {
+ $documentElementResult = $this->documentElementService->add([
+ 'docId' => $this->documentId,
+ 'elementId' => $this->productId,
+ 'storeTo' => 0,
+ 'amount' => 5,
+ 'purchasingPrice' => 10.5,
+ ]);
+ $documentElementId = $documentElementResult->documentElement()->id;
+
+ $this->assertSame($this->documentId, $documentElementResult->documentElement()->docId);
+ $this->assertSame($this->productId, $documentElementResult->documentElement()->elementId);
+
+ $updateResult = $this->documentElementService->update($documentElementId, ['amount' => 8]);
+ $this->assertEqualsWithDelta(8.0, $updateResult->documentElement()->amount, PHP_FLOAT_EPSILON);
+
+ $documentElementsResult = $this->documentElementService->list([], ['docId' => $this->documentId]);
+ $this->assertCount(1, $documentElementsResult->getDocumentElements());
+
+ $this->assertTrue($this->documentElementService->delete($documentElementId)->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[TestDox('test DocumentElement::getFields')]
+ public function testGetFields(): void
+ {
+ $this->assertIsArray($this->documentElementService->getFields()->getFieldsDescription());
+ }
+}
diff --git a/tests/Unit/Services/Catalog/Document/Service/DocumentTest.php b/tests/Unit/Services/Catalog/Document/Service/DocumentTest.php
new file mode 100644
index 00000000..8f2d2bdf
--- /dev/null
+++ b/tests/Unit/Services/Catalog/Document/Service/DocumentTest.php
@@ -0,0 +1,134 @@
+mockCore('catalog.document.add', [
+ 'fields' => ['docType' => 'A', 'currency' => 'RUB', 'responsibleId' => 29],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentResult::class,
+ $this->makeService($core)->add(['docType' => 'A', 'currency' => 'RUB', 'responsibleId' => 29])
+ );
+ }
+
+ public function testUpdateBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.update', [
+ 'id' => 142,
+ 'fields' => ['title' => 'Updated title'],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentResult::class,
+ $this->makeService($core)->update(142, ['title' => 'Updated title'])
+ );
+ }
+
+ public function testListBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.list', [
+ 'select' => ['id', 'docType'],
+ 'filter' => ['docType' => 'A'],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentsResult::class,
+ $this->makeService($core)->list(['id', 'docType'], ['docType' => 'A'])
+ );
+ }
+
+ public function testDeleteBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.delete', ['id' => 142]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->delete(142));
+ }
+
+ public function testDeleteListBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.deleteList', ['documentIds' => [142, 143]]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->deleteList([142, 143]));
+ }
+
+ public function testConductBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.conduct', ['id' => 142]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->conduct(142));
+ }
+
+ public function testConductListBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.conductList', ['documentIds' => [142, 143]]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->conductList([142, 143]));
+ }
+
+ public function testCancelBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.cancel', ['id' => 142]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->cancel(142));
+ }
+
+ public function testCancelListBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.cancelList', ['documentIds' => [142, 143]]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->cancelList([142, 143]));
+ }
+
+ public function testGetFieldsBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.getFields', []);
+
+ self::assertInstanceOf(DocumentFieldsResult::class, $this->makeService($core)->getFields());
+ }
+
+ public function testModeStatusBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.mode.status', []);
+
+ self::assertInstanceOf(DocumentModeStatusResult::class, $this->makeService($core)->modeStatus());
+ }
+
+ private function makeService(CoreInterface $core): Document
+ {
+ return new Document(new Batch(new DocumentBatch($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;
+ }
+}
diff --git a/tests/Unit/Services/Catalog/DocumentElement/Service/DocumentElementTest.php b/tests/Unit/Services/Catalog/DocumentElement/Service/DocumentElementTest.php
new file mode 100644
index 00000000..ff3b48d5
--- /dev/null
+++ b/tests/Unit/Services/Catalog/DocumentElement/Service/DocumentElementTest.php
@@ -0,0 +1,96 @@
+mockCore('catalog.document.element.add', [
+ 'fields' => ['docId' => 64, 'elementId' => 312, 'storeTo' => 2, 'amount' => 15, 'purchasingPrice' => 1250.5],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentElementResult::class,
+ $this->makeService($core)->add(['docId' => 64, 'elementId' => 312, 'storeTo' => 2, 'amount' => 15, 'purchasingPrice' => 1250.5])
+ );
+ }
+
+ public function testUpdateBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.element.update', [
+ 'id' => 148,
+ 'fields' => ['amount' => 12, 'purchasingPrice' => 1180, 'storeTo' => 2],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentElementResult::class,
+ $this->makeService($core)->update(148, ['amount' => 12, 'purchasingPrice' => 1180, 'storeTo' => 2])
+ );
+ }
+
+ public function testListBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.element.list', [
+ 'select' => ['id', 'docId', 'elementId'],
+ 'filter' => ['docId' => 64],
+ 'order' => ['id' => 'ASC'],
+ ]);
+
+ self::assertInstanceOf(
+ DocumentElementsResult::class,
+ $this->makeService($core)->list(['id', 'docId', 'elementId'], ['docId' => 64], ['id' => 'ASC'])
+ );
+ }
+
+ public function testDeleteBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.element.delete', ['id' => 148]);
+
+ self::assertInstanceOf(DeletedItemResult::class, $this->makeService($core)->delete(148));
+ }
+
+ public function testGetFieldsBuildsParameters(): void
+ {
+ $core = $this->mockCore('catalog.document.element.getFields', []);
+
+ self::assertInstanceOf(DocumentElementFieldsResult::class, $this->makeService($core)->getFields());
+ }
+
+ private function makeService(CoreInterface $core): DocumentElement
+ {
+ return new DocumentElement(
+ new Batch(new DocumentElementBatch($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;
+ }
+}