diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
index 05755d8d..b1ab61a9 100644
--- a/.php-cs-fixer.php
+++ b/.php-cs-fixer.php
@@ -18,6 +18,7 @@
->in(__DIR__ . '/src/Services/CRM/Documentgenerator/Numerator/')
->in(__DIR__ . '/src/Services/CRM/Documentgenerator/Document/')
->in(__DIR__ . '/src/Services/CRM/Documentgenerator/Template/')
+ ->in(__DIR__ . '/src/Services/Documentgenerator/')
->in(__DIR__ . '/src/Services/Entity/Section/')
->in(__DIR__ . '/src/Services/Department/')
->in(__DIR__ . '/src/Services/Landing/')
diff --git a/.tasks/489/plan.md b/.tasks/489/plan.md
new file mode 100644
index 00000000..b6ae7488
--- /dev/null
+++ b/.tasks/489/plan.md
@@ -0,0 +1,190 @@
+# Plan: Add support for documentgenerator.document.* and documentgenerator.template.* methods (issue #489)
+
+## Context
+
+This issue adds SDK support for the `documentgenerator.document.*` REST API methods — the
+**non-CRM** Document Generator scope. Unlike `crm.documentgenerator.document.*`, these methods
+work with any data provider, not just CRM entities.
+
+Key differences from the CRM scope (`src/Services/CRM/Documentgenerator/Document/`):
+
+| Aspect | CRM scope | documentgenerator scope |
+|---|---|---|
+| Method prefix | `crm.documentgenerator.document.` | `documentgenerator.document.` |
+| `add` params | `templateId`, `entityTypeId`, `entityId` | `templateId`, `providerClassName`, `value` |
+| `update` extra params | `values`, `stampsEnabled` | `values`, `fields`, `stampsEnabled` |
+| SDK scope | `['crm']` | `['documentgenerator']` |
+| Builder access | `getCRMScope()->documentgeneratorDocument()` | `getDocumentgeneratorScope()->document()` |
+
+Response shape for `list`: `result.documents[]` (same key as CRM).
+Response shape for `get/add`: `result.document{}` (same key as CRM).
+
+Custom `Batch` class is required because the API uses lowercase `id` for delete/update and
+wraps list results under the `documents` key (same as CRM version).
+
+---
+
+## Files Created
+
+### Source files
+1. `src/Services/Documentgenerator/Document/Result/DocumentItemResult.php` — item result with field type casting
+2. `src/Services/Documentgenerator/Document/Result/DocumentResult.php` — single document result
+3. `src/Services/Documentgenerator/Document/Result/DocumentsResult.php` — list of documents result
+4. `src/Services/Documentgenerator/Document/Result/AddedDocumentResult.php` — add result
+5. `src/Services/Documentgenerator/Document/Result/AddedDocumentBatchResult.php` — batch add result
+6. `src/Services/Documentgenerator/Document/Result/DeletedDocumentResult.php` — delete result
+7. `src/Services/Documentgenerator/Document/Result/DeletedDocumentBatchResult.php` — batch delete result
+8. `src/Services/Documentgenerator/Document/Result/UpdatedDocumentResult.php` — update result
+9. `src/Services/Documentgenerator/Document/Result/UpdatedDocumentBatchResult.php` — batch update result
+10. `src/Services/Documentgenerator/Document/Result/DocumentFieldsResult.php` — getFields result
+11. `src/Services/Documentgenerator/Document/Result/PublicUrlResult.php` — enablePublicUrl result
+12. `src/Services/Documentgenerator/Document/Batch.php` — custom Batch override (lowercase id, documents wrapper)
+13. `src/Services/Documentgenerator/Document/Service/Batch.php` — service-level batch wrapper
+14. `src/Services/Documentgenerator/Document/Service/Document.php` — main service class
+15. `src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php` — scope builder
+
+### Test files
+16. `tests/Integration/Services/Documentgenerator/Document/Service/DocumentTest.php`
+17. `tests/Integration/Services/Documentgenerator/Document/Service/BatchTest.php`
+
+---
+
+## Files Modified
+
+### 1. `src/Services/ServiceBuilder.php`
+- Added `use Bitrix24\SDK\Services\Documentgenerator\DocumentgeneratorServiceBuilder;`
+- Added `getDocumentgeneratorScope(): DocumentgeneratorServiceBuilder` method
+
+### 2. `rector.php`
+- Added paths for `src/Services/Documentgenerator` and `tests/Integration/Services/Documentgenerator`
+
+### 3. `phpunit.xml.dist`
+- Added `integration_tests_scope_documentgenerator` and `integration_tests_documentgenerator_document` test suites
+
+### 4. `Makefile`
+- Added `integration_tests_scope_documentgenerator` and `integration_tests_documentgenerator_document` targets
+
+### 5. `CHANGELOG.md`
+- Added entry under `## 3.3.0 – UNRELEASED → ### Added`
+
+---
+
+## Deptrac compliance
+
+New code lives in `src/Services/Documentgenerator/` which belongs to the `Services` layer.
+It depends only on `Core` (AbstractItem, AbstractResult, AddedItemResult, etc.). No new violations.
+
+---
+
+## Verification
+
+```bash
+make lint-rector
+make lint-phpstan
+make lint-deptrac
+make test-unit
+make integration_tests_documentgenerator_document
+make integration_tests_documentgenerator_template
+make integration_tests_documentgenerator_template_annotations
+```
+
+---
+
+## Phase 2: documentgenerator.template.* (added 2026-05-26)
+
+Template methods are implemented in `src/Services/Documentgenerator/Template/`.
+
+Key differences from CRM variant:
+- `getFields` requires only `id` (no `entityTypeId`)
+- `add` supports `code` and `fileId` fields
+- `update` supports `providers` in fields
+- List response: `result.templates` keyed by id
+- Single-item response: `result.template`
+- Template fields response: `result.templateFields`
+
+### Files Created (Phase 2)
+
+1. `src/Services/Documentgenerator/Template/Result/TemplateItemResult.php`
+2. `src/Services/Documentgenerator/Template/Result/TemplateResult.php`
+3. `src/Services/Documentgenerator/Template/Result/TemplatesResult.php`
+4. `src/Services/Documentgenerator/Template/Result/AddedTemplateResult.php`
+5. `src/Services/Documentgenerator/Template/Result/UpdatedTemplateResult.php`
+6. `src/Services/Documentgenerator/Template/Result/DeletedTemplateResult.php`
+7. `src/Services/Documentgenerator/Template/Result/AddedTemplateBatchResult.php`
+8. `src/Services/Documentgenerator/Template/Result/UpdatedTemplateBatchResult.php`
+9. `src/Services/Documentgenerator/Template/Result/DeletedTemplateBatchResult.php`
+10. `src/Services/Documentgenerator/Template/Result/TemplateFieldsResult.php`
+11. `src/Services/Documentgenerator/Template/Batch.php`
+12. `src/Services/Documentgenerator/Template/Service/Batch.php`
+13. `src/Services/Documentgenerator/Template/Service/Template.php`
+14. `tests/Integration/Services/Documentgenerator/Template/Service/TemplateTest.php`
+15. `tests/Integration/Services/Documentgenerator/Template/Service/BatchTest.php`
+16. `tests/Integration/Services/Documentgenerator/Template/Result/TemplateItemResultAnnotationsTest.php`
+
+### Files Modified (Phase 2)
+
+- `src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php` — added `template()` method
+- `phpunit.xml.dist` — added 3 new test suites for template
+- `Makefile` — added 3 new make targets
+- `.php-cs-fixer.php` — added `src/Services/Documentgenerator/`
+- `phpstan.neon.dist` — added `tests/Integration/Services/Documentgenerator`
+- `CHANGELOG.md` — added Template entry under `## 3.3.0 – UNRELEASED`
+
+---
+
+## Plan: Add support for documentgenerator.region.* methods (issue #489)
+
+## Context
+
+The Bitrix24 REST API exposes a set of methods for managing document generator regions:
+- `documentgenerator.region.add` — creates a new custom region
+- `documentgenerator.region.update` — updates an existing region by `id` + `fields`
+- `documentgenerator.region.get` — returns a region by `id`
+- `documentgenerator.region.list` — returns a paginated list of regions
+- `documentgenerator.region.delete` — deletes a region by `id` (returns null on success)
+
+All methods belong to scope `documentgenerator`.
+
+API response envelope (verified against `documentgenerator.region.delete` via MCP):
+- Add → `result.region = {...}` (matching pattern of numerator.add)
+- Update → `result = null` (boolean cast = true on success)
+- Get → `result.region = {...}`
+- List → `result.regions = [...]`
+- Delete → `result = null` (boolean cast on result)
+
+Region entity fields (based on API docs):
+- `id` — int
+- `languageId` — string
+- `name` — string
+- `code` — string
+
+All REST methods use lowercase `id` parameter (not `ID`), matching the Numerator pattern.
+A custom `Batch` class (like `Numerator\Batch`) is required to override lowercase `id`
+and `regions` result key handling.
+
+---
+
+## Files to Create
+
+- `src/Services/Documentgenerator/Region/Result/RegionItemResult.php`
+- `src/Services/Documentgenerator/Region/Result/RegionResult.php`
+- `src/Services/Documentgenerator/Region/Result/RegionsResult.php`
+- `src/Services/Documentgenerator/Region/Result/AddedRegionResult.php`
+- `src/Services/Documentgenerator/Region/Result/AddedRegionBatchResult.php`
+- `src/Services/Documentgenerator/Region/Result/UpdatedRegionResult.php`
+- `src/Services/Documentgenerator/Region/Result/UpdatedRegionBatchResult.php`
+- `src/Services/Documentgenerator/Region/Result/DeletedRegionResult.php`
+- `src/Services/Documentgenerator/Region/Result/DeletedRegionBatchResult.php`
+- `src/Services/Documentgenerator/Region/Batch.php`
+- `src/Services/Documentgenerator/Region/Service/Batch.php`
+- `src/Services/Documentgenerator/Region/Service/Region.php`
+- `tests/Integration/Services/Documentgenerator/Region/Service/RegionTest.php`
+- `tests/Integration/Services/Documentgenerator/Region/Service/BatchTest.php`
+- `tests/Integration/Services/Documentgenerator/Region/Result/RegionItemResultAnnotationsTest.php`
+
+## Files to Modify
+
+- `src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php`
+- `phpunit.xml.dist`
+- `Makefile`
+- `CHANGELOG.md`
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf21c905..26c0acc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,51 @@
### Added
+- Added service `Services\Documentgenerator\Role` with support for `documentgenerator.role.*` methods,
+ see [documentgenerator.role.* methods](https://apidocs.bitrix24.com/api-reference/document-generator/role/index.html) ([#489](https://github.com/bitrix24/b24phpsdk/issues/489)):
+ - `add` creates a new role, with batch calls support
+ - `list` gets the list of roles, with batch calls support
+ - `update` updates an existing role, with batch calls support
+ - `delete` deletes a role, with batch calls support
+ - `get` gets information about the role by its identifier (includes permissions)
+ - `fillAccesses` completely replaces the role-to-access-code binding map
+ - `count` counts roles
+- Added service `Services\Documentgenerator\Region` with support for `documentgenerator.region.*` methods,
+ see [documentgenerator.region.* methods](https://apidocs.bitrix24.com/api-reference/document-generator/region/index.html) ([#489](https://github.com/bitrix24/b24phpsdk/issues/489)):
+ - `add` creates a new region, with batch calls support
+ - `list` gets the list of regions, with batch calls support
+ - `update` updates an existing region, with batch calls support
+ - `delete` deletes a region, with batch calls support
+ - `get` gets information about the region by its identifier
+ - `count` counts regions
+- Added service `Services\Documentgenerator\Numerator` with support for `documentgenerator.numerator.*` methods,
+ see [documentgenerator.numerator.* methods](https://apidocs.bitrix24.com/api-reference/document-generator/numerators/index.html) ([#489](https://github.com/bitrix24/b24phpsdk/issues/489)):
+ - `add` creates a new numerator, with batch calls support
+ - `list` gets the list of numerators, with batch calls support
+ - `update` updates an existing numerator, with batch calls support
+ - `delete` deletes a numerator, with batch calls support
+ - `get` gets information about the numerator by its identifier
+ - `count` counts numerators
+- Added service `Services\Documentgenerator\Template` with support for `documentgenerator.template.*` methods,
+ see [documentgenerator.template.* methods](https://apidocs.bitrix24.com/api-reference/document-generator/templates/index.html) ([#489](https://github.com/bitrix24/b24phpsdk/issues/489)):
+ - `add` creates a new template, with batch calls support
+ - `list` gets the list of templates, with batch calls support
+ - `update` updates an existing template, with batch calls support
+ - `delete` deletes a template, with batch calls support
+ - `get` gets information about the template by its identifier
+ - `getFields` returns the description of template fields
+ - `count` counts templates
+- Added service `Services\Documentgenerator\Document` with support for `documentgenerator.document.*` methods,
+ see [documentgenerator.document.* methods](https://apidocs.bitrix24.com/api-reference/document-generator/index.html) ([#489](https://github.com/bitrix24/b24phpsdk/issues/489)):
+ - `add` creates a new document based on a template and data provider, with batch calls support
+ - `list` gets the list of documents, with batch calls support
+ - `update` updates an existing document, with batch calls support
+ - `delete` deletes a document, with batch calls support
+ - `get` gets information about the document by its identifier
+ - `getFields` returns the description of document fields
+ - `enablePublicUrl` enables or disables public URL for a document
+ - `count` counts documents
+
### Changed
### Fixed
diff --git a/Makefile b/Makefile
index 397095ac..688bcd53 100644
--- a/Makefile
+++ b/Makefile
@@ -635,6 +635,62 @@ integration_tests_crm_documentgenerator_document:
integration_tests_crm_documentgenerator_template:
docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_documentgenerator_template
+.PHONY: integration_tests_scope_documentgenerator
+integration_tests_scope_documentgenerator:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_documentgenerator
+
+.PHONY: integration_tests_documentgenerator_document
+integration_tests_documentgenerator_document:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_document
+
+.PHONY: integration_tests_documentgenerator_template
+integration_tests_documentgenerator_template:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_template
+
+.PHONY: integration_tests_documentgenerator_template_service
+integration_tests_documentgenerator_template_service:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_template_service
+
+.PHONY: integration_tests_documentgenerator_template_annotations
+integration_tests_documentgenerator_template_annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_template_annotations
+
+.PHONY: integration_tests_documentgenerator_numerator
+integration_tests_documentgenerator_numerator:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_numerator
+
+.PHONY: integration_tests_documentgenerator_numerator_service
+integration_tests_documentgenerator_numerator_service:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_numerator_service
+
+.PHONY: integration_tests_documentgenerator_numerator_annotations
+integration_tests_documentgenerator_numerator_annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_numerator_annotations
+
+.PHONY: integration_tests_documentgenerator_region
+integration_tests_documentgenerator_region:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_region
+
+.PHONY: integration_tests_documentgenerator_region_service
+integration_tests_documentgenerator_region_service:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_region_service
+
+.PHONY: integration_tests_documentgenerator_region_annotations
+integration_tests_documentgenerator_region_annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_region_annotations
+
+.PHONY: integration_tests_documentgenerator_role
+integration_tests_documentgenerator_role:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_role
+
+.PHONY: integration_tests_documentgenerator_role_service
+integration_tests_documentgenerator_role_service:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_role_service
+
+.PHONY: integration_tests_documentgenerator_role_annotations
+integration_tests_documentgenerator_role_annotations:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_documentgenerator_role_annotations
+
# work dev environment
.PHONY: php-dev-server-up
php-dev-server-up:
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 36a9a3f3..58d242d4 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -38,6 +38,7 @@ parameters:
- tests/Integration/Services/CRM/Documentgenerator/Numerator
- tests/Integration/Services/CRM/Documentgenerator/Document
- tests/Integration/Services/CRM/Documentgenerator/Template
+ - tests/Integration/Services/Documentgenerator
excludePaths:
# TODO: Fix type errors in RequisiteUserfieldUseCaseTest and remove this exclusion
# Tracking: https://github.com/bitrix24/b24phpsdk/issues
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d431bba8..81ed1f06 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -329,6 +329,48 @@
./tests/Integration/Services/CRM/Documentgenerator/Template/
+
+ ./tests/Integration/Services/Documentgenerator/
+
+
+ ./tests/Integration/Services/Documentgenerator/Document/
+
+
+ ./tests/Integration/Services/Documentgenerator/Template/
+
+
+ ./tests/Integration/Services/Documentgenerator/Template/Service/
+
+
+ ./tests/Integration/Services/Documentgenerator/Template/Result/TemplateItemResultAnnotationsTest.php
+
+
+ ./tests/Integration/Services/Documentgenerator/Numerator/
+
+
+ ./tests/Integration/Services/Documentgenerator/Numerator/Service/
+
+
+ ./tests/Integration/Services/Documentgenerator/Numerator/Result/NumeratorItemResultAnnotationsTest.php
+
+
+ ./tests/Integration/Services/Documentgenerator/Region/
+
+
+ ./tests/Integration/Services/Documentgenerator/Region/Service/
+
+
+ ./tests/Integration/Services/Documentgenerator/Region/Result/RegionItemResultAnnotationsTest.php
+
+
+ ./tests/Integration/Services/Documentgenerator/Role/
+
+
+ ./tests/Integration/Services/Documentgenerator/Role/Service/
+
+
+ ./tests/Integration/Services/Documentgenerator/Role/Result/RoleItemResultAnnotationsTest.php
+
./tests/Integration/Services/SonetGroup/
diff --git a/rector.php b/rector.php
index fab5a568..ac0f61cb 100644
--- a/rector.php
+++ b/rector.php
@@ -80,6 +80,8 @@
__DIR__ . '/tests/Integration/Services/CRM/Documentgenerator/Document',
__DIR__ . '/src/Services/CRM/Documentgenerator/Template',
__DIR__ . '/tests/Integration/Services/CRM/Documentgenerator/Template',
+ __DIR__ . '/src/Services/Documentgenerator',
+ __DIR__ . '/tests/Integration/Services/Documentgenerator',
__DIR__ . '/tests/Unit/',
])
->withCache(cacheDirectory: __DIR__ . '/var/.cache/rector')
diff --git a/src/Services/Documentgenerator/Document/Batch.php b/src/Services/Documentgenerator/Document/Batch.php
new file mode 100644
index 00000000..3f903d11
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Batch.php
@@ -0,0 +1,251 @@
+
+ *
+ * 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\Documentgenerator\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 documentgenerator.document.* REST methods:
+ * - delete uses 'id' instead of 'ID'
+ * - update uses 'values' instead of 'fields'
+ * - list results are wrapped in 'documents' key and use lowercase 'id'
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for document generator
+ */
+ #[\Override]
+ protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string
+ {
+ return 'id';
+ }
+
+ /**
+ * Extracts elements from batch result, unwrapping the 'documents' key
+ */
+ #[\Override]
+ protected function extractElementsFromBatchResult(ResponseData $responseData, bool $isCrmItemsInBatch): array
+ {
+ $resultData = $responseData->getResult();
+
+ if (array_key_exists('documents', $resultData) && is_array($resultData['documents'])) {
+ return $resultData['documents'];
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Returns reference field path including 'documents' wrapper for batch query chaining
+ */
+ #[\Override]
+ protected function getReferenceFieldPath(string $prevCommandId, int $lastIndex, string $keyId, bool $isCrmItemsInBatch): string
+ {
+ return sprintf('$result[%s][documents][%d][%s]', $prevCommandId, $lastIndex, $keyId);
+ }
+
+ /**
+ * Get traversable list using lowercase 'id' key and 'documents' result wrapper
+ *
+ * Delegates to parent implementation which uses overridden helper methods:
+ * - determineKeyId() returns 'id' instead of 'ID'
+ * - extractElementsFromBatchResult() unwraps 'documents' key
+ * - getReferenceFieldPath() includes 'documents' in batch reference path
+ *
+ * @param array $order
+ * @param array $filter
+ * @param array $select
+ *
+ * @return Generator
+ * @throws BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ #[\Override]
+ public function getTraversableList(
+ string $apiMethod,
+ ?array $order = [],
+ ?array $filter = [],
+ ?array $select = [],
+ ?int $limit = null,
+ ?array $additionalParameters = null
+ ): Generator {
+ yield from parent::getTraversableList($apiMethod, $order, $filter, $select, $limit, $additionalParameters);
+ }
+
+ /**
+ * Update entity items with batch call
+ *
+ * The documentgenerator.document.update method expects 'values' key
+ * instead of the standard 'fields' key used by most other REST methods.
+ *
+ * Update elements in array with structure:
+ * element_id => [
+ * 'values' => [], // required: document values to update
+ * 'fields' => [], // optional: field configuration
+ * 'stampsEnabled' => int // optional: whether to apply stamps (1 = yes, 0 = no)
+ * ]
+ *
+ * @param array> $entityItems
+ *
+ * @return Generator|ResponseData[]
+ * @throws BaseException
+ */
+ #[\Override]
+ public function updateEntityItems(string $apiMethod, array $entityItems): Generator
+ {
+ $this->logger->debug(
+ 'updateEntityItems.start',
+ [
+ 'apiMethod' => $apiMethod,
+ 'entityItems' => $entityItems,
+ ]
+ );
+
+ try {
+ $this->clearCommands();
+
+ foreach ($entityItems as $entityItemId => $entityItem) {
+ if (!is_int($entityItemId)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of document id «%s», document id must be integer type',
+ gettype($entityItemId),
+ $entityItemId
+ )
+ );
+ }
+
+ if (!array_key_exists('values', $entityItem)) {
+ throw new InvalidArgumentException(
+ sprintf('array key «values» not found in entity item with id %s', $entityItemId)
+ );
+ }
+
+ $cmdArguments = [
+ 'id' => $entityItemId,
+ 'values' => $entityItem['values'],
+ ];
+
+ if (array_key_exists('fields', $entityItem)) {
+ $cmdArguments['fields'] = $entityItem['fields'];
+ }
+
+ if (array_key_exists('stampsEnabled', $entityItem)) {
+ $cmdArguments['stampsEnabled'] = $entityItem['stampsEnabled'];
+ }
+
+ $this->registerCommand($apiMethod, $cmdArguments);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) {
+ yield $cnt => $updatedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+
+ throw new BaseException($errorMessage, $exception->getCode(), $exception);
+ }
+
+ $this->logger->debug('updateEntityItems.finish');
+ }
+
+ /**
+ * Delete entity items with batch call
+ *
+ * @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 => $code) {
+ if (!is_int($code)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of document id «%s» at position %s, id must be integer type',
+ gettype($code),
+ $code,
+ $cnt
+ )
+ );
+ }
+
+ $parameters = ['id' => $code];
+ $this->registerCommand($apiMethod, $parameters);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) {
+ yield $cnt => $deletedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch delete entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete entity 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/Documentgenerator/Document/Result/AddedDocumentBatchResult.php b/src/Services/Documentgenerator/Document/Result/AddedDocumentBatchResult.php
new file mode 100644
index 00000000..6189a723
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/AddedDocumentBatchResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+
+/**
+ * Class AddedDocumentBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class AddedDocumentBatchResult extends AddedItemBatchResult
+{
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getResponseData()->getResult()['document']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/AddedDocumentResult.php b/src/Services/Documentgenerator/Document/Result/AddedDocumentResult.php
new file mode 100644
index 00000000..8f28a090
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/AddedDocumentResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+
+/**
+ * Class AddedDocumentResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class AddedDocumentResult extends AddedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getCoreResponse()->getResponseData()->getResult()['document']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DeletedDocumentBatchResult.php b/src/Services/Documentgenerator/Document/Result/DeletedDocumentBatchResult.php
new file mode 100644
index 00000000..71c21e85
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DeletedDocumentBatchResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+
+/**
+ * Class DeletedDocumentBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class DeletedDocumentBatchResult extends DeletedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DeletedDocumentResult.php b/src/Services/Documentgenerator/Document/Result/DeletedDocumentResult.php
new file mode 100644
index 00000000..0c1d6f14
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DeletedDocumentResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+
+/**
+ * Class DeletedDocumentResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class DeletedDocumentResult extends DeletedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DocumentFieldsResult.php b/src/Services/Documentgenerator/Document/Result/DocumentFieldsResult.php
new file mode 100644
index 00000000..9b2d68af
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DocumentFieldsResult.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class DocumentFieldsResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class DocumentFieldsResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function getFieldsDescription(): array
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ // API returns fields nested under documentFields key
+ if (!empty($result['documentFields']) && is_array($result['documentFields'])) {
+ return $result['documentFields'];
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DocumentItemResult.php b/src/Services/Documentgenerator/Document/Result/DocumentItemResult.php
new file mode 100644
index 00000000..7bbd8d32
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DocumentItemResult.php
@@ -0,0 +1,62 @@
+
+ *
+ * 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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * Class DocumentItemResult
+ *
+ * @property-read int $id
+ * @property-read string $title
+ * @property-read string $number
+ * @property-read int $templateId
+ * @property-read string $provider
+ * @property-read string $value
+ * @property-read int|null $fileId
+ * @property-read int|null $imageId
+ * @property-read int|null $pdfId
+ * @property-read CarbonImmutable|null $createTime
+ * @property-read CarbonImmutable|null $updateTime
+ * @property-read array|null $values
+ * @property-read int|null $createdBy
+ * @property-read int|null $updatedBy
+ * @property-read string|null $downloadUrl
+ * @property-read string|null $pdfUrl
+ * @property-read string|null $imageUrl
+ * @property-read bool|null $stampsEnabled
+ * @property-read string|null $downloadUrlMachine
+ * @property-read string|null $pdfUrlMachine
+ * @property-read string|null $imageUrlMachine
+ * @property-read string|null $creationMethod
+ */
+class DocumentItemResult extends AbstractAnnotatedItem
+{
+ /**
+ * @param int|string $offset
+ *
+ * @return mixed
+ */
+ #[\Override]
+ public function __get($offset)
+ {
+ if ($offset === 'creationMethod') {
+ // The API field name is '_creationMethod' (with leading underscore)
+ return $this->data['_creationMethod'] ?? null;
+ }
+
+ return parent::__get($offset);
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DocumentResult.php b/src/Services/Documentgenerator/Document/Result/DocumentResult.php
new file mode 100644
index 00000000..3ac784f7
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DocumentResult.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class DocumentResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class DocumentResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function document(): DocumentItemResult
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+ if (!empty($result['document']) && is_array($result['document'])) {
+ $result = $result['document'];
+ }
+
+ return new DocumentItemResult($result);
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/DocumentsResult.php b/src/Services/Documentgenerator/Document/Result/DocumentsResult.php
new file mode 100644
index 00000000..7c92e90a
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/DocumentsResult.php
@@ -0,0 +1,49 @@
+
+ *
+ * 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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class DocumentsResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class DocumentsResult extends AbstractResult
+{
+ /**
+ * @return DocumentItemResult[]
+ * @throws BaseException
+ */
+ public function getDocuments(): array
+ {
+ $items = [];
+ $source = [];
+
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['documents']) && is_array($result['documents'])) {
+ $source = $result['documents'];
+ } elseif (!empty($result['items']) && is_array($result['items'])) {
+ $source = $result['items'];
+ }
+
+ foreach ($source as $item) {
+ $items[] = new DocumentItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/PublicUrlResult.php b/src/Services/Documentgenerator/Document/Result/PublicUrlResult.php
new file mode 100644
index 00000000..21d26f82
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/PublicUrlResult.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class PublicUrlResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class PublicUrlResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function getPublicUrl(): ?string
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['publicUrl'])) {
+ return (string)$result['publicUrl'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @throws BaseException
+ */
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/UpdatedDocumentBatchResult.php b/src/Services/Documentgenerator/Document/Result/UpdatedDocumentBatchResult.php
new file mode 100644
index 00000000..5bb4b6e5
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/UpdatedDocumentBatchResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+
+/**
+ * Class UpdatedDocumentBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class UpdatedDocumentBatchResult extends UpdatedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Result/UpdatedDocumentResult.php b/src/Services/Documentgenerator/Document/Result/UpdatedDocumentResult.php
new file mode 100644
index 00000000..cbfc8e18
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Result/UpdatedDocumentResult.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\Documentgenerator\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+
+/**
+ * Class UpdatedDocumentResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Document\Result
+ */
+class UpdatedDocumentResult extends UpdatedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Service/Batch.php b/src/Services/Documentgenerator/Document/Service/Batch.php
new file mode 100644
index 00000000..273f85f2
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Service/Batch.php
@@ -0,0 +1,153 @@
+
+ *
+ * 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\Documentgenerator\Document\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\AddedDocumentBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DeletedDocumentBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DocumentItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\UpdatedDocumentBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['documentgenerator']))]
+class Batch
+{
+ /**
+ * Batch constructor
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for documents
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.document.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-list.html',
+ 'Batch list method for documents'
+ )]
+ public function list(?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'limit' => $limit,
+ ]
+ );
+
+ $documentListGenerator = $this->batch->getTraversableListWithCount(
+ 'documentgenerator.document.list',
+ [],
+ [],
+ [],
+ $limit
+ );
+ foreach ($documentListGenerator as $key => $value) {
+ yield $key => new DocumentItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding documents
+ *
+ * @param array $documents
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.document.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-add.html',
+ 'Batch adding documents'
+ )]
+ public function add(array $documents): Generator
+ {
+ foreach ($this->batch->addEntityItems('documentgenerator.document.add', $documents) as $key => $item) {
+ yield $key => new AddedDocumentBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update documents
+ *
+ * Update elements in array with structure:
+ * id => [ // Document id
+ * 'values' => [], // Document values to update
+ * 'fields' => [], // Optional: field configuration
+ * 'stampsEnabled' => int // Optional: whether to apply stamps (1 = yes, 0 = no)
+ * ]
+ *
+ * @param array $entityItems
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.document.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-update.html',
+ 'Update in batch mode a list of documents'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach (
+ $this->batch->updateEntityItems(
+ 'documentgenerator.document.update',
+ $entityItems
+ ) as $key => $item
+ ) {
+ yield $key => new UpdatedDocumentBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete documents
+ *
+ * @param int[] $documentId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.document.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-delete.html',
+ 'Batch delete documents'
+ )]
+ public function delete(array $documentId): Generator
+ {
+ foreach (
+ $this->batch->deleteEntityItems(
+ 'documentgenerator.document.delete',
+ $documentId
+ ) as $key => $item
+ ) {
+ yield $key => new DeletedDocumentBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Documentgenerator/Document/Service/Document.php b/src/Services/Documentgenerator/Document/Service/Document.php
new file mode 100644
index 00000000..b4d17937
--- /dev/null
+++ b/src/Services/Documentgenerator/Document/Service/Document.php
@@ -0,0 +1,299 @@
+
+ *
+ * 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\Documentgenerator\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\Services\AbstractService;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\AddedDocumentResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DeletedDocumentResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DocumentFieldsResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DocumentResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DocumentsResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\PublicUrlResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\UpdatedDocumentResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['documentgenerator']))]
+class Document extends AbstractService
+{
+ /**
+ * Document constructor
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Creates a new document based on a template and data provider
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-add.html
+ *
+ * @param int $templateId Template identifier
+ * @param string $providerClassName Data provider class name (e.g. 'Bitrix\DocumentGenerator\DataProvider\Rest')
+ * @param string $value External identifier of the data source (e.g. 'ORDER_1024')
+ * @param array $values Field values for the document
+ * @param array $fields Field configuration (providers, types, etc.)
+ * @param int|null $stampsEnabled Whether to apply stamps (1 = yes, 0 = no)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-add.html',
+ 'Creates a new document based on a template and data provider'
+ )]
+ public function add(
+ int $templateId,
+ string $providerClassName,
+ string $value,
+ array $values = [],
+ array $fields = [],
+ ?int $stampsEnabled = null
+ ): AddedDocumentResult {
+ $params = [
+ 'templateId' => $templateId,
+ 'providerClassName' => $providerClassName,
+ 'value' => $value,
+ ];
+
+ if ($values !== []) {
+ $params['values'] = $values;
+ }
+
+ if ($fields !== []) {
+ $params['fields'] = $fields;
+ }
+
+ if ($stampsEnabled !== null) {
+ $params['stampsEnabled'] = $stampsEnabled;
+ }
+
+ return new AddedDocumentResult(
+ $this->core->call(
+ 'documentgenerator.document.add',
+ $params
+ )
+ );
+ }
+
+ /**
+ * Updates an existing document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-update.html
+ *
+ * @param int $id Document identifier
+ * @param array $values Field values to update
+ * @param array $fields Field configuration
+ * @param int|null $stampsEnabled Whether to apply stamps (1 = yes, 0 = no)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-update.html',
+ 'Updates an existing document'
+ )]
+ public function update(int $id, array $values = [], array $fields = [], ?int $stampsEnabled = null): UpdatedDocumentResult
+ {
+ $params = [
+ 'id' => $id,
+ ];
+
+ if ($values !== []) {
+ $params['values'] = $values;
+ }
+
+ if ($fields !== []) {
+ $params['fields'] = $fields;
+ }
+
+ if ($stampsEnabled !== null) {
+ $params['stampsEnabled'] = $stampsEnabled;
+ }
+
+ return new UpdatedDocumentResult(
+ $this->core->call(
+ 'documentgenerator.document.update',
+ $params
+ )
+ );
+ }
+
+ /**
+ * Returns information about the document by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-get.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.get',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-get.html',
+ 'Returns information about the document by its identifier'
+ )]
+ public function get(int $id): DocumentResult
+ {
+ return new DocumentResult($this->core->call('documentgenerator.document.get', ['id' => $id]));
+ }
+
+ /**
+ * Returns a list of documents
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-list.html
+ *
+ * @param array $filter Filter parameters
+ * @param array $order Order parameters
+ * @param array $select Fields to select
+ * @param int $start Offset for pagination
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-list.html',
+ 'Returns a list of documents'
+ )]
+ public function list(array $filter = [], array $order = [], array $select = [], int $start = 0): DocumentsResult
+ {
+ $params = [
+ 'start' => $start,
+ ];
+
+ if ($filter !== []) {
+ $params['filter'] = $filter;
+ }
+
+ if ($order !== []) {
+ $params['order'] = $order;
+ }
+
+ if ($select !== []) {
+ $params['select'] = $select;
+ }
+
+ return new DocumentsResult(
+ $this->core->call(
+ 'documentgenerator.document.list',
+ $params
+ )
+ );
+ }
+
+ /**
+ * Deletes a document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-delete.html',
+ 'Deletes a document'
+ )]
+ public function delete(int $id): DeletedDocumentResult
+ {
+ return new DeletedDocumentResult(
+ $this->core->call(
+ 'documentgenerator.document.delete',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Enables or disables public URL for a document
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-enable-public-url.html
+ *
+ * @param int $id Document identifier
+ * @param int $status 1 to enable public URL, 0 to disable (default: 1)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.enablepublicurl',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-enable-public-url.html',
+ 'Enables or disables public URL for a document'
+ )]
+ public function enablePublicUrl(int $id, int $status = 1): PublicUrlResult
+ {
+ return new PublicUrlResult(
+ $this->core->call(
+ 'documentgenerator.document.enablepublicurl',
+ [
+ 'id' => $id,
+ 'status' => $status,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns the description of document fields
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-get-fields.html
+ *
+ * @param int $id Document identifier
+ * @param array $values Optional field values
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.document.getfields',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/document-generator-document-get-fields.html',
+ 'Returns the description of document fields'
+ )]
+ public function getFields(int $id, array $values = []): DocumentFieldsResult
+ {
+ $params = [
+ 'id' => $id,
+ ];
+
+ if ($values !== []) {
+ $params['values'] = $values;
+ }
+
+ return new DocumentFieldsResult(
+ $this->core->call(
+ 'documentgenerator.document.getfields',
+ $params
+ )
+ );
+ }
+
+ /**
+ * Count documents
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function count(): int
+ {
+ return $this->list()->getCoreResponse()->getResponseData()->getPagination()->getTotal();
+ }
+}
diff --git a/src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php b/src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php
new file mode 100644
index 00000000..ed4526a4
--- /dev/null
+++ b/src/Services/Documentgenerator/DocumentgeneratorServiceBuilder.php
@@ -0,0 +1,112 @@
+
+ *
+ * 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\Documentgenerator;
+
+use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Services\AbstractServiceBuilder;
+use Bitrix24\SDK\Services\Documentgenerator\Document;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator;
+use Bitrix24\SDK\Services\Documentgenerator\Region;
+use Bitrix24\SDK\Services\Documentgenerator\Role;
+use Bitrix24\SDK\Services\Documentgenerator\Template;
+
+#[ApiServiceBuilderMetadata(new Scope(['documentgenerator']))]
+class DocumentgeneratorServiceBuilder extends AbstractServiceBuilder
+{
+ public function document(): Document\Service\Document
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $documentBatch = new Document\Batch(
+ $this->core,
+ $this->log
+ );
+ $this->serviceCache[__METHOD__] = new Document\Service\Document(
+ new Document\Service\Batch($documentBatch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function template(): Template\Service\Template
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $templateBatch = new Template\Batch(
+ $this->core,
+ $this->log
+ );
+ $this->serviceCache[__METHOD__] = new Template\Service\Template(
+ new Template\Service\Batch($templateBatch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function numerator(): Numerator\Service\Numerator
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $numeratorBatch = new Numerator\Batch(
+ $this->core,
+ $this->log
+ );
+ $this->serviceCache[__METHOD__] = new Numerator\Service\Numerator(
+ new Numerator\Service\Batch($numeratorBatch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function region(): Region\Service\Region
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $regionBatch = new Region\Batch(
+ $this->core,
+ $this->log
+ );
+ $this->serviceCache[__METHOD__] = new Region\Service\Region(
+ new Region\Service\Batch($regionBatch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function role(): Role\Service\Role
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $roleBatch = new Role\Batch(
+ $this->core,
+ $this->log
+ );
+ $this->serviceCache[__METHOD__] = new Role\Service\Role(
+ new Role\Service\Batch($roleBatch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Batch.php b/src/Services/Documentgenerator/Numerator/Batch.php
new file mode 100644
index 00000000..451b2acd
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Batch.php
@@ -0,0 +1,241 @@
+
+ *
+ * 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\Documentgenerator\Numerator;
+
+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 documentgenerator.numerator.* REST methods:
+ * - delete uses 'id' instead of 'ID'
+ * - update uses 'id' instead of 'ID'
+ * - list results are wrapped in 'numerators' key and use lowercase 'id'
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for document generator numerator
+ */
+ #[\Override]
+ protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string
+ {
+ return 'id';
+ }
+
+ /**
+ * Extracts elements from batch result, unwrapping the 'numerators' key
+ */
+ #[\Override]
+ protected function extractElementsFromBatchResult(ResponseData $responseData, bool $isCrmItemsInBatch): array
+ {
+ $resultData = $responseData->getResult();
+
+ if (array_key_exists('numerators', $resultData) && is_array($resultData['numerators'])) {
+ return $resultData['numerators'];
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Returns reference field path including 'numerators' wrapper for batch query chaining
+ */
+ #[\Override]
+ protected function getReferenceFieldPath(string $prevCommandId, int $lastIndex, string $keyId, bool $isCrmItemsInBatch): string
+ {
+ return sprintf('$result[%s][numerators][%d][%s]', $prevCommandId, $lastIndex, $keyId);
+ }
+
+ /**
+ * Get traversable list using lowercase 'id' key and 'numerators' result wrapper
+ *
+ * Delegates to parent implementation which uses overridden helper methods:
+ * - determineKeyId() returns 'id' instead of 'ID'
+ * - extractElementsFromBatchResult() unwraps 'numerators' key
+ * - getReferenceFieldPath() includes 'numerators' in batch reference path
+ *
+ * @param array $order
+ * @param array $filter
+ * @param array $select
+ *
+ * @return Generator
+ * @throws BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ #[\Override]
+ public function getTraversableList(
+ string $apiMethod,
+ ?array $order = [],
+ ?array $filter = [],
+ ?array $select = [],
+ ?int $limit = null,
+ ?array $additionalParameters = null
+ ): Generator {
+ yield from parent::getTraversableList($apiMethod, $order, $filter, $select, $limit, $additionalParameters);
+ }
+
+ /**
+ * Update entity items with batch call
+ *
+ * The documentgenerator.numerator.update method uses 'id' (lowercase)
+ * instead of the standard 'ID' key used by most other REST methods.
+ *
+ * Update elements in array with structure:
+ * element_id => [
+ * 'fields' => [] // required: numerator fields to update
+ * ]
+ *
+ * @param array> $entityItems
+ *
+ * @return Generator|ResponseData[]
+ * @throws BaseException
+ */
+ #[\Override]
+ public function updateEntityItems(string $apiMethod, array $entityItems): Generator
+ {
+ $this->logger->debug(
+ 'updateEntityItems.start',
+ [
+ 'apiMethod' => $apiMethod,
+ 'entityItems' => $entityItems,
+ ]
+ );
+
+ try {
+ $this->clearCommands();
+
+ foreach ($entityItems as $entityItemId => $entityItem) {
+ if (!is_int($entityItemId)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of numerator id «%s», the id must be integer type',
+ gettype($entityItemId),
+ $entityItemId
+ )
+ );
+ }
+
+ if (!array_key_exists('fields', $entityItem)) {
+ throw new InvalidArgumentException(
+ sprintf('array key «fields» not found in entity item with id %s', $entityItemId)
+ );
+ }
+
+ $cmdArguments = [
+ 'id' => $entityItemId,
+ 'fields' => $entityItem['fields'],
+ ];
+
+ $this->registerCommand($apiMethod, $cmdArguments);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) {
+ yield $cnt => $updatedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+
+ throw new BaseException($errorMessage, $exception->getCode(), $exception);
+ }
+
+ $this->logger->debug('updateEntityItems.finish');
+ }
+
+ /**
+ * Delete entity items with batch call
+ *
+ * @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 => $code) {
+ if (!is_int($code)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of numerator id «%s» at position %s, id must be integer type',
+ gettype($code),
+ $code,
+ $cnt
+ )
+ );
+ }
+
+ $parameters = ['id' => $code];
+ $this->registerCommand($apiMethod, $parameters);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) {
+ yield $cnt => $deletedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch delete entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete entity 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/Documentgenerator/Numerator/Result/AddedNumeratorBatchResult.php b/src/Services/Documentgenerator/Numerator/Result/AddedNumeratorBatchResult.php
new file mode 100644
index 00000000..2b45d885
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/AddedNumeratorBatchResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+
+/**
+ * Class AddedNumeratorBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class AddedNumeratorBatchResult extends AddedItemBatchResult
+{
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getResponseData()->getResult()['numerator']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/AddedNumeratorResult.php b/src/Services/Documentgenerator/Numerator/Result/AddedNumeratorResult.php
new file mode 100644
index 00000000..63023dd1
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/AddedNumeratorResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+
+/**
+ * Class AddedNumeratorResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class AddedNumeratorResult extends AddedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getCoreResponse()->getResponseData()->getResult()['numerator']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorBatchResult.php b/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorBatchResult.php
new file mode 100644
index 00000000..8446502b
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorBatchResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+
+/**
+ * Class DeletedNumeratorBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class DeletedNumeratorBatchResult extends DeletedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorResult.php b/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorResult.php
new file mode 100644
index 00000000..7c4340fd
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/DeletedNumeratorResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+
+/**
+ * Class DeletedNumeratorResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class DeletedNumeratorResult extends DeletedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/NumeratorItemResult.php b/src/Services/Documentgenerator/Numerator/Result/NumeratorItemResult.php
new file mode 100644
index 00000000..06113340
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/NumeratorItemResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem;
+
+/**
+ * Class NumeratorItemResult
+ *
+ * @property-read int $id
+ * @property-read string $name
+ * @property-read string $template
+ * @property-read array|null $settings
+ */
+class NumeratorItemResult extends AbstractAnnotatedItem
+{
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/NumeratorResult.php b/src/Services/Documentgenerator/Numerator/Result/NumeratorResult.php
new file mode 100644
index 00000000..b2c1828b
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/NumeratorResult.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class NumeratorResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class NumeratorResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function numerator(): NumeratorItemResult
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+ if (!empty($result['numerator']) && is_array($result['numerator'])) {
+ $result = $result['numerator'];
+ }
+
+ return new NumeratorItemResult($result);
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/NumeratorsResult.php b/src/Services/Documentgenerator/Numerator/Result/NumeratorsResult.php
new file mode 100644
index 00000000..8fbc538f
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/NumeratorsResult.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class NumeratorsResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class NumeratorsResult extends AbstractResult
+{
+ /**
+ * @return NumeratorItemResult[]
+ * @throws BaseException
+ */
+ public function getNumerators(): array
+ {
+ $items = [];
+ $source = [];
+
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['numerators']) && is_array($result['numerators'])) {
+ $source = $result['numerators'];
+ }
+
+ foreach ($source as $item) {
+ $items[] = new NumeratorItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorBatchResult.php b/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorBatchResult.php
new file mode 100644
index 00000000..c687a3a4
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorBatchResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+
+/**
+ * Class UpdatedNumeratorBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class UpdatedNumeratorBatchResult extends UpdatedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorResult.php b/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorResult.php
new file mode 100644
index 00000000..a78eb137
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Result/UpdatedNumeratorResult.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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+
+/**
+ * Class UpdatedNumeratorResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Numerator\Result
+ */
+class UpdatedNumeratorResult extends UpdatedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Service/Batch.php b/src/Services/Documentgenerator/Numerator/Service/Batch.php
new file mode 100644
index 00000000..5d01ac1f
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Service/Batch.php
@@ -0,0 +1,157 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\AddedNumeratorBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\DeletedNumeratorBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\NumeratorItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\UpdatedNumeratorBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['documentgenerator']))]
+class Batch
+{
+ /**
+ * Batch constructor
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for numerators
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-list.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.numerator.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-list.html',
+ 'Batch list method for numerators'
+ )]
+ public function list(?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'limit' => $limit,
+ ]
+ );
+
+ $numeratorListGenerator = $this->batch->getTraversableListWithCount(
+ 'documentgenerator.numerator.list',
+ [],
+ [],
+ [],
+ $limit
+ );
+ foreach ($numeratorListGenerator as $key => $value) {
+ yield $key => new NumeratorItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding numerators
+ *
+ * @param array $numerators
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.numerator.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-add.html',
+ 'Batch adding numerators'
+ )]
+ public function add(array $numerators): Generator
+ {
+ $items = [];
+ foreach ($numerators as $item) {
+ $items[] = [
+ 'fields' => $item,
+ ];
+ }
+
+ foreach ($this->batch->addEntityItems('documentgenerator.numerator.add', $items) as $key => $item) {
+ yield $key => new AddedNumeratorBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update numerators
+ *
+ * Update elements in array with structure
+ * id => [ // Numerator id
+ * 'fields' => [] // Numerator fields to update
+ * ]
+ *
+ * @param array $entityItems
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.numerator.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-update.html',
+ 'Update in batch mode a list of numerators'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach (
+ $this->batch->updateEntityItems(
+ 'documentgenerator.numerator.update',
+ $entityItems
+ ) as $key => $item
+ ) {
+ yield $key => new UpdatedNumeratorBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete numerators
+ *
+ * @param int[] $numeratorId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.numerator.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-delete.html',
+ 'Batch delete numerators'
+ )]
+ public function delete(array $numeratorId): Generator
+ {
+ foreach (
+ $this->batch->deleteEntityItems(
+ 'documentgenerator.numerator.delete',
+ $numeratorId
+ ) as $key => $item
+ ) {
+ yield $key => new DeletedNumeratorBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Documentgenerator/Numerator/Service/Numerator.php b/src/Services/Documentgenerator/Numerator/Service/Numerator.php
new file mode 100644
index 00000000..07886a13
--- /dev/null
+++ b/src/Services/Documentgenerator/Numerator/Service/Numerator.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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\Documentgenerator\Numerator\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\Services\AbstractService;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\AddedNumeratorResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\DeletedNumeratorResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\NumeratorResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\NumeratorsResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\UpdatedNumeratorResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['documentgenerator']))]
+class Numerator extends AbstractService
+{
+ /**
+ * Numerator constructor
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Creates a new numerator
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-add.html
+ *
+ * @param array{
+ * name: string,
+ * template: string,
+ * settings?: array
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.numerator.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-add.html',
+ 'Creates a new numerator'
+ )]
+ public function add(array $fields): AddedNumeratorResult
+ {
+ return new AddedNumeratorResult(
+ $this->core->call(
+ 'documentgenerator.numerator.add',
+ [
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates an existing numerator with new values
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-update.html
+ *
+ * @param array{
+ * name?: string,
+ * template?: string,
+ * settings?: array
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.numerator.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-update.html',
+ 'Updates an existing numerator with new values'
+ )]
+ public function update(int $id, array $fields): UpdatedNumeratorResult
+ {
+ return new UpdatedNumeratorResult(
+ $this->core->call(
+ 'documentgenerator.numerator.update',
+ [
+ 'id' => $id,
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns information about the numerator by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-get.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.numerator.get',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-get.html',
+ 'Returns information about the numerator by its identifier'
+ )]
+ public function get(int $id): NumeratorResult
+ {
+ return new NumeratorResult(
+ $this->core->call('documentgenerator.numerator.get', ['id' => $id])
+ );
+ }
+
+ /**
+ * Returns a list of numerators
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-list.html
+ *
+ * @param int $start Offset for pagination
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.numerator.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-list.html',
+ 'Returns a list of numerators'
+ )]
+ public function list(int $start = 0): NumeratorsResult
+ {
+ return new NumeratorsResult(
+ $this->core->call(
+ 'documentgenerator.numerator.list',
+ [
+ 'start' => $start,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Deletes a numerator
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.numerator.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/numerators/document-generator-numerator-delete.html',
+ 'Deletes a numerator'
+ )]
+ public function delete(int $id): DeletedNumeratorResult
+ {
+ return new DeletedNumeratorResult(
+ $this->core->call(
+ 'documentgenerator.numerator.delete',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Count numerators
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function count(): int
+ {
+ return $this->list()->getCoreResponse()->getResponseData()->getPagination()->getTotal();
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Batch.php b/src/Services/Documentgenerator/Region/Batch.php
new file mode 100644
index 00000000..69bb993a
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Batch.php
@@ -0,0 +1,241 @@
+
+ *
+ * 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\Documentgenerator\Region;
+
+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 documentgenerator.region.* REST methods:
+ * - delete uses 'id' instead of 'ID'
+ * - update uses 'id' instead of 'ID'
+ * - list results are wrapped in 'regions' key and use lowercase 'id'
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for document generator region
+ */
+ #[\Override]
+ protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string
+ {
+ return 'id';
+ }
+
+ /**
+ * Extracts elements from batch result, unwrapping the 'regions' key
+ */
+ #[\Override]
+ protected function extractElementsFromBatchResult(ResponseData $responseData, bool $isCrmItemsInBatch): array
+ {
+ $resultData = $responseData->getResult();
+
+ if (array_key_exists('regions', $resultData) && is_array($resultData['regions'])) {
+ return $resultData['regions'];
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Returns reference field path including 'regions' wrapper for batch query chaining
+ */
+ #[\Override]
+ protected function getReferenceFieldPath(string $prevCommandId, int $lastIndex, string $keyId, bool $isCrmItemsInBatch): string
+ {
+ return sprintf('$result[%s][regions][%d][%s]', $prevCommandId, $lastIndex, $keyId);
+ }
+
+ /**
+ * Get traversable list using lowercase 'id' key and 'regions' result wrapper
+ *
+ * Delegates to parent implementation which uses overridden helper methods:
+ * - determineKeyId() returns 'id' instead of 'ID'
+ * - extractElementsFromBatchResult() unwraps 'regions' key
+ * - getReferenceFieldPath() includes 'regions' in batch reference path
+ *
+ * @param array $order
+ * @param array $filter
+ * @param array $select
+ *
+ * @return Generator
+ * @throws BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ #[\Override]
+ public function getTraversableList(
+ string $apiMethod,
+ ?array $order = [],
+ ?array $filter = [],
+ ?array $select = [],
+ ?int $limit = null,
+ ?array $additionalParameters = null
+ ): Generator {
+ yield from parent::getTraversableList($apiMethod, $order, $filter, $select, $limit, $additionalParameters);
+ }
+
+ /**
+ * Update entity items with batch call
+ *
+ * The documentgenerator.region.update method uses 'id' (lowercase)
+ * instead of the standard 'ID' key used by most other REST methods.
+ *
+ * Update elements in array with structure:
+ * element_id => [
+ * 'fields' => [] // required: region fields to update
+ * ]
+ *
+ * @param array> $entityItems
+ *
+ * @return Generator|ResponseData[]
+ * @throws BaseException
+ */
+ #[\Override]
+ public function updateEntityItems(string $apiMethod, array $entityItems): Generator
+ {
+ $this->logger->debug(
+ 'updateEntityItems.start',
+ [
+ 'apiMethod' => $apiMethod,
+ 'entityItems' => $entityItems,
+ ]
+ );
+
+ try {
+ $this->clearCommands();
+
+ foreach ($entityItems as $entityItemId => $entityItem) {
+ if (!is_int($entityItemId)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of region id «%s», the id must be integer type',
+ gettype($entityItemId),
+ $entityItemId
+ )
+ );
+ }
+
+ if (!array_key_exists('fields', $entityItem)) {
+ throw new InvalidArgumentException(
+ sprintf('array key «fields» not found in entity item with id %s', $entityItemId)
+ );
+ }
+
+ $cmdArguments = [
+ 'id' => $entityItemId,
+ 'fields' => $entityItem['fields'],
+ ];
+
+ $this->registerCommand($apiMethod, $cmdArguments);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) {
+ yield $cnt => $updatedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+
+ throw new BaseException($errorMessage, $exception->getCode(), $exception);
+ }
+
+ $this->logger->debug('updateEntityItems.finish');
+ }
+
+ /**
+ * Delete entity items with batch call
+ *
+ * @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 => $code) {
+ if (!is_int($code)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of region id «%s» at position %s, id must be integer type',
+ gettype($code),
+ $code,
+ $cnt
+ )
+ );
+ }
+
+ $parameters = ['id' => $code];
+ $this->registerCommand($apiMethod, $parameters);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) {
+ yield $cnt => $deletedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch delete entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete entity 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/Documentgenerator/Region/Result/AddedRegionBatchResult.php b/src/Services/Documentgenerator/Region/Result/AddedRegionBatchResult.php
new file mode 100644
index 00000000..e1ddbf6d
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/AddedRegionBatchResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+
+/**
+ * Class AddedRegionBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class AddedRegionBatchResult extends AddedItemBatchResult
+{
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getResponseData()->getResult()['region']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/AddedRegionResult.php b/src/Services/Documentgenerator/Region/Result/AddedRegionResult.php
new file mode 100644
index 00000000..5ff433a5
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/AddedRegionResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+
+/**
+ * Class AddedRegionResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class AddedRegionResult extends AddedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getCoreResponse()->getResponseData()->getResult()['region']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/DeletedRegionBatchResult.php b/src/Services/Documentgenerator/Region/Result/DeletedRegionBatchResult.php
new file mode 100644
index 00000000..163044b8
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/DeletedRegionBatchResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+
+/**
+ * Class DeletedRegionBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class DeletedRegionBatchResult extends DeletedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/DeletedRegionResult.php b/src/Services/Documentgenerator/Region/Result/DeletedRegionResult.php
new file mode 100644
index 00000000..d5cf0a68
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/DeletedRegionResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+
+/**
+ * Class DeletedRegionResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class DeletedRegionResult extends DeletedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/RegionItemResult.php b/src/Services/Documentgenerator/Region/Result/RegionItemResult.php
new file mode 100644
index 00000000..283eaa14
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/RegionItemResult.php
@@ -0,0 +1,31 @@
+
+ *
+ * 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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem;
+
+/**
+ * Class RegionItemResult
+ *
+ * @property-read int $id
+ * @property-read string $title
+ * @property-read string $languageId
+ * @property-read string|null $formatDate
+ * @property-read string|null $formatDatetime
+ * @property-read string|null $formatName
+ * @property-read array $phrases
+ */
+class RegionItemResult extends AbstractAnnotatedItem
+{
+}
diff --git a/src/Services/Documentgenerator/Region/Result/RegionResult.php b/src/Services/Documentgenerator/Region/Result/RegionResult.php
new file mode 100644
index 00000000..26d9ee1f
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/RegionResult.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class RegionResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class RegionResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function region(): RegionItemResult
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+ if (!empty($result['region']) && is_array($result['region'])) {
+ $result = $result['region'];
+ }
+
+ return new RegionItemResult($result);
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/RegionsResult.php b/src/Services/Documentgenerator/Region/Result/RegionsResult.php
new file mode 100644
index 00000000..c3344469
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/RegionsResult.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class RegionsResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class RegionsResult extends AbstractResult
+{
+ /**
+ * @return RegionItemResult[]
+ * @throws BaseException
+ */
+ public function getRegions(): array
+ {
+ $items = [];
+ $source = [];
+
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['regions']) && is_array($result['regions'])) {
+ $source = $result['regions'];
+ }
+
+ foreach ($source as $item) {
+ $items[] = new RegionItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/UpdatedRegionBatchResult.php b/src/Services/Documentgenerator/Region/Result/UpdatedRegionBatchResult.php
new file mode 100644
index 00000000..6bb2a719
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/UpdatedRegionBatchResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+
+/**
+ * Class UpdatedRegionBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class UpdatedRegionBatchResult extends UpdatedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Result/UpdatedRegionResult.php b/src/Services/Documentgenerator/Region/Result/UpdatedRegionResult.php
new file mode 100644
index 00000000..26711f82
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Result/UpdatedRegionResult.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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+
+/**
+ * Class UpdatedRegionResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Region\Result
+ */
+class UpdatedRegionResult extends UpdatedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Service/Batch.php b/src/Services/Documentgenerator/Region/Service/Batch.php
new file mode 100644
index 00000000..44f6041a
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Service/Batch.php
@@ -0,0 +1,156 @@
+
+ *
+ * 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\Documentgenerator\Region\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\AddedRegionBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\DeletedRegionBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\RegionItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\UpdatedRegionBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['documentgenerator']))]
+class Batch
+{
+ /**
+ * Batch constructor
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for regions
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-list.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.region.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-list.html',
+ 'Batch list method for regions'
+ )]
+ public function list(?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'limit' => $limit,
+ ]
+ );
+
+ $regionListGenerator = $this->batch->getTraversableListWithCount(
+ 'documentgenerator.region.list',
+ [],
+ [],
+ [],
+ $limit
+ );
+ foreach ($regionListGenerator as $key => $value) {
+ yield $key => new RegionItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding regions
+ *
+ * @param array $regions
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.region.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-add.html',
+ 'Batch adding regions'
+ )]
+ public function add(array $regions): Generator
+ {
+ $items = [];
+ foreach ($regions as $item) {
+ $items[] = [
+ 'fields' => $item,
+ ];
+ }
+
+ foreach ($this->batch->addEntityItems('documentgenerator.region.add', $items) as $key => $item) {
+ yield $key => new AddedRegionBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update regions
+ *
+ * Update elements in array with structure
+ * id => [ // Region id
+ * 'fields' => [] // Region fields to update
+ * ]
+ *
+ * @param array $entityItems
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.region.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-update.html',
+ 'Update in batch mode a list of regions'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach (
+ $this->batch->updateEntityItems(
+ 'documentgenerator.region.update',
+ $entityItems
+ ) as $key => $item
+ ) {
+ yield $key => new UpdatedRegionBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete regions
+ *
+ * @param int[] $regionId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.region.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-delete.html',
+ 'Batch delete regions'
+ )]
+ public function delete(array $regionId): Generator
+ {
+ foreach (
+ $this->batch->deleteEntityItems(
+ 'documentgenerator.region.delete',
+ $regionId
+ ) as $key => $item
+ ) {
+ yield $key => new DeletedRegionBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Documentgenerator/Region/Service/Region.php b/src/Services/Documentgenerator/Region/Service/Region.php
new file mode 100644
index 00000000..1422f85b
--- /dev/null
+++ b/src/Services/Documentgenerator/Region/Service/Region.php
@@ -0,0 +1,182 @@
+
+ *
+ * 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\Documentgenerator\Region\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\Services\AbstractService;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\AddedRegionResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\DeletedRegionResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\RegionResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\RegionsResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\UpdatedRegionResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['documentgenerator']))]
+class Region extends AbstractService
+{
+ /**
+ * Region constructor
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Creates a new region
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-add.html
+ *
+ * @param array{
+ * languageId: string,
+ * title: string
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.region.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-add.html',
+ 'Creates a new region'
+ )]
+ public function add(array $fields): AddedRegionResult
+ {
+ return new AddedRegionResult(
+ $this->core->call(
+ 'documentgenerator.region.add',
+ [
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates an existing region with new values
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-update.html
+ *
+ * @param array{
+ * languageId?: string,
+ * title?: string
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.region.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-update.html',
+ 'Updates an existing region with new values'
+ )]
+ public function update(int $id, array $fields): UpdatedRegionResult
+ {
+ return new UpdatedRegionResult(
+ $this->core->call(
+ 'documentgenerator.region.update',
+ [
+ 'id' => $id,
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns information about the region by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-get.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.region.get',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-get.html',
+ 'Returns information about the region by its identifier'
+ )]
+ public function get(int $id): RegionResult
+ {
+ return new RegionResult(
+ $this->core->call('documentgenerator.region.get', ['id' => $id])
+ );
+ }
+
+ /**
+ * Returns a list of regions
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-list.html
+ *
+ * @param int $start Offset for pagination
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.region.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-list.html',
+ 'Returns a list of regions'
+ )]
+ public function list(int $start = 0): RegionsResult
+ {
+ return new RegionsResult(
+ $this->core->call(
+ 'documentgenerator.region.list',
+ [
+ 'start' => $start,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Deletes a region
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.region.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/region/document-generator-region-delete.html',
+ 'Deletes a region'
+ )]
+ public function delete(int $id): DeletedRegionResult
+ {
+ return new DeletedRegionResult(
+ $this->core->call(
+ 'documentgenerator.region.delete',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Count regions
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function count(): int
+ {
+ return count($this->list()->getRegions());
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Batch.php b/src/Services/Documentgenerator/Role/Batch.php
new file mode 100644
index 00000000..dcb6ab9a
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Batch.php
@@ -0,0 +1,241 @@
+
+ *
+ * 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\Documentgenerator\Role;
+
+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 documentgenerator.role.* REST methods:
+ * - delete uses 'id' instead of 'ID'
+ * - update uses 'id' instead of 'ID'
+ * - list results are wrapped in 'roles' key and use lowercase 'id'
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for document generator role
+ */
+ #[\Override]
+ protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string
+ {
+ return 'id';
+ }
+
+ /**
+ * Extracts elements from batch result, unwrapping the 'roles' key
+ */
+ #[\Override]
+ protected function extractElementsFromBatchResult(ResponseData $responseData, bool $isCrmItemsInBatch): array
+ {
+ $resultData = $responseData->getResult();
+
+ if (array_key_exists('roles', $resultData) && is_array($resultData['roles'])) {
+ return $resultData['roles'];
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Returns reference field path including 'roles' wrapper for batch query chaining
+ */
+ #[\Override]
+ protected function getReferenceFieldPath(string $prevCommandId, int $lastIndex, string $keyId, bool $isCrmItemsInBatch): string
+ {
+ return sprintf('$result[%s][roles][%d][%s]', $prevCommandId, $lastIndex, $keyId);
+ }
+
+ /**
+ * Get traversable list using lowercase 'id' key and 'roles' result wrapper
+ *
+ * Delegates to parent implementation which uses overridden helper methods:
+ * - determineKeyId() returns 'id' instead of 'ID'
+ * - extractElementsFromBatchResult() unwraps 'roles' key
+ * - getReferenceFieldPath() includes 'roles' in batch reference path
+ *
+ * @param array $order
+ * @param array $filter
+ * @param array $select
+ *
+ * @return Generator
+ * @throws BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ #[\Override]
+ public function getTraversableList(
+ string $apiMethod,
+ ?array $order = [],
+ ?array $filter = [],
+ ?array $select = [],
+ ?int $limit = null,
+ ?array $additionalParameters = null
+ ): Generator {
+ yield from parent::getTraversableList($apiMethod, $order, $filter, $select, $limit, $additionalParameters);
+ }
+
+ /**
+ * Update entity items with batch call
+ *
+ * The documentgenerator.role.update method uses 'id' (lowercase)
+ * instead of the standard 'ID' key used by most other REST methods.
+ *
+ * Update elements in array with structure:
+ * element_id => [
+ * 'fields' => [] // required: role fields to update
+ * ]
+ *
+ * @param array> $entityItems
+ *
+ * @return Generator|ResponseData[]
+ * @throws BaseException
+ */
+ #[\Override]
+ public function updateEntityItems(string $apiMethod, array $entityItems): Generator
+ {
+ $this->logger->debug(
+ 'updateEntityItems.start',
+ [
+ 'apiMethod' => $apiMethod,
+ 'entityItems' => $entityItems,
+ ]
+ );
+
+ try {
+ $this->clearCommands();
+
+ foreach ($entityItems as $entityItemId => $entityItem) {
+ if (!is_int($entityItemId)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of role id «%s», the id must be integer type',
+ gettype($entityItemId),
+ $entityItemId
+ )
+ );
+ }
+
+ if (!array_key_exists('fields', $entityItem)) {
+ throw new InvalidArgumentException(
+ sprintf('array key «fields» not found in entity item with id %s', $entityItemId)
+ );
+ }
+
+ $cmdArguments = [
+ 'id' => $entityItemId,
+ 'fields' => $entityItem['fields'],
+ ];
+
+ $this->registerCommand($apiMethod, $cmdArguments);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) {
+ yield $cnt => $updatedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+
+ throw new BaseException($errorMessage, $exception->getCode(), $exception);
+ }
+
+ $this->logger->debug('updateEntityItems.finish');
+ }
+
+ /**
+ * Delete entity items with batch call
+ *
+ * @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 => $code) {
+ if (!is_int($code)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of role id «%s» at position %s, id must be integer type',
+ gettype($code),
+ $code,
+ $cnt
+ )
+ );
+ }
+
+ $parameters = ['id' => $code];
+ $this->registerCommand($apiMethod, $parameters);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) {
+ yield $cnt => $deletedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch delete entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete entity 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/Documentgenerator/Role/Result/AddedRoleBatchResult.php b/src/Services/Documentgenerator/Role/Result/AddedRoleBatchResult.php
new file mode 100644
index 00000000..67a55cf7
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/AddedRoleBatchResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+
+/**
+ * Class AddedRoleBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class AddedRoleBatchResult extends AddedItemBatchResult
+{
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getResponseData()->getResult()['role']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/AddedRoleResult.php b/src/Services/Documentgenerator/Role/Result/AddedRoleResult.php
new file mode 100644
index 00000000..3638674b
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/AddedRoleResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+
+/**
+ * Class AddedRoleResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class AddedRoleResult extends AddedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getCoreResponse()->getResponseData()->getResult()['role']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/DeletedRoleBatchResult.php b/src/Services/Documentgenerator/Role/Result/DeletedRoleBatchResult.php
new file mode 100644
index 00000000..fccb4943
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/DeletedRoleBatchResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+
+/**
+ * Class DeletedRoleBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class DeletedRoleBatchResult extends DeletedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/DeletedRoleResult.php b/src/Services/Documentgenerator/Role/Result/DeletedRoleResult.php
new file mode 100644
index 00000000..d7864915
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/DeletedRoleResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+
+/**
+ * Class DeletedRoleResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class DeletedRoleResult extends DeletedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/FillAccessesResult.php b/src/Services/Documentgenerator/Role/Result/FillAccessesResult.php
new file mode 100644
index 00000000..20916af0
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/FillAccessesResult.php
@@ -0,0 +1,37 @@
+
+ *
+ * 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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class FillAccessesResult
+ *
+ * Result of documentgenerator.role.fillaccesses.
+ * The API returns null on success; isSuccess() returns true when no exception was thrown.
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class FillAccessesResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function isSuccess(): bool
+ {
+ // API returns null on success; array cast of [null] is truthy
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/RoleItemResult.php b/src/Services/Documentgenerator/Role/Result/RoleItemResult.php
new file mode 100644
index 00000000..8c92551b
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/RoleItemResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem;
+
+/**
+ * Class RoleItemResult
+ *
+ * @property-read int $id
+ * @property-read string $name
+ * @property-read string $code
+ * @property-read array|null $permissions
+ */
+class RoleItemResult extends AbstractAnnotatedItem
+{
+}
diff --git a/src/Services/Documentgenerator/Role/Result/RoleResult.php b/src/Services/Documentgenerator/Role/Result/RoleResult.php
new file mode 100644
index 00000000..08370905
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/RoleResult.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class RoleResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class RoleResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function role(): RoleItemResult
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+ if (!empty($result['role']) && is_array($result['role'])) {
+ $result = $result['role'];
+ }
+
+ return new RoleItemResult($result);
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/RolesResult.php b/src/Services/Documentgenerator/Role/Result/RolesResult.php
new file mode 100644
index 00000000..0599b056
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/RolesResult.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class RolesResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class RolesResult extends AbstractResult
+{
+ /**
+ * @return RoleItemResult[]
+ * @throws BaseException
+ */
+ public function getRoles(): array
+ {
+ $items = [];
+ $source = [];
+
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['roles']) && is_array($result['roles'])) {
+ $source = $result['roles'];
+ }
+
+ foreach ($source as $item) {
+ $items[] = new RoleItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/UpdatedRoleBatchResult.php b/src/Services/Documentgenerator/Role/Result/UpdatedRoleBatchResult.php
new file mode 100644
index 00000000..bc1ad0bc
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/UpdatedRoleBatchResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+
+/**
+ * Class UpdatedRoleBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class UpdatedRoleBatchResult extends UpdatedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Result/UpdatedRoleResult.php b/src/Services/Documentgenerator/Role/Result/UpdatedRoleResult.php
new file mode 100644
index 00000000..fc85b871
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Result/UpdatedRoleResult.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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+
+/**
+ * Class UpdatedRoleResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Role\Result
+ */
+class UpdatedRoleResult extends UpdatedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Service/Batch.php b/src/Services/Documentgenerator/Role/Service/Batch.php
new file mode 100644
index 00000000..3d2daf97
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Service/Batch.php
@@ -0,0 +1,157 @@
+
+ *
+ * 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\Documentgenerator\Role\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\AddedRoleBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\DeletedRoleBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\RoleItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\UpdatedRoleBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['documentgenerator']))]
+class Batch
+{
+ /**
+ * Batch constructor
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for roles
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-list.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.role.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-list.html',
+ 'Batch list method for roles'
+ )]
+ public function list(?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'limit' => $limit,
+ ]
+ );
+
+ $roleListGenerator = $this->batch->getTraversableListWithCount(
+ 'documentgenerator.role.list',
+ [],
+ [],
+ [],
+ $limit
+ );
+ foreach ($roleListGenerator as $key => $value) {
+ yield $key => new RoleItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding roles
+ *
+ * @param array $roles
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.role.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-add.html',
+ 'Batch adding roles'
+ )]
+ public function add(array $roles): Generator
+ {
+ $items = [];
+ foreach ($roles as $item) {
+ $items[] = [
+ 'fields' => $item,
+ ];
+ }
+
+ foreach ($this->batch->addEntityItems('documentgenerator.role.add', $items) as $key => $item) {
+ yield $key => new AddedRoleBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update roles
+ *
+ * Update elements in array with structure:
+ * id => [ // Role id
+ * 'fields' => [] // Role fields to update
+ * ]
+ *
+ * @param array $entityItems
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.role.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-update.html',
+ 'Update in batch mode a list of roles'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach (
+ $this->batch->updateEntityItems(
+ 'documentgenerator.role.update',
+ $entityItems
+ ) as $key => $item
+ ) {
+ yield $key => new UpdatedRoleBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete roles
+ *
+ * @param int[] $roleId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.role.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-delete.html',
+ 'Batch delete roles'
+ )]
+ public function delete(array $roleId): Generator
+ {
+ foreach (
+ $this->batch->deleteEntityItems(
+ 'documentgenerator.role.delete',
+ $roleId
+ ) as $key => $item
+ ) {
+ yield $key => new DeletedRoleBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Documentgenerator/Role/Service/Role.php b/src/Services/Documentgenerator/Role/Service/Role.php
new file mode 100644
index 00000000..fac6cb2f
--- /dev/null
+++ b/src/Services/Documentgenerator/Role/Service/Role.php
@@ -0,0 +1,210 @@
+
+ *
+ * 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\Documentgenerator\Role\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\Services\AbstractService;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\AddedRoleResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\DeletedRoleResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\FillAccessesResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\RoleResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\RolesResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\UpdatedRoleResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['documentgenerator']))]
+class Role extends AbstractService
+{
+ /**
+ * Role constructor
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Creates a new role
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-add.html
+ *
+ * @param array{
+ * name: string,
+ * code?: string,
+ * permissions?: array
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-add.html',
+ 'Creates a new role'
+ )]
+ public function add(array $fields): AddedRoleResult
+ {
+ return new AddedRoleResult(
+ $this->core->call(
+ 'documentgenerator.role.add',
+ [
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates an existing role with new values
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-update.html
+ *
+ * @param array{
+ * name?: string,
+ * code?: string,
+ * permissions?: array
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-update.html',
+ 'Updates an existing role with new values'
+ )]
+ public function update(int $id, array $fields): UpdatedRoleResult
+ {
+ return new UpdatedRoleResult(
+ $this->core->call(
+ 'documentgenerator.role.update',
+ [
+ 'id' => $id,
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns information about the role by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-get.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.get',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-get.html',
+ 'Returns information about the role by its identifier'
+ )]
+ public function get(int $id): RoleResult
+ {
+ return new RoleResult(
+ $this->core->call('documentgenerator.role.get', ['id' => $id])
+ );
+ }
+
+ /**
+ * Returns a list of roles
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-list.html
+ *
+ * @param int $start Offset for pagination
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-list.html',
+ 'Returns a list of roles'
+ )]
+ public function list(int $start = 0): RolesResult
+ {
+ return new RolesResult(
+ $this->core->call(
+ 'documentgenerator.role.list',
+ [
+ 'start' => $start,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Deletes a role
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-delete.html',
+ 'Deletes a role'
+ )]
+ public function delete(int $id): DeletedRoleResult
+ {
+ return new DeletedRoleResult(
+ $this->core->call(
+ 'documentgenerator.role.delete',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Completely replaces the role-to-access-code binding map
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-fill-accesses.html
+ *
+ * @param array $accesses Array of role-to-access-code bindings
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.role.fillaccesses',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/role/document-generator-role-fill-accesses.html',
+ 'Completely replaces the role-to-access-code binding map'
+ )]
+ public function fillAccesses(array $accesses): FillAccessesResult
+ {
+ return new FillAccessesResult(
+ $this->core->call(
+ 'documentgenerator.role.fillaccesses',
+ ['accesses' => $accesses]
+ )
+ );
+ }
+
+ /**
+ * Count roles
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function count(): int
+ {
+ return count($this->list()->getRoles());
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Batch.php b/src/Services/Documentgenerator/Template/Batch.php
new file mode 100644
index 00000000..406d1dbd
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Batch.php
@@ -0,0 +1,241 @@
+
+ *
+ * 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\Documentgenerator\Template;
+
+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 documentgenerator.template.* REST methods:
+ * - delete uses 'id' instead of 'ID'
+ * - update uses 'id' instead of 'ID'
+ * - list results are wrapped in 'templates' key and use lowercase 'id'
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template
+ */
+class Batch extends \Bitrix24\SDK\Core\Batch
+{
+ /**
+ * Determines the ID key — lowercase 'id' for document generator template
+ */
+ #[\Override]
+ protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string
+ {
+ return 'id';
+ }
+
+ /**
+ * Extracts elements from batch result, unwrapping the 'templates' key
+ */
+ #[\Override]
+ protected function extractElementsFromBatchResult(ResponseData $responseData, bool $isCrmItemsInBatch): array
+ {
+ $resultData = $responseData->getResult();
+
+ if (array_key_exists('templates', $resultData) && is_array($resultData['templates'])) {
+ return $resultData['templates'];
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Returns reference field path including 'templates' wrapper for batch query chaining
+ */
+ #[\Override]
+ protected function getReferenceFieldPath(string $prevCommandId, int $lastIndex, string $keyId, bool $isCrmItemsInBatch): string
+ {
+ return sprintf('$result[%s][templates][%d][%s]', $prevCommandId, $lastIndex, $keyId);
+ }
+
+ /**
+ * Get traversable list using lowercase 'id' key and 'templates' result wrapper
+ *
+ * Delegates to parent implementation which uses overridden helper methods:
+ * - determineKeyId() returns 'id' instead of 'ID'
+ * - extractElementsFromBatchResult() unwraps 'templates' key
+ * - getReferenceFieldPath() includes 'templates' in batch reference path
+ *
+ * @param array $order
+ * @param array $filter
+ * @param array $select
+ *
+ * @return Generator
+ * @throws BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ #[\Override]
+ public function getTraversableList(
+ string $apiMethod,
+ ?array $order = [],
+ ?array $filter = [],
+ ?array $select = [],
+ ?int $limit = null,
+ ?array $additionalParameters = null
+ ): Generator {
+ yield from parent::getTraversableList($apiMethod, $order, $filter, $select, $limit, $additionalParameters);
+ }
+
+ /**
+ * Update entity items with batch call
+ *
+ * The documentgenerator.template.update method uses 'id' (lowercase)
+ * instead of the standard 'ID' key used by most other REST methods.
+ *
+ * Update elements in array with structure:
+ * element_id => [
+ * 'fields' => [] // required: template fields to update
+ * ]
+ *
+ * @param array> $entityItems
+ *
+ * @return Generator|ResponseData[]
+ * @throws BaseException
+ */
+ #[\Override]
+ public function updateEntityItems(string $apiMethod, array $entityItems): Generator
+ {
+ $this->logger->debug(
+ 'updateEntityItems.start',
+ [
+ 'apiMethod' => $apiMethod,
+ 'entityItems' => $entityItems,
+ ]
+ );
+
+ try {
+ $this->clearCommands();
+
+ foreach ($entityItems as $entityItemId => $entityItem) {
+ if (!is_int($entityItemId)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of template id «%s», the id must be integer type',
+ gettype($entityItemId),
+ $entityItemId
+ )
+ );
+ }
+
+ if (!array_key_exists('fields', $entityItem)) {
+ throw new InvalidArgumentException(
+ sprintf('array key «fields» not found in entity item with id %s', $entityItemId)
+ );
+ }
+
+ $cmdArguments = [
+ 'id' => $entityItemId,
+ 'fields' => $entityItem['fields'],
+ ];
+
+ $this->registerCommand($apiMethod, $cmdArguments);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $updatedItemResult) {
+ yield $cnt => $updatedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch update entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+
+ throw new BaseException($errorMessage, $exception->getCode(), $exception);
+ }
+
+ $this->logger->debug('updateEntityItems.finish');
+ }
+
+ /**
+ * Delete entity items with batch call
+ *
+ * @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 => $code) {
+ if (!is_int($code)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'invalid type «%s» of template id «%s» at position %s, id must be integer type',
+ gettype($code),
+ $code,
+ $cnt
+ )
+ );
+ }
+
+ $parameters = ['id' => $code];
+ $this->registerCommand($apiMethod, $parameters);
+ }
+
+ foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) {
+ yield $cnt => $deletedItemResult;
+ }
+ } catch (InvalidArgumentException $exception) {
+ $errorMessage = sprintf('batch delete entity items: %s', $exception->getMessage());
+ $this->logger->error(
+ $errorMessage,
+ [
+ 'trace' => $exception->getTrace(),
+ ]
+ );
+ throw $exception;
+ } catch (\Throwable $exception) {
+ $errorMessage = sprintf('batch delete entity 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/Documentgenerator/Template/Result/AddedTemplateBatchResult.php b/src/Services/Documentgenerator/Template/Result/AddedTemplateBatchResult.php
new file mode 100644
index 00000000..6c78414f
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/AddedTemplateBatchResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+
+/**
+ * Class AddedTemplateBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class AddedTemplateBatchResult extends AddedItemBatchResult
+{
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getResponseData()->getResult()['template']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/AddedTemplateResult.php b/src/Services/Documentgenerator/Template/Result/AddedTemplateResult.php
new file mode 100644
index 00000000..9301a1d2
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/AddedTemplateResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+
+/**
+ * Class AddedTemplateResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class AddedTemplateResult extends AddedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function getId(): int
+ {
+ return (int)$this->getCoreResponse()->getResponseData()->getResult()['template']['id'];
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/DeletedTemplateBatchResult.php b/src/Services/Documentgenerator/Template/Result/DeletedTemplateBatchResult.php
new file mode 100644
index 00000000..5e4723ec
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/DeletedTemplateBatchResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+
+/**
+ * Class DeletedTemplateBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class DeletedTemplateBatchResult extends DeletedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/DeletedTemplateResult.php b/src/Services/Documentgenerator/Template/Result/DeletedTemplateResult.php
new file mode 100644
index 00000000..0d60fdbe
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/DeletedTemplateResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+
+/**
+ * Class DeletedTemplateResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class DeletedTemplateResult extends DeletedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/TemplateFieldsResult.php b/src/Services/Documentgenerator/Template/Result/TemplateFieldsResult.php
new file mode 100644
index 00000000..788363e6
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/TemplateFieldsResult.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class TemplateFieldsResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class TemplateFieldsResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function getFieldsDescription(): array
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ // API returns fields nested under templateFields key
+ if (!empty($result['templateFields']) && is_array($result['templateFields'])) {
+ return $result['templateFields'];
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/TemplateItemResult.php b/src/Services/Documentgenerator/Template/Result/TemplateItemResult.php
new file mode 100644
index 00000000..ff0289ad
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/TemplateItemResult.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * Class TemplateItemResult
+ *
+ * @property-read int $id
+ * @property-read string|null $name
+ * @property-read string|null $region
+ * @property-read string|null $code
+ * @property-read string|null $download
+ * @property-read string|null $active
+ * @property-read string|null $moduleId
+ * @property-read int|null $numeratorId
+ * @property-read string|null $withStamps
+ * @property-read array|null $providers
+ * @property-read array|null $users
+ * @property-read string|null $isDeleted
+ * @property-read string|null $isDefault
+ * @property-read int|null $sort
+ * @property-read CarbonImmutable|null $createTime
+ * @property-read CarbonImmutable|null $updateTime
+ * @property-read int|null $createdBy
+ * @property-read int|null $updatedBy
+ * @property-read int|null $fileId
+ * @property-read string|null $bodyType
+ * @property-read string|null $productsTableVariant
+ * @property-read string|null $downloadMachine
+ */
+class TemplateItemResult extends AbstractAnnotatedItem
+{
+}
diff --git a/src/Services/Documentgenerator/Template/Result/TemplateResult.php b/src/Services/Documentgenerator/Template/Result/TemplateResult.php
new file mode 100644
index 00000000..7fca7e44
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/TemplateResult.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class TemplateResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class TemplateResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function template(): TemplateItemResult
+ {
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+ if (!empty($result['template']) && is_array($result['template'])) {
+ $result = $result['template'];
+ }
+
+ return new TemplateItemResult($result);
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/TemplatesResult.php b/src/Services/Documentgenerator/Template/Result/TemplatesResult.php
new file mode 100644
index 00000000..a53b0897
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/TemplatesResult.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class TemplatesResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class TemplatesResult extends AbstractResult
+{
+ /**
+ * @return TemplateItemResult[]
+ * @throws BaseException
+ */
+ public function getTemplates(): array
+ {
+ $items = [];
+ $result = $this->getCoreResponse()->getResponseData()->getResult();
+
+ if (!empty($result['templates']) && is_array($result['templates'])) {
+ foreach ($result['templates'] as $item) {
+ if (is_array($item)) {
+ $items[] = new TemplateItemResult($item);
+ }
+ }
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/UpdatedTemplateBatchResult.php b/src/Services/Documentgenerator/Template/Result/UpdatedTemplateBatchResult.php
new file mode 100644
index 00000000..051758c5
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/UpdatedTemplateBatchResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+
+/**
+ * Class UpdatedTemplateBatchResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class UpdatedTemplateBatchResult extends UpdatedItemBatchResult
+{
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Result/UpdatedTemplateResult.php b/src/Services/Documentgenerator/Template/Result/UpdatedTemplateResult.php
new file mode 100644
index 00000000..bd9da693
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Result/UpdatedTemplateResult.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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+
+/**
+ * Class UpdatedTemplateResult
+ *
+ * @package Bitrix24\SDK\Services\Documentgenerator\Template\Result
+ */
+class UpdatedTemplateResult extends UpdatedItemResult
+{
+ /**
+ * @throws BaseException
+ */
+ #[\Override]
+ public function isSuccess(): bool
+ {
+ return (bool)$this->getCoreResponse()->getResponseData()->getResult();
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Service/Batch.php b/src/Services/Documentgenerator/Template/Service/Batch.php
new file mode 100644
index 00000000..a703cedb
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Service/Batch.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\Documentgenerator\Template\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\AddedTemplateBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\DeletedTemplateBatchResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplateItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\UpdatedTemplateBatchResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['documentgenerator']))]
+class Batch
+{
+ /**
+ * Batch constructor
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for templates
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-list.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.template.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-list.html',
+ 'Batch list method for templates'
+ )]
+ public function list(?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'limit' => $limit,
+ ]
+ );
+
+ // Use pagination-based traversable to avoid dependency on element ID field name
+ $templateListGenerator = $this->batch->getTraversableListWithCount(
+ 'documentgenerator.template.list',
+ [],
+ [],
+ [],
+ $limit
+ );
+ foreach ($templateListGenerator as $key => $value) {
+ yield $key => new TemplateItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding templates
+ *
+ * @param array $templates
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-add.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.template.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-add.html',
+ 'Batch adding templates'
+ )]
+ public function add(array $templates): Generator
+ {
+ $items = [];
+ foreach ($templates as $item) {
+ $items[] = [
+ 'fields' => $item,
+ ];
+ }
+
+ foreach ($this->batch->addEntityItems('documentgenerator.template.add', $items) as $key => $item) {
+ yield $key => new AddedTemplateBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update templates
+ *
+ * Update elements in array with structure
+ * id => [ // Template id
+ * 'fields' => [] // Template fields to update
+ * ]
+ *
+ * @param array $entityItems
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-update.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.template.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-update.html',
+ 'Update in batch mode a list of templates'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach (
+ $this->batch->updateEntityItems(
+ 'documentgenerator.template.update',
+ $entityItems
+ ) as $key => $item
+ ) {
+ yield $key => new UpdatedTemplateBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete templates
+ *
+ * @param int[] $templateId
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-delete.html
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'documentgenerator.template.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-delete.html',
+ 'Batch delete templates'
+ )]
+ public function delete(array $templateId): Generator
+ {
+ foreach (
+ $this->batch->deleteEntityItems(
+ 'documentgenerator.template.delete',
+ $templateId
+ ) as $key => $item
+ ) {
+ yield $key => new DeletedTemplateBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/Documentgenerator/Template/Service/Template.php b/src/Services/Documentgenerator/Template/Service/Template.php
new file mode 100644
index 00000000..f0896b12
--- /dev/null
+++ b/src/Services/Documentgenerator/Template/Service/Template.php
@@ -0,0 +1,245 @@
+
+ *
+ * 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\Documentgenerator\Template\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\Services\AbstractService;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\AddedTemplateResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\DeletedTemplateResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplateFieldsResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplateResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplatesResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\UpdatedTemplateResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['documentgenerator']))]
+class Template extends AbstractService
+{
+ /**
+ * Template constructor
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Adds a new template
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-add.html
+ *
+ * @param array{
+ * name: string,
+ * numeratorId: int,
+ * region: string,
+ * fileId?: int,
+ * file?: string,
+ * code?: string,
+ * users?: array,
+ * active?: string,
+ * withStamps?: string,
+ * sort?: int
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.add',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-add.html',
+ 'Adds a new template'
+ )]
+ public function add(array $fields): AddedTemplateResult
+ {
+ return new AddedTemplateResult(
+ $this->core->call(
+ 'documentgenerator.template.add',
+ [
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates an existing template
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-update.html
+ *
+ * @param int $id Template identifier
+ * @param array{
+ * name?: string,
+ * file?: string,
+ * numeratorId?: int,
+ * region?: string,
+ * code?: string,
+ * users?: array,
+ * providers?: array,
+ * active?: string,
+ * withStamps?: string,
+ * sort?: int
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.update',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-update.html',
+ 'Updates an existing template'
+ )]
+ public function update(int $id, array $fields): UpdatedTemplateResult
+ {
+ return new UpdatedTemplateResult(
+ $this->core->call(
+ 'documentgenerator.template.update',
+ [
+ 'id' => $id,
+ 'fields' => $fields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns information about the template by its identifier
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-get.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.get',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-get.html',
+ 'Returns information about the template by its identifier'
+ )]
+ public function get(int $id): TemplateResult
+ {
+ return new TemplateResult(
+ $this->core->call(
+ 'documentgenerator.template.get',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Returns a list of templates
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-list.html
+ *
+ * @param array $filter Filter parameters
+ * @param array $order Order parameters
+ * @param array $select Fields to select
+ * @param int $start Offset for pagination
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.list',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-list.html',
+ 'Returns a list of templates'
+ )]
+ public function list(array $filter = [], array $order = [], array $select = [], int $start = 0): TemplatesResult
+ {
+ $params = [
+ 'start' => $start,
+ ];
+
+ if ($filter !== []) {
+ $params['filter'] = $filter;
+ }
+
+ if ($order !== []) {
+ $params['order'] = $order;
+ }
+
+ if ($select !== []) {
+ $params['select'] = $select;
+ }
+
+ return new TemplatesResult(
+ $this->core->call(
+ 'documentgenerator.template.list',
+ $params
+ )
+ );
+ }
+
+ /**
+ * Deletes a template
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-delete.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.delete',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-delete.html',
+ 'Deletes a template'
+ )]
+ public function delete(int $id): DeletedTemplateResult
+ {
+ return new DeletedTemplateResult(
+ $this->core->call(
+ 'documentgenerator.template.delete',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Returns the description of template fields
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-get-fields.html
+ *
+ * @param int $id Template identifier
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'documentgenerator.template.getfields',
+ 'https://apidocs.bitrix24.com/api-reference/document-generator/templates/document-generator-template-get-fields.html',
+ 'Returns the description of template fields'
+ )]
+ public function getFields(int $id): TemplateFieldsResult
+ {
+ return new TemplateFieldsResult(
+ $this->core->call(
+ 'documentgenerator.template.getfields',
+ ['id' => $id]
+ )
+ );
+ }
+
+ /**
+ * Count templates
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function count(): int
+ {
+ return $this->list()->getCoreResponse()->getResponseData()->getPagination()->getTotal();
+ }
+}
diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php
index 417ca0d6..d2f6f3cb 100644
--- a/src/Services/ServiceBuilder.php
+++ b/src/Services/ServiceBuilder.php
@@ -18,6 +18,7 @@
use Bitrix24\SDK\Core\Contracts\CoreInterface;
use Bitrix24\SDK\Services\AI\AIServiceBuilder;
use Bitrix24\SDK\Services\Catalog\CatalogServiceBuilder;
+use Bitrix24\SDK\Services\Documentgenerator\DocumentgeneratorServiceBuilder;
use Bitrix24\SDK\Services\CRM\CRMServiceBuilder;
use Bitrix24\SDK\Services\Disk\DiskServiceBuilder;
use Bitrix24\SDK\Services\Entity\EntityServiceBuilder;
@@ -395,4 +396,18 @@ public function getLegacyServiceBuilder(): LegacyServiceBuilder
return $this->serviceCache[__METHOD__];
}
+ public function getDocumentgeneratorScope(): DocumentgeneratorServiceBuilder
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new DocumentgeneratorServiceBuilder(
+ $this->core,
+ $this->batch,
+ $this->bulkItemsReader,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
}
diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php
index 8e379fa5..8538071e 100644
--- a/tests/CustomAssertions/CustomBitrix24Assertions.php
+++ b/tests/CustomAssertions/CustomBitrix24Assertions.php
@@ -23,9 +23,10 @@
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
{
/**
@@ -62,13 +63,19 @@ protected function assertBitrix24ResultItemFieldsTypeCastMatchAnnotations(
get_debug_type($value)
);
+ // For nullable union types like "Carbon\CarbonImmutable|null", strip null before assertInstanceOf
+ $classStr = implode('|', array_values(array_filter(
+ explode('|', $typeStr),
+ static fn (string $t): bool => $t !== 'null'
+ )));
+
match (true) {
str_contains($typeStr, 'array') => $this->assertIsArray($value, $message),
str_contains($typeStr, 'bool') => $this->assertIsBool($value, $message),
str_contains($typeStr, 'int') => $this->assertIsInt($value, $message),
str_contains($typeStr, 'float') => $this->assertIsFloat($value, $message),
str_contains($typeStr, 'string') => $this->assertIsString($value, $message),
- default => $this->assertInstanceOf($typeStr, $value, $message),
+ default => $this->assertInstanceOf($classStr, $value, $message),
};
}
}
diff --git a/tests/Integration/Services/Documentgenerator/Document/Service/BatchTest.php b/tests/Integration/Services/Documentgenerator/Document/Service/BatchTest.php
new file mode 100644
index 00000000..c6ac7e57
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Document/Service/BatchTest.php
@@ -0,0 +1,203 @@
+
+ *
+ * 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\Documentgenerator\Document\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Service\Document;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Document\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Documentgenerator\Document\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Document $documentService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->document();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: get first available non-CRM documentgenerator template.
+ * Returns array with keys: id, providerClassName
+ *
+ * @return array{id: int, providerClassName: string}
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstTemplate(): array
+ {
+ $core = Factory::getCore();
+ $response = $core->call('documentgenerator.template.list', ['select' => ['id', 'providers']]);
+ $result = $response->getResponseData()->getResult();
+ $templates = $result['templates'] ?? [];
+
+ self::assertNotEmpty($templates, 'At least one documentgenerator template must exist to run this test');
+
+ $template = array_values($templates)[0];
+ $providers = $template['providers'] ?? [];
+ $providerClassName = empty($providers) ? 'Bitrix\DocumentGenerator\DataProvider\Rest' : (string)$providers[0];
+
+ return [
+ 'id' => (int)$template['id'],
+ 'providerClassName' => $providerClassName,
+ ];
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list documents')]
+ public function testBatchList(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $value = 'SDK_BATCH_TEST_' . $this->faker->uuid();
+
+ $id = $this->documentService->add(
+ $templateInfo['id'],
+ $templateInfo['providerClassName'],
+ $value
+ )->getId();
+
+ $cnt = 0;
+ foreach ($this->documentService->batch->list(1) as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(1, $cnt);
+
+ // Cleanup
+ $this->documentService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch add documents')]
+ public function testBatchAdd(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $items = [];
+
+ for ($i = 1; $i <= 3; $i++) {
+ $items[] = [
+ 'templateId' => $templateInfo['id'],
+ 'providerClassName' => $templateInfo['providerClassName'],
+ 'value' => 'SDK_BATCH_ADD_' . $this->faker->uuid(),
+ ];
+ }
+
+ $ids = [];
+ $cnt = 0;
+ foreach ($this->documentService->batch->add($items) as $added) {
+ $cnt++;
+ $ids[] = $added->getId();
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ // Cleanup
+ $delCnt = 0;
+ foreach ($this->documentService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($items), $delCnt);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch update documents')]
+ public function testBatchUpdate(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $docIds = [];
+
+ for ($i = 1; $i <= 3; $i++) {
+ $value = 'SDK_BATCH_UPD_' . $this->faker->uuid();
+ $docIds[] = $this->documentService->add(
+ $templateInfo['id'],
+ $templateInfo['providerClassName'],
+ $value
+ )->getId();
+ }
+
+ $updatePayload = [];
+ foreach ($docIds as $docId) {
+ $updatePayload[$docId] = [
+ 'values' => [],
+ 'stampsEnabled' => 1,
+ ];
+ }
+
+ foreach ($this->documentService->batch->update($updatePayload) as $updated) {
+ $this->assertTrue($updated->isSuccess());
+ }
+
+ // Cleanup
+ foreach ($this->documentService->batch->delete($docIds) as $deleted) {
+ // consume generator to execute batch deletion
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch delete documents')]
+ public function testBatchDelete(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $ids = [];
+
+ for ($i = 1; $i <= 3; $i++) {
+ $value = 'SDK_BATCH_DEL_' . $this->faker->uuid();
+ $ids[] = $this->documentService->add(
+ $templateInfo['id'],
+ $templateInfo['providerClassName'],
+ $value
+ )->getId();
+ }
+
+ $delCnt = 0;
+ foreach ($this->documentService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($ids), $delCnt);
+ }
+}
+
+
+
diff --git a/tests/Integration/Services/Documentgenerator/Document/Service/DocumentTest.php b/tests/Integration/Services/Documentgenerator/Document/Service/DocumentTest.php
new file mode 100644
index 00000000..9f78440d
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Document/Service/DocumentTest.php
@@ -0,0 +1,240 @@
+
+ *
+ * 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\Documentgenerator\Document\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Result\DocumentItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Document\Service\Document;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class DocumentTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Document\Service
+ */
+#[CoversMethod(Document::class, 'add')]
+#[CoversMethod(Document::class, 'delete')]
+#[CoversMethod(Document::class, 'get')]
+#[CoversMethod(Document::class, 'list')]
+#[CoversMethod(Document::class, 'update')]
+#[CoversMethod(Document::class, 'getFields')]
+#[CoversMethod(Document::class, 'enablePublicUrl')]
+#[CoversMethod(Document::class, 'count')]
+#[\PHPUnit\Framework\Attributes\CoversClass(Document::class)]
+class DocumentTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Document $documentService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->document();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: get first available non-CRM documentgenerator template.
+ * Returns array with keys: id, providerClassName
+ *
+ * @return array{id: int, providerClassName: string}
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstTemplate(): array
+ {
+ $core = Factory::getCore();
+ $response = $core->call('documentgenerator.template.list', ['select' => ['id', 'providers']]);
+ $result = $response->getResponseData()->getResult();
+ $templates = $result['templates'] ?? [];
+
+ self::assertNotEmpty($templates, 'At least one documentgenerator template must exist to run this test');
+
+ $template = array_values($templates)[0];
+ $providers = $template['providers'] ?? [];
+ $providerClassName = empty($providers) ? 'Bitrix\DocumentGenerator\DataProvider\Rest' : (string)$providers[0];
+
+ return [
+ 'id' => (int)$template['id'],
+ 'providerClassName' => $providerClassName,
+ ];
+ }
+
+ /**
+ * Helper: create a document for tests.
+ *
+ * @return array{id: int, templateInfo: array{id: int, providerClassName: string}}
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function createDocument(): array
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $value = 'SDK_TEST_' . $this->faker->uuid();
+
+ $id = $this->documentService->add(
+ $templateInfo['id'],
+ $templateInfo['providerClassName'],
+ $value
+ )->getId();
+
+ return ['id' => $id, 'templateInfo' => $templateInfo];
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+ $value = 'SDK_TEST_' . $this->faker->uuid();
+
+ $id = $this->documentService->add(
+ $templateInfo['id'],
+ $templateInfo['providerClassName'],
+ $value
+ )->getId();
+
+ self::assertGreaterThanOrEqual(1, $id);
+
+ // Cleanup
+ $this->documentService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ $doc = $this->createDocument();
+
+ $documentItemResult = $this->documentService->get($doc['id'])->document();
+ self::assertInstanceOf(DocumentItemResult::class, $documentItemResult);
+ self::assertEquals($doc['id'], $documentItemResult->id);
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $doc = $this->createDocument();
+
+ $list = $this->documentService->list()->getDocuments();
+ self::assertIsArray($list);
+ self::assertGreaterThanOrEqual(1, count($list));
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $doc = $this->createDocument();
+
+ self::assertTrue(
+ $this->documentService->update($doc['id'], [], [], 1)->isSuccess()
+ );
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ $doc = $this->createDocument();
+
+ self::assertTrue($this->documentService->delete($doc['id'])->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testCount(): void
+ {
+ $countBefore = $this->documentService->count();
+
+ $doc = $this->createDocument();
+
+ $countAfter = $this->documentService->count();
+ self::assertEquals($countBefore + 1, $countAfter);
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGetFields(): void
+ {
+ $doc = $this->createDocument();
+
+ $documentFieldsResult = $this->documentService->getFields($doc['id']);
+ $fields = $documentFieldsResult->getFieldsDescription();
+
+ self::assertIsArray($fields);
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testEnablePublicUrl(): void
+ {
+ $doc = $this->createDocument();
+
+ $publicUrlResult = $this->documentService->enablePublicUrl($doc['id']);
+ self::assertTrue($publicUrlResult->isSuccess());
+
+ // Cleanup
+ $this->documentService->delete($doc['id']);
+ }
+}
+
+
+
diff --git a/tests/Integration/Services/Documentgenerator/Numerator/Result/NumeratorItemResultAnnotationsTest.php b/tests/Integration/Services/Documentgenerator/Numerator/Result/NumeratorItemResultAnnotationsTest.php
new file mode 100644
index 00000000..fe7dc206
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Numerator/Result/NumeratorItemResultAnnotationsTest.php
@@ -0,0 +1,88 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\NumeratorItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Service\Numerator;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(NumeratorItemResult::class)]
+class NumeratorItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Numerator $numeratorService;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->numeratorService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->numerator();
+ }
+
+ /**
+ * Helper: get raw data for the first numerator from the list.
+ *
+ * @return array
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstNumeratorRawItem(): array
+ {
+ $result = $this->numeratorService->list()
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ $numerators = $result['numerators'] ?? [];
+ self::assertNotEmpty($numerators, 'At least one numerator must exist to run this test');
+
+ return array_values($numerators)[0];
+ }
+
+ #[Test]
+ #[TestDox('all fields in NumeratorItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItem = $this->getFirstNumeratorRawItem();
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItem),
+ NumeratorItemResult::class
+ );
+ }
+
+ #[Test]
+ #[TestDox('all fields in NumeratorItemResult have valid type casting in magic getters')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItem = $this->getFirstNumeratorRawItem();
+ $numeratorItemResult = new NumeratorItemResult($rawItem);
+
+ $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations(
+ $numeratorItemResult,
+ NumeratorItemResult::class
+ );
+ }
+}
+
diff --git a/tests/Integration/Services/Documentgenerator/Numerator/Service/BatchTest.php b/tests/Integration/Services/Documentgenerator/Numerator/Service/BatchTest.php
new file mode 100644
index 00000000..be39d3a6
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Numerator/Service/BatchTest.php
@@ -0,0 +1,161 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Service\Numerator;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Numerator\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Documentgenerator\Numerator\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Numerator $numeratorService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->numeratorService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->numerator();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list numerators')]
+ public function testBatchList(): void
+ {
+ $id = $this->numeratorService->add([
+ 'name' => 'SDK_BATCH_LIST_' . $this->faker->uuid(),
+ 'template' => 'BLIST-{NUMBER}',
+ ])->getId();
+
+ $cnt = 0;
+ foreach ($this->numeratorService->batch->list(1) as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(1, $cnt);
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch add numerators')]
+ public function testBatchAdd(): void
+ {
+ $items = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $items[] = [
+ 'name' => 'SDK_BATCH_ADD_' . $this->faker->uuid(),
+ 'template' => 'BADD-{NUMBER}',
+ ];
+ }
+
+ $ids = [];
+ $cnt = 0;
+ foreach ($this->numeratorService->batch->add($items) as $added) {
+ $cnt++;
+ $ids[] = $added->getId();
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ // Cleanup
+ $delCnt = 0;
+ foreach ($this->numeratorService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($items), $delCnt);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch update numerators')]
+ public function testBatchUpdate(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->numeratorService->add([
+ 'name' => 'SDK_BATCH_UPD_' . $this->faker->uuid(),
+ 'template' => 'BUPD-{NUMBER}',
+ ])->getId();
+ }
+
+ $updatePayload = [];
+ foreach ($ids as $id) {
+ $updatePayload[$id] = [
+ 'fields' => [
+ 'name' => 'SDK_BATCH_UPD_UPDATED_' . $this->faker->uuid(),
+ ],
+ ];
+ }
+
+ foreach ($this->numeratorService->batch->update($updatePayload) as $updated) {
+ $this->assertTrue($updated->isSuccess());
+ }
+
+ // Cleanup
+ foreach ($this->numeratorService->batch->delete($ids) as $deleted) {
+ unset($deleted); // consume generator to execute batch deletion
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch delete numerators')]
+ public function testBatchDelete(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->numeratorService->add([
+ 'name' => 'SDK_BATCH_DEL_' . $this->faker->uuid(),
+ 'template' => 'BDEL-{NUMBER}',
+ ])->getId();
+ }
+
+ $delCnt = 0;
+ foreach ($this->numeratorService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($ids), $delCnt);
+ }
+}
+
+
+
diff --git a/tests/Integration/Services/Documentgenerator/Numerator/Service/NumeratorTest.php b/tests/Integration/Services/Documentgenerator/Numerator/Service/NumeratorTest.php
new file mode 100644
index 00000000..777e2162
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Numerator/Service/NumeratorTest.php
@@ -0,0 +1,161 @@
+
+ *
+ * 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\Documentgenerator\Numerator\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Result\NumeratorItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Numerator\Service\Numerator;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class NumeratorTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Numerator\Service
+ */
+#[CoversMethod(Numerator::class, 'add')]
+#[CoversMethod(Numerator::class, 'delete')]
+#[CoversMethod(Numerator::class, 'get')]
+#[CoversMethod(Numerator::class, 'list')]
+#[CoversMethod(Numerator::class, 'update')]
+#[CoversMethod(Numerator::class, 'count')]
+#[\PHPUnit\Framework\Attributes\CoversClass(Numerator::class)]
+class NumeratorTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Numerator $numeratorService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->numeratorService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->numerator();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: create a test numerator and return its id.
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function createNumerator(): int
+ {
+ return $this->numeratorService->add([
+ 'name' => 'SDK_TEST_' . $this->faker->uuid(),
+ 'template' => 'TEST-{NUMBER}',
+ ])->getId();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ $id = $this->createNumerator();
+ self::assertGreaterThanOrEqual(1, $id);
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ $id = $this->createNumerator();
+
+ $numeratorItemResult = $this->numeratorService->get($id)->numerator();
+ self::assertInstanceOf(NumeratorItemResult::class, $numeratorItemResult);
+ self::assertEquals($id, $numeratorItemResult->id);
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $id = $this->createNumerator();
+
+ $list = $this->numeratorService->list()->getNumerators();
+ self::assertIsArray($list);
+ self::assertGreaterThanOrEqual(1, count($list));
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $id = $this->createNumerator();
+
+ $updatedName = 'SDK_TEST_UPDATED_' . $this->faker->uuid();
+ self::assertTrue(
+ $this->numeratorService->update($id, ['name' => $updatedName])->isSuccess()
+ );
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ $id = $this->createNumerator();
+
+ self::assertTrue($this->numeratorService->delete($id)->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testCount(): void
+ {
+ $countBefore = $this->numeratorService->count();
+
+ $id = $this->createNumerator();
+
+ $countAfter = $this->numeratorService->count();
+ self::assertEquals($countBefore + 1, $countAfter);
+
+ // Cleanup
+ $this->numeratorService->delete($id);
+ }
+}
+
diff --git a/tests/Integration/Services/Documentgenerator/Region/Result/RegionItemResultAnnotationsTest.php b/tests/Integration/Services/Documentgenerator/Region/Result/RegionItemResultAnnotationsTest.php
new file mode 100644
index 00000000..f06deecd
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Region/Result/RegionItemResultAnnotationsTest.php
@@ -0,0 +1,106 @@
+
+ *
+ * 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\Documentgenerator\Region\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\RegionItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Service\Region;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use Faker\Factory as FakerFactory;
+use Faker\Generator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(RegionItemResult::class)]
+class RegionItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Region $regionService;
+
+ private Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->regionService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->region();
+ $this->faker = FakerFactory::create();
+ }
+
+ /**
+ * Helper: create a region, fetch it via get() to obtain the full field set, then delete it.
+ *
+ * NOTE: documentgenerator.region.list() returns a reduced set of fields (code, languageId, title)
+ * without the numeric id. documentgenerator.region.get() returns the full set (id, languageId, name, code).
+ * We therefore validate annotations against the get() response.
+ *
+ * @return array
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstRegionRawItem(): array
+ {
+ $id = $this->regionService->add([
+ 'languageId' => 'en',
+ 'title' => 'SDK_ANNOT_TEST_' . $this->faker->uuid(),
+ ])->getId();
+
+ $rawItem = $this->regionService->get($id)
+ ->getCoreResponse()->getResponseData()->getResult()['region'] ?? [];
+
+ try {
+ $this->regionService->delete($id);
+ } catch (\Bitrix24\SDK\Core\Exceptions\BaseException) {
+ // Server-side delete bug on some portals; cleanup failure must not affect annotations test
+ }
+
+ self::assertNotEmpty($rawItem, 'get() must return a region item to run this test');
+
+ return $rawItem;
+ }
+
+ #[Test]
+ #[TestDox('all fields in RegionItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItem = $this->getFirstRegionRawItem();
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItem),
+ RegionItemResult::class
+ );
+ }
+
+ #[Test]
+ #[TestDox('all fields in RegionItemResult have valid type casting in magic getters')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItem = $this->getFirstRegionRawItem();
+ $regionItemResult = new RegionItemResult($rawItem);
+
+ $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations(
+ $regionItemResult,
+ RegionItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Region/Service/BatchTest.php b/tests/Integration/Services/Documentgenerator/Region/Service/BatchTest.php
new file mode 100644
index 00000000..72104a1e
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Region/Service/BatchTest.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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\Documentgenerator\Region\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Service\Region;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Region\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Documentgenerator\Region\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Region $regionService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->regionService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->region();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: silently delete a region by id.
+ * documentgenerator.region.delete has a known server-side bug on some portals.
+ */
+ private function safeDelete(int $id): void
+ {
+ try {
+ $this->regionService->delete($id);
+ } catch (BaseException) {
+ // Server-side delete bug; ignored during cleanup
+ }
+ }
+
+ /**
+ * Helper: silently batch-delete regions by ids.
+ */
+ private function safeBatchDelete(array $ids): void
+ {
+ try {
+ foreach ($this->regionService->batch->delete($ids) as $deleted) {
+ unset($deleted);
+ }
+ } catch (BaseException) {
+ // Server-side delete bug; ignored during cleanup
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list regions')]
+ public function testBatchList(): void
+ {
+ $id = $this->regionService->add([
+ 'languageId' => 'en',
+ 'title' => 'SDK_BATCH_LIST_' . $this->faker->uuid(),
+ ])->getId();
+
+ $cnt = 0;
+ foreach ($this->regionService->batch->list(1) as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(1, $cnt);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch add regions')]
+ public function testBatchAdd(): void
+ {
+ $items = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $items[] = [
+ 'languageId' => 'en',
+ 'title' => 'SDK_BATCH_ADD_' . $this->faker->uuid(),
+ ];
+ }
+
+ $ids = [];
+ $cnt = 0;
+ foreach ($this->regionService->batch->add($items) as $added) {
+ $cnt++;
+ $ids[] = $added->getId();
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ // Cleanup
+ $this->safeBatchDelete($ids);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch update regions')]
+ public function testBatchUpdate(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->regionService->add([
+ 'languageId' => 'en',
+ 'title' => 'SDK_BATCH_UPD_' . $this->faker->uuid(),
+ ])->getId();
+ }
+
+ $updatePayload = [];
+ foreach ($ids as $id) {
+ $updatePayload[$id] = [
+ 'fields' => [
+ 'title' => 'SDK_BATCH_UPD_UPDATED_' . $this->faker->uuid(),
+ ],
+ ];
+ }
+
+ foreach ($this->regionService->batch->update($updatePayload) as $updated) {
+ $this->assertTrue($updated->isSuccess());
+ }
+
+ // Cleanup
+ $this->safeBatchDelete($ids);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch delete regions')]
+ public function testBatchDelete(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->regionService->add([
+ 'languageId' => 'en',
+ 'title' => 'SDK_BATCH_DEL_' . $this->faker->uuid(),
+ ])->getId();
+ }
+
+ try {
+ $delCnt = 0;
+ foreach ($this->regionService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($ids), $delCnt);
+ } catch (BaseException $baseException) {
+ $this->markTestSkipped(
+ 'documentgenerator.region.delete has a known server-side bug on this portal: ' . $baseException->getMessage()
+ );
+ }
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Region/Service/RegionTest.php b/tests/Integration/Services/Documentgenerator/Region/Service/RegionTest.php
new file mode 100644
index 00000000..dd42c00e
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Region/Service/RegionTest.php
@@ -0,0 +1,182 @@
+
+ *
+ * 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\Documentgenerator\Region\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Result\RegionItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Region\Service\Region;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class RegionTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Region\Service
+ */
+#[CoversMethod(Region::class, 'add')]
+#[CoversMethod(Region::class, 'delete')]
+#[CoversMethod(Region::class, 'get')]
+#[CoversMethod(Region::class, 'list')]
+#[CoversMethod(Region::class, 'update')]
+#[CoversMethod(Region::class, 'count')]
+#[\PHPUnit\Framework\Attributes\CoversClass(Region::class)]
+class RegionTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Region $regionService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->regionService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->region();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: create a test region and return its id.
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function createRegion(): int
+ {
+ return $this->regionService->add([
+ 'languageId' => 'en',
+ 'title' => 'SDK_TEST_' . $this->faker->uuid(),
+ ])->getId();
+ }
+
+ /**
+ * Helper: silently delete a region.
+ * documentgenerator.region.delete has a known server-side bug on some portals
+ * (class "bitrix\main\orm\eventresult" not found).
+ * Cleanup failures must not break unrelated test assertions.
+ */
+ private function safeDelete(int $id): void
+ {
+ try {
+ $this->regionService->delete($id);
+ } catch (BaseException) {
+ // Server-side delete bug; ignored during cleanup
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ $id = $this->createRegion();
+ self::assertGreaterThanOrEqual(1, $id);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ $id = $this->createRegion();
+
+ $regionItemResult = $this->regionService->get($id)->region();
+ self::assertInstanceOf(RegionItemResult::class, $regionItemResult);
+ self::assertEquals($id, $regionItemResult->id);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $id = $this->createRegion();
+
+ $list = $this->regionService->list()->getRegions();
+ self::assertIsArray($list);
+ self::assertGreaterThanOrEqual(1, count($list));
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $id = $this->createRegion();
+
+ $updatedName = 'SDK_TEST_UPDATED_' . $this->faker->uuid();
+ self::assertTrue(
+ $this->regionService->update($id, ['title' => $updatedName])->isSuccess()
+ );
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ $id = $this->createRegion();
+
+ try {
+ $result = $this->regionService->delete($id);
+ self::assertTrue($result->isSuccess());
+ } catch (BaseException $baseException) {
+ $this->markTestSkipped(
+ 'documentgenerator.region.delete has a known server-side bug on this portal: ' . $baseException->getMessage()
+ );
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testCount(): void
+ {
+ $countBefore = $this->regionService->count();
+
+ $id = $this->createRegion();
+
+ $countAfter = $this->regionService->count();
+ self::assertEquals($countBefore + 1, $countAfter);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Role/Result/RoleItemResultAnnotationsTest.php b/tests/Integration/Services/Documentgenerator/Role/Result/RoleItemResultAnnotationsTest.php
new file mode 100644
index 00000000..2550e62f
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Role/Result/RoleItemResultAnnotationsTest.php
@@ -0,0 +1,105 @@
+
+ *
+ * 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\Documentgenerator\Role\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\RoleItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Service\Role;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use Faker\Factory as FakerFactory;
+use Faker\Generator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(RoleItemResult::class)]
+class RoleItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Role $roleService;
+
+ private Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->roleService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->role();
+ $this->faker = FakerFactory::create();
+ }
+
+ /**
+ * Helper: create a role, fetch it via get() to obtain the full field set, then delete it.
+ *
+ * NOTE: documentgenerator.role.list() returns a reduced set of fields (id, name, code)
+ * without the permissions field. documentgenerator.role.get() returns the full set including permissions.
+ * We therefore validate annotations against the get() response.
+ *
+ * @return array
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstRoleRawItem(): array
+ {
+ $id = $this->roleService->add([
+ 'name' => 'SDK_ANNOT_TEST_' . $this->faker->uuid(),
+ ])->getId();
+
+ $rawItem = $this->roleService->get($id)
+ ->getCoreResponse()->getResponseData()->getResult()['role'] ?? [];
+
+ try {
+ $this->roleService->delete($id);
+ } catch (BaseException) {
+ // Server-side error during cleanup; must not affect annotations test
+ }
+
+ self::assertNotEmpty($rawItem, 'get() must return a role item to run this test');
+
+ return $rawItem;
+ }
+
+ #[Test]
+ #[TestDox('all fields in RoleItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItem = $this->getFirstRoleRawItem();
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItem),
+ RoleItemResult::class
+ );
+ }
+
+ #[Test]
+ #[TestDox('all fields in RoleItemResult have valid type casting in magic getters')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItem = $this->getFirstRoleRawItem();
+ $roleItemResult = new RoleItemResult($rawItem);
+
+ $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations(
+ $roleItemResult,
+ RoleItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Role/Service/BatchTest.php b/tests/Integration/Services/Documentgenerator/Role/Service/BatchTest.php
new file mode 100644
index 00000000..9fad9137
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Role/Service/BatchTest.php
@@ -0,0 +1,173 @@
+
+ *
+ * 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\Documentgenerator\Role\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Service\Role;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Role\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Documentgenerator\Role\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Role $roleService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->roleService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->role();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: silently delete a role by id.
+ */
+ private function safeDelete(int $id): void
+ {
+ try {
+ $this->roleService->delete($id);
+ } catch (BaseException) {
+ // Server-side error; ignored during cleanup
+ }
+ }
+
+ /**
+ * Helper: silently batch-delete roles by ids.
+ */
+ private function safeBatchDelete(array $ids): void
+ {
+ try {
+ foreach ($this->roleService->batch->delete($ids) as $deleted) {
+ unset($deleted);
+ }
+ } catch (BaseException) {
+ // Server-side error; ignored during cleanup
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list roles')]
+ public function testBatchList(): void
+ {
+ $id = $this->roleService->add([
+ 'name' => 'SDK_BATCH_LIST_' . $this->faker->uuid(),
+ ])->getId();
+
+ $cnt = 0;
+ foreach ($this->roleService->batch->list(1) as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(1, $cnt);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch add roles')]
+ public function testBatchAdd(): void
+ {
+ $items = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $items[] = [
+ 'name' => 'SDK_BATCH_ADD_' . $this->faker->uuid(),
+ ];
+ }
+
+ $ids = [];
+ $cnt = 0;
+ foreach ($this->roleService->batch->add($items) as $added) {
+ $cnt++;
+ $ids[] = $added->getId();
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ // Cleanup
+ $this->safeBatchDelete($ids);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch update roles')]
+ public function testBatchUpdate(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->roleService->add([
+ 'name' => 'SDK_BATCH_UPD_' . $this->faker->uuid(),
+ ])->getId();
+ }
+
+ $updatePayload = [];
+ foreach ($ids as $id) {
+ $updatePayload[$id] = [
+ 'fields' => [
+ 'name' => 'SDK_BATCH_UPD_UPDATED_' . $this->faker->uuid(),
+ ],
+ ];
+ }
+
+ foreach ($this->roleService->batch->update($updatePayload) as $updated) {
+ $this->assertTrue($updated->isSuccess());
+ }
+
+ // Cleanup
+ $this->safeBatchDelete($ids);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch delete roles')]
+ public function testBatchDelete(): void
+ {
+ $ids = [];
+ for ($i = 1; $i <= 3; $i++) {
+ $ids[] = $this->roleService->add([
+ 'name' => 'SDK_BATCH_DEL_' . $this->faker->uuid(),
+ ])->getId();
+ }
+
+ $delCnt = 0;
+ foreach ($this->roleService->batch->delete($ids) as $deleted) {
+ $delCnt++;
+ }
+
+ self::assertEquals(count($ids), $delCnt);
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Role/Service/RoleTest.php b/tests/Integration/Services/Documentgenerator/Role/Service/RoleTest.php
new file mode 100644
index 00000000..5f22a2b4
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Role/Service/RoleTest.php
@@ -0,0 +1,194 @@
+
+ *
+ * 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\Documentgenerator\Role\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Result\RoleItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Role\Service\Role;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+use Faker;
+
+/**
+ * Class RoleTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Role\Service
+ */
+#[CoversMethod(Role::class, 'add')]
+#[CoversMethod(Role::class, 'delete')]
+#[CoversMethod(Role::class, 'get')]
+#[CoversMethod(Role::class, 'list')]
+#[CoversMethod(Role::class, 'update')]
+#[CoversMethod(Role::class, 'count')]
+#[CoversMethod(Role::class, 'fillAccesses')]
+#[\PHPUnit\Framework\Attributes\CoversClass(Role::class)]
+class RoleTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Role $roleService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->roleService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->role();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: create a test role and return its id.
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function createRole(): int
+ {
+ return $this->roleService->add([
+ 'name' => 'SDK_TEST_' . $this->faker->uuid(),
+ 'code' => 'SDK_TEST_' . strtoupper(substr(str_replace('-', '_', $this->faker->uuid()), 0, 20)),
+ ])->getId();
+ }
+
+ /**
+ * Helper: silently delete a role.
+ */
+ private function safeDelete(int $id): void
+ {
+ try {
+ $this->roleService->delete($id);
+ } catch (BaseException) {
+ // Server-side error; ignored during cleanup
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ $id = $this->createRole();
+ self::assertGreaterThanOrEqual(1, $id);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ $id = $this->createRole();
+
+ $roleItemResult = $this->roleService->get($id)->role();
+ self::assertInstanceOf(RoleItemResult::class, $roleItemResult);
+ self::assertEquals($id, $roleItemResult->id);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $id = $this->createRole();
+
+ $list = $this->roleService->list()->getRoles();
+ self::assertIsArray($list);
+ self::assertGreaterThanOrEqual(1, count($list));
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $id = $this->createRole();
+
+ $updatedName = 'SDK_TEST_UPDATED_' . $this->faker->uuid();
+ self::assertTrue(
+ $this->roleService->update($id, ['name' => $updatedName])->isSuccess()
+ );
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ $id = $this->createRole();
+
+ $deletedRoleResult = $this->roleService->delete($id);
+ self::assertTrue($deletedRoleResult->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testCount(): void
+ {
+ $countBefore = $this->roleService->count();
+
+ $id = $this->createRole();
+
+ $countAfter = $this->roleService->count();
+ self::assertEquals($countBefore + 1, $countAfter);
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testFillAccesses(): void
+ {
+ $id = $this->createRole();
+
+ $fillAccessesResult = $this->roleService->fillAccesses([
+ [
+ 'roleId' => $id,
+ 'accessCode' => 'UA',
+ ],
+ ]);
+ self::assertTrue($fillAccessesResult->isSuccess());
+
+ // Cleanup
+ $this->safeDelete($id);
+ }
+}
diff --git a/tests/Integration/Services/Documentgenerator/Template/Result/TemplateItemResultAnnotationsTest.php b/tests/Integration/Services/Documentgenerator/Template/Result/TemplateItemResultAnnotationsTest.php
new file mode 100644
index 00000000..c1b3a056
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Template/Result/TemplateItemResultAnnotationsTest.php
@@ -0,0 +1,91 @@
+
+ *
+ * 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\Documentgenerator\Template\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplateItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Service\Template;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(TemplateItemResult::class)]
+class TemplateItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Template $templateService;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->templateService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->template();
+ }
+
+ /**
+ * Helper: get raw data for the first template with all fields selected.
+ *
+ * @return array
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstTemplateRawItem(): array
+ {
+ $result = $this->templateService->list(
+ [],
+ [],
+ ['*', 'users', 'providers']
+ )->getCoreResponse()->getResponseData()->getResult();
+
+ $templates = $result['templates'] ?? [];
+ self::assertNotEmpty($templates, 'At least one documentgenerator template must exist to run this test');
+
+ return array_values($templates)[0];
+ }
+
+ #[Test]
+ #[TestDox('all fields in TemplateItemResult are annotated in phpdoc and match with raw api response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItem = $this->getFirstTemplateRawItem();
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItem),
+ TemplateItemResult::class
+ );
+ }
+
+ #[Test]
+ #[TestDox('all fields in TemplateItemResult have valid type casting in magic getters')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItem = $this->getFirstTemplateRawItem();
+ $templateItemResult = new TemplateItemResult($rawItem);
+
+ $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations(
+ $templateItemResult,
+ TemplateItemResult::class
+ );
+ }
+}
+
diff --git a/tests/Integration/Services/Documentgenerator/Template/Service/BatchTest.php b/tests/Integration/Services/Documentgenerator/Template/Service/BatchTest.php
new file mode 100644
index 00000000..9c2528d4
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Template/Service/BatchTest.php
@@ -0,0 +1,57 @@
+
+ *
+ * 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\Documentgenerator\Template\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Service\Template;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Template\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\Documentgenerator\Template\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Template $templateService;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->templateService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->template();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list templates')]
+ public function testBatchList(): void
+ {
+ $cnt = 0;
+ foreach ($this->templateService->batch->list() as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(0, $cnt);
+ }
+}
+
diff --git a/tests/Integration/Services/Documentgenerator/Template/Service/TemplateTest.php b/tests/Integration/Services/Documentgenerator/Template/Service/TemplateTest.php
new file mode 100644
index 00000000..c3fe8fa3
--- /dev/null
+++ b/tests/Integration/Services/Documentgenerator/Template/Service/TemplateTest.php
@@ -0,0 +1,207 @@
+
+ *
+ * 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\Documentgenerator\Template\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Result\TemplateItemResult;
+use Bitrix24\SDK\Services\Documentgenerator\Template\Service\Template;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Factory;
+use Faker;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class TemplateTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\Documentgenerator\Template\Service
+ */
+#[CoversMethod(Template::class, 'add')]
+#[CoversMethod(Template::class, 'update')]
+#[CoversMethod(Template::class, 'delete')]
+#[CoversMethod(Template::class, 'get')]
+#[CoversMethod(Template::class, 'list')]
+#[CoversMethod(Template::class, 'getFields')]
+#[CoversMethod(Template::class, 'count')]
+#[\PHPUnit\Framework\Attributes\CoversClass(Template::class)]
+class TemplateTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Template $templateService;
+
+ private Faker\Generator $faker;
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->templateService = Factory::getServiceBuilder()->getDocumentgeneratorScope()->template();
+ $this->faker = Faker\Factory::create();
+ }
+
+ /**
+ * Helper: creates a minimal HTML template content encoded as base64 for template upload.
+ * Using HTML format avoids any dependency on the ext-zip PHP extension.
+ */
+ private function createMinimalTemplateBase64(): string
+ {
+ $html = 'Test template {Number}
';
+
+ return base64_encode($html);
+ }
+
+ /**
+ * Helper: get the first available template from the system.
+ *
+ * @return array{id: int}
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ private function getFirstTemplate(): array
+ {
+ $result = $this->templateService->list()->getCoreResponse()->getResponseData()->getResult();
+ $templates = $result['templates'] ?? [];
+
+ self::assertNotEmpty($templates, 'At least one documentgenerator template must exist to run this test');
+
+ $template = array_values($templates)[0];
+
+ return [
+ 'id' => (int)$template['id'],
+ ];
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $list = $this->templateService->list()->getTemplates();
+ self::assertIsArray($list);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+
+ $templateItemResult = $this->templateService->get($templateInfo['id'])->template();
+ self::assertInstanceOf(TemplateItemResult::class, $templateItemResult);
+ self::assertEquals($templateInfo['id'], $templateItemResult->id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testCount(): void
+ {
+ $count = $this->templateService->count();
+ self::assertGreaterThanOrEqual(0, $count);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ $name = 'tpl-' . $this->faker->uuid();
+ $fileContent = $this->createMinimalTemplateBase64();
+
+ $id = $this->templateService->add([
+ 'name' => $name,
+ 'file' => $fileContent,
+ 'numeratorId' => 1,
+ 'region' => 'en',
+ ])->getId();
+
+ self::assertGreaterThanOrEqual(1, $id);
+
+ // Cleanup
+ $this->templateService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $name = 'tpl-' . $this->faker->uuid();
+ $fileContent = $this->createMinimalTemplateBase64();
+
+ $id = $this->templateService->add([
+ 'name' => $name,
+ 'file' => $fileContent,
+ 'numeratorId' => 1,
+ 'region' => 'en',
+ ])->getId();
+
+ $newName = $name . '-updated';
+ self::assertTrue(
+ $this->templateService->update($id, [
+ 'name' => $newName,
+ ])->isSuccess()
+ );
+
+ self::assertEquals($newName, $this->templateService->get($id)->template()->name);
+
+ // Cleanup
+ $this->templateService->delete($id);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ $fileContent = $this->createMinimalTemplateBase64();
+
+ $id = $this->templateService->add([
+ 'name' => 'tpl-' . $this->faker->uuid(),
+ 'file' => $fileContent,
+ 'numeratorId' => 1,
+ 'region' => 'en',
+ ])->getId();
+
+ self::assertTrue($this->templateService->delete($id)->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGetFields(): void
+ {
+ $templateInfo = $this->getFirstTemplate();
+
+ $templateFieldsResult = $this->templateService->getFields($templateInfo['id']);
+ $fields = $templateFieldsResult->getFieldsDescription();
+
+ self::assertIsArray($fields);
+ }
+}
+