diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
index 6e5393c3..2504fc9a 100644
--- a/.php-cs-fixer.php
+++ b/.php-cs-fixer.php
@@ -29,6 +29,7 @@
->in(__DIR__ . '/src/Services/SonetGroup/')
->in(__DIR__ . '/src/Services/IMOpenLines/')
->in(__DIR__ . '/src/Services/Landing/')
+ ->in(__DIR__ . '/src/Services/Sign/')
->name('*.php')
->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories
->ignoreDotFiles(true)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4bc3b52..79f37dde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,16 @@
### Added
+- Added `Services\Sign\SignServiceBuilder` with support for `sign.b2e.*` methods and events,
+ see [sign.b2e.* methods](https://apidocs.bitrix24.com/api-reference/sign/index.html) ([#504](https://github.com/bitrix24/b24phpsdk/issues/504)):
+ - `document()->send()` — sends a document for company-side signing (КЭДО), application context only
+ - `document()->get()` — returns information about a document and its signing members
+ - `companyProvider()->list()` — returns the list of signature providers for a selected company
+ - `personalTail()->tail()` — returns the list of signed documents for the current user (КЭДО section), application context only
+ - `mySafeTail()->tail()` — returns the list of signed documents in the company safe, application context only
+- Added events support for `sign.b2e` scope via `SignB2eEventsFactory` ([#504](https://github.com/bitrix24/b24phpsdk/issues/504)):
+ - `OnSignB2eDocumentStatusChanged` — fires when document status changes
+ - `OnSignB2eMemberStatusChanged` — fires when member status changes
- Added service `Services\Landing\Site\Service\Site` with support methods,
see [landing.site.* methods](https://github.com/bitrix24/b24phpsdk/issues/267):
- `add` adds a site
diff --git a/Makefile b/Makefile
index c2fc4a9a..446f7e48 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,11 @@ help:
@echo "test-integration-landing-role - run Landing Role integration tests"
@echo "test-integration-landing-repowidget - run Landing RepoWidget integration tests"
@echo "test-integration-scope-landing-template - run Landing Template integration tests"
-
+ @echo "test-integration-scope-sign - run Sign B2e integration tests"
+ @echo "test-integration-sign-document - run Sign Document integration tests"
+ @echo "test-integration-sign-company-provider - run Sign CompanyProvider integration tests"
+ @echo "test-integration-sign-personal-tail - run Sign PersonalTail integration tests"
+ @echo "test-integration-sign-mysafe-tail - run Sign MySafeTail integration tests"
.PHONY: docker-init
docker-init:
@@ -492,6 +496,26 @@ test-integration-landing-role:
test-integration-landing-repowidget:
docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_landing_repowidget
+.PHONY: test-integration-scope-sign
+test-integration-scope-sign:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_sign
+
+.PHONY: test-integration-sign-document
+test-integration-sign-document:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_document
+
+.PHONY: test-integration-sign-company-provider
+test-integration-sign-company-provider:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_company_provider
+
+.PHONY: test-integration-sign-personal-tail
+test-integration-sign-personal-tail:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_personal_tail
+
+.PHONY: test-integration-sign-mysafe-tail
+test-integration-sign-mysafe-tail:
+ docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_mysafe_tail
+
# work dev environment
.PHONY: php-dev-server-up
php-dev-server-up:
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index e6ce503a..603e0e96 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -37,6 +37,7 @@ parameters:
- tests/Integration/Services/CRM/Documentgenerator/Document
- tests/Integration/Services/CRM/Documentgenerator/Template
- tests/Integration/Services/Landing
+ - tests/Integration/Services/Sign
excludePaths:
- tests/Integration/Services/CRM/Requisites/Service/RequisiteUserfieldUseCaseTest.php
- tests/Integration/Services/CRM/Status
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d0187ae5..e0be447e 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -262,6 +262,25 @@
./tests/Integration/Services/Landing/RepoWidget/
+
+ ./tests/Integration/Services/Sign/
+
+
+ ./tests/Integration/Services/Sign/B2e/Document/Service/
+ ./tests/Integration/Services/Sign/B2e/Document/Result/
+
+
+ ./tests/Integration/Services/Sign/B2e/CompanyProvider/Service/
+ ./tests/Integration/Services/Sign/B2e/CompanyProvider/Result/
+
+
+ ./tests/Integration/Services/Sign/B2e/PersonalTail/Service/
+ ./tests/Integration/Services/Sign/B2e/PersonalTail/Result/
+
+
+ ./tests/Integration/Services/Sign/B2e/MySafeTail/Service/
+ ./tests/Integration/Services/Sign/B2e/MySafeTail/Result/
+
diff --git a/rector.php b/rector.php
index fb38e858..401cb9de 100644
--- a/rector.php
+++ b/rector.php
@@ -78,6 +78,8 @@
__DIR__ . '/tests/Integration/Services/CRM/Documentgenerator/Template',
__DIR__ . '/src/Services/Landing',
__DIR__ . '/tests/Integration/Services/Landing',
+ __DIR__ . '/src/Services/Sign',
+ __DIR__ . '/tests/Integration/Services/Sign',
__DIR__ . '/tests/Unit/',
])
->withCache(cacheDirectory: __DIR__ . '/var/.cache/rector')
diff --git a/src/Services/RemoteEventsFactory.php b/src/Services/RemoteEventsFactory.php
index ec225a56..94ed4ddd 100644
--- a/src/Services/RemoteEventsFactory.php
+++ b/src/Services/RemoteEventsFactory.php
@@ -30,6 +30,7 @@
use Bitrix24\SDK\Services\IMOpenLines\Connector\Events\ImConnectorEventsFactory;
use Bitrix24\SDK\Services\Task\Events\TaskEventsFactory;
use Bitrix24\SDK\Services\Sale\Events\SaleEventsFactory;
+use Bitrix24\SDK\Services\Sign\Events\SignB2eEventsFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -159,6 +160,7 @@ public function createEvent(Request $request, ?string $applicationToken): EventI
]);
throw new InvalidArgumentException('key «event» not found in request payload');
}
+
if ($applicationToken !== null && trim($applicationToken) === '') {
throw new InvalidArgumentException('application token cannot be empty');
}
@@ -230,8 +232,9 @@ public static function init(LoggerInterface $logger): self
new SaleEventsFactory(),
new ImConnectorEventsFactory(),
new TaskEventsFactory(),
+ new SignB2eEventsFactory(),
],
$logger
);
}
-}
\ No newline at end of file
+}
diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php
index 4c1026e5..7ad57bef 100644
--- a/src/Services/ServiceBuilder.php
+++ b/src/Services/ServiceBuilder.php
@@ -37,6 +37,7 @@
use Bitrix24\SDK\Services\Paysystem\PaysystemServiceBuilder;
use Bitrix24\SDK\Services\SonetGroup\SonetGroupServiceBuilder;
use Bitrix24\SDK\Services\Landing\LandingServiceBuilder;
+use Bitrix24\SDK\Services\Sign\SignServiceBuilder;
use Psr\Log\LoggerInterface;
class ServiceBuilder extends AbstractServiceBuilder
@@ -349,4 +350,18 @@ public function getLandingScope(): LandingServiceBuilder
return $this->serviceCache[__METHOD__];
}
+
+ public function getSignScope(): SignServiceBuilder
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new SignServiceBuilder(
+ $this->core,
+ $this->batch,
+ $this->bulkItemsReader,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
}
diff --git a/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.php b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.php
new file mode 100644
index 00000000..1f7feee0
--- /dev/null
+++ b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.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\Sign\B2e\CompanyProvider\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read string|null $code Signature provider code
+ * @property-read string|null $uid Unique identifier of the provider
+ * @property-read string|null $name Provider display name
+ * @property-read CarbonImmutable|null $date Provider activation date
+ * @property-read CarbonImmutable|null $expires Provider expiration date
+ */
+class CompanyProviderItemResult extends AbstractItem
+{
+ public function __get($offset)
+ {
+ return match ($offset) {
+ 'date', 'expires' => empty($this->data[$offset])
+ ? null
+ : CarbonImmutable::parse($this->data[$offset]),
+ default => $this->data[$offset] ?? null,
+ };
+ }
+}
diff --git a/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.php b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.php
new file mode 100644
index 00000000..d1ca4c6f
--- /dev/null
+++ b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.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\Sign\B2e\CompanyProvider\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class CompanyProvidersResult extends AbstractResult
+{
+ /**
+ * @return CompanyProviderItemResult[]
+ * @throws BaseException
+ */
+ public function getProviders(): array
+ {
+ $items = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $items[] = new CompanyProviderItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php b/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php
new file mode 100644
index 00000000..2f85cd53
--- /dev/null
+++ b/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php
@@ -0,0 +1,78 @@
+
+ *
+ * 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\Sign\B2e\CompanyProvider\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\Sign\B2e\CompanyProvider\Result\CompanyProvidersResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['sign.b2e']))]
+class CompanyProvider extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Returns the list of signature providers for a selected company.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-company-provider-list.html
+ *
+ * @param string|null $companyUuid Company UUID in HCM Link (required if companyCrmId is not provided)
+ * @param int|null $companyCrmId Company CRM identifier (required if companyUuid is not provided)
+ * @param string|null $language Language for provider name localisation (default: en)
+ * @param int $limit Number of records per page (1–1000, default: 100)
+ * @param int $offset Pagination offset (default: 0)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'sign.b2e.company.provider.list',
+ 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-company-provider-list.html',
+ 'Returns the list of signature providers for a selected company.'
+ )]
+ public function list(
+ ?string $companyUuid = null,
+ ?int $companyCrmId = null,
+ ?string $language = null,
+ int $limit = 100,
+ int $offset = 0
+ ): CompanyProvidersResult {
+ $params = [
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ];
+ if ($companyUuid !== null) {
+ $params['companyUuid'] = $companyUuid;
+ }
+
+ if ($companyCrmId !== null) {
+ $params['companyCrmId'] = $companyCrmId;
+ }
+
+ if ($language !== null) {
+ $params['language'] = $language;
+ }
+
+ return new CompanyProvidersResult($this->core->call('sign.b2e.company.provider.list', $params));
+ }
+}
diff --git a/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php b/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php
new file mode 100644
index 00000000..cb92e672
--- /dev/null
+++ b/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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\Sign\B2e\Document\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read string|null $uid Unique identifier of the document
+ * @property-read array|null $state Document status with 'code' and 'name' keys
+ * @property-read array>|null $members List of signing members
+ */
+class DocumentItemResult extends AbstractItem
+{
+}
diff --git a/src/Services/Sign/B2e/Document/Result/DocumentResult.php b/src/Services/Sign/B2e/Document/Result/DocumentResult.php
new file mode 100644
index 00000000..56ab2da7
--- /dev/null
+++ b/src/Services/Sign/B2e/Document/Result/DocumentResult.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\Sign\B2e\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class DocumentResult extends AbstractResult
+{
+ /**
+ * @throws BaseException
+ */
+ public function getDocument(): DocumentItemResult
+ {
+ return new DocumentItemResult($this->getCoreResponse()->getResponseData()->getResult());
+ }
+}
diff --git a/src/Services/Sign/B2e/Document/Service/Document.php b/src/Services/Sign/B2e/Document/Service/Document.php
new file mode 100644
index 00000000..28bede8e
--- /dev/null
+++ b/src/Services/Sign/B2e/Document/Service/Document.php
@@ -0,0 +1,94 @@
+
+ *
+ * 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\Sign\B2e\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\Sign\B2e\Document\Result\DocumentResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['sign.b2e']))]
+class Document extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Sends a document for company-side signing (КЭДО).
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-send.html
+ *
+ * @param array{
+ * company?: array,
+ * members?: array,
+ * responsible?: array{userId: int},
+ * companyProviderUid?: string,
+ * files?: array,
+ * regionDocumentType?: string,
+ * externalSettings?: array,
+ * language?: string
+ * } $fields Document fields
+ * @param string|null $language Language for status localisation in response (default: en)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'sign.b2e.document.send',
+ 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-send.html',
+ 'Sends a document for company-side signing (КЭДО). Requires application context.'
+ )]
+ public function send(array $fields, ?string $language = null): DocumentResult
+ {
+ $params = ['fields' => $fields];
+ if ($language !== null) {
+ $params['language'] = $language;
+ }
+
+ return new DocumentResult($this->core->call('sign.b2e.document.send', $params));
+ }
+
+ /**
+ * Returns information about a document and its signing members.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-get.html
+ *
+ * @param string $uid Unique document identifier
+ * @param string|null $language Language for status localisation in response (default: en)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'sign.b2e.document.get',
+ 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-get.html',
+ 'Returns information about a document and its signing members.'
+ )]
+ public function get(string $uid, ?string $language = null): DocumentResult
+ {
+ $params = ['uid' => $uid];
+ if ($language !== null) {
+ $params['language'] = $language;
+ }
+
+ return new DocumentResult($this->core->call('sign.b2e.document.get', $params));
+ }
+}
diff --git a/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php
new file mode 100644
index 00000000..d13f4231
--- /dev/null
+++ b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.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\Sign\B2e\MySafeTail\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read int|null $id Signed document identifier
+ * @property-read string|null $title Document title
+ * @property-read CarbonImmutable|null $create_date Document creation date
+ * @property-read CarbonImmutable|null $signed_date Document signing date
+ * @property-read int|null $creator_id User ID who created the document
+ * @property-read int|null $member_id User ID of the signing party
+ * @property-read string|null $role Employee role in the document: editor, reviewer, assignee, signer
+ * @property-read string|null $file_url Download URL for the signed document
+ */
+class MySafeTailItemResult extends AbstractItem
+{
+ public function __get($offset)
+ {
+ return match ($offset) {
+ 'create_date', 'signed_date' => empty($this->data[$offset])
+ ? null
+ : CarbonImmutable::parse($this->data[$offset]),
+ default => $this->data[$offset] ?? null,
+ };
+ }
+}
diff --git a/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.php b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.php
new file mode 100644
index 00000000..696c0d90
--- /dev/null
+++ b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.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\Sign\B2e\MySafeTail\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class MySafeTailResult extends AbstractResult
+{
+ /**
+ * @return MySafeTailItemResult[]
+ * @throws BaseException
+ */
+ public function getItems(): array
+ {
+ $items = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $items[] = new MySafeTailItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php b/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php
new file mode 100644
index 00000000..ad623beb
--- /dev/null
+++ b/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\Sign\B2e\MySafeTail\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\Sign\B2e\MySafeTail\Result\MySafeTailResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['sign.b2e']))]
+class MySafeTail extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Returns the list of signed documents in the company safe.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-mysafe-tail.html
+ *
+ * @param int $limit Number of records per page (1–50, default: 20)
+ * @param int $offset Pagination offset (default: 0)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'sign.b2e.mysafe.tail',
+ 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-mysafe-tail.html',
+ 'Returns the list of signed documents in the company safe. Requires application context.'
+ )]
+ public function tail(int $limit = 20, int $offset = 0): MySafeTailResult
+ {
+ return new MySafeTailResult(
+ $this->core->call('sign.b2e.mysafe.tail', [
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])
+ );
+ }
+}
diff --git a/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php
new file mode 100644
index 00000000..481a2731
--- /dev/null
+++ b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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\Sign\B2e\PersonalTail\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Carbon\CarbonImmutable;
+
+/**
+ * @property-read int|null $id Signed document identifier
+ * @property-read string|null $title Document title
+ * @property-read CarbonImmutable|null $signed_date Document signing date
+ * @property-read string|null $file_url Download URL for the signed document
+ */
+class PersonalTailItemResult extends AbstractItem
+{
+ public function __get($offset)
+ {
+ return match ($offset) {
+ 'signed_date' => empty($this->data[$offset])
+ ? null
+ : CarbonImmutable::parse($this->data[$offset]),
+ default => $this->data[$offset] ?? null,
+ };
+ }
+}
diff --git a/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.php b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.php
new file mode 100644
index 00000000..1254b400
--- /dev/null
+++ b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.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\Sign\B2e\PersonalTail\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class PersonalTailResult extends AbstractResult
+{
+ /**
+ * @return PersonalTailItemResult[]
+ * @throws BaseException
+ */
+ public function getItems(): array
+ {
+ $items = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $items[] = new PersonalTailItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php b/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php
new file mode 100644
index 00000000..092416af
--- /dev/null
+++ b/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\Sign\B2e\PersonalTail\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\Sign\B2e\PersonalTail\Result\PersonalTailResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['sign.b2e']))]
+class PersonalTail extends AbstractService
+{
+ public function __construct(CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Returns the list of signed documents for the current user from the КЭДО section.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-personal-tail.html
+ *
+ * @param int $limit Number of records per page (1–50, default: 20)
+ * @param int $offset Pagination offset (default: 0)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'sign.b2e.personal.tail',
+ 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-personal-tail.html',
+ 'Returns the list of signed documents for the current user from the КЭДО section. Requires application context.'
+ )]
+ public function tail(int $limit = 20, int $offset = 0): PersonalTailResult
+ {
+ return new PersonalTailResult(
+ $this->core->call('sign.b2e.personal.tail', [
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])
+ );
+ }
+}
diff --git a/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php
new file mode 100644
index 00000000..25b6e451
--- /dev/null
+++ b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.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\Sign\Events\OnSignB2eDocumentStatusChanged;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+/**
+ * Event fired when the status of a sign.b2e document changes.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/events/on-sign-b2e-document-status-changed.html
+ */
+class OnSignB2eDocumentStatusChanged extends AbstractEventRequest
+{
+ public const CODE = 'ONSIGNB2EDOCUMENTSTATUSCHANGED';
+
+ public function getPayload(): OnSignB2eDocumentStatusChangedPayload
+ {
+ return new OnSignB2eDocumentStatusChangedPayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php
new file mode 100644
index 00000000..9df3e38e
--- /dev/null
+++ b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * 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\Sign\Events\OnSignB2eDocumentStatusChanged;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read string $documentUid Unique document identifier
+ * @property-read string|null $companyUid Unique company identifier (present when a company integration exists)
+ * @property-read string $statusCode Document status code
+ * @property-read string $statusName Document status name
+ */
+class OnSignB2eDocumentStatusChangedPayload extends AbstractItem
+{
+}
diff --git a/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php
new file mode 100644
index 00000000..e54cabfc
--- /dev/null
+++ b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.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\Sign\Events\OnSignB2eMemberStatusChanged;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+/**
+ * Event fired when the status of a sign.b2e document member changes.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/sign/events/on-sign-b2e-member-status-changed.html
+ */
+class OnSignB2eMemberStatusChanged extends AbstractEventRequest
+{
+ public const CODE = 'ONSIGNB2EMEMBERSTATUSCHANGED';
+
+ public function getPayload(): OnSignB2eMemberStatusChangedPayload
+ {
+ return new OnSignB2eMemberStatusChangedPayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php
new file mode 100644
index 00000000..76ede3e0
--- /dev/null
+++ b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php
@@ -0,0 +1,27 @@
+
+ *
+ * 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\Sign\Events\OnSignB2eMemberStatusChanged;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read string $memberUid Unique member identifier
+ * @property-read string $documentUid Unique document identifier
+ * @property-read string|null $companyUid Unique company identifier (present when a company integration exists)
+ * @property-read string $statusCode Member status code
+ * @property-read string $statusName Member status name
+ */
+class OnSignB2eMemberStatusChangedPayload extends AbstractItem
+{
+}
diff --git a/src/Services/Sign/Events/SignB2eEventsFactory.php b/src/Services/Sign/Events/SignB2eEventsFactory.php
new file mode 100644
index 00000000..d4eb11b0
--- /dev/null
+++ b/src/Services/Sign/Events/SignB2eEventsFactory.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\Sign\Events;
+
+use Bitrix24\SDK\Core\Contracts\Events\EventInterface;
+use Bitrix24\SDK\Core\Contracts\Events\EventsFabricInterface;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Services\Sign\Events\OnSignB2eDocumentStatusChanged\OnSignB2eDocumentStatusChanged;
+use Bitrix24\SDK\Services\Sign\Events\OnSignB2eMemberStatusChanged\OnSignB2eMemberStatusChanged;
+use Symfony\Component\HttpFoundation\Request;
+
+readonly class SignB2eEventsFactory implements EventsFabricInterface
+{
+ #[\Override]
+ public function isSupport(string $eventCode): bool
+ {
+ return in_array(strtoupper($eventCode), [
+ OnSignB2eDocumentStatusChanged::CODE,
+ OnSignB2eMemberStatusChanged::CODE,
+ ], true);
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ #[\Override]
+ public function create(Request $eventRequest): EventInterface
+ {
+ $eventPayload = $eventRequest->request->all();
+ if (!array_key_exists('event', $eventPayload)) {
+ throw new InvalidArgumentException('«event» key not found in event payload');
+ }
+
+ $eventCode = strtoupper((string) $eventPayload['event']);
+
+ return match ($eventCode) {
+ OnSignB2eDocumentStatusChanged::CODE => new OnSignB2eDocumentStatusChanged($eventRequest),
+ OnSignB2eMemberStatusChanged::CODE => new OnSignB2eMemberStatusChanged($eventRequest),
+ default => throw new InvalidArgumentException(
+ sprintf('Unexpected event code «%s»', $eventCode)
+ ),
+ };
+ }
+}
diff --git a/src/Services/Sign/SignServiceBuilder.php b/src/Services/Sign/SignServiceBuilder.php
new file mode 100644
index 00000000..556d73f1
--- /dev/null
+++ b/src/Services/Sign/SignServiceBuilder.php
@@ -0,0 +1,74 @@
+
+ *
+ * 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\Sign;
+
+use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Services\AbstractServiceBuilder;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail;
+
+#[ApiServiceBuilderMetadata(new Scope(['sign.b2e']))]
+class SignServiceBuilder extends AbstractServiceBuilder
+{
+ /**
+ * Document service for sign.b2e.document.* methods.
+ */
+ public function document(): Document
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Document($this->core, $this->log);
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ /**
+ * Company provider service for sign.b2e.company.provider.* methods.
+ */
+ public function companyProvider(): CompanyProvider
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new CompanyProvider($this->core, $this->log);
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ /**
+ * Personal tail service for sign.b2e.personal.tail method.
+ */
+ public function personalTail(): PersonalTail
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new PersonalTail($this->core, $this->log);
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ /**
+ * My safe tail service for sign.b2e.mysafe.tail method.
+ */
+ public function mySafeTail(): MySafeTail
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new MySafeTail($this->core, $this->log);
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+}
diff --git a/tests/.env b/tests/.env
index 6335d1ec..27bcf3a4 100644
--- a/tests/.env
+++ b/tests/.env
@@ -23,6 +23,12 @@ LOGS_FILE=sdk.log
# integration tests assets
INTEGRATION_TEST_OPEN_LINE_CODE=40863c519996e505b5cde98749c97413
+# sign.b2e integration test settings — override in tests/.env.local
+# Set companyCrmId of a company with Electronic Personnel Document Management integration to enable CompanyProvider tests
+SIGN_B2E_COMPANY_CRM_ID=
+# Set a valid document UID from sign.b2e to enable Document.get annotation tests
+SIGN_B2E_DOCUMENT_UID=
+
DOCUMENTATION_DEFAULT_TARGET_BRANCH=
DOCUMENTATION_OPEN_AI_API_KEY=
-DOCUMENTATION_REPOSITORY_FOLDER=
\ No newline at end of file
+DOCUMENTATION_REPOSITORY_FOLDER=
diff --git a/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php
new file mode 100644
index 00000000..b7177363
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\Sign\B2e\CompanyProvider\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProviderItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(CompanyProviderItemResult::class)]
+class CompanyProviderItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private CompanyProvider $companyProviderService;
+
+ private ?int $companyCrmId = null;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->companyProviderService = Fabric::getServiceBuilder(true)->getSignScope()->companyProvider();
+
+ $value = $_ENV['SIGN_B2E_COMPANY_CRM_ID'] ?? '';
+ if ($value !== '') {
+ $this->companyCrmId = (int) $value;
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsAnnotated: all fields in CompanyProviderItemResult are annotated in phpdoc and match raw API response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ if ($this->companyCrmId === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $rawItems = $this->companyProviderService->list(null, $this->companyCrmId)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signature providers found for this company — cannot verify annotation completeness against live API data.'
+ );
+ }
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItems[0]),
+ CompanyProviderItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in CompanyProviderItemResult have valid type annotations')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ if ($this->companyCrmId === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $rawItems = $this->companyProviderService->list(null, $this->companyCrmId)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signature providers found for this company — cannot verify type annotations against live API data.'
+ );
+ }
+
+ $fieldTypesMap = [];
+ foreach (array_keys($rawItems[0]) as $fieldCode) {
+ $fieldTypesMap[$fieldCode] = match ($fieldCode) {
+ 'code', 'uid', 'name' => ['type' => 'string'],
+ 'date', 'expires' => ['type' => 'datetime'],
+ default => throw new \RuntimeException(
+ sprintf('Unknown field «%s» in sign.b2e.company.provider.list response — update the type map.', $fieldCode)
+ ),
+ };
+ }
+
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fieldTypesMap,
+ CompanyProviderItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php b/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php
new file mode 100644
index 00000000..14ec1585
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php
@@ -0,0 +1,109 @@
+
+ *
+ * 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\Sign\B2e\CompanyProvider\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProviderItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProvidersResult;
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(CompanyProvider::class)]
+class CompanyProviderTest extends TestCase
+{
+ private CompanyProvider $companyProviderService;
+
+ private ?int $companyCrmId = null;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->companyProviderService = Fabric::getServiceBuilder(true)->getSignScope()->companyProvider();
+
+ $value = $_ENV['SIGN_B2E_COMPANY_CRM_ID'] ?? '';
+ if ($value !== '') {
+ $this->companyCrmId = (int) $value;
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.company.provider.list returns CompanyProvidersResult with items array')]
+ public function testListReturnsCompanyProvidersResult(): void
+ {
+ if ($this->companyCrmId === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $companyProvidersResult = $this->companyProviderService->list(null, $this->companyCrmId);
+
+ self::assertInstanceOf(CompanyProvidersResult::class, $companyProvidersResult);
+ self::assertIsArray($companyProvidersResult->getProviders());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.company.provider.list items are CompanyProviderItemResult instances when not empty')]
+ public function testListItemsAreCompanyProviderItemResults(): void
+ {
+ if ($this->companyCrmId === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $providers = $this->companyProviderService->list(null, $this->companyCrmId)->getProviders();
+
+ if ($providers === []) {
+ $this->markTestSkipped('No signature providers found for this company — cannot verify item type.');
+ }
+
+ self::assertInstanceOf(CompanyProviderItemResult::class, $providers[0]);
+ self::assertIsString($providers[0]->code);
+ self::assertIsString($providers[0]->uid);
+ self::assertIsString($providers[0]->name);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.company.provider.list respects limit parameter')]
+ public function testListRespectsLimitParameter(): void
+ {
+ if ($this->companyCrmId === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $providers = $this->companyProviderService->list(null, $this->companyCrmId, null, 1, 0)->getProviders();
+
+ self::assertLessThanOrEqual(1, count($providers));
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php
new file mode 100644
index 00000000..449fa1eb
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\Sign\B2e\Document\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(DocumentItemResult::class)]
+class DocumentItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private Document $documentService;
+
+ private ?string $documentUid = null;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Fabric::getServiceBuilder(true)->getSignScope()->document();
+
+ $value = $_ENV['SIGN_B2E_DOCUMENT_UID'] ?? '';
+ if ($value !== '') {
+ $this->documentUid = $value;
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsAnnotated: all fields in DocumentItemResult are annotated in phpdoc and match raw API response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ if ($this->documentUid === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $rawResult = $this->documentService->get($this->documentUid)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawResult),
+ DocumentItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in DocumentItemResult have valid type annotations')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ if ($this->documentUid === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $rawResult = $this->documentService->get($this->documentUid)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ $fieldTypesMap = [];
+ foreach (array_keys($rawResult) as $fieldCode) {
+ $fieldTypesMap[$fieldCode] = match ($fieldCode) {
+ 'uid' => ['type' => 'string'],
+ 'state', 'members' => ['type' => 'array'],
+ default => throw new \RuntimeException(
+ sprintf('Unknown field «%s» in sign.b2e.document.get response — update the type map.', $fieldCode)
+ ),
+ };
+ }
+
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fieldTypesMap,
+ DocumentItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php b/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php
new file mode 100644
index 00000000..cf7cb74e
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php
@@ -0,0 +1,96 @@
+
+ *
+ * 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\Sign\B2e\Document\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentResult;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(Document::class)]
+class DocumentTest extends TestCase
+{
+ private Document $documentService;
+
+ private ?string $documentUid = null;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->documentService = Fabric::getServiceBuilder(true)->getSignScope()->document();
+
+ $value = $_ENV['SIGN_B2E_DOCUMENT_UID'] ?? '';
+ if ($value !== '') {
+ $this->documentUid = $value;
+ }
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.document.get returns DocumentResult for a known document UID')]
+ public function testGetReturnsDocumentResult(): void
+ {
+ if ($this->documentUid === null) {
+ $this->markTestSkipped(
+ 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.'
+ );
+ }
+
+ $documentResult = $this->documentService->get($this->documentUid);
+
+ self::assertInstanceOf(DocumentResult::class, $documentResult);
+
+ $documentItemResult = $documentResult->getDocument();
+ self::assertInstanceOf(DocumentItemResult::class, $documentItemResult);
+ self::assertIsString($documentItemResult->uid);
+ self::assertEquals($this->documentUid, $documentItemResult->uid);
+ self::assertIsArray($documentItemResult->state);
+ self::assertArrayHasKey('code', $documentItemResult->state);
+ self::assertArrayHasKey('name', $documentItemResult->state);
+ }
+
+ /**
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.document.get throws BaseException for non-existent document UID')]
+ public function testGetThrowsExceptionForInvalidUid(): void
+ {
+ $this->expectException(BaseException::class);
+
+ $this->documentService->get('non-existent-uid-00000000-0000-0000-0000-000000000000');
+ }
+
+ /**
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.document.send throws BaseException when required fields are missing')]
+ public function testSendThrowsExceptionWhenRequiredFieldsMissing(): void
+ {
+ $this->expectException(BaseException::class);
+
+ // Sending with empty fields should return BAD_REQUEST from the API
+ $this->documentService->send([]);
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php
new file mode 100644
index 00000000..85ff91a6
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php
@@ -0,0 +1,97 @@
+
+ *
+ * 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\Sign\B2e\MySafeTail\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(MySafeTailItemResult::class)]
+class MySafeTailItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private MySafeTail $mySafeTailService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->mySafeTailService = Fabric::getServiceBuilder(true)->getSignScope()->mySafeTail();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsAnnotated: all fields in MySafeTailItemResult are annotated in phpdoc and match raw API response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItems = $this->mySafeTailService->tail(20, 0)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signed documents found in company safe — cannot verify annotation completeness against live API data.'
+ );
+ }
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItems[0]),
+ MySafeTailItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in MySafeTailItemResult have valid type annotations')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItems = $this->mySafeTailService->tail(20, 0)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signed documents found in company safe — cannot verify type annotations against live API data.'
+ );
+ }
+
+ $fieldTypesMap = [];
+ foreach (array_keys($rawItems[0]) as $fieldCode) {
+ $fieldTypesMap[$fieldCode] = match ($fieldCode) {
+ 'id', 'creator_id', 'member_id' => ['type' => 'integer'],
+ 'title', 'file_url', 'role' => ['type' => 'string'],
+ 'create_date', 'signed_date' => ['type' => 'datetime'],
+ default => throw new \RuntimeException(
+ sprintf('Unknown field «%s» in sign.b2e.mysafe.tail response — update the type map.', $fieldCode)
+ ),
+ };
+ }
+
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fieldTypesMap,
+ MySafeTailItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php b/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php
new file mode 100644
index 00000000..72690d88
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php
@@ -0,0 +1,84 @@
+
+ *
+ * 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\Sign\B2e\MySafeTail\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailResult;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(MySafeTail::class)]
+class MySafeTailTest extends TestCase
+{
+ private MySafeTail $mySafeTailService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->mySafeTailService = Fabric::getServiceBuilder(true)->getSignScope()->mySafeTail();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.mysafe.tail returns MySafeTailResult with items array')]
+ public function testTailReturnsMySafeTailResult(): void
+ {
+ $mySafeTailResult = $this->mySafeTailService->tail(20, 0);
+
+ self::assertInstanceOf(MySafeTailResult::class, $mySafeTailResult);
+ self::assertIsArray($mySafeTailResult->getItems());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.mysafe.tail items are MySafeTailItemResult instances when not empty')]
+ public function testTailItemsAreMySafeTailItemResults(): void
+ {
+ $items = $this->mySafeTailService->tail(20, 0)->getItems();
+
+ if ($items === []) {
+ $this->markTestSkipped('No signed documents found in company safe — cannot verify item type.');
+ }
+
+ self::assertInstanceOf(MySafeTailItemResult::class, $items[0]);
+ self::assertIsInt($items[0]->id);
+ self::assertIsString($items[0]->title);
+ self::assertIsString($items[0]->role);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.mysafe.tail respects limit parameter')]
+ public function testTailRespectsLimitParameter(): void
+ {
+ $items = $this->mySafeTailService->tail(1, 0)->getItems();
+
+ self::assertLessThanOrEqual(1, count($items));
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php
new file mode 100644
index 00000000..3eecd1ec
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php
@@ -0,0 +1,97 @@
+
+ *
+ * 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\Sign\B2e\PersonalTail\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(PersonalTailItemResult::class)]
+class PersonalTailItemResultAnnotationsTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private PersonalTail $personalTailService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->personalTailService = Fabric::getServiceBuilder(true)->getSignScope()->personalTail();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsAnnotated: all fields in PersonalTailItemResult are annotated in phpdoc and match raw API response')]
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $rawItems = $this->personalTailService->tail(20, 0)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signed documents found in personal tail — cannot verify annotation completeness against live API data.'
+ );
+ }
+
+ $this->assertBitrix24AllResultItemFieldsAnnotated(
+ array_keys($rawItems[0]),
+ PersonalTailItemResult::class
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in PersonalTailItemResult have valid type annotations')]
+ public function testAllSystemFieldsHasValidTypeAnnotation(): void
+ {
+ $rawItems = $this->personalTailService->tail(20, 0)
+ ->getCoreResponse()->getResponseData()->getResult();
+
+ if ($rawItems === []) {
+ $this->markTestSkipped(
+ 'No signed documents found in personal tail — cannot verify type annotations against live API data.'
+ );
+ }
+
+ $fieldTypesMap = [];
+ foreach (array_keys($rawItems[0]) as $fieldCode) {
+ $fieldTypesMap[$fieldCode] = match ($fieldCode) {
+ 'id' => ['type' => 'integer'],
+ 'title', 'file_url' => ['type' => 'string'],
+ 'signed_date' => ['type' => 'datetime'],
+ default => throw new \RuntimeException(
+ sprintf('Unknown field «%s» in sign.b2e.personal.tail response — update the type map.', $fieldCode)
+ ),
+ };
+ }
+
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $fieldTypesMap,
+ PersonalTailItemResult::class
+ );
+ }
+}
diff --git a/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php b/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php
new file mode 100644
index 00000000..ff8de114
--- /dev/null
+++ b/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php
@@ -0,0 +1,83 @@
+
+ *
+ * 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\Sign\B2e\PersonalTail\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailItemResult;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailResult;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(PersonalTail::class)]
+class PersonalTailTest extends TestCase
+{
+ private PersonalTail $personalTailService;
+
+ #[\Override]
+ protected function setUp(): void
+ {
+ $this->personalTailService = Fabric::getServiceBuilder(true)->getSignScope()->personalTail();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.personal.tail returns PersonalTailResult with items array')]
+ public function testTailReturnsPersonalTailResult(): void
+ {
+ $personalTailResult = $this->personalTailService->tail(20, 0);
+
+ self::assertInstanceOf(PersonalTailResult::class, $personalTailResult);
+ self::assertIsArray($personalTailResult->getItems());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.personal.tail items are PersonalTailItemResult instances when not empty')]
+ public function testTailItemsArePersonalTailItemResults(): void
+ {
+ $items = $this->personalTailService->tail(20, 0)->getItems();
+
+ if ($items === []) {
+ $this->markTestSkipped('No signed documents found in personal tail — cannot verify item type.');
+ }
+
+ self::assertInstanceOf(PersonalTailItemResult::class, $items[0]);
+ self::assertIsInt($items[0]->id);
+ self::assertIsString($items[0]->title);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[Test]
+ #[TestDox('sign.b2e.personal.tail respects limit parameter')]
+ public function testTailRespectsLimitParameter(): void
+ {
+ $items = $this->personalTailService->tail(1, 0)->getItems();
+
+ self::assertLessThanOrEqual(1, count($items));
+ }
+}
diff --git a/tests/Unit/Services/Sign/SignServiceBuilderTest.php b/tests/Unit/Services/Sign/SignServiceBuilderTest.php
new file mode 100644
index 00000000..010828c6
--- /dev/null
+++ b/tests/Unit/Services/Sign/SignServiceBuilderTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Unit\Services\Sign;
+
+use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider;
+use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document;
+use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail;
+use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail;
+use Bitrix24\SDK\Services\Sign\SignServiceBuilder;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestDox;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(SignServiceBuilder::class)]
+class SignServiceBuilderTest extends TestCase
+{
+ #[Test]
+ #[TestDox('SignServiceBuilder class exists and can be instantiated')]
+ public function testSignServiceBuilderExists(): void
+ {
+ $this->assertTrue(class_exists(SignServiceBuilder::class));
+ }
+
+ #[Test]
+ #[TestDox('Document service class exists')]
+ public function testDocumentServiceClassExists(): void
+ {
+ $this->assertTrue(class_exists(Document::class));
+ }
+
+ #[Test]
+ #[TestDox('CompanyProvider service class exists')]
+ public function testCompanyProviderServiceClassExists(): void
+ {
+ $this->assertTrue(class_exists(CompanyProvider::class));
+ }
+
+ #[Test]
+ #[TestDox('PersonalTail service class exists')]
+ public function testPersonalTailServiceClassExists(): void
+ {
+ $this->assertTrue(class_exists(PersonalTail::class));
+ }
+
+ #[Test]
+ #[TestDox('MySafeTail service class exists')]
+ public function testMySafeTailServiceClassExists(): void
+ {
+ $this->assertTrue(class_exists(MySafeTail::class));
+ }
+}